Skip to content

Commit cf99e5c

Browse files
committed
[capture-promotion] When checking if a (struct_element_addr (project_box box)) is written to, check that all of the operands are loads, instead of returning early when we find one.
I found this bug by inspection. This is an important bug to fix since this pass runs at -Onone and the bug results in the compiler hitting an unreachable. The way the unreachable is triggered is that when we detect that we are going to promote a box, if we see a (struct_element_addr (project_box box)), we don't map the struct_element_addr to a cloned value. If we have a load, this is not an issue, since we are mapping the load to the struct_extract. But if we have /any/ other non-load users of the struct_element_addr, the cloner will attempt to look up the struct_element_addr and will be unable to find it, hitting an unreachable. rdar://32776202
1 parent 4b0597a commit cf99e5c

File tree

4 files changed

+71
-12
lines changed

4 files changed

+71
-12
lines changed

lib/SILOptimizer/IPO/CapturePromotion.cpp

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,13 @@ static SILArgument *getBoxFromIndex(SILFunction *F, unsigned Index) {
694694
return Entry.getArgument(Index);
695695
}
696696

697+
static bool isNonMutatingLoad(SILInstruction *I) {
698+
auto *LI = dyn_cast<LoadInst>(I);
699+
if (!LI)
700+
return false;
701+
return LI->getOwnershipQualifier() != LoadOwnershipQualifier::Take;
702+
}
703+
697704
/// \brief Given a partial_apply instruction and the argument index into its
698705
/// callee's argument list of a box argument (which is followed by an argument
699706
/// for the address of the box's contents), return true if the closure is known
@@ -726,16 +733,15 @@ isNonMutatingCapture(SILArgument *BoxArg) {
726733
// function that mirrors isNonEscapingUse.
727734
auto checkAddrUse = [](SILInstruction *AddrInst) {
728735
if (auto *SEAI = dyn_cast<StructElementAddrInst>(AddrInst)) {
729-
for (auto *UseOper : SEAI->getUses()) {
730-
if (isa<LoadInst>(UseOper->getUser()))
731-
return true;
732-
}
733-
} else if (isa<LoadInst>(AddrInst) || isa<DebugValueAddrInst>(AddrInst)
734-
|| isa<MarkFunctionEscapeInst>(AddrInst)
735-
|| isa<EndAccessInst>(AddrInst)) {
736-
return true;
736+
return all_of(SEAI->getUses(),
737+
[](Operand *Op) -> bool {
738+
return isNonMutatingLoad(Op->getUser());
739+
});
737740
}
738-
return false;
741+
742+
return isNonMutatingLoad(AddrInst) || isa<DebugValueAddrInst>(AddrInst)
743+
|| isa<MarkFunctionEscapeInst>(AddrInst)
744+
|| isa<EndAccessInst>(AddrInst);
739745
};
740746
for (auto *Projection : Projections) {
741747
for (auto *UseOper : Projection->getUses()) {
@@ -744,7 +750,10 @@ isNonMutatingCapture(SILArgument *BoxArg) {
744750
if (!checkAddrUse(AccessUseOper->getUser()))
745751
return false;
746752
}
747-
} else if (!checkAddrUse(UseOper->getUser()))
753+
continue;
754+
}
755+
756+
if (!checkAddrUse(UseOper->getUser()))
748757
return false;
749758
}
750759
}

test/SILOptimizer/capture_promotion.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend %s -emit-sil -o - -verify | %FileCheck %s
1+
// RUN: %target-swift-frontend %s -emit-sil -o - | %FileCheck %s
22

33
class Foo {
44
func foo() -> Int {

test/SILOptimizer/capture_promotion_ownership.sil

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,53 @@ bb0(%0: @trivial $*Int, %1 : @owned $<τ_0_0> { var τ_0_0 } <Foo>, %2 : @owned
332332
%16 = tuple()
333333
return %16 : $()
334334
}
335+
336+
// This test makes sure that we properly handle non load uses of
337+
// struct_element_addr that always have load users with the load occuring before
338+
// and after the element in the use list.
339+
340+
sil @mutate_int : $@convention(thin) (@inout Int) -> ()
341+
342+
// CHECK-LABEL: sil @test_closure_multiple_uses_of_struct_element_addr : $@convention(thin) () -> @owned @callee_owned () -> () {
343+
// CHECK: [[FUNC:%.*]] = function_ref @closure_multiple_uses_of_struct_element_addr : $@convention(thin)
344+
// CHECK: partial_apply [[FUNC]](
345+
// CHECK: } // end sil function 'test_closure_multiple_uses_of_struct_element_addr'
346+
sil @test_closure_multiple_uses_of_struct_element_addr : $@convention(thin) () -> @owned @callee_owned () -> () {
347+
bb0:
348+
%0 = function_ref @baz_init : $@convention(thin) (@thin Baz.Type) -> @owned Baz
349+
%1 = metatype $@thin Baz.Type
350+
351+
%2 = alloc_box $<τ_0_0> { var τ_0_0 } <Baz>
352+
%3 = project_box %2 : $<τ_0_0> { var τ_0_0 } <Baz>, 0
353+
%4 = apply %0(%1) : $@convention(thin) (@thin Baz.Type) -> @owned Baz
354+
store %4 to [init] %3 : $*Baz
355+
356+
%5 = alloc_box $<τ_0_0> { var τ_0_0 } <Baz>
357+
%6 = project_box %5 : $<τ_0_0> { var τ_0_0 } <Baz>, 0
358+
%7 = apply %0(%1) : $@convention(thin) (@thin Baz.Type) -> @owned Baz
359+
store %7 to [init] %6 : $*Baz
360+
361+
%8 = function_ref @closure_multiple_uses_of_struct_element_addr : $@convention(thin) (@owned <τ_0_0> { var τ_0_0 } <Baz>, @owned <τ_0_0> { var τ_0_0 } <Baz>) -> ()
362+
%9 = partial_apply %8(%2, %5) : $@convention(thin) (@owned <τ_0_0> { var τ_0_0 } <Baz>, @owned <τ_0_0> { var τ_0_0 } <Baz>) -> ()
363+
364+
return %9 : $@callee_owned () -> ()
365+
}
366+
367+
sil @closure_multiple_uses_of_struct_element_addr : $@convention(thin) (@owned <τ_0_0> { var τ_0_0 } <Baz>, @owned <τ_0_0> { var τ_0_0 } <Baz>) -> () {
368+
bb0(%0 : @owned $<τ_0_0> { var τ_0_0 } <Baz>, %1 : @owned $<τ_0_0> { var τ_0_0 } <Baz>):
369+
%2 = project_box %0 : $<τ_0_0> { var τ_0_0 } <Baz>, 0
370+
%3 = struct_element_addr %2 : $*Baz, #Baz.x
371+
%4 = load [trivial] %3 : $*Int
372+
%5 = function_ref @mutate_int : $@convention(thin) (@inout Int) -> ()
373+
apply %5(%3) : $@convention(thin) (@inout Int) -> ()
374+
375+
%6 = project_box %1 : $<τ_0_0> { var τ_0_0 } <Baz>, 0
376+
%7 = struct_element_addr %6 : $*Baz, #Baz.x
377+
apply %5(%7) : $@convention(thin) (@inout Int) -> ()
378+
%8 = load [trivial] %7 : $*Int
379+
380+
destroy_value %1 : $<τ_0_0> { var τ_0_0 } <Baz>
381+
destroy_value %0 : $<τ_0_0> { var τ_0_0 } <Baz>
382+
%9999 = tuple()
383+
return %9999 : $()
384+
}

test/SILOptimizer/capture_promotion_ownership.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend %s -enable-sil-ownership -disable-sil-linking -emit-sil -o - -verify | %FileCheck %s
1+
// RUN: %target-swift-frontend %s -enable-sil-ownership -disable-sil-linking -emit-sil -o - | %FileCheck %s
22

33
// NOTE: We add -disable-sil-linking to the compile line to ensure that we have
44
// access to declarations for standard library types, but not definitions. This

0 commit comments

Comments
 (0)