Skip to content

Commit 5494a24

Browse files
authored
Merge pull request #85076 from atrick/lifedep-reassign
Lifetimes: handle MutableSpan reassignment and 'inout' arguments
2 parents d4a9b3f + 630f28d commit 5494a24

15 files changed

+508
-175
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ private struct DiagnoseDependence {
160160
func checkInScope(operand: Operand) -> WalkResult {
161161
if let range, !range.inclusiveRangeContains(operand.instruction) {
162162
log(" out-of-range error: \(operand.instruction)")
163-
reportError(operand: operand, diagID: .lifetime_outside_scope_use)
163+
reportError(escapingValue: operand.value, user: operand.instruction, diagID: .lifetime_outside_scope_use)
164164
return .abortWalk
165165
}
166166
log(" contains: \(operand.instruction)")
@@ -169,7 +169,12 @@ private struct DiagnoseDependence {
169169

170170
func reportEscaping(operand: Operand) {
171171
log(" escaping error: \(operand.instruction)")
172-
reportError(operand: operand, diagID: .lifetime_outside_scope_escape)
172+
reportError(escapingValue: operand.value, user: operand.instruction, diagID: .lifetime_outside_scope_escape)
173+
}
174+
175+
func reportEscaping(value: Value, user: Instruction) {
176+
log(" escaping error: \(value) at \(user)")
177+
reportError(escapingValue: value, user: user, diagID: .lifetime_outside_scope_escape)
173178
}
174179

175180
func reportUnknown(operand: Operand) {
@@ -184,7 +189,8 @@ private struct DiagnoseDependence {
184189
if inoutArg == sourceArg {
185190
return .continueWalk
186191
}
187-
if function.argumentConventions.getDependence(target: inoutArg.index, source: sourceArg.index) != nil {
192+
if function.argumentConventions.parameterDependence(targetArgumentIndex: inoutArg.index,
193+
sourceArgumentIndex: sourceArg.index) != nil {
188194
// The inout result depends on a lifetime that is inherited or borrowed in the caller.
189195
log(" has dependent inout argument: \(inoutArg)")
190196
return .continueWalk
@@ -257,15 +263,15 @@ private struct DiagnoseDependence {
257263
return .abortWalk
258264
}
259265

260-
func reportError(operand: Operand, diagID: DiagID) {
266+
func reportError(escapingValue: Value, user: Instruction, diagID: DiagID) {
261267
// If the dependent value is Escapable, then mark_dependence resolution fails, but this is not a diagnostic error.
262268
if dependence.dependentValue.isEscapable {
263269
return
264270
}
265271
onError()
266272

267273
// Identify the escaping variable.
268-
let escapingVar = LifetimeVariable(usedBy: operand, context)
274+
let escapingVar = LifetimeVariable(definedBy: escapingValue, user: user, context)
269275
if let varDecl = escapingVar.varDecl {
270276
// Use the variable location, not the access location.
271277
// Variable names like $return_value and $implicit_value don't have source locations.
@@ -287,7 +293,7 @@ private struct DiagnoseDependence {
287293
diagnoseImplicitFunction()
288294
reportScope()
289295
// Identify the use point.
290-
if let userSourceLoc = operand.instruction.location.sourceLoc {
296+
if let userSourceLoc = user.location.sourceLoc {
291297
diagnose(userSourceLoc, diagID)
292298
}
293299
}
@@ -358,13 +364,12 @@ private struct LifetimeVariable {
358364
return varDecl?.userFacingName
359365
}
360366

361-
init(usedBy operand: Operand, _ context: some Context) {
362-
self = .init(dependent: operand.value, context)
367+
init(definedBy value: Value, user: Instruction, _ context: some Context) {
368+
self = .init(dependent: value, context)
363369
// variable names like $return_value and $implicit_value don't have source locations.
364-
// For @out arguments, the operand's location is the best answer.
370+
// For @out arguments, the user's location is the best answer.
365371
// Otherwise, fall back to the function's location.
366-
self.sourceLoc = self.sourceLoc ?? operand.instruction.location.sourceLoc
367-
?? operand.instruction.parentFunction.location.sourceLoc
372+
self.sourceLoc = self.sourceLoc ?? user.location.sourceLoc ?? user.parentFunction.location.sourceLoc
368373
}
369374

370375
init(definedBy value: Value, _ context: some Context) {
@@ -552,9 +557,9 @@ extension DiagnoseDependenceWalker : LifetimeDependenceDefUseWalker {
552557
return .abortWalk
553558
}
554559

555-
mutating func inoutDependence(argument: FunctionArgument, on operand: Operand) -> WalkResult {
560+
mutating func inoutDependence(argument: FunctionArgument, functionExit: Instruction) -> WalkResult {
556561
if diagnostics.checkInoutResult(argument: argument) == .abortWalk {
557-
diagnostics.reportEscaping(operand: operand)
562+
diagnostics.reportEscaping(value: argument, user: functionExit)
558563
return .abortWalk
559564
}
560565
return .continueWalk

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ extension ScopeExtension {
685685
do {
686686
// The innermost scope that must be extended must dominate all uses.
687687
var walker = LifetimeDependentUseWalker(function, localReachabilityCache, context) {
688-
inRangeUses.append($0.instruction)
688+
inRangeUses.append($0)
689689
return .continueWalk
690690
}
691691
defer {walker.deinitialize()}
@@ -1064,15 +1064,15 @@ private extension BeginApplyInst {
10641064
private struct LifetimeDependentUseWalker : LifetimeDependenceDefUseWalker {
10651065
let function: Function
10661066
let context: Context
1067-
let visitor: (Operand) -> WalkResult
1067+
let visitor: (Instruction) -> WalkResult
10681068
let localReachabilityCache: LocalVariableReachabilityCache
10691069
var visitedValues: ValueSet
10701070

10711071
/// Set to true if the dependence is returned from the current function.
10721072
var dependsOnCaller = false
10731073

10741074
init(_ function: Function, _ localReachabilityCache: LocalVariableReachabilityCache, _ context: Context,
1075-
visitor: @escaping (Operand) -> WalkResult) {
1075+
visitor: @escaping (Instruction) -> WalkResult) {
10761076
self.function = function
10771077
self.context = context
10781078
self.visitor = visitor
@@ -1091,42 +1091,42 @@ private struct LifetimeDependentUseWalker : LifetimeDependenceDefUseWalker {
10911091
mutating func deadValue(_ value: Value, using operand: Operand?)
10921092
-> WalkResult {
10931093
if let operand {
1094-
return visitor(operand)
1094+
return visitor(operand.instruction)
10951095
}
10961096
return .continueWalk
10971097
}
10981098

10991099
mutating func leafUse(of operand: Operand) -> WalkResult {
1100-
return visitor(operand)
1100+
return visitor(operand.instruction)
11011101
}
11021102

11031103
mutating func escapingDependence(on operand: Operand) -> WalkResult {
11041104
log(">>> Escaping dependence: \(operand)")
1105-
_ = visitor(operand)
1105+
_ = visitor(operand.instruction)
11061106
// Make a best-effort attempt to extend the access scope regardless of escapes. It is possible that some mandatory
11071107
// pass between scope fixup and diagnostics will make it possible for the LifetimeDependenceDefUseWalker to analyze
11081108
// this use.
11091109
return .continueWalk
11101110
}
11111111

1112-
mutating func inoutDependence(argument: FunctionArgument, on operand: Operand) -> WalkResult {
1112+
mutating func inoutDependence(argument: FunctionArgument, functionExit: Instruction) -> WalkResult {
11131113
dependsOnCaller = true
1114-
return visitor(operand)
1114+
return visitor(functionExit)
11151115
}
11161116

11171117
mutating func returnedDependence(result operand: Operand) -> WalkResult {
11181118
dependsOnCaller = true
1119-
return visitor(operand)
1119+
return visitor(operand.instruction)
11201120
}
11211121

11221122
mutating func returnedDependence(address: FunctionArgument,
11231123
on operand: Operand) -> WalkResult {
11241124
dependsOnCaller = true
1125-
return visitor(operand)
1125+
return visitor(operand.instruction)
11261126
}
11271127

11281128
mutating func yieldedDependence(result: Operand) -> WalkResult {
1129-
return visitor(result)
1129+
return visitor(result.instruction)
11301130
}
11311131

11321132
mutating func storeToYieldDependence(address: Value, of operand: Operand) -> WalkResult {

SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ extension AccessBase {
279279
default:
280280
return nil
281281
}
282-
return AddressInitializationWalker.findSingleInitializer(ofAddress: baseAddr, context: context)
282+
return AddressInitializationWalker.findSingleInitializer(ofAddress: baseAddr, requireFullyAssigned: .value, context)
283283
}
284284
}
285285

@@ -311,25 +311,31 @@ extension AccessBase {
311311
// modification of memory.
312312
struct AddressInitializationWalker: AddressDefUseWalker, AddressUseVisitor {
313313
let baseAddress: Value
314+
let requireFullyAssigned: IsFullyAssigned
315+
let onRead: WalkResult
314316
let context: any Context
315317

316318
var walkDownCache = WalkerCache<SmallProjectionPath>()
317319

318320
var isProjected = false
319321
var initializer: AccessBase.Initializer?
320322

321-
static func findSingleInitializer(ofAddress baseAddr: Value, context: some Context)
323+
static func findSingleInitializer(ofAddress baseAddr: Value, requireFullyAssigned: IsFullyAssigned,
324+
allowRead: Bool = true, _ context: some Context)
322325
-> AccessBase.Initializer? {
323326

324-
var walker = AddressInitializationWalker(baseAddress: baseAddr, context)
327+
var walker = AddressInitializationWalker(baseAddress: baseAddr, requireFullyAssigned, allowRead: allowRead, context)
325328
if walker.walkDownUses(ofAddress: baseAddr, path: SmallProjectionPath()) == .abortWalk {
326329
return nil
327330
}
328331
return walker.initializer
329332
}
330333

331-
private init(baseAddress: Value, _ context: some Context) {
334+
private init(baseAddress: Value, _ requireFullyAssigned: IsFullyAssigned, allowRead: Bool, _ context: some Context) {
335+
assert(requireFullyAssigned != .no)
332336
self.baseAddress = baseAddress
337+
self.requireFullyAssigned = requireFullyAssigned
338+
self.onRead = allowRead ? .continueWalk : .abortWalk
333339
self.context = context
334340
if let arg = baseAddress as? FunctionArgument {
335341
assert(!arg.convention.isIndirectIn, "@in arguments cannot be initialized")
@@ -387,12 +393,26 @@ extension AddressInitializationWalker {
387393
// FIXME: check mayWriteToMemory but ignore non-stores. Currently,
388394
// stores should all be checked my isAddressInitialization, but
389395
// this is not robust.
390-
return .continueWalk
396+
return onRead
391397
}
392398

393399
mutating func appliedAddressUse(of operand: Operand, by apply: FullApplySite)
394400
-> WalkResult {
395-
if operand.isAddressInitialization {
401+
switch apply.fullyAssigns(operand: operand) {
402+
case .no:
403+
if onRead == .abortWalk {
404+
return .abortWalk
405+
}
406+
break
407+
case .lifetime:
408+
if onRead == .abortWalk {
409+
return .abortWalk
410+
}
411+
if requireFullyAssigned == .value {
412+
break
413+
}
414+
fallthrough
415+
case .value:
396416
return setInitializer(instruction: operand.instruction)
397417
}
398418
guard let convention = apply.convention(of: operand) else {
@@ -403,26 +423,26 @@ extension AddressInitializationWalker {
403423

404424
mutating func loadedAddressUse(of operand: Operand, intoValue value: Value)
405425
-> WalkResult {
406-
return .continueWalk
426+
return onRead
407427
}
408428

409429
mutating func loadedAddressUse(of operand: Operand, intoAddress address: Operand)
410430
-> WalkResult {
411-
return .continueWalk
431+
return onRead
412432
}
413433

414434
mutating func yieldedAddressUse(of operand: Operand) -> WalkResult {
415435
// An inout yield is a partial write. Initialization via coroutine is not supported, so we assume a prior
416436
// initialization must dominate the yield.
417-
return .continueWalk
437+
return onRead
418438
}
419439

420440
mutating func dependentAddressUse(of operand: Operand, dependentValue value: Value) -> WalkResult {
421-
return .continueWalk
441+
return onRead
422442
}
423443

424444
mutating func dependentAddressUse(of operand: Operand, dependentAddress address: Value) -> WalkResult {
425-
return .continueWalk
445+
return onRead
426446
}
427447

428448
mutating func escapingAddressUse(of operand: Operand) -> WalkResult {
@@ -629,7 +649,7 @@ extension AddressOwnershipLiveRange {
629649
var reachableUses = Stack<LocalVariableAccess>(context)
630650
defer { reachableUses.deinitialize() }
631651

632-
localReachability.gatherKnownLifetimeUses(from: assignment, in: &reachableUses)
652+
localReachability.gatherKnownLivenessUses(from: assignment, in: &reachableUses)
633653

634654
let assignmentInst = assignment.instruction ?? allocation.parentFunction.entryBlock.instructions.first!
635655
var range = InstructionRange(begin: assignmentInst, context)

SwiftCompilerSources/Sources/Optimizer/Utilities/FunctionSignatureTransforms.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ private extension ArgumentConventions {
2626

2727
// Check if `argIndex` is a lifetime source in parameterDependencies
2828
for targetIndex in firstParameterIndex..<self.count {
29-
if getDependence(target: targetIndex, source: argIndex) != nil {
29+
if parameterDependence(targetArgumentIndex: targetIndex, sourceArgumentIndex: argIndex) != nil {
3030
return true
3131
}
3232
}

0 commit comments

Comments
 (0)