Skip to content

Commit eb91bb5

Browse files
authored
Merge pull request #80848 from atrick/read-accessor
LifetimeDependenceScopeFixup: _read accessor: extend temporaries
2 parents 76f8bf8 + 6f86052 commit eb91bb5

16 files changed

+1477
-408
lines changed

SwiftCompilerSources/Sources/Optimizer/DataStructures/InstructionRange.swift

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,122 @@ struct InstructionRange : CustomStringConvertible, NoReflectionChildren {
181181
blockRange.deinitialize()
182182
}
183183
}
184+
185+
extension InstructionRange {
186+
enum PathOverlap {
187+
// range: ---
188+
// | pathBegin
189+
// | |
190+
// | pathEnd
191+
// ---
192+
case containsPath
193+
194+
// range: ---
195+
// | pathBegin
196+
// --- |
197+
// pathEnd
198+
case containsBegin
199+
200+
// pathBegin
201+
// range: --- |
202+
// | pathEnd
203+
// ---
204+
case containsEnd
205+
206+
// pathBegin
207+
// range: --- |
208+
// | |
209+
// --- |
210+
// pathEnd
211+
case overlappedByPath
212+
213+
// either: pathBegin
214+
// |
215+
// pathEnd
216+
// range: ---
217+
// |
218+
// ---
219+
// or: pathBegin
220+
// |
221+
// pathEnd
222+
case disjoint
223+
}
224+
225+
/// Return true if any exclusive path from `begin` to `end` includes an instruction in this exclusive range.
226+
///
227+
/// Returns .containsBegin, if this range has the same begin and end as the path.
228+
///
229+
/// Precondition: `begin` dominates `end`.
230+
func overlaps(pathBegin: Instruction, pathEnd: Instruction, _ context: some Context) -> PathOverlap {
231+
assert(pathBegin != pathEnd, "expect an exclusive path")
232+
if contains(pathBegin) {
233+
// Note: pathEnd != self.begin here since self.contains(pathBegin)
234+
if contains(pathEnd) { return .containsPath }
235+
return .containsBegin
236+
}
237+
if contains(pathEnd) {
238+
if let rangeBegin = self.begin, rangeBegin == pathEnd {
239+
return .disjoint
240+
}
241+
return .containsEnd
242+
}
243+
// Neither end-point is contained. If a backward path walk encouters this range, then it must overlap this
244+
// range. Otherwise, it is disjoint.
245+
var backwardBlocks = BasicBlockWorklist(context)
246+
defer { backwardBlocks.deinitialize() }
247+
backwardBlocks.pushIfNotVisited(pathEnd.parentBlock)
248+
while let block = backwardBlocks.pop() {
249+
if blockRange.inclusiveRangeContains(block) {
250+
// This range overlaps with this block, but there are still three possibilities:
251+
// (1) range, pathBegin, pathEnd = disjoint (range might not begin in this block)
252+
// (2) pathBegin, pathEnd, range = disjoint (pathBegin might not be in this block)
253+
// (3) pathBegin, range, pathEnd = overlappedByPath (range or pathBegin might not be in this block)
254+
//
255+
// Walk backward from pathEnd to find either pathBegin or an instruction in this range.
256+
// Both this range and the path may or may not begin in this block.
257+
let endInBlock = block == pathEnd.parentBlock ? pathEnd : block.terminator
258+
for inst in ReverseInstructionList(first: endInBlock) {
259+
// Check pathBegin first because the range is exclusive.
260+
if inst == pathBegin {
261+
break
262+
}
263+
// Check inclusiveRangeContains() in case the range end is the first instruction in this block.
264+
if inclusiveRangeContains(inst) {
265+
return .overlappedByPath
266+
}
267+
}
268+
// No instructions in this range occur between pathBegin and pathEnd.
269+
return .disjoint
270+
}
271+
// No range blocks have been reached.
272+
if block == pathBegin.parentBlock {
273+
return .disjoint
274+
}
275+
backwardBlocks.pushIfNotVisited(contentsOf: block.predecessors)
276+
}
277+
fatalError("begin: \(pathBegin)\n must dominate end: \(pathEnd)")
278+
}
279+
}
280+
281+
let rangeOverlapsPathTest = FunctionTest("range_overlaps_path") {
282+
function, arguments, context in
283+
let rangeValue = arguments.takeValue()
284+
print("Range of: \(rangeValue)")
285+
var range = computeLinearLiveness(for: rangeValue, context)
286+
defer { range.deinitialize() }
287+
let pathInst = arguments.takeInstruction()
288+
print("Path begin: \(pathInst)")
289+
if let pathBegin = pathInst as? ScopedInstruction {
290+
for end in pathBegin.endInstructions {
291+
print("Overlap kind:", range.overlaps(pathBegin: pathInst, pathEnd: end, context))
292+
}
293+
return
294+
}
295+
if let pathValue = pathInst as? SingleValueInstruction, pathValue.ownership == .owned {
296+
for end in pathValue.uses.endingLifetime {
297+
print("Overlap kind:", range.overlaps(pathBegin: pathInst, pathEnd: end.instruction, context))
298+
}
299+
return
300+
}
301+
print("Test specification error: not a scoped or owned instruction: \(pathInst)")
302+
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ private struct LifetimeVariable {
292292
return .abortWalk
293293
}
294294
defer { useDefVisitor.deinitialize() }
295-
_ = useDefVisitor.walkUp(valueOrAddress: value)
295+
_ = useDefVisitor.walkUp(newLifetime: value)
296296
return introducer
297297
}
298298

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 23 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ func gatherVariableIntroducers(for value: Value, _ context: Context)
318318
return .continueWalk
319319
}
320320
defer { useDefVisitor.deinitialize() }
321-
_ = useDefVisitor.walkUp(valueOrAddress: value)
321+
_ = useDefVisitor.walkUp(newLifetime: value)
322322
assert(!introducers.isEmpty, "missing variable introducer")
323323
return introducers
324324
}
@@ -330,7 +330,7 @@ func gatherVariableIntroducers(for value: Value, _ context: Context)
330330
/// Walk up lifetime dependencies to the first value associated with a variable declaration.
331331
///
332332
/// To start walking:
333-
/// walkUp(valueOrAddress: Value) -> WalkResult
333+
/// walkUp(newLifetime: Value) -> WalkResult
334334
///
335335
/// This utility finds the value or address associated with the lvalue (variable declaration) that is passed as the
336336
/// source of a lifetime dependent argument. If no lvalue is found, then it finds the "root" of the chain of temporary
@@ -382,10 +382,7 @@ func gatherVariableIntroducers(for value: Value, _ context: Context)
382382
/// All of the dependent uses including `end_borrow %5` and `destroy_value %4` must be before the end of the dependence
383383
/// scope: `destroy_value %parent`. In this case, the dependence parent is an owned value, so the scope is simply the
384384
/// value's OSSA lifetime.
385-
struct VariableIntroducerUseDefWalker : ForwardingUseDefWalker {
386-
// The ForwardingUseDefWalker's context is the most recent lifetime owner.
387-
typealias PathContext = Value?
388-
385+
struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefValueWalker, LifetimeDependenceUseDefAddressWalker {
389386
let context: Context
390387

391388
// If the scoped value is trivial, then only the variable's lexical scope is relevant, and access scopes can be
@@ -412,128 +409,45 @@ struct VariableIntroducerUseDefWalker : ForwardingUseDefWalker {
412409
visitedValues.deinitialize()
413410
}
414411

415-
mutating func needWalk(for value: Value, _ owner: Value?) -> Bool {
416-
visitedValues.insert(value)
417-
}
418-
419412
mutating func introducer(_ value: Value, _ owner: Value?) -> WalkResult {
420413
return visitorClosure(value)
421414
}
422415

423-
mutating func walkUp(valueOrAddress: Value) -> WalkResult {
424-
if valueOrAddress.type.isAddress {
425-
return walkUp(address: valueOrAddress)
426-
}
427-
return walkUp(newLifetime: valueOrAddress)
416+
mutating func addressIntroducer(_ address: Value, access: AccessBaseAndScopes) -> WalkResult {
417+
return visitorClosure(address)
418+
}
419+
420+
mutating func needWalk(for value: Value, _ owner: Value?) -> Bool {
421+
visitedValues.insert(value)
428422
}
429-
}
430423

431-
// Helpers
432-
extension VariableIntroducerUseDefWalker {
433424
mutating func walkUp(newLifetime: Value) -> WalkResult {
425+
if newLifetime.type.isAddress {
426+
return walkUp(address: newLifetime)
427+
}
434428
let newOwner = newLifetime.ownership == .owned ? newLifetime : nil
435429
return walkUp(value: newLifetime, newOwner)
436430
}
437431

432+
/// Override to check for variable introducers: move_value, begin_value, before following
433+
/// OwnershipTransitionInstruction.
438434
mutating func walkUp(value: Value, _ owner: Value?) -> WalkResult {
439-
// Check for variable introducers: move_value, begin_value, before following OwnershipTransitionInstruction.
440435
if let inst = value.definingInstruction, VariableScopeInstruction(inst) != nil {
441436
return visitorClosure(value)
442437
}
443-
switch value.definingInstruction {
444-
case let transition as OwnershipTransitionInstruction:
445-
return walkUp(newLifetime: transition.operand.value)
446-
case let load as LoadInstruction:
447-
return walkUp(address: load.address)
448-
default:
449-
break
450-
}
451-
// If the dependence chain has a phi, consider it a root. Dependence roots dominate all dependent values.
452-
if Phi(value) != nil {
453-
return introducer(value, owner)
454-
}
455-
// ForwardingUseDefWalker will callback to introducer() when it finds no forwarding instruction.
456-
return walkUpDefault(forwarded: value, owner)
438+
return walkUpDefault(value: value, owner)
457439
}
458440

459-
// Handle temporary allocations and access scopes.
460-
mutating func walkUp(address: Value) -> WalkResult {
461-
let accessBaseAndScopes = address.accessBaseWithScopes
462-
// Continue walking for some kinds of access base.
463-
switch accessBaseAndScopes.base {
464-
case .box, .global, .class, .tail, .pointer, .index, .unidentified:
465-
break
466-
case let .stack(allocStack):
467-
if allocStack.varDecl == nil {
468-
// Ignore temporary stack locations. Their access scopes do not affect lifetime dependence.
469-
return walkUp(stackInitializer: allocStack, at: address)
470-
}
471-
case let .argument(arg):
472-
// Ignore access scopes for @in or @in_guaranteed arguments when all scopes are reads. Do not ignore a [read]
473-
// access of an inout argument or outer [modify]. Mutation later with the outer scope could invalidate the
474-
// borrowed state in this narrow scope. Do not ignore any mark_depedence on the address.
475-
if arg.convention.isIndirectIn && accessBaseAndScopes.isOnlyReadAccess {
476-
return introducer(arg, nil)
477-
}
478-
// @inout arguments may be singly initialized (when no modification exists in this function), but this is not
479-
// relevant here because they require nested access scopes which can never be ignored.
480-
case let .yield(yieldedAddress):
481-
// Ignore access scopes for @in or @in_guaranteed yields when all scopes are reads.
482-
let apply = yieldedAddress.definingInstruction as! FullApplySite
483-
if apply.convention(of: yieldedAddress).isIndirectIn && accessBaseAndScopes.isOnlyReadAccess {
484-
return introducer(yieldedAddress, nil)
485-
}
486-
case .storeBorrow(let sb):
487-
// Walk up through a store into a temporary.
488-
if accessBaseAndScopes.scopes.isEmpty,
489-
case .stack = sb.destinationOperand.value.accessBase {
490-
return walkUp(newLifetime: sb.source)
491-
}
492-
}
493-
// Skip the access scope for unsafe[Mutable]Address. Treat it like a projection of 'self' rather than a separate
494-
// variable access.
495-
if case let .access(innerAccess) = accessBaseAndScopes.scopes.first,
496-
let addressorSelf = innerAccess.unsafeAddressorSelf {
497-
return walkUp(valueOrAddress: addressorSelf)
498-
}
499-
// Ignore the acces scope for trivial values regardless of whether it is singly-initialized. Trivial values do not
500-
// need to be kept alive in memory and can be safely be overwritten in the same scope. Lifetime dependence only
501-
// cares that the loaded value is within the lexical scope of the trivial value's variable declaration. Rather than
502-
// skipping all access scopes, call 'walkUp' on each nested access in case one of them needs to redirect the walk,
503-
// as required for 'access.unsafeAddressorSelf'.
504-
if isTrivialScope {
505-
switch accessBaseAndScopes.scopes.first {
506-
case .none, .base:
507-
break
508-
case let .access(beginAccess):
509-
return walkUp(address: beginAccess.address)
510-
case let .dependence(markDep):
511-
return walkUp(address: markDep.value)
512-
}
513-
}
514-
return introducer(accessBaseAndScopes.enclosingAccess.address ?? address, nil)
515-
}
516-
517-
// Handle singly-initialized temporary stack locations.
518-
mutating func walkUp(stackInitializer allocStack: AllocStackInst, at address: Value) -> WalkResult {
519-
guard let initializer = allocStack.accessBase.findSingleInitializer(context) else {
520-
return introducer(address, nil)
521-
}
522-
if case let .store(store, _) = initializer {
523-
switch store {
524-
case let store as StoringInstruction:
525-
return walkUp(newLifetime: store.source)
526-
case let srcDestInst as SourceDestAddrInstruction:
527-
return walkUp(address: srcDestInst.destination)
528-
case let apply as FullApplySite:
529-
if let f = apply.referencedFunction, f.isConvertPointerToPointerArgument {
530-
return walkUp(address: apply.parameterOperands[0].value)
531-
}
532-
default:
533-
break
441+
/// Override to check for on-stack variables before following an initializer.
442+
mutating func walkUp(address: Value, access: AccessBaseAndScopes) -> WalkResult {
443+
// Check for stack locations that correspond to an lvalue.
444+
if case let .stack(allocStack) = access.base {
445+
if allocStack.varDecl != nil {
446+
// Report this variable's innermmost access scope.
447+
return addressIntroducer(access.enclosingAccess.address ?? address, access: access)
534448
}
535449
}
536-
return introducer(address, nil)
450+
return walkUpDefault(address: address, access: access)
537451
}
538452
}
539453

0 commit comments

Comments
 (0)