Skip to content

Commit 75f2f88

Browse files
committed
Add AddressOwnershipLiveRange
A live range representing the ownership of addressible memory. This live range represents the minimal guaranteed lifetime of the object being addressed. Uses of derived addresses may be extended up to the ends of this scope without violating ownership. .liveOut objects (@in_guaranteed, @out and globals) have no instruction range. .local objects (alloc_stack, yield, @in, @inout) report the single live range of the full assignment that reaches this address. .owned values (boxes and references) simply report OSSA liveness. .borrow values report each borrow scope's range. The effective live range is their intersection. A valid use must lie within
1 parent e84bcf5 commit 75f2f88

File tree

1 file changed

+212
-0
lines changed

1 file changed

+212
-0
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212

1313
import SIL
1414

15+
private let verbose = true
16+
17+
private func log(_ message: @autoclosure () -> String) {
18+
if verbose {
19+
print("### \(message())")
20+
}
21+
}
22+
1523
/// Classify address uses. This can be used by def-use walkers to
1624
/// ensure complete handling of all legal SIL patterns.
1725
///
@@ -340,3 +348,207 @@ extension AddressInitializationWalker {
340348
return .abortWalk
341349
}
342350
}
351+
352+
/// A live range representing the ownership of addressible memory.
353+
///
354+
/// This live range represents the minimal guaranteed lifetime of the object being addressed. Uses of derived addresses
355+
/// may be extended up to the ends of this scope without violating ownership.
356+
///
357+
/// .liveOut objects (@in_guaranteed, @out) and .global variables have no instruction range.
358+
///
359+
/// .local objects (alloc_stack, yield, @in, @inout) report the single live range of the full assignment that reaches
360+
/// this address.
361+
///
362+
/// .owned values (boxes and references) simply report OSSA liveness.
363+
///
364+
/// .borrow values report each borrow scope's range. The effective live range is their intersection. A valid use must
365+
/// lie within all ranges.
366+
///
367+
/// FIXME: .borrow values should be represented with a single multiply-defined instruction range. Otherwise we will run
368+
/// out of blockset bits as soon as we have multiple ranges (each range uses three sets). It is ok to take the
369+
/// union of the borrow ranges since all address uses that may be extended will be already be dominated by the current
370+
/// address. Alternatively, we can have a utility that folds two InstructionRanges together as an intersection, and
371+
/// repeatedly fold the range of each borrow introducer.
372+
///
373+
/// Example:
374+
///
375+
/// %x = alloc_box
376+
/// %b = begin_borrow %x -+ begin ownership range
377+
/// %p = project_box %b | <--- accessBase
378+
/// %a = begin_access %p |
379+
/// end_access %a | <--- address use
380+
/// end_borrow %b -+ end ownership range
381+
/// destroy_value %x
382+
///
383+
/// This may return multiple ranges if a borrowed reference has multiple introducers:
384+
///
385+
/// %b1 = begin_borrow -+ range1
386+
/// %b2 = begin_borrow | -+ -+ range2
387+
/// %s = struct (%b1, %2) | | |
388+
/// %e = struct_extract %s, #s.0 | | | intersection
389+
/// %d = ref_element_addr %e | | | where ownership
390+
/// %a = begin_access %d | | | is valid
391+
/// end_access %a | | |
392+
/// end_borrow %b1 -+ | -+
393+
/// ... |
394+
/// end_borrow %b2 -+
395+
///
396+
/// Note: The resulting live range must be deinitialized in stack order.
397+
enum AddressOwnershipLiveRange : CustomStringConvertible {
398+
case liveOut(FunctionArgument)
399+
case global(GlobalVariable)
400+
case local(Value, InstructionRange) // Value represents the local allocation
401+
case owned(Value, InstructionRange)
402+
case borrow(SingleInlineArray<(BeginBorrowValue, InstructionRange)>)
403+
404+
mutating func deinitialize() {
405+
switch self {
406+
case .liveOut, .global:
407+
break
408+
case var .local(_, range):
409+
range.deinitialize()
410+
case var .owned(_, range):
411+
range.deinitialize()
412+
case var .borrow(ranges):
413+
for idx in ranges.indices {
414+
ranges[idx].1.deinitialize()
415+
}
416+
}
417+
}
418+
419+
/// Return nil if the live range is unknown.
420+
static func compute(for address: Value, at begin: Instruction,
421+
_ localReachabilityCache: LocalVariableReachabilityCache,
422+
_ context: FunctionPassContext) -> AddressOwnershipLiveRange? {
423+
let accessBase = address.accessBase
424+
switch accessBase {
425+
case .box, .class, .tail:
426+
return computeValueLiveRange(of: accessBase.reference!, context)
427+
case let .stack(allocStack):
428+
return computeLocalLiveRange(allocation: allocStack, begin: begin, localReachabilityCache, context)
429+
case let .global(global):
430+
return .global(global)
431+
case let .argument(arg):
432+
switch arg.convention {
433+
case .indirectInGuaranteed, .indirectOut:
434+
return .liveOut(arg)
435+
case .indirectIn, .indirectInout, .indirectInoutAliasable:
436+
return computeLocalLiveRange(allocation: arg, begin: begin, localReachabilityCache, context)
437+
default:
438+
return nil
439+
}
440+
case let .yield(result):
441+
let apply = result.parentInstruction as! BeginApplyInst
442+
switch apply.convention(of: result) {
443+
case .indirectInGuaranteed:
444+
var range = InstructionRange(for: address, context)
445+
_ = BorrowingInstruction(apply)!.visitScopeEndingOperands(context) {
446+
range.insert($0.instruction)
447+
return .continueWalk
448+
}
449+
return .local(result, range)
450+
case .indirectIn, .indirectInout, .indirectInoutAliasable:
451+
return computeLocalLiveRange(allocation: result, begin: begin, localReachabilityCache, context)
452+
default:
453+
return nil
454+
}
455+
case .pointer, .unidentified:
456+
return nil
457+
}
458+
}
459+
460+
/// Does this inclusive range include `inst`, assuming that `inst` occurs after a definition of the live range.
461+
func coversUse(_ inst: Instruction) -> Bool {
462+
switch self {
463+
case .liveOut, .global:
464+
return true
465+
case let .local(_, range), let .owned(_, range):
466+
return range.inclusiveRangeContains(inst)
467+
case let .borrow(borrowRanges):
468+
for (_, range) in borrowRanges {
469+
if !range.inclusiveRangeContains(inst) {
470+
return false
471+
}
472+
}
473+
return true
474+
}
475+
}
476+
477+
var description: String {
478+
switch self {
479+
case let .liveOut(arg):
480+
return "liveOut: \(arg)"
481+
case let .global(global):
482+
return "global: \(global)"
483+
case let .local(allocation, range):
484+
return "local: \(allocation)\n\(range)"
485+
case let .owned(value, range):
486+
return "owned: \(value)\n\(range)"
487+
case let .borrow(borrowRanges):
488+
var str = ""
489+
for (borrow, range) in borrowRanges {
490+
str += "borrow: \(borrow)\n\(range)"
491+
}
492+
return str
493+
}
494+
}
495+
}
496+
497+
extension AddressOwnershipLiveRange {
498+
/// Compute the ownership live range of any non-address value.
499+
///
500+
/// For an owned value, simply compute its liveness. For a guaranteed value, return a separate range for each borrow
501+
/// introducer.
502+
///
503+
/// For address values, use AccessBase.computeOwnershipRange.
504+
///
505+
/// FIXME: This should use computeLinearLiveness rather than computeInteriorLiveness as soon as lifetime completion
506+
/// runs immediately after SILGen.
507+
private static func computeValueLiveRange(of value: Value, _ context: FunctionPassContext)
508+
-> AddressOwnershipLiveRange? {
509+
switch value.ownership {
510+
case .none, .unowned:
511+
// This is unexpected for a value with derived addresses.
512+
return nil
513+
case .owned:
514+
return .owned(value, computeInteriorLiveness(for: value, context))
515+
case .guaranteed:
516+
return .borrow(computeBorrowLiveRange(for: value, context))
517+
}
518+
}
519+
520+
private static func computeLocalLiveRange(allocation: Value, begin: Instruction,
521+
_ localReachabilityCache: LocalVariableReachabilityCache,
522+
_ context: some Context) -> AddressOwnershipLiveRange? {
523+
guard let localReachability = localReachabilityCache.reachability(for: allocation, context) else {
524+
return nil
525+
}
526+
var reachingAssignments = Stack<LocalVariableAccess>(context)
527+
defer { reachingAssignments.deinitialize() }
528+
529+
if !localReachability.gatherReachingAssignments(for: begin, in: &reachingAssignments) {
530+
return nil
531+
}
532+
// Any one of the reaching assignment is sufficient to compute the minimal live range. The caller presumably only
533+
// cares about the live range that is dominated by 'begin'. Since all assignments gathered above reach
534+
// 'begin', their live ranges must all be identical on all paths through 'addressInst'.
535+
let assignment = reachingAssignments.first!
536+
537+
var reachableUses = Stack<LocalVariableAccess>(context)
538+
defer { reachableUses.deinitialize() }
539+
540+
localReachability.gatherKnownReachableUses(from: assignment, in: &reachableUses)
541+
542+
let assignmentInst = assignment.instruction ?? allocation.parentFunction.entryBlock.instructions.first!
543+
var range = InstructionRange(begin: assignmentInst, context)
544+
for localAccess in reachableUses {
545+
if localAccess.kind == .escape {
546+
log("Local variable: \(allocation)\n escapes at \(localAccess.instruction!)")
547+
}
548+
for end in localAccess.instruction!.endInstructions {
549+
range.insert(end)
550+
}
551+
}
552+
return .local(allocation, range)
553+
}
554+
}

0 commit comments

Comments
 (0)