Skip to content

Commit b80bd16

Browse files
committed
[NFC] Add LifetimeDependenceUseDefWalker utility.
Refactor VariableIntroducerUseDefWalker into a general LifetimeDependenceUseDefWalker for use with LifetimeDependenceScopeFixup.
1 parent 43debc5 commit b80bd16

File tree

5 files changed

+336
-111
lines changed

5 files changed

+336
-111
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ private struct LifetimeVariable {
292292
return .abortWalk
293293
}
294294
defer { useDefVisitor.deinitialize() }
295-
_ = useDefVisitor.walkUp(valueOrAddress: value)
295+
_ = useDefVisitor.walkUp(newLifetime: value)
296296
return introducer
297297
}
298298

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 23 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ func gatherVariableIntroducers(for value: Value, _ context: Context)
318318
return .continueWalk
319319
}
320320
defer { useDefVisitor.deinitialize() }
321-
_ = useDefVisitor.walkUp(valueOrAddress: value)
321+
_ = useDefVisitor.walkUp(newLifetime: value)
322322
assert(!introducers.isEmpty, "missing variable introducer")
323323
return introducers
324324
}
@@ -330,7 +330,7 @@ func gatherVariableIntroducers(for value: Value, _ context: Context)
330330
/// Walk up lifetime dependencies to the first value associated with a variable declaration.
331331
///
332332
/// To start walking:
333-
/// walkUp(valueOrAddress: Value) -> WalkResult
333+
/// walkUp(newLifetime: Value) -> WalkResult
334334
///
335335
/// This utility finds the value or address associated with the lvalue (variable declaration) that is passed as the
336336
/// source of a lifetime dependent argument. If no lvalue is found, then it finds the "root" of the chain of temporary
@@ -382,10 +382,7 @@ func gatherVariableIntroducers(for value: Value, _ context: Context)
382382
/// All of the dependent uses including `end_borrow %5` and `destroy_value %4` must be before the end of the dependence
383383
/// scope: `destroy_value %parent`. In this case, the dependence parent is an owned value, so the scope is simply the
384384
/// value's OSSA lifetime.
385-
struct VariableIntroducerUseDefWalker : ForwardingUseDefWalker {
386-
// The ForwardingUseDefWalker's context is the most recent lifetime owner.
387-
typealias PathContext = Value?
388-
385+
struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefValueWalker, LifetimeDependenceUseDefAddressWalker {
389386
let context: Context
390387

391388
// If the scoped value is trivial, then only the variable's lexical scope is relevant, and access scopes can be
@@ -412,128 +409,45 @@ struct VariableIntroducerUseDefWalker : ForwardingUseDefWalker {
412409
visitedValues.deinitialize()
413410
}
414411

415-
mutating func needWalk(for value: Value, _ owner: Value?) -> Bool {
416-
visitedValues.insert(value)
417-
}
418-
419412
mutating func introducer(_ value: Value, _ owner: Value?) -> WalkResult {
420413
return visitorClosure(value)
421414
}
422415

423-
mutating func walkUp(valueOrAddress: Value) -> WalkResult {
424-
if valueOrAddress.type.isAddress {
425-
return walkUp(address: valueOrAddress)
426-
}
427-
return walkUp(newLifetime: valueOrAddress)
416+
mutating func addressIntroducer(_ address: Value, access: AccessBaseAndScopes) -> WalkResult {
417+
return visitorClosure(address)
418+
}
419+
420+
mutating func needWalk(for value: Value, _ owner: Value?) -> Bool {
421+
visitedValues.insert(value)
428422
}
429-
}
430423

431-
// Helpers
432-
extension VariableIntroducerUseDefWalker {
433424
mutating func walkUp(newLifetime: Value) -> WalkResult {
425+
if newLifetime.type.isAddress {
426+
return walkUp(address: newLifetime)
427+
}
434428
let newOwner = newLifetime.ownership == .owned ? newLifetime : nil
435429
return walkUp(value: newLifetime, newOwner)
436430
}
437431

432+
/// Override to check for variable introducers: move_value, begin_value, before following
433+
/// OwnershipTransitionInstruction.
438434
mutating func walkUp(value: Value, _ owner: Value?) -> WalkResult {
439-
// Check for variable introducers: move_value, begin_value, before following OwnershipTransitionInstruction.
440435
if let inst = value.definingInstruction, VariableScopeInstruction(inst) != nil {
441436
return visitorClosure(value)
442437
}
443-
switch value.definingInstruction {
444-
case let transition as OwnershipTransitionInstruction:
445-
return walkUp(newLifetime: transition.operand.value)
446-
case let load as LoadInstruction:
447-
return walkUp(address: load.address)
448-
default:
449-
break
450-
}
451-
// If the dependence chain has a phi, consider it a root. Dependence roots dominate all dependent values.
452-
if Phi(value) != nil {
453-
return introducer(value, owner)
454-
}
455-
// ForwardingUseDefWalker will callback to introducer() when it finds no forwarding instruction.
456-
return walkUpDefault(forwarded: value, owner)
438+
return walkUpDefault(value: value, owner)
457439
}
458440

459-
// Handle temporary allocations and access scopes.
460-
mutating func walkUp(address: Value) -> WalkResult {
461-
let accessBaseAndScopes = address.accessBaseWithScopes
462-
// Continue walking for some kinds of access base.
463-
switch accessBaseAndScopes.base {
464-
case .box, .global, .class, .tail, .pointer, .index, .unidentified:
465-
break
466-
case let .stack(allocStack):
467-
if allocStack.varDecl == nil {
468-
// Ignore temporary stack locations. Their access scopes do not affect lifetime dependence.
469-
return walkUp(stackInitializer: allocStack, at: address)
470-
}
471-
case let .argument(arg):
472-
// Ignore access scopes for @in or @in_guaranteed arguments when all scopes are reads. Do not ignore a [read]
473-
// access of an inout argument or outer [modify]. Mutation later with the outer scope could invalidate the
474-
// borrowed state in this narrow scope. Do not ignore any mark_depedence on the address.
475-
if arg.convention.isIndirectIn && accessBaseAndScopes.isOnlyReadAccess {
476-
return introducer(arg, nil)
477-
}
478-
// @inout arguments may be singly initialized (when no modification exists in this function), but this is not
479-
// relevant here because they require nested access scopes which can never be ignored.
480-
case let .yield(yieldedAddress):
481-
// Ignore access scopes for @in or @in_guaranteed yields when all scopes are reads.
482-
let apply = yieldedAddress.definingInstruction as! FullApplySite
483-
if apply.convention(of: yieldedAddress).isIndirectIn && accessBaseAndScopes.isOnlyReadAccess {
484-
return introducer(yieldedAddress, nil)
485-
}
486-
case .storeBorrow(let sb):
487-
// Walk up through a store into a temporary.
488-
if accessBaseAndScopes.scopes.isEmpty,
489-
case .stack = sb.destinationOperand.value.accessBase {
490-
return walkUp(newLifetime: sb.source)
491-
}
492-
}
493-
// Skip the access scope for unsafe[Mutable]Address. Treat it like a projection of 'self' rather than a separate
494-
// variable access.
495-
if case let .access(innerAccess) = accessBaseAndScopes.scopes.first,
496-
let addressorSelf = innerAccess.unsafeAddressorSelf {
497-
return walkUp(valueOrAddress: addressorSelf)
498-
}
499-
// Ignore the acces scope for trivial values regardless of whether it is singly-initialized. Trivial values do not
500-
// need to be kept alive in memory and can be safely be overwritten in the same scope. Lifetime dependence only
501-
// cares that the loaded value is within the lexical scope of the trivial value's variable declaration. Rather than
502-
// skipping all access scopes, call 'walkUp' on each nested access in case one of them needs to redirect the walk,
503-
// as required for 'access.unsafeAddressorSelf'.
504-
if isTrivialScope {
505-
switch accessBaseAndScopes.scopes.first {
506-
case .none, .base:
507-
break
508-
case let .access(beginAccess):
509-
return walkUp(address: beginAccess.address)
510-
case let .dependence(markDep):
511-
return walkUp(address: markDep.value)
512-
}
513-
}
514-
return introducer(accessBaseAndScopes.enclosingAccess.address ?? address, nil)
515-
}
516-
517-
// Handle singly-initialized temporary stack locations.
518-
mutating func walkUp(stackInitializer allocStack: AllocStackInst, at address: Value) -> WalkResult {
519-
guard let initializer = allocStack.accessBase.findSingleInitializer(context) else {
520-
return introducer(address, nil)
521-
}
522-
if case let .store(store, _) = initializer {
523-
switch store {
524-
case let store as StoringInstruction:
525-
return walkUp(newLifetime: store.source)
526-
case let srcDestInst as SourceDestAddrInstruction:
527-
return walkUp(address: srcDestInst.destination)
528-
case let apply as FullApplySite:
529-
if let f = apply.referencedFunction, f.isConvertPointerToPointerArgument {
530-
return walkUp(address: apply.parameterOperands[0].value)
531-
}
532-
default:
533-
break
441+
/// Override to check for on-stack variables before following an initializer.
442+
mutating func walkUp(address: Value, access: AccessBaseAndScopes) -> WalkResult {
443+
// Check for stack locations that correspond to an lvalue.
444+
if case let .stack(allocStack) = access.base {
445+
if allocStack.varDecl != nil {
446+
// Report this variable's innermmost access scope.
447+
return addressIntroducer(access.enclosingAccess.address ?? address, access: access)
534448
}
535449
}
536-
return introducer(address, nil)
450+
return walkUpDefault(address: address, access: access)
537451
}
538452
}
539453

0 commit comments

Comments
 (0)