Skip to content

Commit 790d4da

Browse files
authored
Merge pull request #74593 from Azoy/why-are-we-copying
[SILOptimizer] Handle the mark_dependence chain when eliminating copies in ClosureLifetimeFixup
2 parents c9e3e79 + 8ed84e8 commit 790d4da

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
@@ -735,6 +735,7 @@ static SILValue tryRewriteToPartialApplyStack(
735735
unsigned appliedArgStartIdx =
736736
newPA->getOrigCalleeType()->getNumParameters() - newPA->getNumArguments();
737737

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

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)