Skip to content

Commit 196d47b

Browse files
committed
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 4a4833c commit 196d47b

File tree

4 files changed

+237
-37
lines changed

4 files changed

+237
-37
lines changed

lib/SILGen/Conversion.h

Lines changed: 117 additions & 1 deletion
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

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

@@ -1141,20 +1161,6 @@ void ConvertingInitialization::
11411161
});
11421162
}
11431163

1144-
namespace {
1145-
struct CombinedConversions {
1146-
std::optional<Conversion> first;
1147-
std::optional<Conversion> second;
1148-
1149-
explicit CombinedConversions() {}
1150-
explicit CombinedConversions(const Conversion &first)
1151-
: first(first) {}
1152-
explicit CombinedConversions(const Conversion &first,
1153-
const Conversion &second)
1154-
: first(first), second(second) {}
1155-
};
1156-
}
1157-
11581164
static std::optional<CombinedConversions>
11591165
combineConversions(SILGenFunction &SGF, const Conversion &outer,
11601166
const Conversion &inner);
@@ -1258,6 +1264,7 @@ ManagedValue Conversion::emit(SILGenFunction &SGF, SILLocation loc,
12581264
ManagedValue value, SGFContext C) const {
12591265
switch (getKind()) {
12601266
case AnyErasure:
1267+
case BridgingSubtype:
12611268
case Subtype:
12621269
return SGF.emitTransformedValue(loc, value, getBridgingSourceType(),
12631270
getBridgingResultType(), C);
@@ -1312,6 +1319,48 @@ ManagedValue Conversion::emit(SILGenFunction &SGF, SILLocation loc,
13121319
llvm_unreachable("bad kind");
13131320
}
13141321

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

1375+
case BridgingSubtype:
13261376
case Subtype:
13271377
case AnyErasure:
13281378
case BridgeToObjC:
@@ -1344,6 +1394,7 @@ std::optional<Conversion> Conversion::adjustForInitialForceValue() const {
13441394
case BridgeResultFromObjC:
13451395
case ForceOptional:
13461396
case ForceAndBridgeToObjC:
1397+
case BridgingSubtype:
13471398
case Subtype:
13481399
return std::nullopt;
13491400

@@ -1397,6 +1448,8 @@ void Conversion::print(llvm::raw_ostream &out) const {
13971448
return printReabstraction(*this, out, "Reabstract");
13981449
case AnyErasure:
13991450
return printBridging(*this, out, "AnyErasure");
1451+
case BridgingSubtype:
1452+
return printBridging(*this, out, "BridgingSubtype");
14001453
case Subtype:
14011454
return printBridging(*this, out, "Subtype");
14021455
case ForceOptional:
@@ -1749,7 +1802,8 @@ combineBridging(SILGenFunction &SGF,
17491802
sourceType, resultType, loweredResultTy));
17501803
} else {
17511804
return applyPeephole(
1752-
Conversion::getSubtype(sourceType, resultType, loweredResultTy));
1805+
Conversion::getBridging(Conversion::BridgingSubtype,
1806+
sourceType, resultType, loweredResultTy));
17531807
}
17541808
}
17551809

@@ -1777,7 +1831,8 @@ combineBridging(SILGenFunction &SGF,
17771831
// Look for a subtype relationship between the source and destination.
17781832
if (areRelatedTypesForBridgingPeephole(sourceType, resultType)) {
17791833
return applyPeephole(
1780-
Conversion::getSubtype(sourceType, resultType, loweredResultTy));
1834+
Conversion::getBridging(Conversion::BridgingSubtype,
1835+
sourceType, resultType, loweredResultTy));
17811836
}
17821837

17831838
// If the inner conversion is a result conversion that removes
@@ -1792,7 +1847,8 @@ combineBridging(SILGenFunction &SGF,
17921847
sourceType = sourceValueType;
17931848
loweredSourceTy = loweredSourceTy.getOptionalObjectType();
17941849
return applyPeephole(
1795-
Conversion::getSubtype(sourceValueType, resultType, loweredResultTy));
1850+
Conversion::getBridging(Conversion::BridgingSubtype,
1851+
sourceValueType, resultType, loweredResultTy));
17961852
}
17971853
}
17981854
}
@@ -1824,6 +1880,7 @@ combineConversions(SILGenFunction &SGF, const Conversion &outer,
18241880
return std::nullopt;
18251881

18261882
case Conversion::AnyErasure:
1883+
case Conversion::BridgingSubtype:
18271884
case Conversion::BridgeFromObjC:
18281885
case Conversion::BridgeResultFromObjC:
18291886
// TODO: maybe peephole bridging through a Swift type?

0 commit comments

Comments
 (0)