Skip to content

Commit 6a9d087

Browse files
committed
SILCombine: a peephole optimization to optimize alloc_stack of enums.
Replaces an alloc_stack of an enum by an alloc_stack of the payload if only one enum case (with payload) is stored to that location. For example: %loc = alloc_stack $Optional<T> %payload = init_enum_data_addr %loc store %value to %payload ... %take_addr = unchecked_take_enum_data_addr %loc %l = load %take_addr is transformed to %loc = alloc_stack $T store %value to %loc ... %l = load %loc https://bugs.swift.org/browse/SR-12710
1 parent c4b68b8 commit 6a9d087

File tree

7 files changed

+265
-42
lines changed

7 files changed

+265
-42
lines changed

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class SILCombiner :
176176
SILInstruction *optimizeLoadFromStringLiteral(LoadInst *LI);
177177
SILInstruction *visitLoadInst(LoadInst *LI);
178178
SILInstruction *visitIndexAddrInst(IndexAddrInst *IA);
179+
bool optimizeStackAllocatedEnum(AllocStackInst *AS);
179180
SILInstruction *visitAllocStackInst(AllocStackInst *AS);
180181
SILInstruction *visitAllocRefInst(AllocRefInst *AR);
181182
SILInstruction *visitSwitchEnumAddrInst(SwitchEnumAddrInst *SEAI);

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,10 +454,109 @@ static bool somethingIsRetained(SILInstruction *from, AllocStackInst *alloc) {
454454
return false;
455455
}
456456

457+
/// Replaces an alloc_stack of an enum by an alloc_stack of the payload if only
458+
/// one enum case (with payload) is stored to that location.
459+
///
460+
/// For example:
461+
///
462+
/// %loc = alloc_stack $Optional<T>
463+
/// %payload = init_enum_data_addr %loc
464+
/// store %value to %payload
465+
/// ...
466+
/// %take_addr = unchecked_take_enum_data_addr %loc
467+
/// %l = load %take_addr
468+
///
469+
/// is transformed to
470+
///
471+
/// %loc = alloc_stack $T
472+
/// store %value to %loc
473+
/// ...
474+
/// %l = load %loc
475+
bool SILCombiner::optimizeStackAllocatedEnum(AllocStackInst *AS) {
476+
EnumDecl *enumDecl = AS->getType().getEnumOrBoundGenericEnum();
477+
if (!enumDecl)
478+
return false;
479+
480+
EnumElementDecl *element = nullptr;
481+
SILType payloadType;
482+
483+
// First step: check if the stack location is only used to hold one specific
484+
// enum case with payload.
485+
for (auto *use : AS->getUses()) {
486+
SILInstruction *user = use->getUser();
487+
switch (user->getKind()) {
488+
case SILInstructionKind::DebugValueAddrInst:
489+
case SILInstructionKind::DestroyAddrInst:
490+
case SILInstructionKind::DeallocStackInst:
491+
break;
492+
case SILInstructionKind::InitEnumDataAddrInst: {
493+
auto *ieda = cast<InitEnumDataAddrInst>(user);
494+
auto *el = ieda->getElement();
495+
if (element && el != element)
496+
return false;
497+
element = el;
498+
assert(!payloadType || payloadType == ieda->getType());
499+
payloadType = ieda->getType();
500+
break;
501+
}
502+
case SILInstructionKind::InjectEnumAddrInst: {
503+
auto *el = cast<InjectEnumAddrInst>(user)->getElement();
504+
if (element && el != element)
505+
return false;
506+
element = el;
507+
break;
508+
}
509+
case SILInstructionKind::UncheckedTakeEnumDataAddrInst: {
510+
auto *el = cast<UncheckedTakeEnumDataAddrInst>(user)->getElement();
511+
if (element && el != element)
512+
return false;
513+
element = el;
514+
break;
515+
}
516+
default:
517+
return false;
518+
}
519+
}
520+
if (!element || !payloadType)
521+
return false;
522+
523+
// Second step: replace the enum alloc_stack with a payload alloc_stack.
524+
auto *newAlloc = Builder.createAllocStack(
525+
AS->getLoc(), payloadType, AS->getVarInfo(), AS->hasDynamicLifetime());
526+
527+
while (!AS->use_empty()) {
528+
Operand *use = *AS->use_begin();
529+
SILInstruction *user = use->getUser();
530+
switch (user->getKind()) {
531+
case SILInstructionKind::InjectEnumAddrInst:
532+
case SILInstructionKind::DebugValueAddrInst:
533+
eraseInstFromFunction(*user);
534+
break;
535+
case SILInstructionKind::DestroyAddrInst:
536+
case SILInstructionKind::DeallocStackInst:
537+
use->set(newAlloc);
538+
break;
539+
case SILInstructionKind::InitEnumDataAddrInst:
540+
case SILInstructionKind::UncheckedTakeEnumDataAddrInst: {
541+
auto *svi = cast<SingleValueInstruction>(user);
542+
svi->replaceAllUsesWith(newAlloc);
543+
eraseInstFromFunction(*svi);
544+
break;
545+
}
546+
default:
547+
llvm_unreachable("unexpected alloc_stack user");
548+
}
549+
}
550+
return true;
551+
}
552+
457553
SILInstruction *SILCombiner::visitAllocStackInst(AllocStackInst *AS) {
458554
if (AS->getFunction()->hasOwnership())
459555
return nullptr;
460556

557+
if (optimizeStackAllocatedEnum(AS))
558+
return nullptr;
559+
461560
// If we are testing SILCombine and we are asked not to eliminate
462561
// alloc_stacks, just return.
463562
if (DisableAllocStackOpts)

test/SILOptimizer/cast_optimizer_conditional_conformance.sil

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,10 @@ extension S1 : HasFoo where T == UInt8 {
3535
// CHECK: [[EXIS:%.*]] = alloc_stack $HasFoo
3636
// CHECK: [[S1:%.*]] = alloc_stack $S1<UInt8>
3737
// CHECK: store %0 to [[S1]] : $*S1<UInt8>
38-
// CHECK: [[OPT:%.*]] = alloc_stack $Optional<HasFoo>
39-
// CHECK: [[INIT_DATA:%.*]] = init_enum_data_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
40-
// CHECK: [[EXIS_ADDR:%.*]] = init_existential_addr [[INIT_DATA]] : $*HasFoo, $S1<UInt8>
38+
// CHECK: [[HASFOO:%.*]] = alloc_stack $HasFoo
39+
// CHECK: [[EXIS_ADDR:%.*]] = init_existential_addr [[HASFOO]] : $*HasFoo, $S1<UInt8>
4140
// CHECK: copy_addr [take] [[S1]] to [initialization] [[EXIS_ADDR]] : $*S1<UInt8>
42-
// CHECK: inject_enum_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
43-
// CHECK: [[DATA:%.*]] = unchecked_take_enum_data_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
44-
// CHECK: copy_addr [take] [[DATA]] to [initialization] [[EXIS]] : $*HasFoo
41+
// CHECK: copy_addr [take] [[HASFOO]] to [initialization] [[EXIS]] : $*HasFoo
4542
// CHECK: [[OPEN_EXIS_ADDR:%.*]] = open_existential_addr immutable_access [[EXIS]] : $*HasFoo to $*@opened("4E16CBC0-FD9F-11E8-A311-D0817AD9F6DD") HasFoo
4643
// CHECK: [[OPEN_ADDR:%.*]] = unchecked_addr_cast [[OPEN_EXIS_ADDR]] : $*@opened("4E16CBC0-FD9F-11E8-A311-D0817AD9F6DD") HasFoo to $*S1<UInt8>
4744
// CHECK: [[F:%.*]] = function_ref @witnessS1 : $@convention(witness_method: HasFoo) (@in_guaranteed S1<UInt8>) -> ()
@@ -171,13 +168,10 @@ struct IsP : P {}
171168
// CHECK: [[EXIS:%.*]] = alloc_stack $HasFoo
172169
// CHECK: [[S2:%.*]] = alloc_stack $S2<IsP>
173170
// CHECK: store %0 to [[S2]] : $*S2<IsP>
174-
// CHECK: [[OPT:%.*]] = alloc_stack $Optional<HasFoo>
175-
// CHECK: [[INIT_DATA:%.*]] = init_enum_data_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
176-
// CHECK: [[EXIS_ADDR:%.*]] = init_existential_addr [[INIT_DATA]] : $*HasFoo, $S2<IsP>
171+
// CHECK: [[HASFOO:%.*]] = alloc_stack $HasFoo
172+
// CHECK: [[EXIS_ADDR:%.*]] = init_existential_addr [[HASFOO]] : $*HasFoo, $S2<IsP>
177173
// CHECK: copy_addr [take] [[S2]] to [initialization] [[EXIS_ADDR]] : $*S2<IsP>
178-
// CHECK: inject_enum_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
179-
// CHECK: [[DATA:%.*]] = unchecked_take_enum_data_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
180-
// CHECK: copy_addr [take] [[DATA]] to [initialization] [[EXIS]] : $*HasFoo
174+
// CHECK: copy_addr [take] [[HASFOO]] to [initialization] [[EXIS]] : $*HasFoo
181175
// CHECK: [[OPEN_EXIS_ADDR:%.*]] = open_existential_addr immutable_access [[EXIS]] : $*HasFoo to $*@opened("4E16D1CE-FD9F-11E8-A311-D0817AD9F6DD") HasFoo
182176
// CHECK: [[OPEN_ADDR:%.*]] = unchecked_addr_cast [[OPEN_EXIS_ADDR]] : $*@opened("4E16D1CE-FD9F-11E8-A311-D0817AD9F6DD") HasFoo to $*S2<IsP>
183177
// CHECK: [[F:%.*]] = function_ref @$s9witnessS24main3IsPV_Tg5 : $@convention(witness_method: HasFoo) (S2<IsP>) -> ()
@@ -246,13 +240,10 @@ extension S3 : HasFoo where T : AnyObject {
246240
// CHECK: [[EXIS:%.*]] = alloc_stack $HasFoo
247241
// CHECK: [[S3:%.*]] = alloc_stack $S3<C>
248242
// CHECK: store %0 to [[S3]] : $*S3<C>
249-
// CHECK: [[OPT:%.*]] = alloc_stack $Optional<HasFoo>
250-
// CHECK: [[INIT_DATA:%.*]] = init_enum_data_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
251-
// CHECK: [[EXIS_ADDR:%.*]] = init_existential_addr [[INIT_DATA]] : $*HasFoo, $S3<C>
243+
// CHECK: [[HASFOO:%.*]] = alloc_stack $HasFoo
244+
// CHECK: [[EXIS_ADDR:%.*]] = init_existential_addr [[HASFOO]] : $*HasFoo, $S3<C>
252245
// CHECK: copy_addr [take] [[S3]] to [initialization] [[EXIS_ADDR]] : $*S3<C>
253-
// CHECK: inject_enum_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
254-
// CHECK: [[DATA:%.*]] = unchecked_take_enum_data_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
255-
// CHECK: copy_addr [take] [[DATA]] to [initialization] [[EXIS]] : $*HasFoo
246+
// CHECK: copy_addr [take] [[HASFOO]] to [initialization] [[EXIS]] : $*HasFoo
256247
// CHECK: [[OPEN_EXIS_ADDR:%.*]] = open_existential_addr immutable_access [[EXIS]] : $*HasFoo to $*@opened("4E16D5E8-FD9F-11E8-A311-D0817AD9F6DD") HasFoo
257248
// CHECK: [[OPEN_ADDR:%.*]] = unchecked_addr_cast [[OPEN_EXIS_ADDR]] : $*@opened("4E16D5E8-FD9F-11E8-A311-D0817AD9F6DD") HasFoo to $*S3<C>
258249
// CHECK: [[F:%.*]] = function_ref @$s9witnessS34main1CC_Tg5 : $@convention(witness_method: HasFoo) (S3<C>) -> ()
@@ -319,13 +310,10 @@ extension S4 : HasFoo where T : C {
319310
// CHECK: [[EXIS:%.*]] = alloc_stack $HasFoo
320311
// CHECK: [[S3:%.*]] = alloc_stack $S4<SubC>
321312
// CHECK: store %0 to [[S3]] : $*S4<SubC>
322-
// CHECK: [[OPT:%.*]] = alloc_stack $Optional<HasFoo>
323-
// CHECK: [[INIT_DATA:%.*]] = init_enum_data_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
324-
// CHECK: [[EXIS_ADDR:%.*]] = init_existential_addr [[INIT_DATA]] : $*HasFoo, $S4<SubC>
313+
// CHECK: [[HASFOO:%.*]] = alloc_stack $HasFoo
314+
// CHECK: [[EXIS_ADDR:%.*]] = init_existential_addr [[HASFOO]] : $*HasFoo, $S4<SubC>
325315
// CHECK: copy_addr [take] [[S3]] to [initialization] [[EXIS_ADDR]] : $*S4<SubC>
326-
// CHECK: inject_enum_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
327-
// CHECK: [[DATA:%.*]] = unchecked_take_enum_data_addr [[OPT]] : $*Optional<HasFoo>, #Optional.some!enumelt
328-
// CHECK: copy_addr [take] [[DATA]] to [initialization] [[EXIS]] : $*HasFoo
316+
// CHECK: copy_addr [take] [[HASFOO]] to [initialization] [[EXIS]] : $*HasFoo
329317
// CHECK: [[OPEN_EXIS_ADDR:%.*]] = open_existential_addr immutable_access [[EXIS]] : $*HasFoo to $*@opened("4E16E402-FD9F-11E8-A311-D0817AD9F6DD") HasFoo
330318
// CHECK: [[OPEN_ADDR:%.*]] = unchecked_addr_cast [[OPEN_EXIS_ADDR]] : $*@opened("4E16E402-FD9F-11E8-A311-D0817AD9F6DD") HasFoo to $*S4<SubC>
331319
// CHECK: [[F:%.*]] = function_ref @$s9witnessS44main4SubCC_Tg5 : $@convention(witness_method: HasFoo) (S4<SubC>) -> ()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %target-swift-frontend -O -module-name=test -emit-sil -primary-file %s | %FileCheck %s
2+
3+
protocol P { associatedtype A = Int }
4+
protocol Q : P {}
5+
6+
protocol B { var x: Int { get } }
7+
struct Y<T> {}
8+
extension Y : B where T : Q { var x: Int { 0 }}
9+
10+
extension P {
11+
var z: Int? { (Y<Self>() as? B)?.x }
12+
}
13+
14+
struct X : Q {
15+
16+
// Check that this getter can be folded to a simple "return 0"
17+
18+
// CHECK-LABEL: sil hidden @$s4test1XV0A2MeSiSgvg : $@convention(method) (X) -> Optional<Int> {
19+
// CHECK: bb0(%0 : $X):
20+
// CHECK-NEXT: integer_literal ${{.*}}, 0
21+
// CHECK-NEXT: struct $Int
22+
// CHECK-NEXT: enum $Optional<Int>, #Optional.some!enumelt
23+
// CHECK-NEXT: return %3 : $Optional<Int>
24+
// CHECK-NEXT: } // end sil function '$s4test1XV0A2MeSiSgvg'
25+
var testMe: Int? { z }
26+
}

test/SILOptimizer/sil_combine_concrete_existential.sil

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,10 @@ sil @$s37witness_return_optional_indirect_self1SVACycfC : $@convention(method) (
183183
// CHECK: [[R1:%.*]] = alloc_stack $Optional<@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP>
184184
// CHECK: [[W1:%.*]] = witness_method $S, #PPP.returnsOptionalIndirect : <Self where Self : PPP> (Self) -> () -> Self? : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0>
185185
// CHECK: apply [[W1]]<@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP>([[R1]], [[O1]]) : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0>
186-
// CHECK: inject_enum_addr [[OE1:%.*]] : $*Optional<PPP>, #Optional.some!enumelt
187-
// CHECK: [[E1:%.*]] = unchecked_take_enum_data_addr [[OE1]] : $*Optional<PPP>, #Optional.some!enumelt
188-
// CHECK: [[O2:%.*]] = open_existential_addr immutable_access [[E1]] : $*PPP to $*@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP
186+
// CHECK: [[E1:%.*]] = unchecked_take_enum_data_addr
187+
// CHECK: [[O2:%.*]] = open_existential_addr immutable_access {{.*}} : $*PPP to $*@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP
189188
// CHECK: [[R2:%.*]] = alloc_stack $Optional<@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP>
190-
// CHECK: [[W2:%.*]] = witness_method $@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP, #PPP.returnsOptionalIndirect : <Self where Self : PPP> (Self) -> () -> Self?, %19 : $*@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0>
189+
// CHECK: [[W2:%.*]] = witness_method $@opened("{{.*}}") PPP, #PPP.returnsOptionalIndirect
191190
// CHECK: apply [[W2]]<@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP>([[R2]], [[O2]]) : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0>
192191
// CHECK-LABEL: } // end sil function '$s37witness_return_optional_indirect_self37testWitnessReturnOptionalIndirectSelfyyF'
193192
sil @$s37witness_return_optional_indirect_self37testWitnessReturnOptionalIndirectSelfyyF : $@convention(thin) () -> () {
@@ -463,13 +462,10 @@ sil [transparent] [reabstraction_thunk] @testDevirtExistentialEnumThunk : $@conv
463462
//
464463
// CHECK-LABEL: sil shared [noinline] @testDevirtExistentialEnum : $@convention(thin) (@guaranteed Array<Int>) -> () {
465464
// CHECK: [[ALLOC_EXISTENTIAL:%.*]] = alloc_stack $ContiguousBytes
466-
// CHECK: [[ALLOC_ENUM:%.*]] = alloc_stack $Optional<ContiguousBytes>
467-
// CHECK: [[ENUM:%.*]] = init_enum_data_addr [[ALLOC_ENUM]] : $*Optional<ContiguousBytes>, #Optional.some!enumelt
468-
// CHECK: [[INIT_DATA:%.*]] = init_existential_addr [[ENUM]] : $*ContiguousBytes, $Array<Int>
465+
// CHECK: [[ALLOC_ENUM:%.*]] = alloc_stack $ContiguousBytes
466+
// CHECK: [[INIT_DATA:%.*]] = init_existential_addr [[ALLOC_ENUM]] : $*ContiguousBytes, $Array<Int>
469467
// CHECK: store %0 to [[INIT_DATA]] : $*Array<Int>
470-
// CHECK: inject_enum_addr [[ALLOC_ENUM]] : $*Optional<ContiguousBytes>, #Optional.some!enumelt
471-
// CHECK: [[TAKE_DATA:%.*]] = unchecked_take_enum_data_addr [[ALLOC_ENUM]] : $*Optional<ContiguousBytes>, #Optional.some!enumelt
472-
// CHECK: copy_addr [take] [[TAKE_DATA]] to [initialization] [[ALLOC_EXISTENTIAL]] : $*ContiguousBytes
468+
// CHECK: copy_addr [take] [[ALLOC_ENUM]] to [initialization] [[ALLOC_EXISTENTIAL]] : $*ContiguousBytes
473469
// CHECK: [[OPENED:%.*]] = open_existential_addr immutable_access [[ALLOC_EXISTENTIAL]] : $*ContiguousBytes to $*@opened("8402EC1A-F35D-11E8-950A-D0817AD9F6DD") ContiguousBytes
474470
// CHECK: [[THUNK:%.*]] = function_ref @testDevirtExistentialEnumThunk : $@convention(thin) (UnsafeRawBufferPointer) -> (@out (), @error Error)
475471
// CHECK: [[TTF:%.*]] = thin_to_thick_function [[THUNK:%.*]] : $@convention(thin) (UnsafeRawBufferPointer) -> (@out (), @error Error) to $@noescape @callee_guaranteed (UnsafeRawBufferPointer) -> (@out (), @error Error)

test/SILOptimizer/sil_combine_enum_addr.sil

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import Builtin
66
import Swift
77

88
// CHECK-LABEL: sil @convert_inject_enum_addr_select_enum_addr_into_cond_br : $@convention(thin) (@in Int, @inout _Stdout) -> ()
9-
// CHECK: init_existential_addr
10-
// CHECK: inject_enum_addr
9+
// CHECK-NOT: init_existential_addr
10+
// CHECK-NOT: inject_enum_addr
1111
// CHECK-NOT: select_enum_addr
1212
// CHECK-NOT: bb1
1313
// CHECK-NOT: unchecked_take_enum_data_addr
@@ -42,8 +42,8 @@ bb2:
4242

4343

4444
// CHECK-LABEL: sil @convert_inject_enum_addr_switch_enum_addr_into_cond_br : $@convention(thin) (@in Int, @inout _Stdout) -> ()
45-
// CHECK: init_existential_addr
46-
// CHECK: inject_enum_addr
45+
// CHECK-NOT: init_existential_addr
46+
// CHECK-NOT: inject_enum_addr
4747
// CHECK-NOT: switch_enum_addr
4848
// CHECK-NOT: bb1
4949
// CHECK-NOT: unchecked_take_enum_data_addr

0 commit comments

Comments
 (0)