Skip to content

Commit 4951f62

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 4951f62

File tree

1 file changed

+92
-1
lines changed

1 file changed

+92
-1
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

0 commit comments

Comments
 (0)