mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
The common def-use walkers should not return .abortWalk without making a diagnostic callback.
618 lines
24 KiB
Swift
618 lines
24 KiB
Swift
//===--- LifetimeDependenceDiagnostics.swift - Lifetime dependence --------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// Pass dependencies:
|
|
///
|
|
/// - After MoveOnly checking fixes non-Copyable lifetimes.
|
|
///
|
|
/// - Before MoveOnlyTypeEliminator removes ownership operations on trivial types, which loses variable information
|
|
/// required for diagnostics.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import AST
|
|
import SIL
|
|
|
|
private let verbose = false
|
|
|
|
private func log(prefix: Bool = true, _ message: @autoclosure () -> String) {
|
|
if verbose {
|
|
debugLog(prefix: prefix, message())
|
|
}
|
|
}
|
|
|
|
/// Diagnostic pass.
|
|
///
|
|
/// Find the roots of all non-escapable values in this function. All
|
|
/// non-escapable values either depend on a NonEscapingScope, or they
|
|
/// are produced by a LifetimeDependentInstruction that has no
|
|
/// dependence on a parent value (@_unsafeNonEscapableResult).
|
|
let lifetimeDependenceDiagnosticsPass = FunctionPass(
|
|
name: "lifetime-dependence-diagnostics")
|
|
{ (function: Function, context: FunctionPassContext) in
|
|
log(prefix: false, "\n--- Diagnosing lifetime dependence in \(function.name)")
|
|
log("\(function)")
|
|
log("\(function.convention)")
|
|
|
|
for argument in function.arguments
|
|
where !argument.type.isEscapable(in: function)
|
|
{
|
|
// Indirect results are not checked here. Type checking ensures
|
|
// that they have a lifetime dependence.
|
|
if let lifetimeDep = LifetimeDependence(argument, context) {
|
|
_ = analyze(dependence: lifetimeDep, context)
|
|
}
|
|
}
|
|
for instruction in function.instructions {
|
|
if let markDep = instruction as? MarkDependenceInstruction, markDep.isUnresolved {
|
|
if let lifetimeDep = LifetimeDependence(markDep, context) {
|
|
if analyze(dependence: lifetimeDep, context) {
|
|
// Note: This promotes the mark_dependence flag but does not invalidate analyses; preserving analyses is good,
|
|
// although the change won't appear in -sil-print-function. Ideally, we could notify context of a flag change
|
|
// without invalidating analyses.
|
|
lifetimeDep.resolve(context)
|
|
continue
|
|
}
|
|
}
|
|
// For now, if the mark_dependence wasn't recognized as a lifetime dependency, or if the dependencies uses are not
|
|
// in scope, conservatively settle it as escaping. For example, it is not uncommon for the pointer value returned
|
|
// by `unsafeAddress` to outlive its `self` argument. This will not be diagnosed as an error, but the
|
|
// mark_dependence will henceforth be treated as an unknown use by the optimizer. In the future, we should not
|
|
// need to set this flag during diagnostics because, for escapable types, mark_dependence [unresolved] will all be
|
|
// settled during an early LifetimeNormalization pass.
|
|
markDep.settleToEscaping(context)
|
|
continue
|
|
}
|
|
if let apply = instruction as? FullApplySite, !apply.hasResultDependence {
|
|
// Handle ~Escapable results that do not have a lifetime dependence. This includes implicit initializers, calls to
|
|
// closures, and @_unsafeNonescapableResult.
|
|
apply.resultOrYields.forEach {
|
|
if let lifetimeDep = LifetimeDependence(unsafeApplyResult: $0, apply: apply, context) {
|
|
_ = analyze(dependence: lifetimeDep, context)
|
|
}
|
|
}
|
|
apply.indirectResultOperands.forEach {
|
|
if let lifetimeDep = LifetimeDependence(unsafeApplyResult: $0.value, apply: apply, context) {
|
|
_ = analyze(dependence: lifetimeDep, context)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Analyze a single Lifetime dependence and trigger diagnostics.
|
|
///
|
|
/// 1. Compute the LifetimeDependence scope.
|
|
///
|
|
/// 2. Walk down all dependent values checking that they are within range.
|
|
///
|
|
/// Return true on success.
|
|
private func analyze(dependence: LifetimeDependence, _ context: FunctionPassContext) -> Bool {
|
|
log("Dependence scope:\n\(dependence)")
|
|
|
|
if dependence.parentValue.type.objectType.isTrivial(in: dependence.function) {
|
|
// Briefly, some versions of Span in the standard library violated trivial lifetimes; versions of the compiler built
|
|
// at that time simply ignored dependencies on trivial values. For now, disable trivial dependencies to allow newer
|
|
// compilers to build against those older standard libraries. This check is only relevant for ~6 mo (until July
|
|
// 2025).
|
|
if let sourceFileKind = dependence.function.sourceFileKind, sourceFileKind == .interface {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Check for immortal dependence.
|
|
switch dependence.scope {
|
|
case .global:
|
|
log("Immortal global dependence.")
|
|
return true
|
|
case let .unknown(value):
|
|
if value.type.isVoid {
|
|
log("Immortal void dependence.")
|
|
return true
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
|
|
// Compute this dependence scope.
|
|
var range = dependence.computeRange(context)
|
|
defer { range?.deinitialize() }
|
|
|
|
let diagnostics = DiagnoseDependence(dependence: dependence, range: range, context: context)
|
|
|
|
// Check each lifetime-dependent use via a def-use visitor
|
|
var walker = DiagnoseDependenceWalker(diagnostics, context)
|
|
defer { walker.deinitialize() }
|
|
let result = walker.walkDown(dependence: dependence)
|
|
assert(result == .continueWalk || diagnostics.errorStatus != nil,
|
|
"Lifetime diagnostics failed without raising an error")
|
|
return result == .continueWalk
|
|
}
|
|
|
|
/// Analyze and diagnose a single LifetimeDependence.
|
|
private class DiagnoseDependence {
|
|
enum ErrorStatus {
|
|
case diagnostic
|
|
case unresolvedDependence
|
|
}
|
|
|
|
let dependence: LifetimeDependence
|
|
let range: InstructionRange?
|
|
let context: FunctionPassContext
|
|
|
|
init(dependence: LifetimeDependence, range: InstructionRange?, context: FunctionPassContext) {
|
|
self.dependence = dependence
|
|
self.range = range
|
|
self.context = context
|
|
}
|
|
|
|
var errorStatus: ErrorStatus? = nil
|
|
|
|
var function: Function { dependence.function }
|
|
|
|
func diagnose(_ position: SourceLoc?, _ id: DiagID,
|
|
_ args: DiagnosticArgument...) {
|
|
context.diagnosticEngine.diagnose(id, arguments: args, at: position)
|
|
}
|
|
|
|
/// Check that this use is inside the dependence scope.
|
|
func checkInScope(operand: Operand) -> WalkResult {
|
|
if let range, !range.inclusiveRangeContains(operand.instruction) {
|
|
log(" out-of-range error: \(operand.instruction)")
|
|
reportError(escapingValue: operand.value, user: operand.instruction, diagID: .lifetime_outside_scope_use)
|
|
return .abortWalk
|
|
}
|
|
log(" contains: \(operand.instruction)")
|
|
return .continueWalk
|
|
}
|
|
|
|
func reportEscaping(operand: Operand) {
|
|
log(" escaping error: \(operand.instruction)")
|
|
reportError(escapingValue: operand.value, user: operand.instruction, diagID: .lifetime_outside_scope_escape)
|
|
}
|
|
|
|
func reportEscaping(value: Value, user: Instruction) {
|
|
log(" escaping error: \(value) at \(user)")
|
|
reportError(escapingValue: value, user: user, diagID: .lifetime_outside_scope_escape)
|
|
}
|
|
|
|
func reportUnknown(operand: Operand) {
|
|
log("Unknown use: \(operand)\n\(function)")
|
|
reportEscaping(operand: operand)
|
|
}
|
|
|
|
func checkInoutResult(argument inoutArg: FunctionArgument) -> WalkResult {
|
|
// Check that the parameter dependence for this inout argument is the same as the current dependence scope.
|
|
if let sourceArg = dependence.scope.parentValue as? FunctionArgument {
|
|
// If the inout result is also the inout source, then it's always ok.
|
|
if inoutArg == sourceArg {
|
|
return .continueWalk
|
|
}
|
|
if function.argumentConventions.parameterDependence(targetArgumentIndex: inoutArg.index,
|
|
sourceArgumentIndex: sourceArg.index) != nil {
|
|
// The inout result depends on a lifetime that is inherited or borrowed in the caller.
|
|
log(" has dependent inout argument: \(inoutArg)")
|
|
return .continueWalk
|
|
}
|
|
}
|
|
return .abortWalk
|
|
}
|
|
|
|
func checkStoreToYield(address: Value) -> WalkResult {
|
|
var walker = DependentAddressUseDefWalker(context: context, diagnostics: self)
|
|
return walker.walkUp(address: address)
|
|
}
|
|
|
|
func checkYield(operand: Operand) -> WalkResult {
|
|
switch dependence.scope {
|
|
case .caller:
|
|
return checkFunctionResult(operand: operand)
|
|
default:
|
|
// local scopes can be yielded without escaping.
|
|
return .continueWalk
|
|
}
|
|
}
|
|
|
|
func checkFunctionResult(operand: Operand) -> WalkResult {
|
|
|
|
if function.hasUnsafeNonEscapableResult {
|
|
return .continueWalk
|
|
}
|
|
// Check for immortal lifetime.
|
|
//
|
|
// FIXME: remove this immortal check. It should be redundant with the earlier check that bypasses dependence
|
|
// diagnostics.
|
|
switch dependence.scope {
|
|
case .global:
|
|
return .continueWalk
|
|
case let .unknown(value):
|
|
if value.type.isVoid {
|
|
return .continueWalk
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
// Check that the parameter dependence for this result is the same
|
|
// as the current dependence scope.
|
|
if let arg = dependence.scope.parentValue as? FunctionArgument,
|
|
let argDep = function.argumentConventions[resultDependsOn: arg.index] {
|
|
switch argDep {
|
|
case .inherit:
|
|
if dependence.markDepInst != nil {
|
|
// A mark_dependence represents a "borrow" scope. A local borrow scope cannot inherit the caller's dependence
|
|
// because the borrow scope depends on the argument value itself, while the caller allows the result to depend
|
|
// on a value that the argument was copied from.
|
|
break
|
|
}
|
|
fallthrough
|
|
case .scope:
|
|
// The returned value depends on a lifetime that is inherited or
|
|
// borrowed in the caller. The lifetime of the argument value
|
|
// itself is irrelevant here.
|
|
log(" has dependent function result")
|
|
return .continueWalk
|
|
}
|
|
// Briefly (April 2025), RawSpan._extracting, Span._extracting, and UTF8Span.span returned a borrowed value that
|
|
// depended on a copied argument. Continue to support those interfaces. The implementations were correct but
|
|
// needed an explicit _overrideLifetime.
|
|
if let sourceFileKind = dependence.function.sourceFileKind, sourceFileKind == .interface {
|
|
return .continueWalk
|
|
}
|
|
}
|
|
return .abortWalk
|
|
}
|
|
|
|
func reportError(escapingValue: Value, user: Instruction, diagID: DiagID) {
|
|
// If the dependent value is Escapable, then mark_dependence resolution fails, but this is not a diagnostic error.
|
|
if dependence.dependentValue.isEscapable {
|
|
errorStatus = .unresolvedDependence
|
|
return
|
|
}
|
|
errorStatus = .diagnostic
|
|
|
|
// Identify the escaping variable.
|
|
let escapingVar = LifetimeVariable(definedBy: escapingValue, user: user, context)
|
|
if let varDecl = escapingVar.varDecl {
|
|
// Use the variable location, not the access location.
|
|
// Variable names like $return_value and $implicit_value don't have source locations.
|
|
let sourceLoc = varDecl.nameLoc ?? escapingVar.sourceLoc
|
|
diagnose(sourceLoc, .lifetime_variable_outside_scope, escapingVar.name ?? "")
|
|
} else if let sourceLoc = escapingVar.sourceLoc {
|
|
diagnose(sourceLoc, .lifetime_value_outside_scope)
|
|
} else {
|
|
// Always raise an error even if we can't find a source location.
|
|
let sourceLoc = function.location.sourceLoc
|
|
if let accessorKind = escapingVar.accessorKind {
|
|
diagnose(sourceLoc, .lifetime_value_outside_accessor, accessorKind)
|
|
} else {
|
|
// Thunks do not have a source location, but we try to use the function location anyway.
|
|
let thunkSelect = dependence.function.thunkKind == .noThunk ? 0 : 1
|
|
diagnose(sourceLoc, .lifetime_value_outside_thunk, thunkSelect, function.name)
|
|
}
|
|
}
|
|
diagnoseImplicitFunction()
|
|
reportScope()
|
|
// Identify the use point.
|
|
if let userSourceLoc = user.location.sourceLoc {
|
|
diagnose(userSourceLoc, diagID)
|
|
}
|
|
}
|
|
|
|
// Identify the dependence scope. If no source location is found, bypass this diagnostic.
|
|
func reportScope() {
|
|
let parentVar = LifetimeVariable(definedBy: dependence.parentValue, context)
|
|
// First check if the dependency is limited to an access scope. If the access has no source location then
|
|
// fall-through to report possible dependence on an argument.
|
|
if parentVar.isAccessScope, let accessLoc = parentVar.sourceLoc {
|
|
diagnose(accessLoc, .lifetime_outside_scope_access, parentVar.name ?? "")
|
|
return
|
|
}
|
|
// If the argument does not have a source location (e.g. a synthesized accessor), report the function location. The
|
|
// function's source location is sufficient for argument diagnostics, but if the function has no location, don't
|
|
// report any scope.
|
|
if parentVar.isArgument, let argLoc = parentVar.sourceLoc ?? function.location.sourceLoc {
|
|
if parentVar.isClosureCapture {
|
|
diagnose(argLoc, .lifetime_outside_scope_capture)
|
|
} else if let parentName = parentVar.name {
|
|
diagnose(argLoc, .lifetime_outside_scope_argument, parentName)
|
|
} else {
|
|
diagnose(argLoc, .lifetime_outside_scope_synthesized_argument, parentVar.accessorKind ?? function.name)
|
|
}
|
|
return
|
|
}
|
|
// Now diagnose dependencies on regular variable and value scopes.
|
|
// Thunks do not have a function location, so any scopes inside the thunk will be ignored.
|
|
if let parentLoc = parentVar.sourceLoc {
|
|
if let parentName = parentVar.name {
|
|
diagnose(parentLoc, .lifetime_outside_scope_variable, parentName)
|
|
} else {
|
|
diagnose(parentLoc, .lifetime_outside_scope_value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func diagnoseImplicitFunction() {
|
|
guard let funcLoc = function.location.sourceLoc else {
|
|
return
|
|
}
|
|
if let kindName = {
|
|
if function.isInitializer {
|
|
return "init"
|
|
}
|
|
if function.isDeinitializer {
|
|
return "deinit"
|
|
}
|
|
return function.accessorKindName
|
|
}() {
|
|
diagnose(funcLoc, .implicit_function_note, kindName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Identify a best-effort variable declaration based on a defining SIL
|
|
// value or any lifetime dependent use of that SIL value.
|
|
private struct LifetimeVariable {
|
|
var varDecl: VarDecl? = nil
|
|
var sourceLoc: SourceLoc? = nil
|
|
var isAccessScope: Bool = false
|
|
var isArgument: Bool = false
|
|
var isClosureCapture: Bool = false
|
|
var accessorKind: String?
|
|
var thunkKind: Function.ThunkKind = .noThunk
|
|
|
|
var name: StringRef? {
|
|
return varDecl?.userFacingName
|
|
}
|
|
|
|
init(definedBy value: Value, user: Instruction, _ context: some Context) {
|
|
self = .init(dependent: value, context)
|
|
// variable names like $return_value and $implicit_value don't have source locations.
|
|
// For @out arguments, the user's location is the best answer.
|
|
// Otherwise, fall back to the function's location.
|
|
self.sourceLoc = self.sourceLoc ?? user.location.sourceLoc ?? user.parentFunction.location.sourceLoc
|
|
}
|
|
|
|
init(definedBy value: Value, _ context: some Context) {
|
|
self = .init(dependent: value, context)
|
|
// Fall back to the function's location.
|
|
self.sourceLoc = self.sourceLoc ?? value.parentFunction.location.sourceLoc
|
|
}
|
|
|
|
private init(dependent value: Value, _ context: some Context) {
|
|
guard let introducer = getFirstVariableIntroducer(of: value, context) else {
|
|
return
|
|
}
|
|
if introducer.type.isAddress {
|
|
if let beginAccess = introducer as? BeginAccessInst {
|
|
// Recurse through beginAccess to find the variable introducer rather than the variable access.
|
|
self = .init(dependent: beginAccess.address, context)
|
|
self.isAccessScope = true
|
|
// However, remember source location of the innermost access.
|
|
self.sourceLoc = beginAccess.location.sourceLoc ?? self.sourceLoc
|
|
return
|
|
}
|
|
self = .init(accessBase: introducer.accessBase, context)
|
|
return
|
|
}
|
|
self = Self(introducer: introducer, context)
|
|
}
|
|
|
|
private func getFirstVariableIntroducer(of value: Value, _ context: some Context) -> Value? {
|
|
var introducer: Value?
|
|
var useDefVisitor = VariableIntroducerUseDefWalker(context, scopedValue: value, ignoreTrivialCopies: false) {
|
|
introducer = $0
|
|
return .abortWalk
|
|
}
|
|
defer { useDefVisitor.deinitialize() }
|
|
_ = useDefVisitor.walkUp(newLifetime: value)
|
|
return introducer
|
|
}
|
|
|
|
private init(introducer: Value, _ context: some Context) {
|
|
if let arg = introducer as? FunctionArgument {
|
|
self.varDecl = arg.findVarDecl()
|
|
self.sourceLoc = arg.sourceLoc
|
|
self.isArgument = true
|
|
self.isClosureCapture = arg.isClosureCapture
|
|
return
|
|
}
|
|
if let varDecl = introducer.definingInstruction?.findVarDecl() {
|
|
self.varDecl = varDecl
|
|
self.sourceLoc = varDecl.nameLoc
|
|
} else if let sourceLoc = introducer.definingInstruction?.location.sourceLoc {
|
|
self.sourceLoc = sourceLoc
|
|
} else {
|
|
self.accessorKind = introducer.parentFunction.accessorKindName
|
|
self.thunkKind = introducer.parentFunction.thunkKind
|
|
}
|
|
}
|
|
|
|
// Record the source location of the variable decl if possible. The
|
|
// caller will already have a source location for the formal access,
|
|
// which is more relevant for diagnostics.
|
|
private init(accessBase: AccessBase, _ context: some Context) {
|
|
switch accessBase {
|
|
case .box(let projectBox):
|
|
// Note: referenceRoot looks through `begin_borrow [var_decl]` and `move_value [var_decl]`. But the box should
|
|
// never be produced by one of these, except when it is redundant with the `alloc_box` VarDecl. It does not seem
|
|
// possible for a box to be moved/borrowed directly into another variable's box. Reassignment always loads/stores
|
|
// the value.
|
|
self = .init(introducer: projectBox.box.referenceRoot, context)
|
|
case .stack(let allocStack):
|
|
self = .init(introducer: allocStack, context)
|
|
case .global(let globalVar):
|
|
self.varDecl = globalVar.varDecl
|
|
self.sourceLoc = varDecl?.nameLoc
|
|
case .class(let refAddr):
|
|
self = .init(introducer: refAddr, context)
|
|
case .tail(let refTail):
|
|
self = .init(introducer: refTail.instance, context)
|
|
case .argument(let arg):
|
|
self = .init(introducer: arg, context)
|
|
case .yield(let result):
|
|
// TODO: bridge VarDecl for FunctionConvention.Yields
|
|
self = .init(introducer: result, context)
|
|
case .storeBorrow(let sb):
|
|
self = .init(dependent: sb.source, context)
|
|
case .pointer(let ptrToAddr):
|
|
self = .init(introducer: ptrToAddr, context)
|
|
case .index, .unidentified:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 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.
|
|
///
|
|
/// This supports store-to-yield. Storing to a yield is an escape unless the yielded memory location depends on another
|
|
/// lifetime that already depends on the current scope. When setter depends on 'newValue', 'newValue' is stored to the
|
|
/// yielded address, and the yielded addresses depends on the lifetime of 'self'. A mark_dependence should have already
|
|
/// been inserted for that lifetime dependence:
|
|
///
|
|
/// (%a, %t) = begin_apply %f(%self)
|
|
/// : $@yield_once @convention(method) (@inout Self) -> _inherit(0) @yields @inout Self.field
|
|
/// %dep = mark_dependence [nonescaping] %yield_addr on %self
|
|
/// store %newValue to [assign] %dep : $*Self.field
|
|
///
|
|
private struct DependentAddressUseDefWalker {
|
|
let context: Context
|
|
var diagnostics: DiagnoseDependence
|
|
}
|
|
|
|
extension DependentAddressUseDefWalker: AddressUseDefWalker {
|
|
// Follow the dependence base, not the forwarded value. Similar to the way LifetimeDependenceUseDefWalker handles
|
|
// MarkDependenceInst.
|
|
mutating func walkUp(address: Value, path: UnusedWalkingPath = UnusedWalkingPath()) -> WalkResult {
|
|
if let markDep = address as? MarkDependenceInst, let addressDep = LifetimeDependence(markDep, context) {
|
|
switch addressDep.scope {
|
|
case let .caller(arg):
|
|
return diagnostics.checkInoutResult(argument: arg)
|
|
case .owned, .initialized:
|
|
// Storing a nonescaping value to local memory cannot escape.
|
|
return .abortWalk
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
return walkUpDefault(address: address, path: UnusedWalkingPath())
|
|
}
|
|
|
|
mutating func rootDef(address: Value, path: UnusedWalkingPath) -> WalkResult {
|
|
// This only searches for mark_dependence scopes.
|
|
return .continueWalk
|
|
}
|
|
}
|
|
|
|
/// Walk down lifetime dependence uses. For each check that all dependent
|
|
/// leaf uses are non-escaping and within the dependence scope. The walk
|
|
/// starts with add address for .access dependencies. The walk can
|
|
/// transition from an address to a value at a load. The walk can
|
|
/// transition from a value to an address as follows:
|
|
///
|
|
/// %dependent_addr = mark_dependence [nonescaping] %base_addr on %value
|
|
///
|
|
/// TODO: handle stores to singly initialized temporaries like copies using a standard reaching-def analysis.
|
|
private struct DiagnoseDependenceWalker {
|
|
let context: Context
|
|
var diagnostics: DiagnoseDependence
|
|
let localReachabilityCache = LocalVariableReachabilityCache()
|
|
var visitedValues: ValueSet
|
|
|
|
var function: Function { diagnostics.function }
|
|
|
|
init(_ diagnostics: DiagnoseDependence, _ context: Context) {
|
|
self.context = context
|
|
self.diagnostics = diagnostics
|
|
self.visitedValues = ValueSet(context)
|
|
}
|
|
|
|
mutating func deinitialize() {
|
|
visitedValues.deinitialize()
|
|
}
|
|
}
|
|
|
|
extension DiagnoseDependenceWalker : LifetimeDependenceDefUseWalker {
|
|
mutating func needWalk(for value: Value) -> Bool {
|
|
visitedValues.insert(value)
|
|
}
|
|
|
|
mutating func leafUse(of operand: Operand) -> WalkResult {
|
|
return diagnostics.checkInScope(operand: operand)
|
|
}
|
|
|
|
mutating func deadValue(_ value: Value, using operand: Operand?)
|
|
-> WalkResult {
|
|
// Ignore a dead root value. It never escapes.
|
|
if let operand {
|
|
return diagnostics.checkInScope(operand: operand)
|
|
}
|
|
return .continueWalk
|
|
}
|
|
|
|
mutating func escapingDependence(on operand: Operand) -> WalkResult {
|
|
diagnostics.reportEscaping(operand: operand)
|
|
return .abortWalk
|
|
}
|
|
|
|
mutating func inoutDependence(argument: FunctionArgument, functionExit: Instruction) -> WalkResult {
|
|
if diagnostics.checkInoutResult(argument: argument) == .abortWalk {
|
|
diagnostics.reportEscaping(value: argument, user: functionExit)
|
|
return .abortWalk
|
|
}
|
|
return .continueWalk
|
|
}
|
|
|
|
mutating func returnedDependence(result: Operand) -> WalkResult {
|
|
if diagnostics.checkFunctionResult(operand: result) == .abortWalk {
|
|
diagnostics.reportEscaping(operand: result)
|
|
return .abortWalk
|
|
}
|
|
return .continueWalk
|
|
}
|
|
|
|
mutating func returnedDependence(address: FunctionArgument,
|
|
on operand: Operand) -> WalkResult {
|
|
if diagnostics.checkFunctionResult(operand: operand) == .abortWalk {
|
|
diagnostics.reportEscaping(operand: operand)
|
|
return .abortWalk
|
|
}
|
|
return .continueWalk
|
|
}
|
|
|
|
mutating func yieldedDependence(result: Operand) -> WalkResult {
|
|
if diagnostics.checkYield(operand: result) == .abortWalk {
|
|
diagnostics.reportEscaping(operand: result)
|
|
return .abortWalk
|
|
}
|
|
return .continueWalk
|
|
}
|
|
|
|
mutating func storeToYieldDependence(address: Value, of operand: Operand) -> WalkResult {
|
|
if diagnostics.checkStoreToYield(address: address) == .abortWalk {
|
|
diagnostics.reportEscaping(operand: operand)
|
|
return .abortWalk
|
|
}
|
|
return .continueWalk
|
|
}
|
|
|
|
// Override AddressUseVisitor here because LifetimeDependenceDefUseWalker returns .abortWalk and
|
|
// DiagnoseDependenceWalker requires a diagnostic error for all aborts.
|
|
mutating func unknownAddressUse(of operand: Operand) -> WalkResult {
|
|
diagnostics.reportUnknown(operand: operand)
|
|
return .abortWalk
|
|
}
|
|
}
|