Skip to content

Commit 86e6b81

Browse files
committed
IRGen: Make case numbering consistent for enums with empty payloads
If an enum case has a payload but the unsubstituted payload type is zero-sized, we would convert the case into a no-payload case. This was valid when the only invariant that had to be preserved is that an enum's layout is the same between all substitutions of a generic type. However this is now wrong if the payload type is resiliently-sized, because other resilience domains may not have knowledge that it is zero-sized. The new utility methods will also be used in class layout.
1 parent a456b98 commit 86e6b81

File tree

5 files changed

+105
-35
lines changed

5 files changed

+105
-35
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: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4598,17 +4598,22 @@ EnumImplStrategy *EnumImplStrategy::get(TypeConverter &TC,
45984598
std::vector<Element> elementsWithPayload;
45994599
std::vector<Element> elementsWithNoPayload;
46004600

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;
4606-
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;
4601+
// Resilient enums are manipulated as opaque values, except we still
4602+
// make the following assumptions:
4603+
// 1) Physical case indices won't change
4604+
// 2) The indirect-ness of cases won't change
4605+
// 3) Payload types won't change in a non-resilient way
4606+
bool isResilient = TC.IGM.isResilient(theEnum, ResilienceScope::Component);
4607+
4608+
// The most general resilience scope where the enum type is visible.
4609+
// Case numbering must not depend on any information that is not static
4610+
// in this resilience scope.
4611+
ResilienceScope accessScope = TC.IGM.getResilienceScopeForAccess(theEnum);
4612+
4613+
// The most general resilience scope where the enum's layout is known.
4614+
// Fixed-size optimizations can be applied if all payload types are
4615+
// fixed-size from this resilience scope.
4616+
ResilienceScope layoutScope = TC.IGM.getResilienceScopeForLayout(theEnum);
46124617

46134618
for (auto elt : theEnum->getAllElements()) {
46144619
numElements++;
@@ -4638,14 +4643,20 @@ EnumImplStrategy *EnumImplStrategy::get(TypeConverter &TC,
46384643
= TC.tryGetCompleteTypeInfo(origArgLoweredTy.getSwiftRValueType());
46394644
assert(origArgTI && "didn't complete type info?!");
46404645

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))
4646+
// If the unsubstituted argument contains a generic parameter type, or
4647+
// is not fixed-size in all resilience domains that have knowledge of
4648+
// this enum's layout, we need to constrain our layout optimizations to
4649+
// what the runtime can reproduce.
4650+
if (!isResilient &&
4651+
!origArgTI->isFixedSize(layoutScope))
46454652
alwaysFixedSize = IsNotFixedSize;
46464653

4647-
auto loadableOrigArgTI = dyn_cast<LoadableTypeInfo>(origArgTI);
4648-
if (loadableOrigArgTI && loadableOrigArgTI->isKnownEmpty()) {
4654+
// If the payload is empty, turn the case into a no-payload case, but
4655+
// only if case numbering remains unchanged from all resilience domains
4656+
// that can see the enum.
4657+
if (origArgTI->isFixedSize(accessScope) &&
4658+
isa<LoadableTypeInfo>(origArgTI) &&
4659+
cast<LoadableTypeInfo>(origArgTI)->isKnownEmpty()) {
46494660
elementsWithNoPayload.push_back({elt, nullptr, nullptr});
46504661
} else {
46514662
// *Now* apply the substitutions and get the type info for the instance's
@@ -4655,16 +4666,20 @@ EnumImplStrategy *EnumImplStrategy::get(TypeConverter &TC,
46554666
auto *substArgTI = &TC.IGM.getTypeInfo(fieldTy);
46564667

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

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;
4670+
if (!isResilient) {
4671+
if (!substArgTI->isFixedSize(ResilienceScope::Component))
4672+
tik = Opaque;
4673+
else if (!substArgTI->isLoadable() && tik > Fixed)
4674+
tik = Fixed;
4675+
4676+
// If the substituted argument contains a type that is not fixed-size
4677+
// in all resilience domains that have knowledge of this enum's layout,
4678+
// we need to constrain our layout optimizations to what the runtime
4679+
// can reproduce.
4680+
if (!substArgTI->isFixedSize(layoutScope))
4681+
alwaysFixedSize = IsNotFixedSize;
4682+
}
46684683
}
46694684
}
46704685

@@ -4673,12 +4688,7 @@ EnumImplStrategy *EnumImplStrategy::get(TypeConverter &TC,
46734688
+ elementsWithNoPayload.size()
46744689
&& "not all elements accounted for");
46754690

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)) {
4691+
if (isResilient) {
46824692
return new ResilientEnumImplStrategy(TC.IGM,
46834693
numElements,
46844694
std::move(elementsWithPayload),

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)