Skip to content

Commit 117a5ec

Browse files
authored
Merge pull request #67920 from tshortli/unavailable-decl-opt-complete-resilient-enum-switch
SILOptimizer: Remove switch cases matching unavailable enum elements
2 parents 9e21bd5 + 9eda39c commit 117a5ec

18 files changed

+631
-15
lines changed

include/swift/AST/Decl.h

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -650,13 +650,16 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
650650
/// address diversified ptrauth qualified field.
651651
IsNonTrivialPtrAuth : 1);
652652

653-
SWIFT_INLINE_BITFIELD(EnumDecl, NominalTypeDecl, 2+1,
653+
SWIFT_INLINE_BITFIELD(EnumDecl, NominalTypeDecl, 2+1+1,
654654
/// True if the enum has cases and at least one case has associated values.
655655
HasAssociatedValues : 2,
656656
/// True if the enum has at least one case that has some availability
657657
/// attribute. A single bit because it's lazily computed along with the
658658
/// HasAssociatedValues bit.
659-
HasAnyUnavailableValues : 1
659+
HasAnyUnavailableValues : 1,
660+
/// True if \c isAvailableDuringLowering() is false for any cases. Lazily
661+
/// computed along with HasAssociatedValues.
662+
HasAnyUnavailableDuringLoweringValues : 1
660663
);
661664

662665
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1,
@@ -1237,7 +1240,10 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
12371240
/// Returns true if this declaration should be considered available during
12381241
/// SIL/IR lowering. A declaration would not be available during lowering if,
12391242
/// for example, it is annotated as unavailable with `@available` and
1240-
/// optimization settings require that it be omitted.
1243+
/// optimization settings require that it be omitted. Canonical SIL must not
1244+
/// contain any references to declarations that are unavailable during
1245+
/// lowering because the resulting IR could reference non-existent symbols
1246+
/// and fail to link.
12411247
bool isAvailableDuringLowering() const;
12421248

12431249
/// Returns true if ABI compatibility stubs must be emitted for the given
@@ -4204,6 +4210,11 @@ class EnumDecl final : public NominalTypeDecl {
42044210
/// A range for iterating the elements of an enum.
42054211
using ElementRange = DowncastFilterRange<EnumElementDecl, DeclRange>;
42064212

4213+
/// A range for iterating the elements of an enum that are available during
4214+
/// lowering.
4215+
using ElementRangeForLowering = OptionalTransformRange<
4216+
ElementRange, AvailableDuringLoweringDeclFilter<EnumElementDecl>>;
4217+
42074218
/// A range for iterating the cases of an enum.
42084219
using CaseRange = DowncastFilterRange<EnumCaseDecl, DeclRange>;
42094220

@@ -4217,6 +4228,13 @@ class EnumDecl final : public NominalTypeDecl {
42174228
return std::distance(eltRange.begin(), eltRange.end());
42184229
}
42194230

4231+
/// Returns a range that iterates over all the elements of an enum for which
4232+
/// \c isAvailableDuringLowering() is true.
4233+
ElementRangeForLowering getAllElementsForLowering() const {
4234+
return ElementRangeForLowering(
4235+
getAllElements(), AvailableDuringLoweringDeclFilter<EnumElementDecl>());
4236+
}
4237+
42204238
/// If this enum has a unique element, return it. A unique element can
42214239
/// either hold a value or not, and the existence of one unique element does
42224240
/// not imply the existence or non-existence of the opposite unique element.
@@ -4279,6 +4297,11 @@ class EnumDecl final : public NominalTypeDecl {
42794297
/// Note that this is false for enums with absolutely no cases.
42804298
bool hasPotentiallyUnavailableCaseValue() const;
42814299

4300+
/// True if \c isAvailableDuringLowering() is false for any cases.
4301+
///
4302+
/// Note that this is false for enums with absolutely no cases.
4303+
bool hasCasesUnavailableDuringLowering() const;
4304+
42824305
/// True if the enum has cases.
42834306
bool hasCases() const {
42844307
return !getAllElements().empty();

include/swift/SIL/SILInstruction.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6583,7 +6583,7 @@ class SelectEnumInstBase : public BaseTy {
65836583
}
65846584

65856585
llvm::SmallPtrSet<EnumElementDecl *, 4> unswitchedElts;
6586-
for (auto elt : decl->getAllElements())
6586+
for (auto elt : decl->getAllElementsForLowering())
65876587
unswitchedElts.insert(elt);
65886588

65896589
for (unsigned i = 0, e = this->getNumCases(); i != e; ++i) {
@@ -9812,7 +9812,7 @@ class SwitchEnumInstBase : public BaseTy {
98129812
assert(decl && "switch_enum operand is not an enum");
98139813

98149814
SmallPtrSet<EnumElementDecl *, 4> unswitchedElts;
9815-
for (auto elt : decl->getAllElements())
9815+
for (auto elt : decl->getAllElementsForLowering())
98169816
unswitchedElts.insert(elt);
98179817

98189818
for (unsigned i = 0, e = getNumCases(); i != e; ++i) {

lib/AST/Availability.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ bool Decl::isAvailableDuringLowering() const {
312312
UnavailableDeclOptimization::Complete)
313313
return true;
314314

315+
if (hasClangNode())
316+
return true;
317+
315318
return !isUnconditionallyUnavailable(this);
316319
}
317320

lib/AST/Decl.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5944,14 +5944,13 @@ EnumCaseDecl *EnumCaseDecl::create(SourceLoc CaseLoc,
59445944
}
59455945

59465946
bool EnumDecl::hasPotentiallyUnavailableCaseValue() const {
5947-
switch (static_cast<AssociatedValueCheck>(Bits.EnumDecl.HasAssociatedValues)) {
5948-
case AssociatedValueCheck::Unchecked:
5949-
// Compute below
5950-
this->hasOnlyCasesWithoutAssociatedValues();
5951-
LLVM_FALLTHROUGH;
5952-
default:
5953-
return static_cast<bool>(Bits.EnumDecl.HasAnyUnavailableValues);
5954-
}
5947+
(void)this->hasOnlyCasesWithoutAssociatedValues(); // Prime the cache
5948+
return static_cast<bool>(Bits.EnumDecl.HasAnyUnavailableValues);
5949+
}
5950+
5951+
bool EnumDecl::hasCasesUnavailableDuringLowering() const {
5952+
(void)this->hasOnlyCasesWithoutAssociatedValues(); // Prime the cache
5953+
return static_cast<bool>(Bits.EnumDecl.HasAnyUnavailableDuringLoweringValues);
59555954
}
59565955

59575956
bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
@@ -5970,6 +5969,7 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
59705969
}
59715970

59725971
bool hasAnyUnavailableValues = false;
5972+
bool hasAnyUnavailableDuringLoweringValues = false;
59735973
bool hasAssociatedValues = false;
59745974

59755975
for (auto elt : getAllElements()) {
@@ -5980,13 +5980,18 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
59805980
}
59815981
}
59825982

5983+
if (!elt->isAvailableDuringLowering())
5984+
hasAnyUnavailableDuringLoweringValues = true;
5985+
59835986
if (elt->hasAssociatedValues())
59845987
hasAssociatedValues = true;
59855988
}
59865989

59875990
EnumDecl *enumDecl = const_cast<EnumDecl *>(this);
59885991

59895992
enumDecl->Bits.EnumDecl.HasAnyUnavailableValues = hasAnyUnavailableValues;
5993+
enumDecl->Bits.EnumDecl.HasAnyUnavailableDuringLoweringValues =
5994+
hasAnyUnavailableDuringLoweringValues;
59905995
enumDecl->Bits.EnumDecl.HasAssociatedValues = static_cast<unsigned>(
59915996
hasAssociatedValues ? AssociatedValueCheck::HasAssociatedValues
59925997
: AssociatedValueCheck::NoAssociatedValues);

lib/DriverTool/sil_opt_main.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,21 @@ struct SILOptOptions {
499499
llvm::cl::opt<bool> DebugDiagnosticNames = llvm::cl::opt<bool>(
500500
"debug-diagnostic-names",
501501
llvm::cl::desc("Include diagnostic names when printing"));
502+
503+
llvm::cl::opt<swift::UnavailableDeclOptimization>
504+
UnavailableDeclOptimization =
505+
llvm::cl::opt<swift::UnavailableDeclOptimization>(
506+
"unavailable-decl-optimization",
507+
llvm::cl::desc("Optimization mode for unavailable declarations"),
508+
llvm::cl::values(
509+
clEnumValN(swift::UnavailableDeclOptimization::None, "none",
510+
"Don't optimize unavailable decls"),
511+
clEnumValN(swift::UnavailableDeclOptimization::Stub, "stub",
512+
"Lower unavailable functions to stubs"),
513+
clEnumValN(
514+
swift::UnavailableDeclOptimization::Complete, "complete",
515+
"Eliminate unavailable decls from lowered SIL/IR")),
516+
llvm::cl::init(swift::UnavailableDeclOptimization::None));
502517
};
503518

504519
/// Regular expression corresponding to the value given in one of the
@@ -646,6 +661,9 @@ int sil_opt_main(ArrayRef<const char *> argv, void *MainAddr) {
646661

647662
Invocation.getLangOptions().EnableCXXInterop = options.EnableCxxInterop;
648663

664+
Invocation.getLangOptions().UnavailableDeclOptimizationMode =
665+
options.UnavailableDeclOptimization;
666+
649667
Invocation.getDiagnosticOptions().VerifyMode =
650668
options.VerifyMode ? DiagnosticOptions::Verify : DiagnosticOptions::NoVerify;
651669

lib/SIL/IR/SILType.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,12 @@ TypeBase::replaceSubstitutedSILFunctionTypesWithUnsubstituted(SILModule &M) cons
940940
bool SILType::isEffectivelyExhaustiveEnumType(SILFunction *f) {
941941
EnumDecl *decl = getEnumOrBoundGenericEnum();
942942
assert(decl && "Called for a non enum type");
943+
944+
// Since unavailable enum elements cannot be referenced in canonical SIL,
945+
// enums with these elements cannot be treated as exhaustive types.
946+
if (decl->hasCasesUnavailableDuringLowering())
947+
return false;
948+
943949
return decl->isEffectivelyExhaustive(f->getModule().getSwiftModule(),
944950
f->getResilienceExpansion());
945951
}

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4906,6 +4906,14 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
49064906
requireSameType(
49074907
result->getType(), SEO->getType(),
49084908
"select_enum case operand must match type of instruction");
4909+
4910+
// In canonical SIL, select instructions must not cover any enum elements
4911+
// that are unavailable.
4912+
if (F.getModule().getStage() >= SILStage::Canonical) {
4913+
require(elt->isAvailableDuringLowering(),
4914+
"select_enum dispatches on enum element that is unavailable "
4915+
"during lowering.");
4916+
}
49094917
}
49104918

49114919
// If the select is non-exhaustive, we require a default.
@@ -4994,6 +5002,15 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
49945002
"arguments");
49955003
return;
49965004
}
5005+
5006+
// In canonical SIL, switch instructions must not cover any enum elements
5007+
// that are unavailable.
5008+
if (F.getModule().getStage() >= SILStage::Canonical) {
5009+
require(elt->isAvailableDuringLowering(),
5010+
"switch_enum dispatches on enum element that is unavailable "
5011+
"during lowering.");
5012+
}
5013+
49975014
// Check for a valid switch result type.
49985015
if (dest->getArguments().size() == 1) {
49995016
SILType eltArgTy = uTy.getEnumElementType(elt, F.getModule(),
@@ -5098,6 +5115,14 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
50985115
"more than once");
50995116
unswitchedElts.erase(elt);
51005117

5118+
// In canonical SIL, switch instructions must not cover any enum elements
5119+
// that are unavailable.
5120+
if (F.getModule().getStage() >= SILStage::Canonical) {
5121+
require(elt->isAvailableDuringLowering(),
5122+
"switch_enum_addr dispatches on enum element that is "
5123+
"unavailable during lowering.");
5124+
}
5125+
51015126
// The destination BB must not have BB arguments.
51025127
require(dest->getArguments().empty(),
51035128
"switch_enum_addr destination must take no BB args");

lib/SILOptimizer/Mandatory/DiagnoseUnreachable.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ enum class UnreachableKind {
5151
FoldedBranch,
5252
FoldedSwitchEnum,
5353
NoreturnCall,
54+
UnavailableEnumElement,
5455
};
5556

5657
/// Information about a folded conditional branch instruction: it's location
@@ -867,6 +868,96 @@ static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,
867868
return true;
868869
}
869870

871+
/// Replaces `switch_enum` / `switch_enum_addr` instructions that have cases
872+
/// matching unavailable enum elements with new instructions that have those
873+
/// cases removed since unavailable enum elements cannot be instantiated at
874+
/// run time.
875+
///
876+
/// If this pass removes a case, it will add a default case that traps if there
877+
/// was not already an existing default case. This artificial default case
878+
/// serves two purposes. The first is to ensure the new switch instruction still
879+
/// "covers" the entire enum as required by SIL verification (this analysis
880+
/// could be changed in the future to ignore unavailable elements). Secondly,
881+
/// the trapping default case may help developers catch issues at runtime where
882+
/// UB has been invoked and an unavailable element has been instantiated
883+
/// unexpectedly. Ideally, this debugging aid would be available consistently,
884+
/// but since we cannot leave any references to the unavailable elements in SIL
885+
/// it is not possible to transform switch instructions that already have
886+
/// default cases in such a way that unavailable elements would be detected.
887+
static bool eliminateSwitchDispatchOnUnavailableElements(
888+
SILBasicBlock &BB, UnreachableUserCodeReportingState *State) {
889+
SwitchEnumTermInst SWI(BB.getTerminator());
890+
if (!SWI)
891+
return false;
892+
893+
EnumDecl *ED = SWI->getOperand(0)->getType().getEnumOrBoundGenericEnum();
894+
assert(ED && "operand is not an enum");
895+
896+
// No need to check the instruction if all elements are available.
897+
if (!ED->hasCasesUnavailableDuringLowering())
898+
return false;
899+
900+
ASTContext &ctx = BB.getModule().getASTContext();
901+
SILLocation Loc = SWI->getLoc();
902+
bool DidRemoveUnavailableCase = false;
903+
904+
SmallVector<std::pair<EnumElementDecl *, SILBasicBlock *>, 4> NewCaseBBs;
905+
for (unsigned i : range(SWI.getNumCases())) {
906+
auto CaseBB = SWI.getCase(i);
907+
auto availableAtr = CaseBB.first->getAttrs().getUnavailable(ctx);
908+
909+
if (availableAtr && availableAtr->isUnconditionallyUnavailable()) {
910+
// Mark the basic block as potentially unreachable.
911+
SILBasicBlock *UnreachableBlock = CaseBB.second;
912+
if (!State->PossiblyUnreachableBlocks.contains(UnreachableBlock)) {
913+
// If this is the first time we see this unreachable block, store it.
914+
State->PossiblyUnreachableBlocks.insert(UnreachableBlock);
915+
State->MetaMap.insert(
916+
{UnreachableBlock,
917+
UnreachableInfo{UnreachableKind::UnavailableEnumElement, Loc,
918+
true}});
919+
}
920+
DidRemoveUnavailableCase = true;
921+
} else {
922+
NewCaseBBs.push_back(CaseBB);
923+
}
924+
}
925+
926+
if (!DidRemoveUnavailableCase)
927+
return false;
928+
929+
// Since at least one case was removed, we need to add a default case that
930+
// traps if there isn't already an existing default case. The resulting SIL
931+
// will have a structure that matches what SILGen would have produced for a
932+
// switch statment that was written in source with unavailable cases
933+
// unhandled.
934+
SILBasicBlock *DefaultBB = SWI.getDefaultBBOrNull().getPtrOrNull();
935+
bool DidCreateDefault = false;
936+
if (!DefaultBB) {
937+
DefaultBB = BB.getParent()->createBasicBlock();
938+
SILLocation genLoc = Loc.asAutoGenerated();
939+
SILBuilder B(DefaultBB);
940+
B.createUnconditionalFail(genLoc, "unexpected enum value");
941+
B.createUnreachable(genLoc);
942+
DidCreateDefault = true;
943+
}
944+
945+
if (isa<SwitchEnumInst>(*SWI)) {
946+
auto *SEI = SILBuilderWithScope(SWI).createSwitchEnum(
947+
Loc, SWI.getOperand(), DefaultBB, NewCaseBBs);
948+
949+
if (DidCreateDefault)
950+
SEI->createDefaultResult();
951+
} else {
952+
assert(isa<SwitchEnumAddrInst>(*SWI) && "unknown switch_enum instruction");
953+
SILBuilderWithScope(SWI).createSwitchEnumAddr(Loc, SWI.getOperand(),
954+
DefaultBB, NewCaseBBs);
955+
}
956+
SWI->eraseFromParent();
957+
958+
return true;
959+
}
960+
870961
/// Issue an "unreachable code" diagnostic if the blocks contains or
871962
/// leads to another block that contains user code.
872963
///
@@ -935,6 +1026,11 @@ static bool diagnoseUnreachableBlock(
9351026
}
9361027
break;
9371028
}
1029+
case (UnreachableKind::UnavailableEnumElement): {
1030+
// Don't diagnose blocks removed because they were only reachable from
1031+
// a case matching an unavailable enum element.
1032+
break;
1033+
}
9381034
}
9391035

9401036
// Record that we've reported this unreachable block to avoid duplicates
@@ -1076,6 +1172,11 @@ static void diagnoseUnreachable(SILFunction &Fn) {
10761172
// function.
10771173
if (simplifyBlocksWithCallsToNoReturn(BB, &State))
10781174
continue;
1175+
1176+
// Remove the cases of switch_enum / switch_enum_addr instructions that
1177+
// match unavailable enum elements.
1178+
if (eliminateSwitchDispatchOnUnavailableElements(BB, &State))
1179+
continue;
10791180
}
10801181

10811182
// Remove unreachable blocks.
@@ -1094,6 +1195,10 @@ static void diagnoseUnreachable(SILFunction &Fn) {
10941195
// function.
10951196
if (simplifyBlocksWithCallsToNoReturn(BB, &State))
10961197
continue;
1198+
// Remove the cases of switch_enum / switch_enum_addr instructions that
1199+
// match unavailable enum elements.
1200+
if (eliminateSwitchDispatchOnUnavailableElements(BB, &State))
1201+
continue;
10971202
}
10981203

10991204
// Remove unreachable blocks.

lib/Serialization/ModuleFileSharedCore.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,9 +531,8 @@ std::string serialization::StatusToString(Status S) {
531531
case Status::TargetIncompatible: return "TargetIncompatible";
532532
case Status::TargetTooNew: return "TargetTooNew";
533533
case Status::SDKMismatch: return "SDKMismatch";
534-
default:
535-
llvm_unreachable("The switch should cover all cases");
536534
}
535+
llvm_unreachable("The switch should cover all cases");
537536
}
538537

539538
bool serialization::isSerializedAST(StringRef data) {

0 commit comments

Comments
 (0)