Skip to content

Commit 5c17f0f

Browse files
committed
SILCombine: peephole to propagate resilient enum cases
Basically the pattern to optimize is: inject_enum_addr %stackloc, #SomeCase switch_enum_addr %stackloc ... This works even if the enum is resilient and the case does not have a payload. As long as we don't have opaque values in SIL we need this peephole to optimize the pattern. This change fixes the code generation for Float.rounded(). rdar://problem/46353885
1 parent 36d3311 commit 5c17f0f

File tree

4 files changed

+134
-6
lines changed

4 files changed

+134
-6
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);

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)