Skip to content

Commit 8ed84e8

Browse files
committed
Handle the mark_dependence chain when eliminating copies in ClosureLifetimeFixup
1 parent f6cfa08 commit 8ed84e8

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

lib/SILOptimizer/Mandatory/ClosureLifetimeFixup.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ static SILValue tryRewriteToPartialApplyStack(
734734
unsigned appliedArgStartIdx =
735735
newPA->getOrigCalleeType()->getNumParameters() - newPA->getNumArguments();
736736

737+
MarkDependenceInst *markDepChain = nullptr;
737738
for (unsigned i : indices(newPA->getArgumentOperands())) {
738739
auto &arg = newPA->getArgumentOperands()[i];
739740
SILValue copy = arg.get();
@@ -781,6 +782,20 @@ static SILValue tryRewriteToPartialApplyStack(
781782
continue;
782783
}
783784
if (auto mark = dyn_cast<MarkDependenceInst>(use->getUser())) {
785+
// When we insert mark_dependence for non-trivial address operands, we
786+
// emit a chain that looks like:
787+
// %md = mark_dependence %pai on %0
788+
// %md2 = mark_dependence %md on %1
789+
// to tie all of those operands together on the same partial_apply.
790+
//
791+
// FIXME: Should we not be chaining like this and just emit independent
792+
// mark_dependence?
793+
if (markDepChain && mark->getValue() == markDepChain) {
794+
markDep = mark;
795+
markDepChain = mark;
796+
continue;
797+
}
798+
784799
// If we're marking dependence of the current partial_apply on this
785800
// stack slot, that's fine.
786801
if (mark->getValue() != newPA
@@ -792,6 +807,11 @@ static SILValue tryRewriteToPartialApplyStack(
792807
break;
793808
}
794809
markDep = mark;
810+
811+
if (!markDepChain) {
812+
markDepChain = mark;
813+
}
814+
795815
continue;
796816
}
797817

test/SILOptimizer/closure_lifetime_fixup.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,29 @@ public class F<T> {
446446
public func testClosureMethodParam<T>(f: F<T>) throws {
447447
try f.test { return f.t! }
448448
}
449+
450+
struct AddressOnlyNoncopyableStruct: ~Copyable {
451+
let x: Any = 123
452+
453+
borrowing func hello() {}
454+
}
455+
456+
func simpleNonescapingClosure(with body: () -> ()) {
457+
body()
458+
}
459+
460+
// CHECK-LABEL: s22closure_lifetime_fixup27trySimpleNonescapingClosure
461+
// CHECK: [[FIRST:%.*]] = alloc_stack [var_decl] $AddressOnlyNoncopyableStruct, let, name "foo"
462+
// CHECK: [[SECOND:%.*]] = alloc_stack [var_decl] $AddressOnlyNoncopyableStruct, let, name "bar"
463+
// CHECK: [[PA:%.*]] = partial_apply [callee_guaranteed] [on_stack] %{{.*}}([[FIRST]], [[SECOND]])
464+
// CHECK: [[MD_ONE:%.*]] = mark_dependence [nonescaping] [[PA]] : $@noescape @callee_guaranteed () -> () on [[FIRST]] : $*AddressOnlyNoncopyableStruct
465+
// CHECK: [[MD_TWO:%.*]] = mark_dependence [nonescaping] [[MD_ONE]] : $@noescape @callee_guaranteed () -> () on [[SECOND]] : $*AddressOnlyNoncopyableStruct
466+
func trySimpleNonescapingClosure() {
467+
let foo = AddressOnlyNoncopyableStruct()
468+
let bar = AddressOnlyNoncopyableStruct()
469+
470+
simpleNonescapingClosure {
471+
foo.hello() // OK
472+
bar.hello() // OK
473+
}
474+
}

test/SILOptimizer/stdlib/Atomics.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import Synchronization
66

7+
@_silgen_name("testInt")
8+
func testInt(_: Int)
9+
710
//===----------------------------------------------------------------------===//
811
// Ensure that we don't destroy the atomic before operations
912
//===----------------------------------------------------------------------===//
@@ -77,3 +80,38 @@ func deadAtomic() {
7780
let _ = Atomic(0)
7881
let _ = Atomic<UnsafeRawPointer?>(nil)
7982
}
83+
84+
//===----------------------------------------------------------------------===//
85+
// Closure Lifetime Fixup
86+
//===----------------------------------------------------------------------===//
87+
88+
func nonescapingClosure(with body: () -> ()) {
89+
body()
90+
}
91+
92+
// CHECK-LABEL: sil {{.*}} @testNonescapingClosure {{.*}} {
93+
// CHECK: {{%.*}} = alloc_stack [lexical] [var_decl] $Atomic<Int>, let, name "foo"
94+
// CHECK: {{%.*}} = alloc_stack [lexical] [var_decl] $Atomic<Int>, let, name "bar"
95+
// CHECK: builtin "atomicrmw_add_monotonic_Int[[PTR_SIZE]]"
96+
// CHECK: builtin "atomicrmw_add_monotonic_Int[[PTR_SIZE]]"
97+
98+
// Make sure there are no moves
99+
// CHECK-NOT: alloc_stack $Atomic<Int>
100+
101+
// Make sure we don't emit a partial application
102+
// CHECK-NOT: partial_apply
103+
104+
// CHECK-LABEL: } // end sil function 'testNonescapingClosure'
105+
@_silgen_name("testNonescapingClosure")
106+
func testNonescapingClosure() {
107+
let foo = Atomic(0)
108+
let bar = Atomic(1)
109+
110+
nonescapingClosure {
111+
foo.wrappingAdd(1, ordering: .relaxed)
112+
bar.wrappingAdd(1, ordering: .relaxed)
113+
}
114+
115+
testInt(foo.load(ordering: .relaxed)) // OK
116+
testInt(bar.load(ordering: .relaxed)) // OK
117+
}

0 commit comments

Comments
 (0)