Skip to content

Commit c246b49

Browse files
committed
Insert end_cow_mutation_addr for lifetime dependent values dependent on modify accesses
Array/ArraySlice/ContiguousArray have support for mutableSpan property. These types are "optimized COW" types, we use compiler builtins begin_cow_mutation/end_cow_mutation to optimize uniqueness checks. Since mutableSpan is a property and not a coroutine there is no way to schedule the end_cow_mutaton operation at the end of the access. This can lead to miscompiles in rare cases where we can end up using a stale storage buffer after a cow. This PR inserts end_cow_mutation_addr to avoid this issue. Note: We can end up with unnecessary end_cow_mutation_addr. But it is just a barrier to prevent invalid optimizations and has no impact.
1 parent 124cd26 commit c246b49

File tree

4 files changed

+257
-3
lines changed

4 files changed

+257
-3
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,25 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
121121
var scopeExtension = ScopeExtension(localReachabilityCache, context)
122122
let args = scopeExtension.extendScopes(dependence: newLifetimeDep)
123123

124+
if args.empty {
125+
createEndCOWMutationIfNeeded(lifetimeDep: newLifetimeDep, context)
126+
}
127+
124128
// Redirect the dependence base to the function arguments. This may create additional mark_dependence instructions.
125129
markDep.redirectFunctionReturn(to: args, context)
126130
}
127131
}
128132

133+
private func createEndCOWMutationIfNeeded(lifetimeDep: LifetimeDependence, _ context: FunctionPassContext) {
134+
if let accessBase = lifetimeDep.parentValue as? BeginAccessInst,
135+
accessBase.accessKind == .modify {
136+
for endAccess in accessBase.endAccessInstructions {
137+
let builder = Builder(before: endAccess, location: endAccess.location, context)
138+
builder.createEndCOWMutationAddr(instance: accessBase)
139+
}
140+
}
141+
}
142+
129143
private extension MarkDependenceInstruction {
130144
/// Rewrite the mark_dependence base operand to ignore inner borrow scopes (begin_borrow, load_borrow).
131145
///

SwiftCompilerSources/Sources/SIL/Utilities/SequenceUtilities.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ public struct SingleInlineArray<Element>: RandomAccessCollection, FormattedLikeA
133133
singleElement == nil ? 0 : multipleElements.count + 1
134134
}
135135

136+
public var empty: Bool {
137+
endIndex == 0
138+
}
139+
136140
public subscript(_ index: Int) -> Element {
137141
_read {
138142
if index == 0 {

stdlib/public/core/Array.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1736,9 +1736,7 @@ extension Array {
17361736
@_alwaysEmitIntoClient
17371737
mutating get {
17381738
_makeMutableAndUnique()
1739-
// NOTE: We don't have the ability to schedule a call to
1740-
// ContiguousArrayBuffer.endCOWMutation().
1741-
// rdar://146785284 (lifetime analysis for end of mutation)
1739+
// LifetimeDependence analysis inserts call to Builtin.endCOWMutation.
17421740
let pointer = unsafe _buffer.firstElementAddress
17431741
let count = _buffer.mutableCount
17441742
let span = unsafe MutableSpan(_unsafeStart: pointer, count: count)
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// RUN:%target-swift-frontend %s -emit-sil \
2+
// RUN: -verify \
3+
// RUN: -sil-verify-all \
4+
// RUN: -disable-availability-checking \
5+
// RUN: -enable-experimental-feature LifetimeDependence | %FileCheck %s
6+
7+
// REQUIRES: swift_in_compiler
8+
// REQUIRES: swift_feature_LifetimeDependence
9+
10+
@inline(never)
11+
func use<T>(_ t: T) {
12+
13+
}
14+
15+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow11testNoWriteyySaySiGzF :
16+
// CHECK: [[BA:%.*]] = begin_access [modify] [static] %0
17+
// CHECK: [[FUN1:%.*]] = function_ref @$sSa11mutableSpans07MutableB0VyxGvg :
18+
// CHECK: [[APPLY1:%.*]] = apply [[FUN1]]<Int>([[BA]]) : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
19+
// CHECK: [[FUN2:%.*]] = function_ref @$s23lifetime_dependence_cow3useyyxlF : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
20+
// CHECK: [[APPLY2:%.*]] = apply [[FUN2]]<Int>({{.*}}) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
21+
// CHECK: end_cow_mutation_addr [[BA]]
22+
// CHECK: end_access [[BA]]
23+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow11testNoWriteyySaySiGzF'
24+
func testNoWrite(_ array: inout [Int]) {
25+
let span = array.mutableSpan // begin_cow_mutation
26+
use(span.count)
27+
}
28+
29+
// Generic element version
30+
func testNoWrite<T>(_ array: inout [T]) {
31+
let span = array.mutableSpan // begin_cow_mutation
32+
use(span.count)
33+
}
34+
35+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow9testWriteyySaySiGzF :
36+
// CHECK: [[BA:%.*]] = begin_access [modify] [static] %0
37+
// CHECK: [[FUN1:%.*]] = function_ref @$sSa11mutableSpans07MutableB0VyxGvg :
38+
// CHECK: [[APPLY1:%.*]] = apply [[FUN1]]<Int>([[BA]]) : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
39+
// CHECK: end_cow_mutation_addr [[BA]]
40+
// CHECK: end_access [[BA]]
41+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow9testWriteyySaySiGzF'
42+
func testWrite(_ array: inout [Int]) {
43+
var span = array.mutableSpan // begin_cow_mutation
44+
for i in span.indices {
45+
span[i] = 0
46+
}
47+
}
48+
49+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow9testWriteyys10ArraySliceVySiGzF :
50+
// CHECK: [[BA:%.*]] = begin_access [modify] [static] %0
51+
// CHECK: [[FUN1:%.*]] = function_ref @$ss10ArraySliceV11mutableSpans07MutableD0VyxGvg : $@convention(method) <τ_0_0> (@inout ArraySlice<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
52+
// CHECK: [[APPLY1:%.*]] = apply [[FUN1]]<Int>(%3) : $@convention(method) <τ_0_0> (@inout ArraySlice<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
53+
// CHECK: end_cow_mutation_addr [[BA]]
54+
// CHECK: end_access [[BA]]
55+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow9testWriteyys10ArraySliceVySiGzF'
56+
func testWrite(_ array: inout ArraySlice<Int>) {
57+
var span = array.mutableSpan
58+
for i in span.indices {
59+
span[i] = 0
60+
}
61+
}
62+
63+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow9testWriteyys15ContiguousArrayVySiGzF :
64+
// CHECK: [[BA:%.*]] = begin_access [modify] [static] %0
65+
// CHECK: [[FUN1:%.*]] = function_ref @$ss15ContiguousArrayV11mutableSpans07MutableD0VyxGvg : $@convention(method) <τ_0_0> (@inout ContiguousArray<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
66+
// CHECK: [[APPLY1:%.*]] = apply [[FUN1]]<Int>([[BA]]) : $@convention(method) <τ_0_0> (@inout ContiguousArray<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
67+
// CHECK: end_cow_mutation_addr [[BA]]
68+
// CHECK: end_access [[BA]]
69+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow9testWriteyys15ContiguousArrayVySiGzF'
70+
func testWrite(_ array: inout ContiguousArray<Int>) {
71+
var span = array.mutableSpan
72+
for i in span.indices {
73+
span[i] = 0
74+
}
75+
}
76+
77+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow9testLocalyyF :
78+
// CHECK: [[BA:%.*]] = begin_access [modify] [static] %0
79+
// CHECK: [[FUN1:%.*]] = function_ref @$sSa11mutableSpans07MutableB0VyxGvg :
80+
// CHECK: [[APPLY1:%.*]] = apply [[FUN1]]<Int>([[BA]]) : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
81+
// CHECK: end_cow_mutation_addr [[BA]]
82+
// CHECK: end_access [[BA]]
83+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow9testLocalyyF'
84+
func testLocal() {
85+
var array = [1, 2, 3]
86+
var span = array.mutableSpan // begin_cow_mutation
87+
for i in span.indices {
88+
span[i] = 0
89+
}
90+
}
91+
92+
// CHECK-LABEL: sil hidden [noinline] @$s23lifetime_dependence_cow14getMutableSpanys0eF0VySiGSaySiGzF :
93+
// CHECK-NOT: end_cow_mutation
94+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow14getMutableSpanys0eF0VySiGSaySiGzF'
95+
@inline(never)
96+
func getMutableSpan(_ array: inout [Int]) -> MutableSpan<Int> {
97+
return array.mutableSpan // begin_cow_mutation
98+
}
99+
100+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow13modifyViaSpanyySaySiGzF :
101+
// CHECK: [[BA:%.*]] = begin_access [modify] [static] %0
102+
// CHECK: [[FUN:%.*]] = function_ref @$s23lifetime_dependence_cow14getMutableSpanys0eF0VySiGSaySiGzF : $@convention(thin) (@inout Array<Int>) -> @lifetime(borrow 0) @owned MutableSpan<Int>
103+
// CHECK: [[APPLY:%.*]] = apply [[FUN]]([[BA]]) : $@convention(thin) (@inout Array<Int>) -> @lifetime(borrow 0) @owned MutableSpan<Int>
104+
// CHECK: end_cow_mutation_addr [[BA]]
105+
// CHECK: end_access [[BA]]
106+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow13modifyViaSpanyySaySiGzF'
107+
func modifyViaSpan(_ array: inout [Int]) {
108+
var span = getMutableSpan(&array)
109+
for i in span.indices {
110+
span[i] = 0
111+
}
112+
}
113+
114+
@inline(never)
115+
func modifyBlackhole(_ array: inout [Int]) {}
116+
117+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow11modifyTwiceyySaySiGzF :
118+
// CHECK: [[BA:%.*]] = begin_access [modify] [static] %0
119+
// CHECK: [[FUN:%.*]] = function_ref @$s23lifetime_dependence_cow14getMutableSpanys0eF0VySiGSaySiGzF : $@convention(thin) (@inout Array<Int>) -> @lifetime(borrow 0) @owned MutableSpan<Int>
120+
// CHECK: [[APPLY:%.*]] = apply [[FUN]]([[BA]]) : $@convention(thin) (@inout Array<Int>) -> @lifetime(borrow 0) @owned MutableSpan<Int>
121+
// CHECK: end_cow_mutation_addr [[BA]]
122+
// CHECK: end_access [[BA]]
123+
// CHECK: [[FUN2:%.*]] = function_ref @$s23lifetime_dependence_cow15modifyBlackholeyySaySiGzF : $@convention(thin) (@inout Array<Int>) -> ()
124+
// CHECK: [[APPLY2:%.*]] = apply [[FUN2]]({{.*}}) : $@convention(thin) (@inout Array<Int>) -> ()
125+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow11modifyTwiceyySaySiGzF'
126+
func modifyTwice(_ array: inout [Int]) {
127+
var span = getMutableSpan(&array)
128+
for i in span.indices {
129+
span[i] = 0
130+
}
131+
modifyBlackhole(&array)
132+
}
133+
134+
// CHECK-LABEL: sil hidden [noinline] @$s23lifetime_dependence_cow7getSpanys0E0VySiGSaySiGzF :
135+
// CHECK-NOT: end_cow_mutation
136+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow7getSpanys0E0VySiGSaySiGzF'
137+
@inline(never)
138+
@lifetime(&array)
139+
func getSpan(_ array: inout [Int]) -> Span<Int> {
140+
return array.span
141+
}
142+
143+
// This test has an unnecessary end_cow_mutation
144+
145+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow17inoutNoModifySpanyySaySiGzF : $@convention(thin) (@inout Array<Int>) -> () {
146+
// CHECK: end_cow_mutation
147+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow17inoutNoModifySpanyySaySiGzF'
148+
func inoutNoModifySpan(_ array: inout [Int]) {
149+
let span = getSpan(&array)
150+
for i in span.indices {
151+
use(span[i])
152+
}
153+
}
154+
155+
struct Holder {
156+
var array: [Int]
157+
}
158+
159+
// CHECK-LABEL: sil hidden [noinline] @$s23lifetime_dependence_cow14getMutableSpanys0eF0VySiGAA6HolderVzF :
160+
// CHECK-NOT: end_cow_mutation
161+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow14getMutableSpanys0eF0VySiGAA6HolderVzF'
162+
@inline(never)
163+
func getMutableSpan(_ holder: inout Holder) -> MutableSpan<Int> {
164+
return holder.array.mutableSpan // begin_cow_mutation
165+
}
166+
167+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow14invisibleArrayyyAA6HolderVzF :
168+
// CHECK: [[BA:%.*]] = begin_access [modify] [static] %0
169+
// CHECK: [[FUNC:%.*]] = function_ref @$s23lifetime_dependence_cow14getMutableSpanys0eF0VySiGAA6HolderVzF : $@convention(thin) (@inout Holder) -> @lifetime(borrow 0) @owned MutableSpan<Int>
170+
// CHECK: [[APPLY:%.*]] = apply [[FUNC]]([[BA]]) : $@convention(thin) (@inout Holder) -> @lifetime(borrow 0) @owned MutableSpan<Int>
171+
// CHECK: end_cow_mutation_addr [[BA]]
172+
// CHECK: end_access [[BA]]
173+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow14invisibleArrayyyAA6HolderVzF'
174+
func invisibleArray(_ holder: inout Holder) {
175+
var span = getMutableSpan(&holder)
176+
for i in span.indices {
177+
span[i] = 0
178+
}
179+
}
180+
181+
@inline(never)
182+
func mayGetMutableSpan(_ array: inout [Int]) -> MutableSpan<Int>? {
183+
if Int.random(in: 1...10_000) % 2 == 0 {
184+
return array.mutableSpan // begin_cow_mutation
185+
}
186+
return nil
187+
}
188+
189+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow16mayModifyViaSpanyySaySiGzF :
190+
// CHECK: end_cow_mutation_addr
191+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow16mayModifyViaSpanyySaySiGzF'
192+
func mayModifyViaSpan(_ array: inout [Int]) {
193+
guard var span = mayGetMutableSpan(&array) else {
194+
return
195+
}
196+
for i in span.indices {
197+
span[i] = 0
198+
}
199+
}
200+
201+
@inline(never)
202+
@lifetime(&array1, &array2)
203+
func getOneMutableSpan(_ array1: inout [Int], _ array2: inout [Int]) -> MutableSpan<Int> {
204+
if Int.random(in: 1...10_000) % 2 == 0 {
205+
return array1.mutableSpan
206+
}
207+
return array2.mutableSpan
208+
}
209+
210+
// CHECK-LABEL: sil hidden @$s23lifetime_dependence_cow16modifyViaOneSpanyySaySiGz_ACztF :
211+
// CHECK: end_cow_mutation_addr
212+
// CHECK: end_cow_mutation_addr
213+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow16modifyViaOneSpanyySaySiGz_ACztF'
214+
func modifyViaOneSpan(_ array1: inout [Int], _ array2: inout [Int]) {
215+
var span = getOneMutableSpan(&array1, &array2)
216+
for i in span.indices {
217+
span[i] = 0
218+
}
219+
}
220+
221+
protocol P {
222+
mutating func getMutableSpan() -> MutableSpan<Int>
223+
}
224+
225+
struct C : P {
226+
var array: [Int]
227+
mutating func getMutableSpan() -> MutableSpan<Int> {
228+
array.mutableSpan
229+
}
230+
}
231+
232+
func testWriteOpaque<T : P>(_ data: inout T) {
233+
var span = data.getMutableSpan()
234+
for i in span.indices {
235+
span[i] = 0
236+
}
237+
}
238+

0 commit comments

Comments
 (0)