Skip to content

Commit 05a9afe

Browse files
authored
Merge pull request #82450 from atrick/unsafe-mutablespan
Fix MutableSpan exclusive access to unsafe pointers
2 parents e24e676 + 7c5d4b8 commit 05a9afe

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)