Skip to content

LocalVariableUtils: fix data flow propagation of escapes. #80754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import SIL

private let verbose = false

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

Expand Down Expand Up @@ -226,7 +226,7 @@ class LocalVariableAccessInfo: CustomStringConvertible {
}

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

Expand Down Expand Up @@ -329,7 +329,7 @@ struct LocalVariableAccessMap: Collection, CustomStringConvertible {
subscript(instruction: Instruction) -> LocalVariableAccessInfo? { accessMap[instruction] }

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

Expand Down Expand Up @@ -699,6 +699,7 @@ extension LocalVariableReachableAccess {
case .escape:
break
}
break
}
return currentEffect
}
Expand Down Expand Up @@ -808,8 +809,8 @@ extension LocalVariableReachableAccess {
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"))")
log("\n\(accessMap)")
log(prefix: false, "Reachable access:\n\(accessStack.map({ String(describing: $0)}).joined(separator: "\n"))")
return true
}

Expand Down Expand Up @@ -873,7 +874,7 @@ extension LocalVariableReachableAccess {
private func findAllEscapesPriorToAccess() {
var visitedBlocks = BasicBlockSet(context)
var escapedBlocks = BasicBlockSet(context)
var blockList = BasicBlockWorklist(context)
var blockList = Stack<BasicBlock>(context)
defer {
visitedBlocks.deinitialize()
escapedBlocks.deinitialize()
Expand All @@ -886,19 +887,19 @@ extension LocalVariableReachableAccess {
for successor in from.successors {
if hasEscaped {
if escapedBlocks.insert(successor) {
blockList.pushIfNotVisited(successor)
blockList.push(successor)
}
} else if visitedBlocks.insert(successor) {
blockList.pushIfNotVisited(successor)
blockList.push(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 = escapedBlocks.contains(block)
hasEscaped = propagateEscapeInBlock(after: block.instructions.first!, hasEscaped: hasEscaped)
forwardPropagate(accessMap.allocation.parentBlock, hasEscaped)
forwardPropagate(block, hasEscaped)
}
}

Expand All @@ -917,3 +918,49 @@ extension LocalVariableReachableAccess {
return hasEscaped
}
}

let localVariableReachingAssignmentsTest = FunctionTest("local_variable_reaching_assignments") {
function, arguments, context in
let allocation = arguments.takeValue()
let instruction = arguments.takeInstruction()
print("### Allocation: \(allocation)")
let localReachabilityCache = LocalVariableReachabilityCache()
guard let localReachability = localReachabilityCache.reachability(for: allocation, context) else {
print("No reachability")
return
}
print("### Access map:")
print(localReachability.accessMap)
print("### Instruction: \(instruction)")
var reachingAssignments = Stack<LocalVariableAccess>(context)
defer { reachingAssignments.deinitialize() }
guard localReachability.gatherReachingAssignments(for: instruction, in: &reachingAssignments) else {
print("!!! Reaching escape")
return
}
print("### Reachable assignments:")
print(reachingAssignments.map({ String(describing: $0)}).joined(separator: "\n"))
}

let localVariableReachableUsesTest = FunctionTest("local_variable_reachable_uses") {
function, arguments, context in
let allocation = arguments.takeValue()
let modify = arguments.takeInstruction()
print("### Allocation: \(allocation)")
let localReachabilityCache = LocalVariableReachabilityCache()
guard let localReachability = localReachabilityCache.reachability(for: allocation, context) else {
print("No reachability")
return
}
print("### Access map:")
print(localReachability.accessMap)
print("### Modify: \(modify)")
var reachableUses = Stack<LocalVariableAccess>(context)
defer { reachableUses.deinitialize() }
guard localReachability.gatherAllReachableUses(of: modify, in: &reachableUses) else {
print("!!! Reachable escape")
return
}
print("### Reachable access:")
print(reachableUses.map({ String(describing: $0)}).joined(separator: "\n"))
}
2 changes: 2 additions & 0 deletions SwiftCompilerSources/Sources/Optimizer/Utilities/Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ public func registerOptimizerTests() {
lifetimeDependenceScopeTest,
lifetimeDependenceUseTest,
linearLivenessTest,
localVariableReachableUsesTest,
localVariableReachingAssignmentsTest,
parseTestSpecificationTest,
variableIntroducerTest,
gatherCallSitesTest,
Expand Down
134 changes: 134 additions & 0 deletions test/SILOptimizer/lifetime_dependence/local_var_util.sil
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// RUN: %target-sil-opt -test-runner %s \
// RUN: -enable-experimental-feature LifetimeDependence \
// RUN: -o /dev/null 2>&1 | %FileCheck %s

// REQUIRES: swift_in_compiler
// REQUIRES: swift_feature_LifetimeDependence

sil_stage raw

import Builtin
import Swift
import SwiftShims

struct NE: ~Escapable {}

sil @makeDepNE : $@convention(thin) (@inout NE) -> @lifetime(borrow address_for_deps 0) @out NE

// CHECK-LABEL: testNEInitNoEscape: local_variable_reachable_uses with: %1, @instruction
// CHECK: ### Access map:
// CHECK-NEXT: Access map for: %{{.*}} = alloc_box ${ var NE }, var, name "self"
// CHECK-NEXT: assign: true, store destroy_value %{{.*}} : ${ var NE }
// CHECK-NEXT: assign: false, escape %{{.*}} = address_to_pointer %{{.*}} : $*NE to $Builtin.RawPointer
// CHECK-NEXT: assign: true, beginAccess %{{.*}} = begin_access [modify] [unknown] %{{.*}} : $*NE
// CHECK-NEXT: assign: false, load %{{.*}} = load [copy] %{{.*}} : $*NE
// CHECK-NEXT: assign: true, beginAccess %{{.*}} = begin_access [modify] [unknown] %{{.*}} : $*NE
// CHECK-NEXT: ### Modify: %{{.*}} = begin_access [modify] [unknown] %4 : $*NE
// CHECK-NEXT: ### Reachable access:
// CHECK-NEXT: load %{{.*}} = load [copy] %{{.*}} : $*NE
// CHECK-NEXT: testNEInitNoEscape: local_variable_reachable_uses with: %1, @instruction

// CHECK-LABEL: testNEInitNoEscape: local_variable_reaching_assignments with: %1, @instruction
// CHECK: ### Instruction: end_access %{{.*}} : $*NE
// CHECK-NEXT: ### Reachable assignments:
// CHECK-NEXT: beginAccess %21 = begin_access [modify] [unknown] %4 : $*NE
// CHECK-NEXT: testNEInitNoEscape: local_variable_reaching_assignments with: %1, @instruction
sil [ossa] @testNEInitNoEscape : $@convention(thin) (@inout NE) -> @lifetime(borrow 0) @owned NE {
bb0(%0 : $*NE):
%1 = alloc_box ${ var NE }, var, name "self"
%2 = mark_uninitialized [rootself] %1
%3 = begin_borrow [lexical] [var_decl] %2
%4 = project_box %3, 0
cond_br undef, bb1, bb2

bb1:
br bb3

bb2:
br bb3

bb3:
%8 = alloc_stack $NE

%9 = begin_access [modify] [unknown] %0

%10 = function_ref @makeDepNE : $@convention(thin) (@inout NE) -> @lifetime(borrow address_for_deps 0) @out NE
%11 = apply %10(%8, %9) : $@convention(thin) (@inout NE) -> @lifetime(borrow address_for_deps 0) @out NE

mark_dependence_addr [unresolved] %8 on %9
end_access %9

%14 = load [take] %8

specify_test "local_variable_reachable_uses %1 @instruction"
%15 = begin_access [modify] [unknown] %4
assign %14 to %15
end_access %15
dealloc_stack %8
%19 = load [copy] %4

%21 = load [take] %8
%22 = begin_access [modify] [unknown] %4
assign %21 to %22
specify_test "local_variable_reaching_assignments %1 @instruction"
end_access %22
%escape = address_to_pointer %4 : $*NE to $Builtin.RawPointer
end_borrow %3
destroy_value %2
return %19
}

// CHECK-LABEL: testNEInitEscape: local_variable_reaching_assignments with: %1, @instruction
// CHECK: ### Instruction: %{{.*}} = load [take] %{{.*}} : $*NE
// CHECK-NEXT: !!! Reaching escape
// CHECK-NEXT: testNEInitEscape: local_variable_reaching_assignments with: %1, @instruction

// CHECK-LABEL: testNEInitEscape: local_variable_reachable_uses with: %1, @instruction
// CHECK: ### Access map:
// CHECK-NEXT: Access map for: %{{.*}} = alloc_box ${ var NE }, var, name "self"
// CHECK-NEXT: assign: true, store destroy_value %{{.*}} : ${ var NE }
// CHECK-NEXT: assign: false, load %{{.*}} = load [copy] %{{.*}} : $*NE
// CHECK-NEXT: assign: true, beginAccess %{{.*}} = begin_access [modify] [unknown] %{{.*}} : $*NE
// CHECK-NEXT: assign: false, escape %6 = address_to_pointer %4 : $*NE to $Builtin.RawPointer
// CHECK-NEXT: ### Modify: %{{.*}} = begin_access [modify] [unknown] %4 : $*NE
// CHECK-NEXT: !!! Reachable escape
// CHECK-NEXT: testNEInitEscape: local_variable_reachable_uses with: %1, @instruction
sil [ossa] @testNEInitEscape : $@convention(thin) (@inout NE) -> @lifetime(borrow 0) @owned NE {
bb0(%0 : $*NE):
%1 = alloc_box ${ var NE }, var, name "self"
%2 = mark_uninitialized [rootself] %1
%3 = begin_borrow [lexical] [var_decl] %2
%4 = project_box %3, 0
cond_br undef, bb1, bb2

bb1:
%escape = address_to_pointer %4 : $*NE to $Builtin.RawPointer
br bb3

bb2:
br bb3

bb3:
%8 = alloc_stack $NE

%9 = begin_access [modify] [unknown] %0

%10 = function_ref @makeDepNE : $@convention(thin) (@inout NE) -> @lifetime(borrow address_for_deps 0) @out NE
%11 = apply %10(%8, %9) : $@convention(thin) (@inout NE) -> @lifetime(borrow address_for_deps 0) @out NE

mark_dependence_addr [unresolved] %8 on %9
end_access %9

specify_test "local_variable_reaching_assignments %1 @instruction"
%14 = load [take] %8

specify_test "local_variable_reachable_uses %1 @instruction"
%15 = begin_access [modify] [unknown] %4
assign %14 to %15
end_access %15
dealloc_stack %8
%19 = load [copy] %4
end_borrow %3
destroy_value %2
return %19
}