Add InstructionRange.overlapsWithPath() utility.

This commit is contained in:
Andrew Trick
2025-04-15 10:04:05 -07:00
parent 2bdaa68e98
commit 28daec01a7
3 changed files with 543 additions and 3 deletions

View File

@@ -181,3 +181,122 @@ struct InstructionRange : CustomStringConvertible, NoReflectionChildren {
blockRange.deinitialize()
}
}
extension InstructionRange {
enum PathOverlap {
// range: ---
// | pathBegin
// | |
// | pathEnd
// ---
case containsPath
// range: ---
// | pathBegin
// --- |
// pathEnd
case containsBegin
// pathBegin
// range: --- |
// | pathEnd
// ---
case containsEnd
// pathBegin
// range: --- |
// | |
// --- |
// pathEnd
case overlappedByPath
// either: pathBegin
// |
// pathEnd
// range: ---
// |
// ---
// or: pathBegin
// |
// pathEnd
case disjoint
}
/// Return true if any exclusive path from `begin` to `end` includes an instruction in this exclusive range.
///
/// Returns .containsBegin, if this range has the same begin and end as the path.
///
/// Precondition: `begin` dominates `end`.
func overlaps(pathBegin: Instruction, pathEnd: Instruction, _ context: some Context) -> PathOverlap {
assert(pathBegin != pathEnd, "expect an exclusive path")
if contains(pathBegin) {
// Note: pathEnd != self.begin here since self.contains(pathBegin)
if contains(pathEnd) { return .containsPath }
return .containsBegin
}
if contains(pathEnd) {
if let rangeBegin = self.begin, rangeBegin == pathEnd {
return .disjoint
}
return .containsEnd
}
// Neither end-point is contained. If a backward path walk encouters this range, then it must overlap this
// range. Otherwise, it is disjoint.
var backwardBlocks = BasicBlockWorklist(context)
defer { backwardBlocks.deinitialize() }
backwardBlocks.pushIfNotVisited(pathEnd.parentBlock)
while let block = backwardBlocks.pop() {
if blockRange.inclusiveRangeContains(block) {
// This range overlaps with this block, but there are still three possibilities:
// (1) range, pathBegin, pathEnd = disjoint (range might not begin in this block)
// (2) pathBegin, pathEnd, range = disjoint (pathBegin might not be in this block)
// (3) pathBegin, range, pathEnd = overlappedByPath (range or pathBegin might not be in this block)
//
// Walk backward from pathEnd to find either pathBegin or an instruction in this range.
// Both this range and the path may or may not begin in this block.
let endInBlock = block == pathEnd.parentBlock ? pathEnd : block.terminator
for inst in ReverseInstructionList(first: endInBlock) {
// Check pathBegin first because the range is exclusive.
if inst == pathBegin {
break
}
// Check inclusiveRangeContains() in case the range end is the first instruction in this block.
if inclusiveRangeContains(inst) {
return .overlappedByPath
}
}
// No instructions in this range occur between pathBegin and pathEnd.
return .disjoint
}
// No range blocks have been reached.
if block == pathBegin.parentBlock {
return .disjoint
}
backwardBlocks.pushIfNotVisited(contentsOf: block.predecessors)
}
fatalError("begin: \(pathBegin)\n must dominate end: \(pathEnd)")
}
}
let rangeOverlapsPathTest = FunctionTest("range_overlaps_path") {
function, arguments, context in
let rangeValue = arguments.takeValue()
print("Range of: \(rangeValue)")
var range = computeLinearLiveness(for: rangeValue, context)
defer { range.deinitialize() }
let pathInst = arguments.takeInstruction()
print("Path begin: \(pathInst)")
if let pathBegin = pathInst as? ScopedInstruction {
for end in pathBegin.endInstructions {
print("Overlap kind:", range.overlaps(pathBegin: pathInst, pathEnd: end, context))
}
return
}
if let pathValue = pathInst as? SingleValueInstruction, pathValue.ownership == .owned {
for end in pathValue.uses.endingLifetime {
print("Overlap kind:", range.overlaps(pathBegin: pathInst, pathEnd: end.instruction, context))
}
return
}
print("Test specification error: not a scoped or owned instruction: \(pathInst)")
}

View File

@@ -160,6 +160,7 @@ public func registerOptimizerTests() {
enclosingValuesTest,
forwardingDefUseTest,
forwardingUseDefTest,
gatherCallSitesTest,
interiorLivenessTest,
lifetimeDependenceScopeTest,
lifetimeDependenceUseTest,
@@ -167,10 +168,10 @@ public func registerOptimizerTests() {
localVariableReachableUsesTest,
localVariableReachingAssignmentsTest,
parseTestSpecificationTest,
variableIntroducerTest,
gatherCallSitesTest,
rangeOverlapsPathTest,
rewrittenCallerBodyTest,
specializedFunctionSignatureAndBodyTest,
rewrittenCallerBodyTest
variableIntroducerTest
)
// Finally register the thunk they all call through.

View File

@@ -0,0 +1,420 @@
// RUN: %target-sil-opt -test-runner %s -o /dev/null 2>&1 | %FileCheck %s
// REQUIRES: swift_in_compiler
sil_stage canonical
class C {}
// CHECK-LABEL: testIdentity: range_overlaps_path
// CHECK: Overlap kind: containsBegin
sil [ossa] @testIdentity : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %range"
%range = begin_borrow %0
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsPath1: range_overlaps_path
// CHECK: Overlap kind: containsPath
sil [ossa] @testContainsPath1 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
%path = begin_borrow %0
end_borrow %path
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsPath2: range_overlaps_path
// CHECK: Overlap kind: containsPath
sil [ossa] @testContainsPath2 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
br bb1
bb1:
%path = begin_borrow %0
end_borrow %path
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsPath3: range_overlaps_path
// CHECK: Overlap kind: containsPath
sil [ossa] @testContainsPath3 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
br bb1
bb1:
%path = begin_borrow %0
br bb2
bb2:
end_borrow %path
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsPath4: range_overlaps_path
// CHECK: Overlap kind: containsPath
sil [ossa] @testContainsPath4 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
br bb1
bb1:
%path = begin_borrow %0
br bb2
bb2:
end_borrow %path
br bb3
bb3:
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsBegin1: range_overlaps_path
// CHECK: Overlap kind: containsBegin
sil [ossa] @testContainsBegin1 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
%path = begin_borrow %0
end_borrow %range
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsBegin2: range_overlaps_path
// CHECK: Overlap kind: containsBegin
sil [ossa] @testContainsBegin2 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
br bb1
bb1:
%path = begin_borrow %0
end_borrow %range
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsBegin3: range_overlaps_path
// CHECK: Overlap kind: containsBegin
sil [ossa] @testContainsBegin3 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
br bb1
bb1:
%path = begin_borrow %0
br bb2
bb2:
end_borrow %range
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsBegin4: range_overlaps_path
// CHECK: Overlap kind: containsBegin
sil [ossa] @testContainsBegin4 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
br bb1
bb1:
%path = begin_borrow %0
br bb2
bb2:
end_borrow %range
br bb3
bb3:
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsEnd1: range_overlaps_path
// CHECK: Overlap kind: containsEnd
sil [ossa] @testContainsEnd1 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
%range = begin_borrow %0
end_borrow %path
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsEnd2: range_overlaps_path
// CHECK: Overlap kind: containsEnd
sil [ossa] @testContainsEnd2 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
br bb1
bb1:
%range = begin_borrow %0
end_borrow %path
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsEnd3: range_overlaps_path
// CHECK: Overlap kind: containsEnd
sil [ossa] @testContainsEnd3 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
br bb1
bb1:
%range = begin_borrow %0
br bb2
bb2:
end_borrow %path
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testContainsEnd4: range_overlaps_path
// CHECK: Overlap kind: containsEnd
sil [ossa] @testContainsEnd4 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
br bb1
bb1:
%range = begin_borrow %0
br bb2
bb2:
end_borrow %path
br bb3
bb3:
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testOverlappedByPath1: range_overlaps_path
// CHECK: Overlap kind: overlappedByPath
sil [ossa] @testOverlappedByPath1 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
%range = begin_borrow %0
end_borrow %range
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testOverlappedByPath2: range_overlaps_path
// CHECK: Overlap kind: overlappedByPath
sil [ossa] @testOverlappedByPath2 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
br bb1
bb1:
%range = begin_borrow %0
end_borrow %range
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testOverlappedByPath3: range_overlaps_path
// CHECK: Overlap kind: overlappedByPath
sil [ossa] @testOverlappedByPath3 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
br bb1
bb1:
%range = begin_borrow %0
br bb2
bb2:
end_borrow %range
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testOverlappedByPath4: range_overlaps_path
// CHECK: Overlap kind: overlappedByPath
sil [ossa] @testOverlappedByPath4 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
br bb1
bb1:
%range = begin_borrow %0
br bb2
bb2:
end_borrow %range
br bb3
bb3:
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testOverlappedByPath5: range_overlaps_path
// CHECK: Overlap kind: overlappedByPath
sil [ossa] @testOverlappedByPath5 : $@convention(thin) (@owned C, @owned C) -> () {
entry(%0 : @owned $C, %1 : @owned $C):
specify_test "range_overlaps_path %range %path"
%path = move_value %0
%range = move_value %1
%tuple = tuple (%path, %range)
destroy_value %tuple
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testDisjoint1: range_overlaps_path
// CHECK: Overlap kind: disjoint
sil [ossa] @testDisjoint1 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
end_borrow %path
%range = begin_borrow %0
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testDisjoint2: range_overlaps_path
// CHECK: Overlap kind: disjoint
sil [ossa] @testDisjoint2 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
end_borrow %range
%path = begin_borrow %0
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testDisjoint3: range_overlaps_path
// CHECK: Overlap kind: disjoint
sil [ossa] @testDisjoint3 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
end_borrow %range
%path = begin_borrow %0
br bb1
bb1:
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testDisjoint4: range_overlaps_path
// CHECK: Overlap kind: disjoint
sil [ossa] @testDisjoint4 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
br bb1
bb1:
end_borrow %path
%range = begin_borrow %0
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testDisjoint5: range_overlaps_path
// CHECK: Overlap kind: disjoint
sil [ossa] @testDisjoint5 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%range = begin_borrow %0
br bb1
bb1:
end_borrow %range
%path = begin_borrow %0
br bb2
bb2:
end_borrow %path
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testDisjoint6: range_overlaps_path
// CHECK: Overlap kind: disjoint
sil [ossa] @testDisjoint6 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
br bb1
bb1:
end_borrow %path
%range = begin_borrow %0
br bb2
bb2:
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testDisjoint7: range_overlaps_path
// CHECK: Overlap kind: disjoint
sil [ossa] @testDisjoint7 : $@convention(thin) (@guaranteed C) -> () {
entry(%0 : @guaranteed $C):
specify_test "range_overlaps_path %range %path"
%path = begin_borrow %0
br bb1
bb1:
end_borrow %path
br bb2
bb2:
%range = begin_borrow %0
br bb3
bb3:
end_borrow %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testDisjoint8: range_overlaps_path
// CHECK: Overlap kind: disjoint
sil [ossa] @testDisjoint8 : $@convention(thin) (@owned C) -> () {
entry(%0 : @owned $C):
specify_test "range_overlaps_path %range %path"
%path = move_value %0
%range = move_value %path
destroy_value %range
%retval = tuple ()
return %retval : $()
}
// CHECK-LABEL: testDisjoint9: range_overlaps_path
// CHECK: Overlap kind: disjoint
sil [ossa] @testDisjoint9 : $@convention(thin) (@owned C) -> () {
entry(%0 : @owned $C):
specify_test "range_overlaps_path %range %path"
%range = move_value %0
%path = move_value %range
destroy_value %path
%retval = tuple ()
return %retval : $()
}