Skip to content

Commit 7732b98

Browse files
authored
Merge pull request swiftlang#21172 from eeckstein/silcombine-enums
SILCombine: peephole to propagate resilient enum cases
2 parents cfb4678 + 5c17f0f commit 7732b98

File tree

6 files changed

+180
-26
lines changed

6 files changed

+180
-26
lines changed

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,23 +106,91 @@ SILCombiner::visitAllocExistentialBoxInst(AllocExistentialBoxInst *AEBI) {
106106
return nullptr;
107107
}
108108

109+
/// Return the enum case injected by an inject_enum_addr if it is the only
110+
/// instruction which writes to \p Addr.
111+
static EnumElementDecl *getInjectEnumCaseTo(SILValue Addr) {
112+
while (true) {
113+
// For everything else than an alloc_stack we cannot easily prove that we
114+
// see all writes.
115+
if (!isa<AllocStackInst>(Addr))
116+
return nullptr;
117+
118+
SILInstruction *WritingInst = nullptr;
119+
int NumWrites = 0;
120+
for (auto *Use : getNonDebugUses(Addr)) {
121+
SILInstruction *User = Use->getUser();
122+
switch (User->getKind()) {
123+
// Handle a very narrow set of known not harmful instructions.
124+
case swift::SILInstructionKind::DestroyAddrInst:
125+
case swift::SILInstructionKind::DeallocStackInst:
126+
case swift::SILInstructionKind::SwitchEnumAddrInst:
127+
break;
128+
case swift::SILInstructionKind::ApplyInst:
129+
case swift::SILInstructionKind::TryApplyInst: {
130+
// Check if the addr is only passed to in_guaranteed arguments.
131+
FullApplySite AI(User);
132+
for (Operand &Op : AI.getArgumentOperands()) {
133+
if (Op.get() == Addr &&
134+
AI.getArgumentConvention(Op) !=
135+
SILArgumentConvention::Indirect_In_Guaranteed)
136+
return nullptr;
137+
}
138+
break;
139+
}
140+
case swift::SILInstructionKind::InjectEnumAddrInst:
141+
WritingInst = User;
142+
++NumWrites;
143+
break;
144+
case swift::SILInstructionKind::CopyAddrInst:
145+
if (Addr == cast<CopyAddrInst>(User)->getDest()) {
146+
WritingInst = User;
147+
++NumWrites;
148+
}
149+
break;
150+
default:
151+
return nullptr;
152+
}
153+
}
154+
if (NumWrites != 1)
155+
return nullptr;
156+
if (auto *IEA = dyn_cast<InjectEnumAddrInst>(WritingInst))
157+
return IEA->getElement();
158+
159+
// In case of a copy_addr continue with the source of the copy.
160+
Addr = dyn_cast<CopyAddrInst>(WritingInst)->getSrc();
161+
}
162+
}
163+
109164
SILInstruction *SILCombiner::visitSwitchEnumAddrInst(SwitchEnumAddrInst *SEAI) {
165+
// Convert switch_enum_addr -> br
166+
// if the only thing which writes to the address is an inject_enum_addr.
167+
SILValue Addr = SEAI->getOperand();
168+
if (EnumElementDecl *EnumCase = getInjectEnumCaseTo(Addr)) {
169+
SILBasicBlock *Dest = SEAI->getCaseDestination(EnumCase);
170+
// If the only instruction which writes to Addr is an inject_enum_addr we
171+
// know that there cannot be an enum payload.
172+
assert(Dest->getNumArguments() == 0 &&
173+
"didn't expect a payload argument");
174+
Builder.createBranch(SEAI->getLoc(), Dest);
175+
return eraseInstFromFunction(*SEAI);
176+
}
177+
178+
SILType Ty = Addr->getType();
179+
if (!Ty.isLoadable(SEAI->getModule()))
180+
return nullptr;
181+
110182
// Promote switch_enum_addr to switch_enum if the enum is loadable.
111183
// switch_enum_addr %ptr : $*Optional<SomeClass>, case ...
112184
// ->
113185
// %value = load %ptr
114186
// switch_enum %value
115-
SILType Ty = SEAI->getOperand()->getType();
116-
if (!Ty.isLoadable(SEAI->getModule()))
117-
return nullptr;
118-
119187
SmallVector<std::pair<EnumElementDecl*, SILBasicBlock*>, 8> Cases;
120188
for (int i = 0, e = SEAI->getNumCases(); i < e; ++i)
121189
Cases.push_back(SEAI->getCase(i));
122190

123191
Builder.setCurrentDebugScope(SEAI->getDebugScope());
124192
SILBasicBlock *Default = SEAI->hasDefault() ? SEAI->getDefaultBB() : nullptr;
125-
LoadInst *EnumVal = Builder.createLoad(SEAI->getLoc(), SEAI->getOperand(),
193+
LoadInst *EnumVal = Builder.createLoad(SEAI->getLoc(), Addr,
126194
LoadOwnershipQualifier::Unqualified);
127195
Builder.createSwitchEnum(SEAI->getLoc(), EnumVal, Default, Cases);
128196
return eraseInstFromFunction(*SEAI);

lib/SILOptimizer/Transforms/DeadObjectElimination.cpp

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ removeInstructions(ArrayRef<SILInstruction*> UsersToRemove) {
193193

194194
/// Returns false if Inst is an instruction that would require us to keep the
195195
/// alloc_ref alive.
196-
static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts) {
196+
static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts,
197+
bool onlyAcceptTrivialStores) {
197198
if (isa<SetDeallocatingInst>(Inst) || isa<FixLifetimeInst>(Inst))
198199
return true;
199200

@@ -204,6 +205,24 @@ static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts) {
204205
isa<DeallocPartialRefInst>(Inst))
205206
return acceptRefCountInsts;
206207

208+
if (isa<InjectEnumAddrInst>(Inst))
209+
return true;
210+
211+
// We know that the destructor has no side effects so we can remove the
212+
// deallocation instruction too.
213+
if (isa<DeallocationInst>(Inst) || isa<AllocationInst>(Inst))
214+
return true;
215+
216+
// Much like deallocation, destroy addr is safe.
217+
if (isa<DestroyAddrInst>(Inst))
218+
return true;
219+
220+
// The only store instructions which is guaranteed to store a trivial value
221+
// is an inject_enum_addr witout a payload (i.e. without init_enum_data_addr).
222+
// There can also be a 'store [trivial]', but we don't handle that yet.
223+
if (onlyAcceptTrivialStores)
224+
return false;
225+
207226
// If we see a store here, we have already checked that we are storing into
208227
// the pointer before we added it to the worklist, so we can skip it.
209228
if (isa<StoreInst>(Inst))
@@ -215,15 +234,6 @@ static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts) {
215234
!isa<TermInst>(Inst))
216235
return true;
217236

218-
// We know that the destructor has no side effects so we can remove the
219-
// deallocation instruction too.
220-
if (isa<DeallocationInst>(Inst))
221-
return true;
222-
223-
// Much like deallocation, destroy addr is safe.
224-
if (isa<DestroyAddrInst>(Inst))
225-
return true;
226-
227237
// Otherwise we do not know how to handle this instruction. Be conservative
228238
// and don't zap it.
229239
return false;
@@ -233,7 +243,7 @@ static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts) {
233243
/// zapping it completely.
234244
static bool
235245
hasUnremovableUsers(SILInstruction *AllocRef, UserList &Users,
236-
bool acceptRefCountInsts) {
246+
bool acceptRefCountInsts, bool onlyAcceptTrivialStores) {
237247
SmallVector<SILInstruction *, 16> Worklist;
238248
Worklist.push_back(AllocRef);
239249

@@ -252,7 +262,7 @@ hasUnremovableUsers(SILInstruction *AllocRef, UserList &Users,
252262
}
253263

254264
// If we can't zap this instruction... bail...
255-
if (!canZapInstruction(I, acceptRefCountInsts)) {
265+
if (!canZapInstruction(I, acceptRefCountInsts, onlyAcceptTrivialStores)) {
256266
LLVM_DEBUG(llvm::dbgs() << " Found instruction we can't zap...\n");
257267
return true;
258268
}
@@ -701,7 +711,8 @@ bool DeadObjectElimination::processAllocRef(AllocRefInst *ARI) {
701711
// escape, then we can completely remove the use graph of this alloc_ref.
702712
UserList UsersToRemove;
703713
if (hasUnremovableUsers(ARI, UsersToRemove,
704-
/*acceptRefCountInsts=*/ !HasSideEffects)) {
714+
/*acceptRefCountInsts=*/ !HasSideEffects,
715+
/*onlyAcceptTrivialStores*/false)) {
705716
LLVM_DEBUG(llvm::dbgs() << " Found a use that cannot be zapped...\n");
706717
return false;
707718
}
@@ -716,12 +727,11 @@ bool DeadObjectElimination::processAllocRef(AllocRefInst *ARI) {
716727
}
717728

718729
bool DeadObjectElimination::processAllocStack(AllocStackInst *ASI) {
719-
// Trivial types don't have destructors. Let's try to zap this AllocStackInst.
720-
if (!ASI->getElementType().isTrivial(ASI->getModule()))
721-
return false;
722-
730+
// Trivial types don't have destructors.
731+
bool isTrivialType = ASI->getElementType().isTrivial(ASI->getModule());
723732
UserList UsersToRemove;
724-
if (hasUnremovableUsers(ASI, UsersToRemove, /*acceptRefCountInsts=*/ true)) {
733+
if (hasUnremovableUsers(ASI, UsersToRemove, /*acceptRefCountInsts=*/ true,
734+
/*onlyAcceptTrivialStores*/!isTrivialType)) {
725735
LLVM_DEBUG(llvm::dbgs() << " Found a use that cannot be zapped...\n");
726736
return false;
727737
}

test/SILOptimizer/dead_alloc_elim.sil

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,9 @@ sil @non_trivial_destructor_objc_destructor : $@convention(thin) () -> () {
325325
}
326326

327327
// CHECK-LABEL: sil @non_trivial_destructor_on_stack : $@convention(thin) () -> () {
328-
// CHECK: alloc_stack
329-
// CHECK: return
328+
// CHECK: bb0:
329+
// CHECK-NEXT: %0 = tuple ()
330+
// CHECK-NEXT: return %0
330331
sil @non_trivial_destructor_on_stack : $@convention(thin) () -> () {
331332
%0 = alloc_stack $NonTrivialDestructor
332333
dealloc_stack %0 : $*NonTrivialDestructor
@@ -358,3 +359,18 @@ sil @trivial_destructor_may_trap : $@convention(thin) () -> () {
358359
%4 = tuple()
359360
return %4 : $()
360361
}
362+
363+
// CHECK-LABEL: sil @remove_dead_enum_stackloc
364+
// CHECK: bb0:
365+
// CHECK-NEXT: %0 = tuple ()
366+
// CHECK-NEXT: return %0
367+
sil @remove_dead_enum_stackloc : $@convention(thin) () -> () {
368+
bb0:
369+
%0 = alloc_stack $FloatingPointRoundingRule
370+
inject_enum_addr %0 : $*FloatingPointRoundingRule, #FloatingPointRoundingRule.toNearestOrEven!enumelt
371+
destroy_addr %0 : $*FloatingPointRoundingRule
372+
dealloc_stack %0 : $*FloatingPointRoundingRule
373+
%1 = tuple ()
374+
return %1 : $()
375+
}
376+

test/SILOptimizer/fp_rounding.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: %target-swift-frontend -parse-as-library -O -emit-sil %s | %FileCheck %s
2+
// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib,CPU=x86_64
3+
4+
// This is an end-to-end test to ensure that the optimizer can propagate
5+
// resilient enum cases (FloatingPointRoundingRule) and produces optimal
6+
// code for Float.rounded().
7+
8+
// CHECK-LABEL: sil @{{.*}}propagate_roundingmode
9+
// CHECK: bb0:
10+
// CHECK-NEXT: %0 = integer_literal {{.*}}, 0
11+
// CHECK-NEXT: %1 = struct $Int (%0 {{.*}})
12+
// CHECK-NEXT: return %1
13+
public func propagate_roundingmode() -> Int {
14+
let rm = FloatingPointRoundingRule.toNearestOrEven
15+
switch rm {
16+
case .toNearestOrAwayFromZero:
17+
return 1
18+
default:
19+
return 0
20+
}
21+
}
22+
23+
// CHECK-LABEL: sil @{{.*}}round_floating_point
24+
// CHECK: bb0({{.*}}):
25+
// CHECK: [[R:%[0-9]+]] = builtin "int_round{{.*}}"
26+
// CHECK: [[F:%[0-9]+]] = struct $Float ([[R]]
27+
// CHECK: return [[F]]
28+
public func round_floating_point(_ x: Float) -> Float {
29+
return x.rounded()
30+
}

test/SILOptimizer/sil_combine.sil

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2106,6 +2106,36 @@ bb3:
21062106
return %2 : $()
21072107
}
21082108

2109+
// CHECK-LABEL: sil @resilient_enum_case_propagation
2110+
// CHECK: bb0:
2111+
// CHECK: br bb2
2112+
// CHECK: bb1:
2113+
// CHECK: return
2114+
sil @resilient_enum_case_propagation : $@convention(thin) () -> Builtin.Int64 {
2115+
bb0:
2116+
%0 = alloc_stack $FloatingPointRoundingRule
2117+
inject_enum_addr %0 : $*FloatingPointRoundingRule, #FloatingPointRoundingRule.toNearestOrEven!enumelt
2118+
%2 = alloc_stack $FloatingPointRoundingRule
2119+
copy_addr %0 to [initialization] %2 : $*FloatingPointRoundingRule
2120+
destroy_addr %0 : $*FloatingPointRoundingRule
2121+
switch_enum_addr %2 : $*FloatingPointRoundingRule, case #FloatingPointRoundingRule.toNearestOrAwayFromZero!enumelt: bb1, default bb2
2122+
2123+
bb1:
2124+
%6 = integer_literal $Builtin.Int64, 1
2125+
br bb3(%6 : $Builtin.Int64)
2126+
2127+
bb2:
2128+
destroy_addr %2 : $*FloatingPointRoundingRule
2129+
%9 = integer_literal $Builtin.Int64, 0
2130+
br bb3(%9 : $Builtin.Int64)
2131+
2132+
bb3(%11 : $Builtin.Int64):
2133+
dealloc_stack %2 : $*FloatingPointRoundingRule
2134+
dealloc_stack %0 : $*FloatingPointRoundingRule
2135+
return %11 : $Builtin.Int64
2136+
}
2137+
2138+
21092139
@objc(XX) protocol XX {
21102140
}
21112141

validation-test/Evolution/test_backward_deploy_enum.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import StdlibUnittest
55
import backward_deploy_enum
66

77
// <rdar://problem/46438568>
8-
// XFAIL: *
8+
// REQUIRES: rdar46438568
99

1010
var BackwardDeployEnumTest = TestSuite("BackwardDeployEnum")
1111

0 commit comments

Comments
 (0)