Skip to content

Commit a040df9

Browse files
committedMar 22, 2024
Teach optional injection to peephole into more converting contexts
This allows us to propagate abstraction patterns from optional parameters all the way to closure emission, which optimizes some code patterns but (more importantly) propagates our knowledge that we're converting to an `@isolated(any)` function type down to closure emission, allowing us to set the isolation correctly. There are still some conversion cases missing --- if we can't combine conversions for whatever reason, we should at least shift our knowledge that we need to produce an `@isolated(any)` type down, the same way that we shift it when emitting a closure that we can't directly emit in the target abstraction pattern. But this completes the obvious cases of peepholing for closure emission.
1 parent 783dd20 commit a040df9

File tree

4 files changed

+237
-37
lines changed

4 files changed

+237
-37
lines changed
 

‎lib/SILGen/Conversion.h

+117-1
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@
2424
namespace swift {
2525
namespace Lowering {
2626

27+
class OptionalInjectionConversion;
28+
2729
/// An abstraction representing certain kinds of conversion that SILGen can
28-
/// do automatically in various situations.
30+
/// do automatically in various situations. These are used primarily with
31+
/// ConvertingInitialization in order to guide the emission of an expression.
32+
/// Some of these conversions are semantically required, such as propagating
33+
/// @isolated(any) down to the emission of the function reference, or some of
34+
/// the bridging conversions.
2935
class Conversion {
3036
public:
3137
enum KindTy {
@@ -52,6 +58,13 @@ class Conversion {
5258
/// and that's easier.
5359
AnyErasure,
5460

61+
/// A subtype conversion, except it's allowed to do optional injections
62+
/// and existential erasures. This comes up with bridging peepholes
63+
/// and is annoying to not have a way to represent. The conversion
64+
/// should always involve class references and so will be harmless
65+
/// in terms of representations.
66+
BridgingSubtype,
67+
5568
/// A subtype conversion.
5669
Subtype,
5770

@@ -68,6 +81,7 @@ class Conversion {
6881
case BridgeFromObjC:
6982
case BridgeResultFromObjC:
7083
case AnyErasure:
84+
case BridgingSubtype:
7185
case Subtype:
7286
return true;
7387

@@ -88,6 +102,7 @@ class Conversion {
88102
case BridgeFromObjC:
89103
case BridgeResultFromObjC:
90104
case AnyErasure:
105+
case BridgingSubtype:
91106
case Subtype:
92107
return false;
93108
}
@@ -104,6 +119,16 @@ class Conversion {
104119
bool IsExplicit;
105120
};
106121

122+
/// The types we store for reabstracting contexts. In general, when
123+
/// we're just emitting an expression, it's expected that the input
124+
/// abstraction type and lowered type will match the input formal type,
125+
/// which will be the type of the expression we're emitting. They can
126+
/// therefore simply be replaced if we're e.g. prepending a subtype
127+
/// conversion to the reabstraction. But it's very useful to be able to
128+
/// represent both sides of the conversion uniformly so that e.g. we can
129+
/// elegantly perform a single (perhaps identity) reabstraction when
130+
/// receiving a function result or loading a value from abstracted
131+
/// storage.
107132
struct ReabstractionTypes {
108133
AbstractionPattern InputOrigType;
109134
AbstractionPattern OutputOrigType;
@@ -123,6 +148,7 @@ class Conversion {
123148
case BridgeFromObjC:
124149
case BridgeResultFromObjC:
125150
case AnyErasure:
151+
case BridgingSubtype:
126152
case Subtype:
127153
return Members::indexOf<BridgingTypes>();
128154

@@ -153,6 +179,33 @@ class Conversion {
153179
inputLoweredTy, outputLoweredTy);
154180
}
155181

182+
static bool isAllowedConversion(CanType inputType, CanType outputType) {
183+
// Allow all identity conversions. (This should only happen with
184+
// reabstraction.)
185+
if (inputType == outputType) return true;
186+
187+
// Allow optional-to-optional conversions, but not injections
188+
// into optional. Emitters can be expected to just strip optionality
189+
// from the result type when peepholing through an optional injection,
190+
// and doing so avoids the need to handle injections specially in
191+
// emitters, like those for function references and closures.
192+
while (auto outputObjectType = outputType.getOptionalObjectType()) {
193+
auto inputObjectType = inputType.getOptionalObjectType();
194+
if (!inputObjectType) return false;
195+
outputType = outputObjectType;
196+
inputType = inputObjectType;
197+
}
198+
199+
// Disallow existential erasures from being directly represented here
200+
// because it may involve a representation change for the value. Emitters
201+
// shouldn't have to specially recognize those.
202+
if (outputType.isExistentialType())
203+
return inputType.isExistentialType();
204+
205+
assert(!inputType.getOptionalObjectType());
206+
return true;
207+
}
208+
156209
public:
157210
static Conversion getOrigToSubst(AbstractionPattern origType,
158211
CanType substType,
@@ -176,6 +229,8 @@ class Conversion {
176229
AbstractionPattern outputOrigType,
177230
CanType outputSubstType,
178231
SILType outputLoweredTy) {
232+
assert(isAllowedConversion(inputSubstType, outputSubstType) &&
233+
"don't build subtype conversions that do existential erasures");
179234
return Conversion(inputOrigType, inputSubstType, inputLoweredTy,
180235
outputOrigType, outputSubstType, outputLoweredTy);
181236
}
@@ -184,6 +239,8 @@ class Conversion {
184239
CanType resultType, SILType loweredResultTy,
185240
bool isExplicit = false) {
186241
assert(isBridgingKind(kind));
242+
assert((kind != Subtype || isAllowedConversion(origType, resultType)) &&
243+
"disallowed conversion for subtype relationship");
187244
return Conversion(kind, origType, resultType, loweredResultTy, isExplicit);
188245
}
189246

@@ -274,6 +331,8 @@ class Conversion {
274331
/// this conversion.
275332
std::optional<Conversion> adjustForInitialForceValue() const;
276333

334+
OptionalInjectionConversion adjustForInitialOptionalInjection() const;
335+
277336
void dump() const LLVM_ATTRIBUTE_USED;
278337
void print(llvm::raw_ostream &out) const;
279338
};
@@ -318,10 +377,67 @@ class ConversionPeepholeHint {
318377
bool isForced() const { return Forced; }
319378
};
320379

380+
struct CombinedConversions {
381+
std::optional<Conversion> first;
382+
std::optional<Conversion> second;
383+
384+
explicit CombinedConversions() {}
385+
explicit CombinedConversions(const Conversion &first)
386+
: first(first) {}
387+
explicit CombinedConversions(const Conversion &first,
388+
const Conversion &second)
389+
: first(first), second(second) {}
390+
};
391+
321392
bool canPeepholeConversions(SILGenFunction &SGF,
322393
const Conversion &outer,
323394
const Conversion &inner);
324395

396+
/// The result of trying to combine an optional injection with an existing
397+
/// conversion.
398+
class OptionalInjectionConversion {
399+
enum Kind {
400+
None,
401+
Injection,
402+
Value
403+
};
404+
405+
std::optional<Conversion> conversion;
406+
Kind kind;
407+
408+
OptionalInjectionConversion(Kind kind, const Conversion &conv)
409+
: conversion(conv), kind(kind) {}
410+
411+
public:
412+
OptionalInjectionConversion() : kind(None) {}
413+
static OptionalInjectionConversion forInjection(const Conversion &conv) {
414+
return { Injection, conv };
415+
}
416+
static OptionalInjectionConversion forValue(const Conversion &conv) {
417+
return { Value, conv };
418+
}
419+
420+
/// Is the result of this combination a conversion that produces a
421+
/// value of the original optional type?
422+
bool isInjection() const {
423+
return kind == Injection;
424+
}
425+
const Conversion &getInjectionConversion() const {
426+
assert(isInjection());
427+
return *conversion;
428+
}
429+
430+
/// Is the result of this combination a conversion that produces a
431+
/// value of the element of the original optional type?
432+
bool isValue() const {
433+
return kind == Value;
434+
}
435+
const Conversion &getValueConversion() const {
436+
assert(isValue());
437+
return *conversion;
438+
}
439+
};
440+
325441
/// An initialization where we ultimately want to apply a conversion to
326442
/// the value before completing the initialization.
327443
///

‎lib/SILGen/SILGenConvert.cpp

+86-29
Original file line numberDiff line numberDiff line change
@@ -320,20 +320,40 @@ ManagedValue
320320
SILGenFunction::emitOptionalSome(SILLocation loc, SILType optTy,
321321
ValueProducerRef produceValue,
322322
SGFContext C) {
323-
// If the conversion is a bridging conversion from an optional type,
324-
// do a bridging conversion from the non-optional type instead.
325-
// TODO: should this be a general thing for all conversions?
323+
// If we're emitting into a conversion, try to peephole the
324+
// injection into it.
326325
if (auto optInit = C.getAsConversion()) {
327326
const auto &optConversion = optInit->getConversion();
328-
if (optConversion.isBridging()) {
329-
auto sourceValueType =
330-
optConversion.getBridgingSourceType().getOptionalObjectType();
331-
assert(sourceValueType);
332-
if (auto valueConversion =
333-
optConversion.adjustForInitialOptionalConversions(sourceValueType)){
334-
return optInit->emitWithAdjustedConversion(*this, loc, *valueConversion,
335-
produceValue);
336-
}
327+
328+
auto adjustment = optConversion.adjustForInitialOptionalInjection();
329+
330+
// If the adjustment gives us a conversion that produces an optional
331+
// value, that completely takes over emission. This generally happens
332+
// only because of bridging.
333+
if (adjustment.isInjection()) {
334+
return optInit->emitWithAdjustedConversion(*this, loc,
335+
adjustment.getInjectionConversion(),
336+
produceValue);
337+
338+
// If the adjustment gives us a conversion that produces a non-optional
339+
// value, we need to produce the value under that conversion and then
340+
// inject that into an optional. We can do that by recursing. This
341+
// will terminate because the recursive call to emitOptionalSome gets
342+
// passed a strictly "smaller" context: the parent context of the
343+
// converting context we were passed.
344+
} else if (adjustment.isValue()) {
345+
auto produceConvertedValue = [&](SILGenFunction &SGF,
346+
SILLocation loc,
347+
SGFContext C) {
348+
return SGF.emitConvertedRValue(loc, adjustment.getValueConversion(),
349+
C, produceValue);
350+
};
351+
auto result = emitOptionalSome(loc, optConversion.getLoweredResultType(),
352+
produceConvertedValue,
353+
optInit->getFinalContext());
354+
optInit->initWithConvertedValue(*this, loc, result);
355+
optInit->finishInitialization(*this);
356+
return ManagedValue::forInContext();
337357
}
338358
}
339359

@@ -1133,20 +1153,6 @@ void ConvertingInitialization::
11331153
});
11341154
}
11351155

1136-
namespace {
1137-
struct CombinedConversions {
1138-
std::optional<Conversion> first;
1139-
std::optional<Conversion> second;
1140-
1141-
explicit CombinedConversions() {}
1142-
explicit CombinedConversions(const Conversion &first)
1143-
: first(first) {}
1144-
explicit CombinedConversions(const Conversion &first,
1145-
const Conversion &second)
1146-
: first(first), second(second) {}
1147-
};
1148-
}
1149-
11501156
static std::optional<CombinedConversions>
11511157
combineConversions(SILGenFunction &SGF, const Conversion &outer,
11521158
const Conversion &inner);
@@ -1250,6 +1256,7 @@ ManagedValue Conversion::emit(SILGenFunction &SGF, SILLocation loc,
12501256
ManagedValue value, SGFContext C) const {
12511257
switch (getKind()) {
12521258
case AnyErasure:
1259+
case BridgingSubtype:
12531260
case Subtype:
12541261
return SGF.emitTransformedValue(loc, value, getBridgingSourceType(),
12551262
getBridgingResultType(), C);
@@ -1304,6 +1311,48 @@ ManagedValue Conversion::emit(SILGenFunction &SGF, SILLocation loc,
13041311
llvm_unreachable("bad kind");
13051312
}
13061313

1314+
OptionalInjectionConversion
1315+
Conversion::adjustForInitialOptionalInjection() const {
1316+
switch (getKind()) {
1317+
case Reabstract:
1318+
return OptionalInjectionConversion::forValue(
1319+
getReabstract(
1320+
getReabstractionInputOrigType().getOptionalObjectType(),
1321+
getReabstractionInputSubstType().getOptionalObjectType(),
1322+
getReabstractionInputLoweredType().getOptionalObjectType(),
1323+
getReabstractionOutputOrigType().getOptionalObjectType(),
1324+
getReabstractionOutputSubstType().getOptionalObjectType(),
1325+
getReabstractionOutputLoweredType().getOptionalObjectType())
1326+
);
1327+
1328+
case Subtype:
1329+
return OptionalInjectionConversion::forValue(
1330+
getSubtype(
1331+
getBridgingSourceType().getOptionalObjectType(),
1332+
getBridgingResultType().getOptionalObjectType(),
1333+
getBridgingLoweredResultType().getOptionalObjectType())
1334+
);
1335+
1336+
// TODO: can these actually happen?
1337+
case ForceOptional:
1338+
case ForceAndBridgeToObjC:
1339+
case BridgingSubtype:
1340+
return OptionalInjectionConversion();
1341+
1342+
case AnyErasure:
1343+
case BridgeToObjC:
1344+
case BridgeFromObjC:
1345+
case BridgeResultFromObjC:
1346+
return OptionalInjectionConversion::forInjection(
1347+
getBridging(getKind(), getBridgingSourceType().getOptionalObjectType(),
1348+
getBridgingResultType(),
1349+
getBridgingLoweredResultType(),
1350+
isBridgingExplicit())
1351+
);
1352+
}
1353+
llvm_unreachable("bad kind");
1354+
}
1355+
13071356
std::optional<Conversion>
13081357
Conversion::adjustForInitialOptionalConversions(CanType newSourceType) const {
13091358
switch (getKind()) {
@@ -1315,6 +1364,7 @@ Conversion::adjustForInitialOptionalConversions(CanType newSourceType) const {
13151364
case ForceAndBridgeToObjC:
13161365
return std::nullopt;
13171366

1367+
case BridgingSubtype:
13181368
case Subtype:
13191369
case AnyErasure:
13201370
case BridgeToObjC:
@@ -1336,6 +1386,7 @@ std::optional<Conversion> Conversion::adjustForInitialForceValue() const {
13361386
case BridgeResultFromObjC:
13371387
case ForceOptional:
13381388
case ForceAndBridgeToObjC:
1389+
case BridgingSubtype:
13391390
case Subtype:
13401391
return std::nullopt;
13411392

@@ -1389,6 +1440,8 @@ void Conversion::print(llvm::raw_ostream &out) const {
13891440
return printReabstraction(*this, out, "Reabstract");
13901441
case AnyErasure:
13911442
return printBridging(*this, out, "AnyErasure");
1443+
case BridgingSubtype:
1444+
return printBridging(*this, out, "BridgingSubtype");
13921445
case Subtype:
13931446
return printBridging(*this, out, "Subtype");
13941447
case ForceOptional:
@@ -1741,7 +1794,8 @@ combineBridging(SILGenFunction &SGF,
17411794
sourceType, resultType, loweredResultTy));
17421795
} else {
17431796
return applyPeephole(
1744-
Conversion::getSubtype(sourceType, resultType, loweredResultTy));
1797+
Conversion::getBridging(Conversion::BridgingSubtype,
1798+
sourceType, resultType, loweredResultTy));
17451799
}
17461800
}
17471801

@@ -1769,7 +1823,8 @@ combineBridging(SILGenFunction &SGF,
17691823
// Look for a subtype relationship between the source and destination.
17701824
if (areRelatedTypesForBridgingPeephole(sourceType, resultType)) {
17711825
return applyPeephole(
1772-
Conversion::getSubtype(sourceType, resultType, loweredResultTy));
1826+
Conversion::getBridging(Conversion::BridgingSubtype,
1827+
sourceType, resultType, loweredResultTy));
17731828
}
17741829

17751830
// If the inner conversion is a result conversion that removes
@@ -1784,7 +1839,8 @@ combineBridging(SILGenFunction &SGF,
17841839
sourceType = sourceValueType;
17851840
loweredSourceTy = loweredSourceTy.getOptionalObjectType();
17861841
return applyPeephole(
1787-
Conversion::getSubtype(sourceValueType, resultType, loweredResultTy));
1842+
Conversion::getBridging(Conversion::BridgingSubtype,
1843+
sourceValueType, resultType, loweredResultTy));
17881844
}
17891845
}
17901846
}
@@ -1816,6 +1872,7 @@ combineConversions(SILGenFunction &SGF, const Conversion &outer,
18161872
return std::nullopt;
18171873

18181874
case Conversion::AnyErasure:
1875+
case Conversion::BridgingSubtype:
18191876
case Conversion::BridgeFromObjC:
18201877
case Conversion::BridgeResultFromObjC:
18211878
// TODO: maybe peephole bridging through a Swift type?

0 commit comments

Comments
 (0)
Please sign in to comment.