File: LocalVariableUtils.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (857 lines) | stat: -rw-r--r-- 31,319 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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
//===--- LocalVariableUtils.swift - Utilities for local variable access ---===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// SIL operates on three kinds of addressible memory:
///
/// 1. Temporary RValues. These are recognied by AddressInitializationWalker. These largely disappear with opaque SIL
/// values.
///
/// 2. Local variables. These are always introduced by either a VarDeclInstruction or a Function argument with non-nil
/// Argument.varDecl. They are accessed according to the structural rules define in this file. Loading or reassigning a
/// property requires a formal access (begin_access).
///
/// 3. Stored properties in heap objects or global variables. These are always formally accessed.
///
//===----------------------------------------------------------------------===//

import SIL

private let verbose = false

private func log(_ message: @autoclosure () -> String) {
  if verbose {
    print("### \(message())")
  }
}

// Local variables are accessed in one of these ways.
//
// Note: @in is only immutable up to when it is destroyed, so still requies a local live range.
struct LocalVariableAccess: CustomStringConvertible {
  enum Kind {
    case incomingArgument // @in, @inout, @inout_aliasable
    case outgoingArgument // @inout, @inout_aliasable
    case beginAccess // Reading or reassinging a 'var'
    case load        // Reading a 'let'. Returning 'var' from an initializer.
    case store       // 'var' initialization and destruction
    case apply       // indirect arguments
    case escape      // alloc_box captures
  }
  let kind: Kind
  // All access have an operand except .incomingArgument and .outgoingArgument.
  let operand: Operand?

  // All access have a representative instruction except .incomingArgument.
  var instruction: Instruction?

  init(_ kind: Kind, _ operand: Operand) {
    self.kind = kind
    self.operand = operand
    self.instruction = operand.instruction
  }

  init(_ kind: Kind, _ instruction: Instruction?) {
    self.kind = kind
    self.operand = nil
    self.instruction = instruction
  }

  /// Does this access either fully or partially modify the variable?
  var isModify: Bool {
    switch kind {
    case .beginAccess:
      switch (instruction as! BeginAccessInst).accessKind {
      case .read, .deinit:
        return false
      case .`init`, .modify:
        return true
      }
    case .load:
      return false
    case .incomingArgument, .outgoingArgument, .store:
      return true
    case .apply:
      let apply = instruction as! FullApplySite
      if let convention = apply.convention(of: operand!) {
        // A direct argument may modify a captured variable.
        return !convention.isIndirectIn
      }
      // A callee argument may modify a captured variable.
      return true
    case .escape:
      return true
    }
  }

  var isEscape: Bool {
    switch kind {
    case .escape:
      return true
    default:
      return false
    }
  }

  var description: String {
    var str = ""
    switch self.kind {
    case .incomingArgument:
      str += "incomingArgument"
    case .outgoingArgument:
      str += "outgoingArgument"
    case .beginAccess:
      str += "beginAccess"
    case .load:
      str += "load"
    case .store:
      str += "store"
    case .apply:
      str += "apply"
    case .escape:
      str += "escape"
    }
    if let inst = instruction {
      str += "\(inst)"
    }
    return str
  }
}

/// Class instance for caching local variable information.
class LocalVariableAccessInfo: CustomStringConvertible {
  let access: LocalVariableAccess

  private var _isFullyAssigned: Bool?

  /// Cache whether the allocation has escaped prior to this access.
  /// For alloc_box, this returns `nil` until reachability is computed.
  var hasEscaped: Bool?

  init(localAccess: LocalVariableAccess) {
    self.access = localAccess
    switch localAccess.kind {
    case .beginAccess:
      switch (localAccess.instruction as! BeginAccessInst).accessKind {
      case .read, .deinit:
        self._isFullyAssigned = false
      case .`init`, .modify:
        break // lazily compute full assignment
      }
    case .load:
      self._isFullyAssigned = false
    case .store:
      self._isFullyAssigned = true
    case .apply:
      let apply = localAccess.instruction as! FullApplySite
      if let convention = apply.convention(of: localAccess.operand!) {
        self._isFullyAssigned = convention.isIndirectOut
      } else {
        self._isFullyAssigned = false
      }
    case .escape:
      self._isFullyAssigned = false
      self.hasEscaped = true
    case .incomingArgument, .outgoingArgument:
      fatalError("Function arguments are never mapped to LocalVariableAccessInfo")
    }
  }

  var instruction: Instruction { access.instruction! }

  var isModify: Bool { access.isModify }

  var isEscape: Bool { access.isEscape }

  /// Is this access a full assignment such that none of the variable's components are reachable from a previous
  /// access.
  func isFullyAssigned(_ context: Context) -> Bool {
    if let cached = _isFullyAssigned {
      return cached
    }
    if access.kind != .beginAccess {
      fatalError("Invalid LocalVariableAccess")
    }
    assert(isModify)
    let beginAccess = access.instruction as! BeginAccessInst
    let initializer = AddressInitializationWalker.findSingleInitializer(ofAddress: beginAccess, context: context)
    _isFullyAssigned = (initializer != nil) ? true : false
    return _isFullyAssigned!
  }

  var description: String {
    return "full-assign: \(_isFullyAssigned == nil ? "unknown" : String(describing: _isFullyAssigned!)) "
      + "\(access)"
  }
}

/// Model the formal accesses of an addressible variable introduced by an alloc_box, alloc_stack, or indirect
/// FunctionArgument.
///
/// This instantiates a unique LocalVariableAccessInfo instances for each access instruction, caching it an an access
/// map.
///
/// TODO: In addition to isFullyAssigned, consider adding a lazily computed access path if any need arises.
struct LocalVariableAccessMap: Collection, CustomStringConvertible {
  let context: Context
  let allocation: Value

  let liveInAccess: LocalVariableAccess?

  // All mapped accesses have a valid instruction.
  //
  // TODO: replace the List,Dictionary with an OrderedDictionary.
  private var accessList: [LocalVariableAccessInfo]
  private var accessMap: Dictionary<Instruction, LocalVariableAccessInfo>

  var function: Function { allocation.parentFunction }

  var isBoxed: Bool { allocation is AllocBoxInst }

  var mayAlias: Bool {
    if let arg = allocation as? FunctionArgument, arg.convention == .indirectInoutAliasable {
      return true
    }
    return false
  }

  init?(allocation: Value, _ context: Context) {
    switch allocation {
    case is AllocBoxInst, is AllocStackInst:
      self.liveInAccess = nil
      break
    case let arg as FunctionArgument:
      switch arg.convention {
      case .indirectIn, .indirectInout, .indirectInoutAliasable:
        self.liveInAccess = LocalVariableAccess(.incomingArgument, nil)
      default:
        return nil
      }
    default:
      return nil
    }
    self.context = context
    self.allocation = allocation
    accessList = []
    accessMap = [:]
    if walkAccesses(context) == .abortWalk {
      return nil
    }
  }

  private mutating func walkAccesses(_ context: Context) -> WalkResult {
    var walker = LocalVariableAccessWalker(context)
    defer { walker.deinitialize() }
    if walker.walkDown(allocation: allocation) == .abortWalk {
      return .abortWalk
    }
    for localAccess in walker.accessStack {
      let info = LocalVariableAccessInfo(localAccess: localAccess)
      if mayAlias {
        // Local allocations can only escape prior to assignment if they are boxed or inout_aliasable.
        info.hasEscaped = true
      } else if !isBoxed {
        // Boxed allocation requires reachability to determine whether the box escaped prior to assignment.
        info.hasEscaped = info.isEscape
      }
      accessMap[localAccess.instruction!] = info
      accessList.append(info)
    }
    return .continueWalk
  }

  var startIndex: Int { 0 }

  var endIndex: Int { accessList.count }

  func index(after index: Int) -> Int {
    return index + 1
  }

  subscript(_ accessIndex: Int) -> LocalVariableAccessInfo { accessList[accessIndex] }

  subscript(instruction: Instruction) -> LocalVariableAccessInfo? { accessMap[instruction] }

  public var description: String {
    "Access map:\n" + map({String(describing: $0)}).joined(separator: "\n")
  }
}

/// Gather the accesses of a local allocation: alloc_box, alloc_stack, @in, @inout.
///
/// This is used to populate LocalVariableAccessMap.
///
/// Start walk:
///   walkDown(allocation:)
///
/// TODO: This should only handle allocations that have a var decl. And SIL verification should guarantee that the
/// allocated address is never used by a path projection outside of an access.
struct LocalVariableAccessWalker {
  let context: Context
  var visitedValues: ValueSet
  var accessStack: Stack<LocalVariableAccess>

  init(_ context: Context) {
    self.context = context
    self.visitedValues = ValueSet(context)
    self.accessStack = Stack(context)
  }

  mutating func deinitialize() {
    visitedValues.deinitialize()
    accessStack.deinitialize()
  }

  mutating func walkDown(allocation: Value) -> WalkResult {
    if allocation.type.isAddress {
      return walkDownAddressUses(address: allocation)
    }
    return walkDown(root: allocation)
  }

  private mutating func visit(_ localAccess: LocalVariableAccess) {
    accessStack.push(localAccess)
  }
}

// Extend ForwardingDefUseWalker to walk down uses of the box.
extension LocalVariableAccessWalker : ForwardingDefUseWalker {
  mutating func needWalk(for value: Value) -> Bool {
    visitedValues.insert(value)
  }

  mutating func nonForwardingUse(of operand: Operand) -> WalkResult {
    if operand.instruction.isIncidentalUse {
      return .continueWalk
    }
    switch operand.instruction {
    case let pbi as ProjectBoxInst:
      return walkDownAddressUses(address: pbi)
    case let transition as OwnershipTransitionInstruction:
      return walkDownUses(of: transition.ownershipResult, using: operand)
    case is DestroyValueInst:
      visit(LocalVariableAccess(.store, operand))
    case is DeallocBoxInst:
      break
    default:
      visit(LocalVariableAccess(.escape, operand))
    }
    return .continueWalk
  }

  mutating func deadValue(_ value: Value, using operand: Operand?) -> WalkResult {
    return .continueWalk
  }
}

// Extend AddressUseVisitor to find all access scopes, initializing stores, and captures.
extension LocalVariableAccessWalker: AddressUseVisitor {
  private mutating func walkDownAddressUses(address: Value) -> WalkResult {
    for operand in address.uses.ignoreTypeDependence {
      if classifyAddress(operand: operand) == .abortWalk {
        return .abortWalk
      }
    }
    return .continueWalk
  }

  // Handle storage type projections, like MarkUninitializedInst. Path projections should not be visited. They only
  // occur inside the access.
  //
  // Exception: stack-allocated temporaries may be treated like local variables for the purpose of finding all
  // uses. Such temporaries do not have access scopes, so we need to walk down any projection that may be used to
  // initialize the temporary.
  mutating func projectedAddressUse(of operand: Operand, into value: Value) -> WalkResult {
    // TODO: we need an abstraction for path projections. For local variables, these cannot occur outside of an access.
    switch operand.instruction {
    case is StructElementAddrInst, is TupleElementAddrInst, is IndexAddrInst, is TailAddrInst,
         is UncheckedTakeEnumDataAddrInst, is OpenExistentialAddrInst:
      return .abortWalk
    // Projections used to initialize a temporary
    case is InitEnumDataAddrInst, is InitExistentialAddrInst:
      fallthrough
    default:
      return walkDownAddressUses(address: value)
    }
  }

  mutating func scopedAddressUse(of operand: Operand) -> WalkResult {
    switch operand.instruction {
    case is BeginAccessInst:
      visit(LocalVariableAccess(.beginAccess, operand))
      return .continueWalk
    case is BeginApplyInst:
      visit(LocalVariableAccess(.apply, operand))
      return .continueWalk
    case is LoadBorrowInst:
      visit(LocalVariableAccess(.load, operand))
      return .continueWalk
    default:
      // A StoreBorrow should be guarded by an access scope.
      //
      // TODO: verify that we never hit this case.
      return .abortWalk // unexpected
    }
  }

  mutating func scopeEndingAddressUse(of operand: Operand) -> WalkResult {
    return .abortWalk // unexpected
  }

  mutating func leafAddressUse(of operand: Operand) -> WalkResult {
    switch operand.instruction {
    case is StoringInstruction, is SourceDestAddrInstruction, is DestroyAddrInst, is DeinitExistentialAddrInst,
         is InjectEnumAddrInst, is TupleAddrConstructorInst, is InitBlockStorageHeaderInst, is PackElementSetInst:
      // Handle instructions that initialize both temporaries and local variables.
      visit(LocalVariableAccess(.store, operand))
    case is DeallocStackInst:
      break
    default:
      if !operand.instruction.isIncidentalUse {
        visit(LocalVariableAccess(.escape, operand))
      }
    }
    return .continueWalk
  }

  mutating func appliedAddressUse(of operand: Operand, by apply: FullApplySite) -> WalkResult {
    visit(LocalVariableAccess(.apply, operand))
    return .continueWalk
  }

  mutating func dependentAddressUse(of operand: Operand, into value: Value) -> WalkResult {
    // Find all uses of partial_apply [on_stack].
    if let pai = value as? PartialApplyInst, !pai.mayEscape {
      var walker = NonEscapingClosureDefUseWalker(context)
      defer { walker.deinitialize() }
      if walker.walkDown(closure: pai) == .abortWalk {
        return .abortWalk
      }
      for operand in walker.applyOperandStack {
        visit(LocalVariableAccess(.apply, operand))
      }
    }
    // No other dependent uses can access to memory at this address.
    return .continueWalk
  }

  mutating func loadedAddressUse(of operand: Operand, into value: Value) -> WalkResult {
    visit(LocalVariableAccess(.load, operand))
    return .continueWalk
  }

  mutating func loadedAddressUse(of operand: Operand, into address: Operand) -> WalkResult {
    visit(LocalVariableAccess(.load, operand))
    return .continueWalk
  }

  mutating func escapingAddressUse(of operand: Operand) -> WalkResult {
    visit(LocalVariableAccess(.escape, operand))
    return .continueWalk
  }

  mutating func unknownAddressUse(of operand: Operand) -> WalkResult {
    return .abortWalk
  }
}

/// Map LocalVariableAccesses to basic blocks.
///
/// This caches flow-insensitive information about the local variable's accesses, for use with a flow-sensitive
/// analysis.
///
/// This allocates a dictionary for the block state rather than using BasicBlockSets in case the client wants to cache
/// it as an analysis. We expect a very small number of accesses per local variable.
struct LocalVariableAccessBlockMap {
  // Lattice, from most information to least information:
  // none -> read -> modify -> escape -> assign
  enum BlockEffect: Int {
    case read    // no modification or escape
    case modify  // no full assignment or escape
    case escape  // no full assignment
    case assign  // full assignment, other accesses may be before or after it.

    /// Return a merged lattice state such that the result has strictly less information.
    func meet(_ other: BlockEffect?) -> BlockEffect {
      guard let other else {
        return self
      }
      return other.rawValue > self.rawValue ? other : self
    }
  }
  struct BlockInfo {
    var effect: BlockEffect?
    var hasDealloc: Bool
  }
  var blockAccess: Dictionary<BasicBlock, BlockInfo>

  subscript(_ block: BasicBlock) -> BlockInfo? { blockAccess[block] }

  init(accessMap: LocalVariableAccessMap) {
    blockAccess = [:]
    for accessInfo in accessMap {
      let block = accessInfo.instruction.parentBlock
      let oldEffect = blockAccess[block]?.effect
      let newEffect = BlockEffect(for: accessInfo, accessMap.context).meet(oldEffect)
      blockAccess[block] = BlockInfo(effect: newEffect, hasDealloc: false)
    }
    // Find blocks that end the variable's scope. This is destroy_value for boxes.
    //
    // TODO: SIL verify that owned boxes are never forwarded.
    let deallocations = accessMap.allocation.uses.lazy.filter {
      $0.instruction is Deallocation || $0.instruction is DestroyValueInst
    }
    for dealloc in deallocations {
      let block = dealloc.instruction.parentBlock
      blockAccess[block, default: BlockInfo(effect: nil, hasDealloc: true)].hasDealloc = true
    }
  }
}

extension LocalVariableAccessBlockMap.BlockEffect {
  init(for accessInfo: LocalVariableAccessInfo, _ context: some Context) {
    // Assign from the lowest to the highest lattice values...
    self = .read
    if accessInfo.isModify {
      self = .modify
    }
    if accessInfo.isEscape {
      self = .escape
    }
    if accessInfo.isFullyAssigned(context) {
      self = .assign
    }
  }
}

/// Map an allocation (alloc_box, alloc_stack, @in, @inout) onto its reachable accesses.
class LocalVariableReachabilityCache {
  var cache = Dictionary<HashableValue, LocalVariableReachableAccess>()

  func reachability(for allocation: Value, _ context: some Context) -> LocalVariableReachableAccess? {
    if let reachability = cache[allocation.hashable] {
      return reachability
    }
    if let reachabilty = LocalVariableReachableAccess(allocation: allocation, context) {
      cache[allocation.hashable] = reachabilty
      return reachabilty
    }
    return nil
  }
}

/// Flow-sensitive, pessimistic data flow of local variable access. This finds all potentially reachable uses of an
/// assignment. This does not determine whether the assignment is available at each use; that would require optimistic,
/// iterative data flow. The only data flow state is pessimistic reachability, which is implicit in the block worklist.
struct LocalVariableReachableAccess {
  let context: Context
  let accessMap: LocalVariableAccessMap
  let blockMap: LocalVariableAccessBlockMap

  init?(allocation: Value, _ context: Context) {
    guard let accessMap = LocalVariableAccessMap(allocation: allocation, context) else {
      return nil
    }
    self.context = context
    self.accessMap = accessMap
    self.blockMap = LocalVariableAccessBlockMap(accessMap: accessMap)
  }
}

// Find reaching assignments...
extension LocalVariableReachableAccess {
  // Gather all fully assigned accesses that reach `instruction`.
  func gatherReachingAssignments(for instruction: Instruction, in accessStack: inout Stack<LocalVariableAccess>)
    -> Bool {
    var blockList = BasicBlockWorklist(context)
    defer { blockList.deinitialize() }

    let initialEffect = backwardScanAccesses(before: instruction, accessStack: &accessStack)
    if !backwardPropagateEffect(in: instruction.parentBlock, effect: initialEffect, blockList: &blockList,
                                accessStack: &accessStack) {
      return false
    }
    while let block = blockList.pop() {
      let blockInfo = blockMap[block]
      var currentEffect = blockInfo?.effect
      // lattice: none -> read -> modify -> escape -> assign
      //
      // `blockInfo.effect` is the same as `currentEffect` returned by backwardScanAccesses, except when an early escape
      // happens after an assign.
      switch currentEffect {
      case .none, .read, .modify, .escape:
        break
      case .assign:
        currentEffect = backwardScanAccesses(before: block.instructions.reversed().first!, accessStack: &accessStack)
      }
      if !backwardPropagateEffect(in: block, effect: currentEffect, blockList: &blockList, accessStack: &accessStack) {
        return false
      }
    }
    // TODO: Verify that the accessStack.isEmpty condition never occurs.
    return !accessStack.isEmpty
  }

  private func backwardPropagateEffect(in block: BasicBlock, effect: BlockEffect?, blockList: inout BasicBlockWorklist,
                                       accessStack: inout Stack<LocalVariableAccess>)
    -> Bool {
    switch effect {
    case .none, .read, .modify:
      if block != accessMap.allocation.parentBlock {
        for predecessor in block.predecessors { blockList.pushIfNotVisited(predecessor) }
      } else if block == accessMap.function.entryBlock {
        accessStack.push(accessMap.liveInAccess!)
      }
    case .assign:
      break
    case .escape:
      return false
    }
    return true
  }

  // Check all instructions in this block before and including `first`. Return a BlockEffect indicating the combined
  // effects seen before stopping the scan. A .escape or .assign stops the scan.
  private func backwardScanAccesses(before first: Instruction, accessStack: inout Stack<LocalVariableAccess>)
    -> BlockEffect? {
    var currentEffect: BlockEffect?
    for inst in ReverseInstructionList(first: first) {
      guard let accessInfo = accessMap[inst] else {
        continue
      }
      currentEffect = BlockEffect(for: accessInfo, accessMap.context).meet(currentEffect)
      switch currentEffect! {
      case .read, .modify:
        continue
      case .assign:
        accessStack.push(accessInfo.access)
        break
      case .escape:
        break
      }
    }
    return currentEffect
  }
}

// Find reachable accesses...
extension LocalVariableReachableAccess {
  /// This performs a forward CFG walk to find known reachable uses from `assignment`. This ignores aliasing and
  /// escapes.
  func gatherKnownReachableUses(from assignment: LocalVariableAccess,
                                in accessStack: inout Stack<LocalVariableAccess>) {
    if let modifyInst = assignment.instruction {
      _ = gatherReachableUses(after: modifyInst, in: &accessStack, allowEscape: true)
    }
    gatherKnownReachableUsesFromEntry(in: &accessStack)
  }

  /// This performs a forward CFG walk to find known reachable uses from the function entry. This ignores aliasing and
  /// escapes.
  private func gatherKnownReachableUsesFromEntry(in accessStack: inout Stack<LocalVariableAccess>) {
    assert(accessMap.liveInAccess!.kind == .incomingArgument, "only an argument access is live in to the function")
    let firstInst = accessMap.function.entryBlock.instructions.first!
    _ = gatherReachableUses(onOrAfter: firstInst, in: &accessStack, allowEscape: true)
  }

  /// This performs a forward CFG walk to find all reachable uses of `modifyInst`. `modifyInst` may be a `begin_access
  /// [modify]` or instruction that initializes the local variable.
  ///
  /// Returns true if all possible reachable uses were visited. Returns false if any escapes may reach `modifyInst` are
  /// reachable from `modifyInst`.
  ///
  /// This does not gather the escaping accesses themselves. When escapes are reachable, it also does not guarantee that
  /// previously reachable accesses are gathered.
  ///
  /// This computes reachability separately for each store. If this store is a fully assigned access, then
  /// this never repeats work (it is a linear-time analysis over all assignments), because the walk always stops at the
  /// next fully-assigned access. Field assignment can result in an analysis that is quadratic in the number
  /// stores. Nonetheless, the analysis is highly efficient because it maintains no block state other than the
  /// block's intrusive bit set.
  func gatherAllReachableUses(of modifyInst: Instruction, in accessStack: inout Stack<LocalVariableAccess>) -> Bool {
    guard let accessInfo = accessMap[modifyInst] else {
      return false
    }
    if accessInfo.hasEscaped == nil {
      findAllEscapesPriorToAccess()
    }
    if accessInfo.hasEscaped! {
      return false
    }
    return gatherReachableUses(after: modifyInst, in: &accessStack, allowEscape: false)
  }

  /// This performs a forward CFG walk to find all uses of this local variable reachable after `begin`.
  ///
  /// If `allowEscape` is true, then this returns false if the walk ended early because of a reachable escape.
  private func gatherReachableUses(after begin: Instruction, in accessStack: inout Stack<LocalVariableAccess>,
                                   allowEscape: Bool) -> Bool {
    if let term = begin as? TermInst {
      for succ in term.successors {
        if !gatherReachableUses(onOrAfter: succ.instructions.first!, in: &accessStack, allowEscape: allowEscape) {
          return false
        }
      }
      return true
    } else {
      return gatherReachableUses(onOrAfter: begin.next!, in: &accessStack, allowEscape: allowEscape)
    }
  }

  /// This performs a forward CFG walk to find all uses of this local variable reachable after and including `begin`.
  ///
  /// If `allowEscape` is true, then this returns false if the walk ended early because of a reachable escape.
  private func gatherReachableUses(onOrAfter begin: Instruction, in accessStack: inout Stack<LocalVariableAccess>,
                                   allowEscape: Bool) -> Bool {
    var blockList = BasicBlockWorklist(context)
    defer { blockList.deinitialize() }

    let initialBlock = begin.parentBlock
    let initialEffect = forwardScanAccesses(after: begin, accessStack: &accessStack, allowEscape: allowEscape)
    if !allowEscape, initialEffect == .escape {
      return false
    }
    forwardPropagateEffect(in: initialBlock, blockInfo: blockMap[initialBlock], effect: initialEffect,
                           blockList: &blockList, accessStack: &accessStack)
    while let block = blockList.pop() {
      let blockInfo = blockMap[block]
      var currentEffect = blockInfo?.effect
      // lattice: none -> read -> modify -> escape -> assign
      //
      // `blockInfo.effect` is the same as `currentEffect` returned by forwardScanAccesses, except when an early
      // disallowed escape happens before an assign.
      switch currentEffect {
      case .none:
        break
      case .escape:
        if !allowEscape {
          break
        }
        fallthrough
      case .read, .modify, .assign:
        let firstInst = block.instructions.first!
        currentEffect = forwardScanAccesses(after: firstInst, accessStack: &accessStack, allowEscape: allowEscape)
      }
      if !allowEscape, currentEffect == .escape {
        return false
      }
      forwardPropagateEffect(in: block, blockInfo: blockInfo, effect: currentEffect, blockList: &blockList,
                             accessStack: &accessStack)
    }
    log("\(accessMap)")
    log("Reachable access:\n\(accessStack.map({ String(describing: $0)}).joined(separator: "\n"))")
    return true
  }

  typealias BlockEffect = LocalVariableAccessBlockMap.BlockEffect
  typealias BlockInfo = LocalVariableAccessBlockMap.BlockInfo

  private func forwardPropagateEffect(in block: BasicBlock, blockInfo: BlockInfo?, effect: BlockEffect?,
                                      blockList: inout BasicBlockWorklist,
                                      accessStack: inout Stack<LocalVariableAccess>) {
    switch effect {
    case .none, .read, .modify, .escape:
      if let blockInfo, blockInfo.hasDealloc {
        break
      }
      if block.terminator.isFunctionExiting {
        accessStack.push(LocalVariableAccess(.outgoingArgument, block.terminator))
      } else {
        for successor in block.successors { blockList.pushIfNotVisited(successor) }
      }
    case .assign:
      break
    }
  }

  // Check all instructions in this block after and including `begin`. Return a BlockEffect indicating the combined
  // effects seen before stopping the scan. An .assign stops the scan. A .escape stops the scan if allowEscape is false.
  private func forwardScanAccesses(after first: Instruction, accessStack: inout Stack<LocalVariableAccess>,
                                   allowEscape: Bool)
    -> BlockEffect? {
    var currentEffect: BlockEffect?
    for inst in InstructionList(first: first) {
      guard let accessInfo = accessMap[inst] else {
        continue
      }
      currentEffect = BlockEffect(for: accessInfo, accessMap.context).meet(currentEffect)
      switch currentEffect! {
      case .assign:
        return currentEffect
      case .escape:
        if !allowEscape {
          log("Local variable: \(accessMap.allocation)\n    escapes at \(inst)")
          return currentEffect
        }
        fallthrough
      case .read, .modify:
        accessStack.push(accessInfo.access)
      }
    }
    return currentEffect
  }
}

// Find prior escapes...
extension LocalVariableReachableAccess {
  /// For alloc_box only, find escapes (captures) of the box prior to each access.
  /// As a result, AccessInfo.hasEscaped will be non-nil for every access.
  ///
  /// This is an optimistic forward dataflow that propagates the escape bit to accesses.
  /// A block can be scanned at most twice. Once after it is marked visited to find any escapes within the block. The
  /// second time after it is marked escaped to propagate the hasEscaped bit to accesses within the block.
  private func findAllEscapesPriorToAccess() {
    var visitedBlocks = BasicBlockSet(context)
    var escapedBlocks = BasicBlockSet(context)
    var blockList = BasicBlockWorklist(context)
    defer {
      visitedBlocks.deinitialize()
      escapedBlocks.deinitialize()
      blockList.deinitialize()
    }
    let forwardPropagate = { (from: BasicBlock, hasEscaped: Bool) in
      if let blockInfo = blockMap[from], blockInfo.hasDealloc {
        return
      }
      for successor in from.successors {
        if hasEscaped {
          if escapedBlocks.insert(successor) {
            blockList.pushIfNotVisited(successor)
          }
        } else if visitedBlocks.insert(successor) {
          blockList.pushIfNotVisited(successor)
        }
      }
    }
    var hasEscaped = propagateEscapeInBlock(after: accessMap.allocation.nextInstruction, hasEscaped: false)
    forwardPropagate(accessMap.allocation.parentBlock, hasEscaped)
    while let block = blockList.pop() {
      hasEscaped = escapedBlocks.insert(block)
      hasEscaped = propagateEscapeInBlock(after: block.instructions.first!, hasEscaped: hasEscaped)
      forwardPropagate(accessMap.allocation.parentBlock, hasEscaped)
    }
  }

  private func propagateEscapeInBlock(after begin: Instruction, hasEscaped: Bool) -> Bool {
    var hasEscaped = hasEscaped
    for inst in InstructionList(first: begin) {
      guard let accessInfo = accessMap[inst] else {
        continue
      }
      if accessInfo.isEscape {
        hasEscaped = true
      } else {
        accessInfo.hasEscaped = hasEscaped
      }
    }
    return hasEscaped
  }
}