Skip to content

SIL: add mark_dependence_addr #80263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ struct AliasAnalysis {
case let storeBorrow as StoreBorrowInst:
return memLoc.mayAlias(with: storeBorrow.destination, self) ? .init(write: true) : .noEffects

case let mdi as MarkDependenceInst:
case let mdi as MarkDependenceInstruction:
if mdi.base.type.isAddress && memLoc.mayAlias(with: mdi.base, self) {
return .init(read: true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private func analyze(dependence: LifetimeDependence, _ context: FunctionPassCont
// Check each lifetime-dependent use via a def-use visitor
var walker = DiagnoseDependenceWalker(diagnostics, context)
defer { walker.deinitialize() }
let result = walker.walkDown(root: dependence.dependentValue)
let result = walker.walkDown(dependence: dependence)
// The walk may abort without a diagnostic error.
assert(!error || result == .abortWalk)
return result == .continueWalk
Expand Down Expand Up @@ -354,7 +354,7 @@ private struct LifetimeVariable {
}

/// Walk up an address into which a dependent value has been stored. If any address in the use-def chain is a
/// mark_dependence, follow the depenedence base rather than the forwarded value. If any of the dependence bases in
/// mark_dependence, follow the dependence base rather than the forwarded value. If any of the dependence bases in
/// within the current scope is with (either local checkInoutResult), then storing a value into that address is
/// nonescaping.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,26 +286,21 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
_ context: FunctionPassContext) {
var currentValue = value
for base in bases {
let markDep = builder.createMarkDependence(
value: currentValue, base: base, kind: .Unresolved)

if value.type.isAddress {
// Address dependencies cannot be represented as SSA values, so it does not make sense to replace any uses of the
// dependent address.
//
// TODO: insert a separate mark_dependence_addr instruction with no return value and do not update currentValue.
} else {
// TODO: implement non-inout parameter dependencies. This assumes that currentValue is the apply immediately
// preceeding the mark_dependence.
let uses = currentValue.uses.lazy.filter {
if $0.isScopeEndingUse {
return false
}
let inst = $0.instruction
return inst != markDep && inst != initializer && !(inst is Deallocation)
_ = builder.createMarkDependenceAddr(value: currentValue, base: base, kind: .Unresolved)
continue
}
let markDep = builder.createMarkDependence(value: currentValue, base: base, kind: .Unresolved)
let uses = currentValue.uses.lazy.filter {
if $0.isScopeEndingUse {
return false
}
uses.replaceAll(with: markDep, context)
let inst = $0.instruction
return inst != markDep && inst != initializer && !(inst is Deallocation)
}
uses.replaceAll(with: markDep, context)
currentValue = markDep
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
let localReachabilityCache = LocalVariableReachabilityCache()

for instruction in function.instructions {
guard let markDep = instruction as? MarkDependenceInst else {
guard let markDep = instruction as? MarkDependenceInstruction else {
continue
}
guard let innerLifetimeDep = LifetimeDependence(markDep, context) else {
Expand All @@ -129,46 +129,71 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
}
}

private extension MarkDependenceInst {
private extension MarkDependenceInstruction {
/// Rewrite the mark_dependence base operand to ignore inner borrow scopes (begin_borrow, load_borrow).
///
/// Note: this could be done as a general simplification, e.g. after inlining. But currently this is only relevant for
/// diagnostics.
func rewriteSkippingBorrow(scope: LifetimeDependence.Scope, _ context: FunctionPassContext) -> LifetimeDependence {
guard let newScope = scope.ignoreBorrowScope(context) else {
return LifetimeDependence(scope: scope, dependentValue: self)
return LifetimeDependence(scope: scope, markDep: self)!
}
let newBase = newScope.parentValue
if newBase != self.baseOperand.value {
self.baseOperand.set(to: newBase, context)
}
return LifetimeDependence(scope: newScope, dependentValue: self)
return LifetimeDependence(scope: newScope, markDep: self)!
}

/// Rewrite the mark_dependence base operand, setting it to a function argument.
///
/// To handle more than one function argument, new mark_dependence instructions will be chained.
/// This is called when the dependent value is returned by the function and the dependence base is in the caller.
func redirectFunctionReturn(to args: SingleInlineArray<FunctionArgument>, _ context: FunctionPassContext) {
var updatedMarkDep: MarkDependenceInst?
var updatedMarkDep: MarkDependenceInstruction?
for arg in args {
guard let currentMarkDep = updatedMarkDep else {
self.baseOperand.set(to: arg, context)
updatedMarkDep = self
continue
}
let newMarkDep = Builder(after: currentMarkDep, location: currentMarkDep.location, context)
.createMarkDependence(value: currentMarkDep, base: arg, kind: .Unresolved)
let uses = currentMarkDep.uses.lazy.filter {
let inst = $0.instruction
return inst != newMarkDep
switch currentMarkDep {
case let mdi as MarkDependenceInst:
updatedMarkDep = mdi.redirectFunctionReturnForward(to: arg, input: mdi, context)
case let mdi as MarkDependenceAddrInst:
updatedMarkDep = mdi.redirectFunctionReturnAddress(to: arg, context)
default:
fatalError("unexpected MarkDependenceInstruction")
}
uses.replaceAll(with: newMarkDep, context)
updatedMarkDep = newMarkDep
}
}
}

private extension MarkDependenceInst {
/// Rewrite the mark_dependence base operand, setting it to a function argument.
///
/// This is called when the dependent value is returned by the function and the dependence base is in the caller.
func redirectFunctionReturnForward(to arg: FunctionArgument, input: MarkDependenceInst,
_ context: FunctionPassContext) -> MarkDependenceInst {
// To handle more than one function argument, new mark_dependence instructions will be chained.
let newMarkDep = Builder(after: input, location: input.location, context)
.createMarkDependence(value: input, base: arg, kind: .Unresolved)
let uses = input.uses.lazy.filter {
let inst = $0.instruction
return inst != newMarkDep
}
uses.replaceAll(with: newMarkDep, context)
return newMarkDep
}
}

private extension MarkDependenceAddrInst {
/// Rewrite the mark_dependence_addr base operand, setting it to a function argument.
///
/// This is called when the dependent value is returned by the function and the dependence base is in the caller.
func redirectFunctionReturnAddress(to arg: FunctionArgument, _ context: FunctionPassContext)
-> MarkDependenceAddrInst {
return Builder(after: self, location: self.location, context)
.createMarkDependenceAddr(value: self.address, base: arg, kind: .Unresolved)
}
}

/// Transitively extend nested scopes that enclose the dependence base.
///
/// If the parent function returns the dependent value, then this returns the function arguments that represent the
Expand Down Expand Up @@ -211,8 +236,8 @@ private func extendScopes(dependence: LifetimeDependence,
var dependsOnArgs = SingleInlineArray<FunctionArgument>()
for scopeExtension in scopeExtensions {
var scopeExtension = scopeExtension
guard var useRange = computeDependentUseRange(of: dependence.dependentValue, within: &scopeExtension,
localReachabilityCache, context) else {
guard var useRange = computeDependentUseRange(of: dependence, within: &scopeExtension, localReachabilityCache,
context) else {
continue
}

Expand Down Expand Up @@ -463,20 +488,19 @@ extension ScopeExtension {
}
}

/// Return an InstructionRange covering all the dependent uses of 'value'.
private func computeDependentUseRange(of value: Value, within scopeExtension: inout ScopeExtension,
/// Return an InstructionRange covering all the dependent uses of 'dependence'.
private func computeDependentUseRange(of dependence: LifetimeDependence, within scopeExtension: inout ScopeExtension,
_ localReachabilityCache: LocalVariableReachabilityCache,
_ context: FunctionPassContext)
-> InstructionRange? {

let function = dependence.function
guard var ownershipRange = scopeExtension.computeRange(localReachabilityCache, context) else {
return nil
}
defer {ownershipRange.deinitialize()}

// The innermost scope that must be extended must dominate all uses.
var useRange = InstructionRange(begin: scopeExtension.innerScope.extendableBegin!.instruction, context)
let function = value.parentFunction
var walker = LifetimeDependentUseWalker(function, localReachabilityCache, context) {
// Do not extend the useRange past the ownershipRange.
let dependentInst = $0.instruction
Expand All @@ -487,17 +511,20 @@ private func computeDependentUseRange(of value: Value, within scopeExtension: in
}
defer {walker.deinitialize()}

_ = walker.walkDown(root: value)
_ = walker.walkDown(dependence: dependence)

log("Scope fixup for dependent uses:\n\(useRange)")

scopeExtension.dependsOnCaller = walker.dependsOnCaller

// Lifetime dependenent uses may not be dominated by the access. The dependent value may be used by a phi or stored
// into a memory location. The access may be conditional relative to such uses. If any use was not dominated, then
// `useRange` will include the function entry.
// `useRange` will include the function entry. There is not way to directly check
// useRange.isValid. useRange.blockRange.isValid is not a strong enough check because it will always succeed when
// useRange.begin == entryBlock even if a use if above useRange.begin.
let firstInst = function.entryBlock.instructions.first!
if firstInst != useRange.begin, useRange.contains(firstInst) {
useRange.deinitialize()
return nil
}
return useRange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,8 @@ private func isValidUseOfObject(_ use: Operand) -> Bool {
is DeallocStackRefInst,
is StrongRetainInst,
is StrongReleaseInst,
is FixLifetimeInst:
is FixLifetimeInst,
is MarkDependenceAddrInst:
return true

case let mdi as MarkDependenceInst:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ private extension Instruction {
is BuiltinInst,
is StoreBorrowInst,
is MarkDependenceInst,
is MarkDependenceAddrInst,
is DebugValueInst:
return true
default:
Expand Down
Loading