|
| 1 | +//===--- LifetimeDependenceInsertion.swift - insert lifetime dependence ---===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | +/// |
| 13 | +/// Insert mark_dependence [nonescaping] markers on the owned returned |
| 14 | +/// or yielded value of a call whose return type is non-escaping. |
| 15 | +/// |
| 16 | +/// Pass dependencies: This must run as a SILGen cleanup pass before |
| 17 | +/// any lifetime canonicalization or optimization can be performed. |
| 18 | +/// |
| 19 | +//===----------------------------------------------------------------------===// |
| 20 | + |
| 21 | +import SIL |
| 22 | + |
| 23 | +private let verbose = false |
| 24 | + |
| 25 | +private func log(_ message: @autoclosure () -> String) { |
| 26 | + if verbose { |
| 27 | + print("### \(message())") |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +let lifetimeDependenceInsertionPass = FunctionPass( |
| 32 | + name: "lifetime-dependence-insertion") |
| 33 | +{ (function: Function, context: FunctionPassContext) in |
| 34 | + if !context.options.hasFeature(.NonescapableTypes) { |
| 35 | + return |
| 36 | + } |
| 37 | + log("Inserting lifetime dependence markers in \(function.name)") |
| 38 | + |
| 39 | + for instruction in function.instructions { |
| 40 | + if let dependentApply = LifetimeDependentApply(instruction) { |
| 41 | + insertDependencies(for: dependentApply, context) |
| 42 | + } |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +/// An apply that produces a non-escapable value, linking it to a parent value. |
| 47 | +private struct LifetimeDependentApply { |
| 48 | + let applySite: FullApplySite |
| 49 | + |
| 50 | + init?(_ instruction: Instruction) { |
| 51 | + guard let apply = instruction as? FullApplySite else { |
| 52 | + return nil |
| 53 | + } |
| 54 | + if !apply.hasResultDependence { |
| 55 | + return nil |
| 56 | + } |
| 57 | + self.applySite = apply |
| 58 | + } |
| 59 | + |
| 60 | + init?(withResult value: Value) { |
| 61 | + switch value { |
| 62 | + case let apply as ApplyInst: |
| 63 | + if let dependentApply = LifetimeDependentApply(apply) { |
| 64 | + self = dependentApply |
| 65 | + } |
| 66 | + case let arg as Argument: |
| 67 | + guard let termResult = TerminatorResult(arg) else { return nil } |
| 68 | + switch termResult.terminator { |
| 69 | + case let ta as TryApplyInst: |
| 70 | + if termResult.successor == ta.errorBlock { |
| 71 | + if let dependentApply = LifetimeDependentApply(ta) { |
| 72 | + self = dependentApply |
| 73 | + } |
| 74 | + } |
| 75 | + default: |
| 76 | + break |
| 77 | + } |
| 78 | + default: |
| 79 | + break |
| 80 | + } |
| 81 | + return nil |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +extension LifetimeDependentApply { |
| 86 | + /// A lifetime argument copies, borrows, or mutatably borrows the |
| 87 | + /// lifetime of the argument value. |
| 88 | + struct LifetimeArgument { |
| 89 | + let convention: LifetimeDependenceConvention |
| 90 | + let value: Value |
| 91 | + } |
| 92 | + |
| 93 | + func getLifetimeArguments() -> SingleInlineArray<LifetimeArgument> { |
| 94 | + var args = SingleInlineArray<LifetimeArgument>() |
| 95 | + for operand in applySite.parameterOperands { |
| 96 | + guard let dep = applySite.resultDependence(on: operand) else { |
| 97 | + continue |
| 98 | + } |
| 99 | + args.push(LifetimeArgument(convention: dep, value: operand.value)) |
| 100 | + } |
| 101 | + return args |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +/// Replace the each dependent apply result with a chain of |
| 106 | +/// mark_dependence [nonescaping] instructions; one for each base. |
| 107 | +private func insertDependencies(for apply: LifetimeDependentApply, |
| 108 | + _ context: FunctionPassContext ) { |
| 109 | + precondition(apply.applySite.results.count > 0, |
| 110 | + "a lifetime-dependent instruction must have at least one result") |
| 111 | + |
| 112 | + let bases = recursivelyFindDependenceBases(of: apply, context) |
| 113 | + let builder = Builder(after: apply.applySite, context) |
| 114 | + for dependentValue in apply.applySite.resultOrYields { |
| 115 | + insertMarkDependencies(value: dependentValue, initializer: nil, |
| 116 | + bases: bases, builder: builder, context) |
| 117 | + } |
| 118 | + for resultOper in apply.applySite.indirectResultOperands { |
| 119 | + let accessBase = resultOper.value.accessBase |
| 120 | + guard let (initialAddress, initializingStore) = |
| 121 | + accessBase.findSingleInitializer(context) else { |
| 122 | + continue |
| 123 | + } |
| 124 | + // TODO: This is currently too strict for a diagnostic pass. We |
| 125 | + // should handle/cleanup projections and casts that occur before |
| 126 | + // the initializingStore. Or check in the SIL verifier that all |
| 127 | + // stores without an access scope follow this form. Then convert |
| 128 | + // this bail-out to an assert. |
| 129 | + guard initialAddress.usesOccurOnOrAfter(instruction: initializingStore, |
| 130 | + context) else { |
| 131 | + continue |
| 132 | + } |
| 133 | + assert(initializingStore == resultOper.instruction, |
| 134 | + "an indirect result is a store") |
| 135 | + insertMarkDependencies(value: initialAddress, |
| 136 | + initializer: initializingStore, bases: bases, |
| 137 | + builder: builder, context) |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +private func insertMarkDependencies(value: Value, initializer: Instruction?, |
| 142 | + bases: [Value], builder: Builder, |
| 143 | + _ context: FunctionPassContext) { |
| 144 | + var currentValue = value |
| 145 | + for base in bases { |
| 146 | + let markDep = builder.createMarkDependence( |
| 147 | + value: currentValue, base: base, kind: .Unresolved) |
| 148 | + |
| 149 | + let uses = currentValue.uses.lazy.filter { |
| 150 | + let inst = $0.instruction |
| 151 | + return inst != markDep && inst != initializer && !(inst is Deallocation) |
| 152 | + } |
| 153 | + uses.replaceAll(with: markDep, context) |
| 154 | + currentValue = markDep |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +/// Return base values that this return value depends on. |
| 159 | +/// |
| 160 | +/// For lifetime copies, walk up the dependence chain to find the |
| 161 | +/// dependence roots, inserting dependencies for any |
| 162 | +/// LifetimeDependentApply. |
| 163 | +private func recursivelyFindDependenceBases(of apply: LifetimeDependentApply, |
| 164 | + _ context: FunctionPassContext) |
| 165 | + -> [Value] { |
| 166 | + log("Creating dependencies for \(apply.applySite)") |
| 167 | + var bases: [Value] = [] |
| 168 | + for lifetimeArg in apply.getLifetimeArguments() { |
| 169 | + switch lifetimeArg.convention { |
| 170 | + case .inherit: |
| 171 | + // Inherit the argument's lifetime dependence by finding the |
| 172 | + // roots. This requires that a mark_dependence [nonescaping] |
| 173 | + // already be created for any earlier LifetimeDependentApply. |
| 174 | + _ = LifetimeDependence.visitDependenceRoots(enclosing: lifetimeArg.value, |
| 175 | + context) |
| 176 | + { (scope: LifetimeDependence.Scope) in |
| 177 | + if let updatedScope = recursivelyUpdate(scope: scope, context) { |
| 178 | + log("Inherited lifetime from \(lifetimeArg.value)") |
| 179 | + log(" depends on: \(updatedScope)") |
| 180 | + bases.append(updatedScope.parentValue) |
| 181 | + } |
| 182 | + return .continueWalk |
| 183 | + } |
| 184 | + case .scope: |
| 185 | + // Create a new dependence on the apply's access to the argument. |
| 186 | + if let scope = |
| 187 | + LifetimeDependence.Scope(base: lifetimeArg.value, context) { |
| 188 | + log("Scoped lifetime from \(lifetimeArg.value)") |
| 189 | + log(" scope: \(scope)") |
| 190 | + bases.append(scope.parentValue) |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | + return bases |
| 195 | +} |
| 196 | + |
| 197 | +// Recursively insert dependencies, assuming no cycle of dependent applies. |
| 198 | +// |
| 199 | +// TODO: needs unit test. |
| 200 | +private func recursivelyUpdate(scope: LifetimeDependence.Scope, |
| 201 | + _ context: FunctionPassContext) -> LifetimeDependence.Scope? { |
| 202 | + if let dependentApply = |
| 203 | + LifetimeDependentApply(withResult: scope.parentValue) { |
| 204 | + insertDependencies(for: dependentApply, context) |
| 205 | + // If a mark_dependence [nonescaping] was created for this apply, |
| 206 | + // then return it as the updated dependence. Otherwise, return the |
| 207 | + // original dependence. |
| 208 | + if let markDep = scope.parentValue.uses.singleUse?.instruction |
| 209 | + as? MarkDependenceInst { |
| 210 | + return LifetimeDependence(markDep, context)?.scope |
| 211 | + } |
| 212 | + } |
| 213 | + return scope |
| 214 | +} |
0 commit comments