File: LifetimeDependenceInsertion.swift

package info (click to toggle)
swiftlang 6.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,791,532 kB
  • sloc: cpp: 9,901,743; ansic: 2,201,431; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (269 lines) | stat: -rw-r--r-- 10,072 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
//===--- LifetimeDependenceInsertion.swift - insert lifetime dependence ---===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 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
//
//===----------------------------------------------------------------------===//
///
/// Insert mark_dependence [nonescaping] markers on the owned returned
/// or yielded value of a call whose return type is non-escaping.
///
/// Pass dependencies: This must run as a SILGen cleanup pass before
/// any lifetime canonicalization or optimization can be performed.
///
//===----------------------------------------------------------------------===//

import SIL

private let verbose = false

private func log(prefix: Bool = true, _ message: @autoclosure () -> String) {
  if verbose {
    debugLog(prefix: prefix, message())
  }
}

let lifetimeDependenceInsertionPass = FunctionPass(
  name: "lifetime-dependence-insertion")
{ (function: Function, context: FunctionPassContext) in
#if os(Windows)
  if !context.options.hasFeature(.NonescapableTypes) {
    return
  }
#endif
  log(prefix: false, "\n--- Inserting lifetime dependence markers in \(function.name)")

  for instruction in function.instructions {
    if let dependentApply = LifetimeDependentApply(instruction) {
      for operand in dependentApply.applySite.parameterOperands {
        insertParameterDependencies(apply: dependentApply, target: operand, context)
      }
      insertResultDependencies(for: dependentApply, context)
    }
  }
}

/// An apply that produces a non-escapable value, linking it to a parent value.
private struct LifetimeDependentApply {
  let applySite: FullApplySite

  init?(_ instruction: Instruction) {
    guard let apply = instruction as? FullApplySite else {
      return nil
    }
    if !apply.hasLifetimeDependence {
      return nil
    }
    self.applySite = apply
  }

  init?(withResult value: Value) {
    switch value {
    case let apply as ApplyInst:
      if let dependentApply = LifetimeDependentApply(apply) {
        self = dependentApply
      }
    case let arg as Argument:
      guard let termResult = TerminatorResult(arg) else { return nil }
      switch termResult.terminator {
      case let ta as TryApplyInst:
        if termResult.successor == ta.errorBlock {
          if let dependentApply = LifetimeDependentApply(ta) {
            self = dependentApply
          }
        }
      default:
        break
      }
    default:
      break
    }
    return nil
  }
}

extension LifetimeDependentApply {
  enum TargetKind {
    case result
    case inParameter
    case inoutParameter
    case yield
    case yieldAddress
  }
  
  /// A lifetime argument that either inherits or creates a new scope for the lifetime of the argument value.
  struct LifetimeSource {
    let convention: LifetimeDependenceConvention
    let value: Value
  }

  /// List of lifetime dependencies for a single target.
  struct LifetimeSources {
    let targetKind: TargetKind
    var sources = SingleInlineArray<LifetimeSource>()
  }

  func getResultDependenceSources() -> LifetimeSources? {
    guard applySite.hasResultDependence else { return nil }
    var sources: LifetimeSources
    switch applySite {
    case let beginApply as BeginApplyInst:
      if beginApply.yieldedValues.contains(where: { $0.type.isAddress }) {
        sources = LifetimeSources(targetKind: .yieldAddress)
      } else {
        sources = LifetimeSources(targetKind: .yield)
      }
    default:
      sources = LifetimeSources(targetKind: .result)
    }
    for operand in applySite.parameterOperands {
      guard let dep = applySite.resultDependence(on: operand) else {
        continue
      }
      sources.sources.push(LifetimeSource(convention: dep, value: operand.value))
    }
    return sources
  }

  func getParameterDependenceSources(target: Operand) -> LifetimeSources? {
    guard let deps = applySite.parameterDependencies(target: target) else {
      return nil
    }
    var sources: LifetimeSources
    let convention = applySite.convention(of: target)!
    switch convention {
    case .indirectInout, .indirectInoutAliasable, .packInout:
      sources = LifetimeSources(targetKind: .inoutParameter)
    case .indirectIn, .indirectInGuaranteed, .indirectInCXX, .directOwned, .directUnowned, .directGuaranteed,
         .packOwned, .packGuaranteed:
      sources = LifetimeSources(targetKind: .inParameter)
    case .indirectOut, .packOut:
      debugLog("\(applySite)")
      fatalError("Lifetime dependencies cannot target \(convention) parameter")
    }
    for (dep, operand) in zip(deps, applySite.parameterOperands) {
      guard let dep = dep else {
        continue
      }
      sources.sources.push(LifetimeSource(convention: dep, value: operand.value))
    }
    return sources
  }

  // Scoped dependencies require a mark_dependence for every variable that introduces this scope.
  //
  // Inherited dependencies do not require a mark_dependence if the target is a result or yielded value. The inherited
  // lifetime is nonescapable, so either
  //
  // (a) the result or yield is never returned from this function
  //
  // (b) the inherited lifetime has a dependence root within this function (it comes from a dependent function argument
  // or scoped dependence). In this case, when that depedence root is diagnosed, the analysis will find transtive uses
  // of this apply's result.
  //
  // (c) the dependent value is passed to another call with a dependent inout argument, or it is stored to a yielded
  // address of a coroutine that has a dependent inout argument. In this case, a mark_dependence will already be created
  // for that inout argument.
  //
  // Parameter dependencies and yielded addresses always require a mark_dependence.
  static func findDependenceBases(sources: LifetimeSources, _ context: FunctionPassContext) -> [Value] {
    var bases: [Value] = []
    for source in sources.sources {
      switch source.convention {
      case .inherit:
        switch sources.targetKind {
        case .result, .yield:
          continue
        case .inParameter, .inoutParameter, .yieldAddress:
          _ = LifetimeDependence.visitDependenceRoots(enclosing: source.value, context) { scope in
            log("Inherited lifetime from \(source.value)")
            log("  scope: \(scope)")
            bases.append(scope.parentValue)
            return .continueWalk
          }
        }
      case .scope:
        // Create a new dependence on the apply's access to the argument.
        for varIntoducer in gatherVariableIntroducers(for: source.value, context) {
          if let scope = LifetimeDependence.Scope(base: varIntoducer, context) {
            log("Scoped lifetime from \(source.value)")
            log("  scope: \(scope)")
            bases.append(scope.parentValue)
          }
        }
      }
    }
    return bases
  }
}

/// If the result of this apply depends on the scope of one or more
/// arguments, then insert a mark_dependence [unresolved] from the
/// result on each argument so that the result is recognized as a
/// dependent value within each scope.
private func insertResultDependencies(for apply: LifetimeDependentApply, _ context: FunctionPassContext ) {
  guard let sources = apply.getResultDependenceSources() else {
    return
  }
  log("Creating dependencies for \(apply.applySite)")

  let bases = LifetimeDependentApply.findDependenceBases(sources: sources, context)

  for dependentValue in apply.applySite.resultOrYields {
    let builder = Builder(before: dependentValue.nextInstruction, context)
    insertMarkDependencies(value: dependentValue, initializer: nil, bases: bases, builder: builder, context)
  }
  for resultOper in apply.applySite.indirectResultOperands {
    let accessBase = resultOper.value.accessBase
    guard let (initialAddress, initializingStore) = accessBase.findSingleInitializer(context) else {
      continue
    }
    // TODO: This might bail-out on SIL that should be diagnosed. We should handle/cleanup projections and casts that
    // occur before the initializingStore. Or check in the SIL verifier that all stores without an access scope follow
    // this form. Then convert this bail-out to an assert.
    guard initialAddress.usesOccurOnOrAfter(instruction: initializingStore, context) else {
      continue
    }
    assert(initializingStore == resultOper.instruction, "an indirect result is a store")
    Builder.insert(after: apply.applySite, context) { builder in
      insertMarkDependencies(value: initialAddress, initializer: initializingStore, bases: bases, builder: builder,
                             context)
    }
  }
}

private func insertParameterDependencies(apply: LifetimeDependentApply, target: Operand,
                                         _ context: FunctionPassContext ) {
  guard let sources = apply.getParameterDependenceSources(target: target) else {
    return
  }
  log("Creating dependencies for \(apply.applySite)")

  let bases = LifetimeDependentApply.findDependenceBases(sources: sources, context)

  Builder.insert(after: apply.applySite, context) {
    insertMarkDependencies(value: target.value, initializer: nil, bases: bases, builder: $0, context)    
  }
}

private func insertMarkDependencies(value: Value, initializer: Instruction?,
                                    bases: [Value], builder: Builder,
                                    _ context: FunctionPassContext) {
  var currentValue = value
  for base in bases {
    let markDep = builder.createMarkDependence(
      value: currentValue, base: base, kind: .Unresolved)

    let uses = currentValue.uses.lazy.filter {
      let inst = $0.instruction
      return inst != markDep && inst != initializer && !(inst is Deallocation)
    }
    uses.replaceAll(with: markDep, context)
    currentValue = markDep
  }
}