Skip to content

Commit 54b22f4

Browse files
committed
Re-apply "IRGen: Make case numbering consistent for enums with empty payloads"
Now with a small fix.
1 parent 0dd03ba commit 54b22f4

File tree

5 files changed

+116
-39
lines changed

5 files changed

+116
-39
lines changed

lib/IRGen/GenDecl.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2588,7 +2588,14 @@ StringRef IRGenModule::mangleType(CanType type, SmallVectorImpl<char> &buffer) {
25882588
return StringRef(buffer.data(), buffer.size());
25892589
}
25902590

2591-
/// Is the given declaration resilient?
2591+
/// Do we have to use resilient access patterns when working with this
2592+
/// declaration?
2593+
///
2594+
/// IRGen is primarily concerned with resilient handling of the following:
2595+
/// - For structs, a struct's size might change
2596+
/// - For enums, new cases can be added
2597+
/// - For classes, the superclass might change the size or number
2598+
/// of stored properties
25922599
bool IRGenModule::isResilient(Decl *D, ResilienceScope scope) {
25932600
auto NTD = dyn_cast<NominalTypeDecl>(D);
25942601
if (!NTD)
@@ -2604,6 +2611,23 @@ bool IRGenModule::isResilient(Decl *D, ResilienceScope scope) {
26042611
llvm_unreachable("Bad resilience scope");
26052612
}
26062613

2614+
// The most general resilience scope where the given declaration is visible.
2615+
ResilienceScope IRGenModule::getResilienceScopeForAccess(NominalTypeDecl *decl) {
2616+
if (decl->getModuleContext() == SILMod->getSwiftModule() &&
2617+
decl->getFormalAccess() != Accessibility::Public)
2618+
return ResilienceScope::Component;
2619+
return ResilienceScope::Universal;
2620+
}
2621+
2622+
// The most general resilience scope which has knowledge of the declaration's
2623+
// layout. Calling isResilient() with this scope will always return false.
2624+
ResilienceScope IRGenModule::getResilienceScopeForLayout(NominalTypeDecl *decl) {
2625+
if (isResilient(decl, ResilienceScope::Universal))
2626+
return ResilienceScope::Component;
2627+
2628+
return getResilienceScopeForAccess(decl);
2629+
}
2630+
26072631
/// Fetch the witness table access function for a protocol conformance.
26082632
llvm::Function *
26092633
IRGenModule::getAddrOfWitnessTableAccessFunction(

lib/IRGen/GenEnum.cpp

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2707,6 +2707,7 @@ namespace {
27072707

27082708
CopyDestroyStrategy CopyDestroyKind;
27092709
ReferenceCounting Refcounting;
2710+
bool AllowFixedLayoutOptimizations;
27102711

27112712
static EnumPayloadSchema getPayloadSchema(ArrayRef<Element> payloads) {
27122713
// TODO: We might be able to form a nicer schema if the payload elements
@@ -2726,6 +2727,7 @@ namespace {
27262727
MultiPayloadEnumImplStrategy(IRGenModule &IGM,
27272728
TypeInfoKind tik,
27282729
IsFixedSize_t alwaysFixedSize,
2730+
bool allowFixedLayoutOptimizations,
27292731
unsigned NumElements,
27302732
std::vector<Element> &&WithPayload,
27312733
std::vector<Element> &&WithNoPayload)
@@ -2734,7 +2736,8 @@ namespace {
27342736
std::move(WithPayload),
27352737
std::move(WithNoPayload),
27362738
getPayloadSchema(WithPayload)),
2737-
CopyDestroyKind(Normal)
2739+
CopyDestroyKind(Normal),
2740+
AllowFixedLayoutOptimizations(allowFixedLayoutOptimizations)
27382741
{
27392742
assert(ElementsWithPayload.size() > 1);
27402743

@@ -2794,7 +2797,7 @@ namespace {
27942797
// we might need the payload size if from another module the enum has
27952798
// a dynamic size, which can happen if the enum contains a resilient
27962799
// payload.
2797-
return !AlwaysFixedSize;
2800+
return !AllowFixedLayoutOptimizations;
27982801
}
27992802

28002803
unsigned getPayloadSizeForMetadata() const override {
@@ -4595,20 +4598,26 @@ EnumImplStrategy *EnumImplStrategy::get(TypeConverter &TC,
45954598
unsigned numElements = 0;
45964599
TypeInfoKind tik = Loadable;
45974600
IsFixedSize_t alwaysFixedSize = IsFixedSize;
4601+
bool allowFixedLayoutOptimizations = true;
45984602
std::vector<Element> elementsWithPayload;
45994603
std::vector<Element> elementsWithNoPayload;
46004604

4601-
// The most general resilience scope that can have knowledge of this
4602-
// enum's layout. If all payload types have a fixed size in this
4603-
// resilience scope, we can make further assumptions to optimize
4604-
// layout.
4605-
ResilienceScope scope = ResilienceScope::Universal;
4605+
// Resilient enums are manipulated as opaque values, except we still
4606+
// make the following assumptions:
4607+
// 1) Physical case indices won't change
4608+
// 2) The indirect-ness of cases won't change
4609+
// 3) Payload types won't change in a non-resilient way
4610+
bool isResilient = TC.IGM.isResilient(theEnum, ResilienceScope::Component);
4611+
4612+
// The most general resilience scope where the enum type is visible.
4613+
// Case numbering must not depend on any information that is not static
4614+
// in this resilience scope.
4615+
ResilienceScope accessScope = TC.IGM.getResilienceScopeForAccess(theEnum);
46064616

4607-
// TODO: Replace this with 'public or internal with @availability' check
4608-
// once that is in place
4609-
if (theEnum->getFormalAccess() != Accessibility::Public ||
4610-
TC.IGM.isResilient(theEnum, ResilienceScope::Universal))
4611-
scope = ResilienceScope::Component;
4617+
// The most general resilience scope where the enum's layout is known.
4618+
// Fixed-size optimizations can be applied if all payload types are
4619+
// fixed-size from this resilience scope.
4620+
ResilienceScope layoutScope = TC.IGM.getResilienceScopeForLayout(theEnum);
46124621

46134622
for (auto elt : theEnum->getAllElements()) {
46144623
numElements++;
@@ -4638,14 +4647,20 @@ EnumImplStrategy *EnumImplStrategy::get(TypeConverter &TC,
46384647
= TC.tryGetCompleteTypeInfo(origArgLoweredTy.getSwiftRValueType());
46394648
assert(origArgTI && "didn't complete type info?!");
46404649

4641-
// If the unsubstituted argument contains a generic parameter type, or if
4642-
// the substituted argument is not universally fixed-size, we need to
4643-
// constrain our layout optimizations to what the runtime can reproduce.
4644-
if (!origArgTI->isFixedSize(scope))
4645-
alwaysFixedSize = IsNotFixedSize;
4646-
4647-
auto loadableOrigArgTI = dyn_cast<LoadableTypeInfo>(origArgTI);
4648-
if (loadableOrigArgTI && loadableOrigArgTI->isKnownEmpty()) {
4650+
// If the unsubstituted argument contains a generic parameter type, or
4651+
// is not fixed-size in all resilience domains that have knowledge of
4652+
// this enum's layout, we need to constrain our layout optimizations to
4653+
// what the runtime can reproduce.
4654+
if (!isResilient &&
4655+
!origArgTI->isFixedSize(layoutScope))
4656+
allowFixedLayoutOptimizations = false;
4657+
4658+
// If the payload is empty, turn the case into a no-payload case, but
4659+
// only if case numbering remains unchanged from all resilience domains
4660+
// that can see the enum.
4661+
if (origArgTI->isFixedSize(accessScope) &&
4662+
isa<LoadableTypeInfo>(origArgTI) &&
4663+
cast<LoadableTypeInfo>(origArgTI)->isKnownEmpty()) {
46494664
elementsWithNoPayload.push_back({elt, nullptr, nullptr});
46504665
} else {
46514666
// *Now* apply the substitutions and get the type info for the instance's
@@ -4655,16 +4670,22 @@ EnumImplStrategy *EnumImplStrategy::get(TypeConverter &TC,
46554670
auto *substArgTI = &TC.IGM.getTypeInfo(fieldTy);
46564671

46574672
elementsWithPayload.push_back({elt, substArgTI, origArgTI});
4658-
if (!substArgTI->isFixedSize())
4659-
tik = Opaque;
4660-
else if (!substArgTI->isLoadable() && tik > Fixed)
4661-
tik = Fixed;
46624673

4663-
// If the substituted argument contains a type that is not universally
4664-
// fixed-size, we need to constrain our layout optimizations to what
4665-
// the runtime can reproduce.
4666-
if (!substArgTI->isFixedSize(scope))
4667-
alwaysFixedSize = IsNotFixedSize;
4674+
if (!isResilient) {
4675+
if (!substArgTI->isFixedSize(ResilienceScope::Component))
4676+
tik = Opaque;
4677+
else if (!substArgTI->isLoadable() && tik > Fixed)
4678+
tik = Fixed;
4679+
4680+
// If the substituted argument contains a type that is not fixed-size
4681+
// in all resilience domains that have knowledge of this enum's layout,
4682+
// we need to constrain our layout optimizations to what the runtime
4683+
// can reproduce.
4684+
if (!substArgTI->isFixedSize(layoutScope)) {
4685+
alwaysFixedSize = IsNotFixedSize;
4686+
allowFixedLayoutOptimizations = false;
4687+
}
4688+
}
46684689
}
46694690
}
46704691

@@ -4673,12 +4694,7 @@ EnumImplStrategy *EnumImplStrategy::get(TypeConverter &TC,
46734694
+ elementsWithNoPayload.size()
46744695
&& "not all elements accounted for");
46754696

4676-
// Resilient enums are manipulated as opaque values, except we still
4677-
// make the following assumptions:
4678-
// 1) Physical case indices won't change
4679-
// 2) The indirect-ness of cases won't change
4680-
// 3) Payload types won't change in a non-resilient way
4681-
if (TC.IGM.isResilient(theEnum, ResilienceScope::Component)) {
4697+
if (isResilient) {
46824698
return new ResilientEnumImplStrategy(TC.IGM,
46834699
numElements,
46844700
std::move(elementsWithPayload),
@@ -4702,6 +4718,7 @@ EnumImplStrategy *EnumImplStrategy::get(TypeConverter &TC,
47024718
std::move(elementsWithNoPayload));
47034719
if (elementsWithPayload.size() > 1)
47044720
return new MultiPayloadEnumImplStrategy(TC.IGM, tik, alwaysFixedSize,
4721+
allowFixedLayoutOptimizations,
47054722
numElements,
47064723
std::move(elementsWithPayload),
47074724
std::move(elementsWithNoPayload));
@@ -5216,7 +5233,7 @@ MultiPayloadEnumImplStrategy::completeFixedLayout(TypeConverter &TC,
52165233
// The runtime currently does not track spare bits, so we can't use them
52175234
// if the type is layout-dependent. (Even when the runtime does, it will
52185235
// likely only track a subset of the spare bits.)
5219-
if (!AlwaysFixedSize || TIK < Loadable) {
5236+
if (!AllowFixedLayoutOptimizations || TIK < Loadable) {
52205237
if (CommonSpareBits.size() < payloadBits)
52215238
CommonSpareBits.extendWithClearBits(payloadBits);
52225239
continue;

lib/IRGen/IRGenModule.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,8 @@ class IRGenModule {
504504
}
505505

506506
bool isResilient(Decl *decl, ResilienceScope scope);
507+
ResilienceScope getResilienceScopeForAccess(NominalTypeDecl *decl);
508+
ResilienceScope getResilienceScopeForLayout(NominalTypeDecl *decl);
507509

508510
SpareBitVector getSpareBitsForType(llvm::Type *scalarTy, Size size);
509511

test/Inputs/resilient_enum.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ public struct Color {
4040
case Bespoke(Color, Color)
4141
}
4242

43-
// Fixed-layout enum with resilient members
44-
4543
// Resilient enum
4644
public enum Medium {
4745
// Empty cases
@@ -60,6 +58,21 @@ public indirect enum IndirectApproach {
6058
case Angle(Double)
6159
}
6260

61+
// Resilient enum with resilient empty payload case
62+
public struct EmptyStruct {
63+
public init() {}
64+
}
65+
66+
public enum ResilientEnumWithEmptyCase {
67+
case A // should always be case 1
68+
case B // should always be case 2
69+
case Empty(EmptyStruct) // should always be case 0
70+
}
71+
72+
public func getResilientEnumWithEmptyCase() -> [ResilientEnumWithEmptyCase] {
73+
return [.A, .B, .Empty(EmptyStruct())]
74+
}
75+
6376
// Specific enum implementations for executable tests
6477
public enum ResilientEmptyEnum {
6578
case X

test/Interpreter/enum_resilience.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,4 +379,25 @@ ResilientEnumTestSuite.test("DynamicLayoutMultiPayload2") {
379379
expectEqual(b, [0, 1, 2, 3])
380380
}
381381

382+
// Make sure case numbers round-trip if payload has zero size
383+
384+
ResilientEnumTestSuite.test("ResilientEnumWithEmptyCase") {
385+
let a: [ResilientEnumWithEmptyCase] = getResilientEnumWithEmptyCase()
386+
387+
let b: [Int] = a.map {
388+
switch $0 {
389+
case .A:
390+
return 0
391+
case .B:
392+
return 1
393+
case .Empty:
394+
return 2
395+
default:
396+
return -1
397+
}
398+
}
399+
400+
expectEqual(b, [0, 1, 2])
401+
}
402+
382403
runAllTests()

0 commit comments

Comments
 (0)