Skip to content

Commit 7c5d4b8

Browse files
committed
Fix MutableSpan exclusive access to unsafe pointers
This fix enables exclusive access to a MutableSpan created from an UnsafeMutablePointer. The compiler has a special case that allows MutableSpan to depend on a mutable pointer *without* extending that pointer's access scope. That lets us implement standard library code like this: mutating public func extracting(droppingLast k: Int) -> Self { //... let newSpan = unsafe Self(_unchecked: _pointer, byteCount: newCount) return unsafe _overrideLifetime(newSpan, mutating: &self) Refine this special case so that is does not apply to inout parameters where the programmer has an expectation that the unsafe pointer is not copied when being passed as an argument. Now, we safely get an exclusivity violation when creating two mutable spans from the same pointer field: @Lifetime(&self) mutating func getSpan() -> MutableSpan<T> { let span1 = makeMutableSpan(&self.pointer) let span2 = makeMutableSpan(&self.pointer) // ERROR: overlapping access return span1 } If we don't fix this now, it will likely be source breaking in the future. Fixes rdar://153745332 (Swift allows constructing two MutableSpans to the same underlying pointer)
1 parent 62c886e commit 7c5d4b8

File tree

3 files changed

+53
-12
lines changed

3 files changed

+53
-12
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ private struct LifetimeVariable {
359359

360360
private func getFirstVariableIntroducer(of value: Value, _ context: some Context) -> Value? {
361361
var introducer: Value?
362-
var useDefVisitor = VariableIntroducerUseDefWalker(context, scopedValue: value) {
362+
var useDefVisitor = VariableIntroducerUseDefWalker(context, scopedValue: value, ignoreTrivialCopies: false) {
363363
introducer = $0
364364
return .abortWalk
365365
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ extension LifetimeDependentApply {
9595
struct LifetimeSource {
9696
let targetKind: TargetKind
9797
let convention: LifetimeDependenceConvention
98+
let isInout: Bool
9899
let value: Value
99100
}
100101

@@ -116,7 +117,8 @@ extension LifetimeDependentApply {
116117
guard let dep = applySite.resultDependence(on: operand) else {
117118
continue
118119
}
119-
info.sources.push(LifetimeSource(targetKind: .result, convention: dep, value: operand.value))
120+
let isInout = applySite.convention(of: operand)?.isInout ?? false
121+
info.sources.push(LifetimeSource(targetKind: .result, convention: dep, isInout: isInout, value: operand.value))
120122
}
121123
return info
122124
}
@@ -135,6 +137,7 @@ extension LifetimeDependentApply {
135137
? TargetKind.yieldAddress : TargetKind.yield
136138
info.sources.push(LifetimeSource(targetKind: targetKind,
137139
convention: .scope(addressable: false, addressableForDeps: false),
140+
isInout: false,
138141
value: beginApply.token))
139142
}
140143
for operand in applySite.parameterOperands {
@@ -151,7 +154,9 @@ extension LifetimeDependentApply {
151154
// However this is neccessary for safety when begin_apply gets inlined which will delete the dependence on the token.
152155
for yieldedValue in beginApply.yieldedValues {
153156
let targetKind = yieldedValue.type.isAddress ? TargetKind.yieldAddress : TargetKind.yield
154-
info.sources.push(LifetimeSource(targetKind: targetKind, convention: dep, value: operand.value))
157+
let isInout = applySite.convention(of: operand)?.isInout ?? false
158+
info.sources.push(LifetimeSource(targetKind: targetKind, convention: dep, isInout: isInout,
159+
value: operand.value))
155160
}
156161
}
157162
}
@@ -180,7 +185,8 @@ extension LifetimeDependentApply {
180185
guard let dep = dep else {
181186
continue
182187
}
183-
info.sources.push(LifetimeSource(targetKind: targetKind, convention: dep, value: operand.value))
188+
let isInout = applySite.convention(of: operand)?.isInout ?? false
189+
info.sources.push(LifetimeSource(targetKind: targetKind, convention: dep, isInout: isInout, value: operand.value))
184190
}
185191
return info
186192
}
@@ -223,7 +229,7 @@ private extension LifetimeDependentApply.LifetimeSourceInfo {
223229
return
224230
}
225231
// Create a new dependence on the apply's access to the argument.
226-
for varIntroducer in gatherVariableIntroducers(for: source.value, context) {
232+
for varIntroducer in gatherVariableIntroducers(for: source.value, ignoreTrivialCopies: !source.isInout, context) {
227233
let scope = LifetimeDependence.Scope(base: varIntroducer, context)
228234
log("Scoped lifetime from \(source.value)")
229235
log(" scope: \(scope)")
@@ -316,11 +322,12 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
316322
/// - a variable declaration (begin_borrow [var_decl], move_value [var_decl])
317323
/// - a begin_access for a mutable variable access
318324
/// - the value or address "root" of the dependence chain
319-
func gatherVariableIntroducers(for value: Value, _ context: Context)
325+
func gatherVariableIntroducers(for value: Value, ignoreTrivialCopies: Bool, _ context: Context)
320326
-> SingleInlineArray<Value>
321327
{
322328
var introducers = SingleInlineArray<Value>()
323-
var useDefVisitor = VariableIntroducerUseDefWalker(context, scopedValue: value) {
329+
var useDefVisitor = VariableIntroducerUseDefWalker(context, scopedValue: value,
330+
ignoreTrivialCopies: ignoreTrivialCopies) {
324331
introducers.push($0)
325332
return .continueWalk
326333
}
@@ -403,11 +410,15 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefValueWalker, Lif
403410
// Call \p visit rather than calling this directly.
404411
private let visitorClosure: (Value) -> WalkResult
405412

406-
init(_ context: Context, scopedValue: Value, _ visitor: @escaping (Value) -> WalkResult) {
413+
init(_ context: Context, scopedValue: Value, ignoreTrivialCopies: Bool, _ visitor: @escaping (Value) -> WalkResult) {
407414
self.context = context
408-
self.isTrivialScope = scopedValue.type.isAddress
409-
? scopedValue.type.objectType.isTrivial(in: scopedValue.parentFunction)
410-
: scopedValue.isTrivial(context)
415+
if ignoreTrivialCopies {
416+
self.isTrivialScope = scopedValue.type.isAddress
417+
? scopedValue.type.objectType.isTrivial(in: scopedValue.parentFunction)
418+
: scopedValue.isTrivial(context)
419+
} else {
420+
self.isTrivialScope = false
421+
}
411422
self.visitedValues = ValueSet(context)
412423
self.visitorClosure = visitor
413424
}
@@ -472,5 +483,5 @@ let variableIntroducerTest = FunctionTest("variable_introducer") {
472483
function, arguments, context in
473484
let value = arguments.takeValue()
474485
print("Variable introducers of: \(value)")
475-
print(gatherVariableIntroducers(for: value, context))
486+
print(gatherVariableIntroducers(for: value, ignoreTrivialCopies: false, context))
476487
}

test/SILOptimizer/lifetime_dependence/semantics.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,21 @@ struct InnerTrivial {
106106
struct TrivialHolder {
107107
var p: UnsafePointer<Int>
108108
var pa: UnsafePointer<AddressableInt>
109+
var mp: UnsafeMutablePointer<Int>
109110

110111
var addressableInt: AddressableInt { unsafeAddress { pa } }
111112

112113
@_lifetime(borrow self)
113114
borrowing func span() -> Span<Int> {
114115
Span(base: p, count: 1)
115116
}
117+
118+
@_lifetime(&self)
119+
mutating func mutableSpan() -> MutableSpan<Int> {
120+
MutableSpan(base: mp, count: 1)
121+
}
122+
123+
mutating func modify() {}
116124
}
117125

118126
struct Holder {
@@ -454,6 +462,28 @@ func testInoutMutableBorrow(a: inout [Int]) -> MutableSpan<Int> {
454462
a.mutableSpan()
455463
}
456464

465+
@_lifetime(&h)
466+
func testTrivialWriteConflict(h: inout TrivialHolder) -> MutableSpan<Int> {
467+
let span = h.mutableSpan() // expected-error{{overlapping accesses to 'h', but modification requires exclusive access; consider copying to a local variable}}
468+
h.modify() // expected-note{{conflicting access is here}}
469+
return span
470+
}
471+
472+
func makeMutableSpan(_ p: inout UnsafeMutablePointer<UInt8>) -> MutableSpan<UInt8> {
473+
MutableSpan(base: p, count: 1)
474+
}
475+
476+
struct TestInoutUnsafePointerExclusivity {
477+
var pointer: UnsafeMutablePointer<UInt8>
478+
479+
@_lifetime(&self)
480+
mutating func testInoutUnsafePointerExclusivity() -> MutableSpan<UInt8> {
481+
let span1 = makeMutableSpan(&self.pointer) // expected-error{{overlapping accesses to 'self.pointer', but modification requires exclusive access; consider copying to a local variable}}
482+
_ = makeMutableSpan(&self.pointer) // expected-note{{conflicting access is here}}
483+
return span1
484+
}
485+
}
486+
457487
// =============================================================================
458488
// Scoped dependence on property access
459489
// =============================================================================

0 commit comments

Comments
 (0)