Skip to content

Commit f40f5cb

Browse files
committed
Insert end_cow_mutation_addr for lifetime dependent values dependent on mutable addresses
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 2746a83 commit f40f5cb

File tree

5 files changed

+579
-4
lines changed

5 files changed

+579
-4
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,102 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
124124
}
125125
let args = scopeExtension.findArgumentDependencies()
126126

127+
// If the scope cannot be extended to the caller, this must be the outermost dependency level.
128+
// Insert end_cow_mutation_addr if needed.
129+
if args.isEmpty {
130+
createEndCOWMutationIfNeeded(lifetimeDep: newLifetimeDep, context)
131+
}
132+
127133
// Redirect the dependence base to the function arguments. This may create additional mark_dependence instructions.
128134
markDep.redirectFunctionReturn(to: args, context)
129135
}
130136
}
131137

138+
private extension Type {
139+
func mayHaveMutableSpan(in function: Function, _ context: FunctionPassContext) -> Bool {
140+
if hasArchetype {
141+
return true
142+
}
143+
if isBuiltinType {
144+
return false
145+
}
146+
// Only result types that are nominal can have a MutableSpan derived from an inout array access.
147+
if nominal == nil {
148+
return false
149+
}
150+
if nominal == context.swiftMutableSpan {
151+
return true
152+
}
153+
if isStruct {
154+
guard let fields = getNominalFields(in: function) else {
155+
return false
156+
}
157+
return fields.contains { $0.mayHaveMutableSpan(in: function, context) }
158+
}
159+
if isTuple {
160+
return tupleElements.contains { $0.mayHaveMutableSpan(in: function, context) }
161+
}
162+
if isEnum {
163+
guard let cases = getEnumCases(in: function) else {
164+
return true
165+
}
166+
return cases.contains { $0.payload?.mayHaveMutableSpan(in: function, context) ?? false }
167+
}
168+
// Classes cannot be ~Escapable, therefore cannot hold a MutableSpan.
169+
if isClass {
170+
return false
171+
}
172+
return false
173+
}
174+
}
175+
176+
/// Insert end_cow_mutation_addr for lifetime dependent values that maybe of type MutableSpan and depend on a mutable address.
177+
private func createEndCOWMutationIfNeeded(lifetimeDep: LifetimeDependence, _ context: FunctionPassContext) {
178+
var scoped : ScopedInstruction
179+
180+
// Handle cases which generate mutable addresses: begin_access [modify] and yield &
181+
switch lifetimeDep.scope {
182+
case let .access(beginAccess):
183+
if beginAccess.accessKind != .modify {
184+
return
185+
}
186+
scoped = beginAccess
187+
case let .yield(value):
188+
let beginApply = value.definingInstruction as! BeginApplyInst
189+
if value == beginApply.token {
190+
return
191+
}
192+
if beginApply.convention(of: value as! MultipleValueInstructionResult) != .indirectInout {
193+
return
194+
}
195+
scoped = beginApply
196+
// None of the below cases can generate a mutable address.
197+
case let .owned:
198+
fallthrough
199+
case let .borrowed:
200+
fallthrough
201+
case let .local:
202+
fallthrough
203+
case let .initialized:
204+
fallthrough
205+
case let .caller:
206+
fallthrough
207+
case let .global:
208+
fallthrough
209+
case let .unknown:
210+
return
211+
}
212+
213+
guard lifetimeDep.dependentValue.type.mayHaveMutableSpan(in: lifetimeDep.dependentValue.parentFunction, context) else {
214+
return
215+
}
216+
217+
for endInstruction in scoped.endInstructions {
218+
let builder = Builder(before: endInstruction, context)
219+
builder.createEndCOWMutationAddr(address: lifetimeDep.parentValue)
220+
}
221+
}
222+
132223
private extension MarkDependenceInstruction {
133224
/// Rewrite the mark_dependence base operand to ignore inner borrow scopes (begin_borrow, load_borrow).
134225
///
@@ -194,7 +285,7 @@ private extension MarkDependenceAddrInst {
194285
}
195286
}
196287

197-
/// A scope extension is a set of nested scopes and their owners. The owner is a value that represents ownerhip of
288+
/// A scope extension is a set of nested scopes and their owners. The owner is a value that represents ownership of
198289
/// the outermost scopes, which cannot be extended; it limits how far the nested scopes can be extended.
199290
private struct ScopeExtension {
200291
let context: FunctionPassContext

docs/SIL/Instructions.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2146,6 +2146,19 @@ not replace this reference with a not uniquely reference object.
21462146

21472147
For details see [Copy-on-Write Representation](SIL.md#Copy-on-Write-Representation).
21482148

2149+
### end_cow_mutation_addr
2150+
2151+
```
2152+
sil-instruction ::= 'end_cow_mutation_addr' sil-operand
2153+
2154+
end_cow_mutation_addr %0 : $*T
2155+
// %0 must be of an address $*T type
2156+
```
2157+
2158+
This instruction marks the end of mutation of a reference counted object. It
2159+
is currently only generated in cases where is it is not possible to schedule an
2160+
`end_cow_mutation` in the standard library automatically. Ex: Array.mutableSpan
2161+
21492162
### destroy_not_escaped_closure
21502163

21512164
```

stdlib/public/core/Array.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,9 +1738,7 @@ extension Array {
17381738
@_alwaysEmitIntoClient
17391739
mutating get {
17401740
_makeMutableAndUnique()
1741-
// NOTE: We don't have the ability to schedule a call to
1742-
// ContiguousArrayBuffer.endCOWMutation().
1743-
// rdar://146785284 (lifetime analysis for end of mutation)
1741+
// LifetimeDependence analysis inserts call to Builtin.endCOWMutation.
17441742
let pointer = unsafe _buffer.firstElementAddress
17451743
let count = _buffer.mutableCount
17461744
let span = unsafe MutableSpan(_unsafeStart: pointer, count: count)
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// RUN: %target-sil-opt %s \
2+
// RUN: -lifetime-dependence-scope-fixup -sil-verify-all \
3+
// RUN: -enable-experimental-feature LifetimeDependence | %FileCheck %s
4+
5+
// REQUIRES: swift_in_compiler
6+
// REQUIRES: swift_feature_LifetimeDependence
7+
8+
import Swift
9+
import SwiftShims
10+
import Builtin
11+
12+
struct ArrayWrapper {
13+
private var a: [Int]
14+
var array: [Int]
15+
init(a: [Int])
16+
}
17+
18+
sil hidden [ossa] @$s23lifetime_dependence_cow12ArrayWrapperV5arraySaySiGvM : $@yield_once @convention(method) (@inout ArrayWrapper) -> @yields @inout Array<Int> {
19+
bb0(%0 : $*ArrayWrapper):
20+
debug_value %0, var, name "self", argno 1, expr op_deref
21+
%2 = begin_access [modify] [static] %0
22+
%3 = struct_element_addr %2, #ArrayWrapper.a
23+
yield %3, resume bb1, unwind bb2
24+
25+
bb1:
26+
end_access %2
27+
%6 = tuple ()
28+
return %6
29+
30+
bb2:
31+
end_access %2
32+
unwind
33+
}
34+
35+
sil hidden_external [serialized] [available 9999] @$sSa11mutableSpans07MutableB0VyxGvg : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
36+
37+
sil hidden_external [serialized] [available 10.14.4] @$ss11MutableSpanVsRi_zrlE7indicesSnySiGvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@guaranteed MutableSpan<τ_0_0>) -> Range<Int>
38+
39+
sil [serialized] [always_inline] @$sSlss16IndexingIteratorVyxG0B0RtzrlE04makeB0ACyF : $@convention(method) <τ_0_0 where τ_0_0 : Collection, τ_0_0.Iterator == IndexingIterator<τ_0_0>> (@in τ_0_0) -> @out IndexingIterator<τ_0_0>
40+
41+
sil [serialized] [always_inline] @$ss16IndexingIteratorV4next7ElementQzSgyF : $@convention(method) <τ_0_0 where τ_0_0 : Collection> (@inout IndexingIterator<τ_0_0>) -> @out Optional<τ_0_0.Element>
42+
43+
sil hidden_external [serialized] [available 10.14.4] @$ss11MutableSpanVss15BitwiseCopyableRzlEyxSicis : $@convention(method) <τ_0_0 where τ_0_0 : BitwiseCopyable> (@in τ_0_0, Int, @lifetime(copy 2) @inout MutableSpan<τ_0_0>) -> ()
44+
45+
sil [transparent] [serialized] @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int
46+
47+
sil hidden [noinline] [ossa] @$s23lifetime_dependence_cow14getMutableSpanys0eF0VySiGSaySiGzF : $@convention(thin) (@inout Array<Int>) -> @lifetime(borrow 0) @owned MutableSpan<Int> {
48+
bb0(%0 : $*Array<Int>):
49+
debug_value %0, var, name "array", argno 1, expr op_deref
50+
%2 = begin_access [modify] [static] %0
51+
%3 = function_ref @$sSa11mutableSpans07MutableB0VyxGvg : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
52+
%4 = apply %3<Int>(%2) : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> @lifetime(borrow 0) @owned MutableSpan<τ_0_0>
53+
%5 = mark_dependence [nonescaping] %4 on %0
54+
end_access %2
55+
return %5
56+
}
57+
58+
// CHECK-LABEL: sil hidden [ossa] @$s23lifetime_dependence_cow9testWriteyyAA12ArrayWrapperVzF :
59+
// CHECK: [[FUN:%.*]] = function_ref @$s23lifetime_dependence_cow12ArrayWrapperV5arraySaySiGvM : $@yield_once @convention(method) (@inout ArrayWrapper) -> @yields @inout Array<Int>
60+
// CHECK: ([[Y:%.*]], [[T:%.*]]) = begin_apply %6(%5) : $@yield_once @convention(method) (@inout ArrayWrapper) -> @yields @inout Array<Int>
61+
// CHECK: end_cow_mutation_addr [[Y]]
62+
// CHECK: end_apply [[T]] as $()
63+
// CHECK-LABEL: } // end sil function '$s23lifetime_dependence_cow9testWriteyyAA12ArrayWrapperVzF'
64+
sil hidden [ossa] @$s23lifetime_dependence_cow9testWriteyyAA12ArrayWrapperVzF : $@convention(thin) (@inout ArrayWrapper) -> () {
65+
bb0(%0 : $*ArrayWrapper):
66+
debug_value %0, var, name "w", argno 1, expr op_deref
67+
%2 = alloc_box ${ var MutableSpan<Int> }, var, name "span"
68+
%3 = begin_borrow [lexical] [var_decl] %2
69+
%4 = project_box %3, 0
70+
%5 = begin_access [modify] [unknown] %0
71+
%6 = function_ref @$s23lifetime_dependence_cow12ArrayWrapperV5arraySaySiGvM : $@yield_once @convention(method) (@inout ArrayWrapper) -> @yields @inout Array<Int>
72+
(%7, %8) = begin_apply %6(%5) : $@yield_once @convention(method) (@inout ArrayWrapper) -> @yields @inout Array<Int>
73+
%9 = function_ref @$s23lifetime_dependence_cow14getMutableSpanys0eF0VySiGSaySiGzF : $@convention(thin) (@inout Array<Int>) -> @lifetime(borrow 0) @owned MutableSpan<Int>
74+
%10 = apply %9(%7) : $@convention(thin) (@inout Array<Int>) -> @lifetime(borrow 0) @owned MutableSpan<Int>
75+
%11 = mark_dependence [unresolved] %10 on %7
76+
%12 = end_apply %8 as $()
77+
end_access %5
78+
store %11 to [init] %4
79+
%15 = alloc_box ${ var IndexingIterator<Range<Int>> }, var, name "$i$generator"
80+
%16 = begin_borrow [var_decl] %15
81+
%17 = project_box %16, 0
82+
%18 = begin_access [read] [unknown] %4
83+
%19 = mark_unresolved_non_copyable_value [no_consume_or_assign] %18
84+
%20 = load_borrow [unchecked] %19
85+
%21 = function_ref @$ss11MutableSpanVsRi_zrlE7indicesSnySiGvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@guaranteed MutableSpan<τ_0_0>) -> Range<Int>
86+
%22 = apply %21<Int>(%20) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@guaranteed MutableSpan<τ_0_0>) -> Range<Int>
87+
end_borrow %20
88+
end_access %18
89+
%25 = alloc_stack $Range<Int>
90+
store %22 to [trivial] %25
91+
%27 = function_ref @$sSlss16IndexingIteratorVyxG0B0RtzrlE04makeB0ACyF : $@convention(method) <τ_0_0 where τ_0_0 : Collection, τ_0_0.Iterator == IndexingIterator<τ_0_0>> (@in τ_0_0) -> @out IndexingIterator<τ_0_0>
92+
%28 = apply %27<Range<Int>>(%17, %25) : $@convention(method) <τ_0_0 where τ_0_0 : Collection, τ_0_0.Iterator == IndexingIterator<τ_0_0>> (@in τ_0_0) -> @out IndexingIterator<τ_0_0>
93+
dealloc_stack %25
94+
br bb1
95+
96+
bb1:
97+
%31 = alloc_stack $Optional<Int>
98+
%32 = begin_access [modify] [unknown] %17
99+
%33 = function_ref @$ss16IndexingIteratorV4next7ElementQzSgyF : $@convention(method) <τ_0_0 where τ_0_0 : Collection> (@inout IndexingIterator<τ_0_0>) -> @out Optional<τ_0_0.Element>
100+
%34 = apply %33<Range<Int>>(%31, %32) : $@convention(method) <τ_0_0 where τ_0_0 : Collection> (@inout IndexingIterator<τ_0_0>) -> @out Optional<τ_0_0.Element>
101+
end_access %32
102+
%36 = load [trivial] %31
103+
dealloc_stack %31
104+
switch_enum %36, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb3
105+
106+
bb2(%39 : $Int):
107+
%40 = move_value [var_decl] %39
108+
debug_value %40, let, name "i"
109+
%42 = integer_literal $Builtin.IntLiteral, 0
110+
%43 = metatype $@thin Int.Type
111+
112+
%44 = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int
113+
%45 = apply %44(%42, %43) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int
114+
%46 = alloc_stack $Int
115+
store %45 to [trivial] %46
116+
%48 = begin_access [modify] [unknown] %4
117+
%49 = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %48
118+
%50 = function_ref @$ss11MutableSpanVss15BitwiseCopyableRzlEyxSicis : $@convention(method) <τ_0_0 where τ_0_0 : BitwiseCopyable> (@in τ_0_0, Int, @lifetime(copy 2) @inout MutableSpan<τ_0_0>) -> ()
119+
%51 = apply %50<Int>(%46, %40, %49) : $@convention(method) <τ_0_0 where τ_0_0 : BitwiseCopyable> (@in τ_0_0, Int, @lifetime(copy 2) @inout MutableSpan<τ_0_0>) -> ()
120+
end_access %48
121+
dealloc_stack %46
122+
extend_lifetime %40
123+
br bb1
124+
125+
bb3:
126+
end_borrow %16
127+
destroy_value %15
128+
end_borrow %3
129+
destroy_value %2
130+
%60 = tuple ()
131+
return %60
132+
}

0 commit comments

Comments
 (0)