Skip to content

Commit 975b2e5

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 a9df0bd commit 975b2e5

File tree

5 files changed

+530
-4
lines changed

5 files changed

+530
-4
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,83 @@ 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 extension Type {
134+
func mayHaveMutableSpan(function: Function, _ context: FunctionPassContext) -> Bool {
135+
if hasTypeParameter {
136+
return true
137+
}
138+
if isBuiltinType {
139+
return false
140+
}
141+
if nominal == nil {
142+
return true
143+
}
144+
if nominal == context.swiftMutableSpan {
145+
return true
146+
}
147+
if isStruct {
148+
guard let fields = getNominalFields(in: function) else {
149+
return false
150+
}
151+
return fields.contains { $0.mayHaveMutableSpan(function: function, context) }
152+
}
153+
if isTuple {
154+
return tupleElements.contains { $0.mayHaveMutableSpan(function: function, context) }
155+
}
156+
if isEnum {
157+
guard let cases = getEnumCases(in: function) else {
158+
return true
159+
}
160+
return cases.contains { $0.payload?.mayHaveMutableSpan(function: function, context) ?? false }
161+
}
162+
return false
163+
}
164+
}
165+
166+
/// Insert end_cow_mutation_addr for lifetime dependent values that depend on a mutable address that maybe of
167+
/// type Array/ArraySlice/ContiguousArray.
168+
private func createEndCOWMutationIfNeeded(lifetimeDep: LifetimeDependence, _ context: FunctionPassContext) {
169+
var scoped : ScopedInstruction?
170+
171+
// Handle cases which generate mutable addresses: begin_access [modify] and yield &
172+
switch lifetimeDep.scope {
173+
case let .access(beginAccess):
174+
if beginAccess.accessKind != .modify {
175+
return
176+
}
177+
scoped = beginAccess
178+
case let .yield(value):
179+
let beginApply = value.definingInstruction as! BeginApplyInst
180+
if value == beginApply.token {
181+
return
182+
}
183+
if beginApply.convention(of: value as! MultipleValueInstructionResult) != .indirectInout {
184+
return
185+
}
186+
scoped = beginApply
187+
default:
188+
return
189+
}
190+
191+
guard lifetimeDep.dependentValue.type.mayHaveMutableSpan(function: lifetimeDep.dependentValue.parentFunction, context) else {
192+
return
193+
}
194+
195+
for endInstruction in scoped!.endInstructions {
196+
let builder = Builder(before: endInstruction, location: endInstruction.location, context)
197+
builder.createEndCOWMutationAddr(instance: lifetimeDep.parentValue)
198+
}
199+
}
200+
129201
private extension MarkDependenceInstruction {
130202
/// Rewrite the mark_dependence base operand to ignore inner borrow scopes (begin_borrow, load_borrow).
131203
///
@@ -191,7 +263,7 @@ private extension MarkDependenceAddrInst {
191263
}
192264
}
193265

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

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: 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)