Skip to content

Commit 950ee3b

Browse files
committed
IRGen: Generalize the nullable optimization for single-payload enums.
Augment the `isSingleRetainablePointer` check that allows IRGen to avoid adding branching around retain/release operations on enums that use the null pointer extra inhabitant with a more general "can value witness extra inhabitants" method on TypeInfo, which says whether a type's retain/release operations are safe to invoke on some or all of its extra inhabitants. This lets us generalize the optimization to include things like `String?` or `ClassProtocol?` which are common types with a nullable pointer in them.
1 parent 869e579 commit 950ee3b

File tree

10 files changed

+252
-7
lines changed

10 files changed

+252
-7
lines changed

lib/IRGen/GenEnum.cpp

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,14 @@ namespace {
812812
return false;
813813
return singleton->isSingleRetainablePointer(expansion, rc);
814814
}
815+
816+
bool canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
817+
unsigned index) const override {
818+
auto singleton = getSingleton();
819+
if (!singleton)
820+
return false;
821+
return singleton->canValueWitnessExtraInhabitantsUpTo(IGM, index);
822+
}
815823
};
816824

817825
/// Implementation strategy for no-payload enums, in other words, 'C-like'
@@ -1674,6 +1682,9 @@ namespace {
16741682
/// copy and destroy can pass through to retain and release entry
16751683
/// points.
16761684
NullableRefcounted,
1685+
/// The payload's value witnesses can handle the extra inhabitants we use
1686+
/// for no-payload tags, so we can forward all our calls to them.
1687+
ForwardToPayload,
16771688
};
16781689

16791690
CopyDestroyStrategy CopyDestroyKind;
@@ -1802,6 +1813,26 @@ namespace {
18021813
&& cast<FixedTypeInfo>(payloadTI)
18031814
.getFixedExtraInhabitantCount(IGM) > 0) {
18041815
CopyDestroyKind = NullableRefcounted;
1816+
// If the payload's value witnesses can accept the extra inhabitants we
1817+
// use, then we can forward to them instead of checking for empty tags.
1818+
// TODO: Do this for all types, not just loadable types.
1819+
} else if (tik >= TypeInfoKind::Loadable) {
1820+
ReferenceCounting refCounting;
1821+
(void)refCounting;
1822+
// Ensure that asking `canValueWitnessExtraInhabitantsUpTo` doesn't
1823+
// regress any places we were previously able to ask
1824+
// `isSingleRetainablePointer`.
1825+
assert(
1826+
(!payloadTI.isSingleRetainablePointer(ResilienceExpansion::Maximal,
1827+
&refCounting)
1828+
|| payloadTI.canValueWitnessExtraInhabitantsUpTo(IGM, 0))
1829+
&& "single-refcounted thing should be able to value-witness "
1830+
"extra inhabitant zero");
1831+
1832+
unsigned numTags = ElementsWithNoPayload.size();
1833+
if (payloadTI.canValueWitnessExtraInhabitantsUpTo(IGM, numTags-1)) {
1834+
CopyDestroyKind = ForwardToPayload;
1835+
}
18051836
}
18061837
}
18071838

@@ -1811,6 +1842,12 @@ namespace {
18111842
assert(NumExtraInhabitantTagValues != ~0U);
18121843
return NumExtraInhabitantTagValues;
18131844
}
1845+
1846+
bool canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
1847+
unsigned index) const override {
1848+
return getPayloadTypeInfo().canValueWitnessExtraInhabitantsUpTo(IGM,
1849+
index + NumExtraInhabitantTagValues);
1850+
}
18141851

18151852
/// Emit a call into the runtime to get the current enum payload tag.
18161853
/// This returns a tag index in the range [0..NumElements-1].
@@ -2412,6 +2449,7 @@ namespace {
24122449
switch (CopyDestroyKind) {
24132450
case NullableRefcounted:
24142451
return IGM.getReferenceType(Refcounting);
2452+
case ForwardToPayload:
24152453
case POD:
24162454
case Normal:
24172455
case ABIInaccessible:
@@ -2427,6 +2465,7 @@ namespace {
24272465
case NullableRefcounted:
24282466
IGF.emitStrongRetain(ptr, Refcounting, IGF.getDefaultAtomicity());
24292467
return;
2468+
case ForwardToPayload:
24302469
case POD:
24312470
case Normal:
24322471
case ABIInaccessible:
@@ -2440,6 +2479,7 @@ namespace {
24402479
case NullableRefcounted:
24412480
IGF.emitFixLifetime(ptr);
24422481
return;
2482+
case ForwardToPayload:
24432483
case POD:
24442484
case Normal:
24452485
case ABIInaccessible:
@@ -2453,6 +2493,7 @@ namespace {
24532493
case NullableRefcounted:
24542494
IGF.emitStrongRelease(ptr, Refcounting, IGF.getDefaultAtomicity());
24552495
return;
2496+
case ForwardToPayload:
24562497
case POD:
24572498
case Normal:
24582499
case ABIInaccessible:
@@ -2471,6 +2512,25 @@ namespace {
24712512
if (extraTag)
24722513
out.add(extraTag);
24732514
}
2515+
2516+
void unpackIntoPayloadExplosion(IRGenFunction &IGF,
2517+
Explosion &asEnumIn,
2518+
Explosion &asPayloadOut) const {
2519+
auto &payloadTI = getLoadablePayloadTypeInfo();
2520+
// Unpack as an instance of the payload type and use its copy operation.
2521+
auto srcBits = EnumPayload::fromExplosion(IGF.IGM, asEnumIn,
2522+
PayloadSchema);
2523+
payloadTI.unpackFromEnumPayload(IGF, srcBits, asPayloadOut, 0);
2524+
}
2525+
2526+
void packFromPayloadExplosion(IRGenFunction &IGF,
2527+
Explosion &asPayloadIn,
2528+
Explosion &asEnumOut) const {
2529+
auto &payloadTI = getLoadablePayloadTypeInfo();
2530+
auto payload = EnumPayload::zero(IGF.IGM, PayloadSchema);
2531+
payloadTI.packIntoEnumPayload(IGF, payload, asPayloadIn, 0);
2532+
payload.explode(IGF.IGM, asEnumOut);
2533+
}
24742534

24752535
public:
24762536
void copy(IRGenFunction &IGF, Explosion &src, Explosion &dest,
@@ -2530,6 +2590,15 @@ namespace {
25302590
dest.add(val);
25312591
return;
25322592
}
2593+
2594+
case ForwardToPayload: {
2595+
auto &payloadTI = getLoadablePayloadTypeInfo();
2596+
Explosion srcAsPayload, destAsPayload;
2597+
unpackIntoPayloadExplosion(IGF, src, srcAsPayload);
2598+
payloadTI.copy(IGF, srcAsPayload, destAsPayload, atomicity);
2599+
packFromPayloadExplosion(IGF, destAsPayload, dest);
2600+
return;
2601+
}
25332602
}
25342603
}
25352604

@@ -2585,6 +2654,16 @@ namespace {
25852654
releaseRefcountedPayload(IGF, ptr);
25862655
return;
25872656
}
2657+
2658+
case ForwardToPayload: {
2659+
auto &payloadTI = getLoadablePayloadTypeInfo();
2660+
// Unpack as an instance of the payload type and use its consume
2661+
// operation.
2662+
Explosion srcAsPayload;
2663+
unpackIntoPayloadExplosion(IGF, src, srcAsPayload);
2664+
payloadTI.consume(IGF, srcAsPayload, atomicity);
2665+
return;
2666+
}
25882667
}
25892668
}
25902669

@@ -2630,6 +2709,16 @@ namespace {
26302709
fixLifetimeOfRefcountedPayload(IGF, ptr);
26312710
return;
26322711
}
2712+
2713+
case ForwardToPayload: {
2714+
auto &payloadTI = getLoadablePayloadTypeInfo();
2715+
// Unpack as an instance of the payload type and use its fixLifetime
2716+
// operation.
2717+
Explosion srcAsPayload;
2718+
unpackIntoPayloadExplosion(IGF, srcAsPayload, srcAsPayload);
2719+
payloadTI.fixLifetime(IGF, src);
2720+
return;
2721+
}
26332722
}
26342723

26352724
}
@@ -2667,13 +2756,22 @@ namespace {
26672756
}
26682757

26692758
case NullableRefcounted: {
2670-
// Load the value as swift.refcounted, then hand to swift_release.
2759+
// Apply the payload's operation.
26712760
addr = IGF.Builder.CreateBitCast(
26722761
addr, getRefcountedPtrType(IGM)->getPointerTo());
26732762
llvm::Value *ptr = IGF.Builder.CreateLoad(addr);
26742763
releaseRefcountedPayload(IGF, ptr);
26752764
return;
26762765
}
2766+
2767+
case ForwardToPayload: {
2768+
auto &payloadTI = getPayloadTypeInfo();
2769+
// Apply the payload's operation.
2770+
addr = IGF.Builder.CreateBitCast(
2771+
addr, payloadTI.getStorageType()->getPointerTo());
2772+
payloadTI.destroy(IGF, addr, getPayloadType(IGF.IGM, T), isOutlined);
2773+
return;
2774+
}
26772775
}
26782776
} else {
26792777
if (!IGF.IGM.getOptions().UseTypeLayoutValueHandling) {
@@ -2702,8 +2800,9 @@ namespace {
27022800
Address addr) const override {
27032801
// There is no need to bitcast from the enum address. Loading from the
27042802
// reference type emits a bitcast to the proper reference type first.
2705-
return cast<LoadableTypeInfo>(getPayloadTypeInfo()).loadRefcountedPtr(
2706-
IGF, loc, addr).getValue();
2803+
return getLoadablePayloadTypeInfo()
2804+
.loadRefcountedPtr(IGF, loc, addr)
2805+
.getValue();
27072806
}
27082807
private:
27092808
llvm::ConstantInt *getZeroExtraTagConstant(IRGenModule &IGM) const {
@@ -2843,6 +2942,18 @@ namespace {
28432942
releaseRefcountedPayload(IGF, oldPtr);
28442943
return;
28452944
}
2945+
2946+
case ForwardToPayload: {
2947+
auto &payloadTI = getPayloadTypeInfo();
2948+
// Apply the payload's operation.
2949+
dest = IGF.Builder.CreateBitCast(dest,
2950+
payloadTI.getStorageType()->getPointerTo());
2951+
src = IGF.Builder.CreateBitCast(src,
2952+
payloadTI.getStorageType()->getPointerTo());
2953+
payloadTI.assign(IGF, dest, src, isTake,
2954+
getPayloadType(IGF.IGM, T), isOutlined);
2955+
return;
2956+
}
28462957
}
28472958

28482959
}
@@ -2911,6 +3022,18 @@ namespace {
29113022
IGF.Builder.CreateStore(srcPtr, destAddr);
29123023
return;
29133024
}
3025+
3026+
case ForwardToPayload: {
3027+
auto &payloadTI = getPayloadTypeInfo();
3028+
// Apply the payload's operation.
3029+
dest = IGF.Builder.CreateBitCast(dest,
3030+
payloadTI.getStorageType()->getPointerTo());
3031+
src = IGF.Builder.CreateBitCast(src,
3032+
payloadTI.getStorageType()->getPointerTo());
3033+
payloadTI.initialize(IGF, dest, src, isTake,
3034+
getPayloadType(IGF.IGM, T), isOutlined);
3035+
return;
3036+
}
29143037
}
29153038
}
29163039

@@ -6093,6 +6216,10 @@ namespace {
60936216
SILType ty) const override {
60946217
return Strategy.buildTypeLayoutEntry(IGM, ty);
60956218
}
6219+
bool canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
6220+
unsigned index) const override {
6221+
return Strategy.canValueWitnessExtraInhabitantsUpTo(IGM, index);
6222+
}
60966223
};
60976224

60986225
template <class Base>

lib/IRGen/GenEnum.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,8 +463,10 @@ class EnumImplStrategy {
463463

464464
void emitResilientTagIndices(IRGenModule &IGM) const;
465465

466-
protected:
467-
466+
virtual bool canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
467+
unsigned index) const {
468+
return false;
469+
}
468470

469471
private:
470472
EnumImplStrategy(const EnumImplStrategy &) = delete;

lib/IRGen/GenExistential.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,11 @@ class ClassExistentialTypeInfo final
10491049
if (refcounting) *refcounting = Refcounting;
10501050
return getNumStoredProtocols() == 0;
10511051
}
1052+
1053+
bool canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
1054+
unsigned index) const override {
1055+
return index == 0;
1056+
}
10521057

10531058
const LoadableTypeInfo &
10541059
getValueTypeInfoForExtraInhabitants(IRGenModule &IGM) const {

lib/IRGen/GenRecord.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,28 @@ class RecordTypeInfo<Impl, Base, FieldImpl,
595595
return 0;
596596
}
597597

598+
bool canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
599+
unsigned index) const override {
600+
if (auto field = asImpl().getFixedExtraInhabitantProvidingField(IGM)) {
601+
// The non-extra-inhabitant-providing fields of the type must be
602+
// trivial, because an enum may contain garbage values in those fields'
603+
// storage which the value witness operation won't handle.
604+
for (auto &otherField : asImpl().getFields()) {
605+
if (field == &otherField)
606+
continue;
607+
auto &ti = otherField.getTypeInfo();
608+
if (!ti.isPOD(ResilienceExpansion::Maximal)) {
609+
return false;
610+
}
611+
}
612+
613+
return field->getTypeInfo()
614+
.canValueWitnessExtraInhabitantsUpTo(IGM, index);
615+
}
616+
617+
return false;
618+
}
619+
598620
APInt getFixedExtraInhabitantValue(IRGenModule &IGM,
599621
unsigned bits,
600622
unsigned index) const override {

lib/IRGen/GenStruct.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ namespace {
219219
return false;
220220
return fields[0].getTypeInfo().isSingleRetainablePointer(expansion, rc);
221221
}
222-
222+
223223
void verify(IRGenTypeVerifierFunction &IGF,
224224
llvm::Value *metadata,
225225
SILType structType) const override {

lib/IRGen/GenValueWitness.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,18 @@ FixedPacking TypeInfo::getFixedPacking(IRGenModule &IGM) const {
12451245
return FixedPacking::Allocate;
12461246
}
12471247

1248+
bool TypeInfo::canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
1249+
unsigned index) const {
1250+
// If this type is POD, then its value witnesses are trivial, so can handle
1251+
// any bit pattern.
1252+
if (isPOD(ResilienceExpansion::Maximal)) {
1253+
return true;
1254+
}
1255+
1256+
// By default, assume that extra inhabitants must be branched out on.
1257+
return false;
1258+
}
1259+
12481260
Address TypeInfo::indexArray(IRGenFunction &IGF, Address base,
12491261
llvm::Value *index, SILType T) const {
12501262
// The stride of a Swift type may not match its LLVM size. If we know we have

lib/IRGen/HeapTypeInfo.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ class HeapTypeInfo
7575
return true;
7676
}
7777

78+
bool canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
79+
unsigned index) const override {
80+
// Refcounting functions are no-ops when passed a null pointer, which is the
81+
// first extra inhabitant.
82+
return index == 0;
83+
}
84+
7885
IsaEncoding getIsaEncoding(ResilienceExpansion expansion) const {
7986
switch (asDerived().getReferenceCounting()) {
8087
// We can access the isa of pure Swift heap objects directly.

lib/IRGen/TypeInfo.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,17 @@ class TypeInfo {
408408
/// have extra inhabitants based on type arguments?
409409
virtual bool mayHaveExtraInhabitants(IRGenModule &IGM) const = 0;
410410

411+
/// Returns true if the value witness operations on this type work correctly
412+
/// with extra inhabitants up to the given index.
413+
///
414+
/// An example of this is retainable pointers. The first extra inhabitant for
415+
/// these types is the null pointer, on which swift_retain is a harmless
416+
/// no-op. If this predicate returns true, then a single-payload enum with
417+
/// this type as its payload (like Optional<T>) can avoid additional branching
418+
/// on the enum tag for value witness operations.
419+
virtual bool canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
420+
unsigned index) const;
421+
411422
/// Get the tag of a single payload enum with a payload of this type (\p T) e.g
412423
/// Optional<T>.
413424
virtual llvm::Value *getEnumTagSinglePayload(IRGenFunction &IGF,

0 commit comments

Comments
 (0)