Swift Optimizer: improve ergonomics of EscapeUtils

Replace the `struct EscapeInfo` with a simpler API, just consisting of methods of `ProjectedValue` and `Value`:
* `isEscaping()`
* `isAddressEscaping()`
* `visit()`
* `visitAddress()`
This commit is contained in:
Erik Eckstein
2022-09-19 12:20:24 +02:00
parent 572d51364a
commit a8b58735fc
6 changed files with 276 additions and 257 deletions

View File

@@ -30,12 +30,11 @@ let computeEffects = FunctionPass(name: "compute-effects", {
(function: Function, context: PassContext) in
var argsWithDefinedEffects = getArgIndicesWithDefinedEffects(of: function)
struct IgnoreRecursiveCallVisitor : EscapeInfoVisitor {
struct IgnoreRecursiveCallVisitor : EscapeVisitor {
func visitUse(operand: Operand, path: EscapePath) -> UseResult {
return isOperandOfRecursiveCall(operand) ? .ignore : .continueWalk
}
}
var escapeInfo = EscapeInfo(calleeAnalysis: context.calleeAnalysis, visitor: IgnoreRecursiveCallVisitor())
var newEffects = Stack<ArgumentEffect>(context)
let returnInst = function.returnInstruction
@@ -47,7 +46,8 @@ let computeEffects = FunctionPass(name: "compute-effects", {
if argsWithDefinedEffects.contains(arg.index) { continue }
// First check: is the argument (or a projected value of it) escaping at all?
if !escapeInfo.isEscapingWhenWalkingDown(object: arg, path: SmallProjectionPath(.anything)) {
if !arg.at(.anything).isEscaping(using: IgnoreRecursiveCallVisitor(),
startWalkingDown: true, context) {
newEffects.push(ArgumentEffect(.notEscaping, argumentIndex: arg.index, pathPattern: SmallProjectionPath(.anything)))
continue
}
@@ -78,7 +78,7 @@ func addArgEffects(_ arg: FunctionArgument, argPath ap: SmallProjectionPath,
// containing one or more references.
let argPath = arg.type.isClass ? ap : ap.push(.anyValueFields)
struct ArgEffectsVisitor : EscapeInfoVisitor {
struct ArgEffectsVisitor : EscapeVisitorWithResult {
enum EscapeDestination {
case notSet
case toReturn(SmallProjectionPath)
@@ -131,8 +131,8 @@ func addArgEffects(_ arg: FunctionArgument, argPath ap: SmallProjectionPath,
}
}
var walker = EscapeInfo(calleeAnalysis: context.calleeAnalysis, visitor: ArgEffectsVisitor())
if walker.isEscapingWhenWalkingDown(object: arg, path: argPath) {
guard let result = arg.at(argPath).visit(using: ArgEffectsVisitor(),
startWalkingDown: true, context) else {
return false
}
@@ -142,7 +142,7 @@ func addArgEffects(_ arg: FunctionArgument, argPath ap: SmallProjectionPath,
}
let effect: ArgumentEffect
switch walker.visitor.result {
switch result {
case .notSet:
effect = ArgumentEffect(.notEscaping, argumentIndex: arg.index, pathPattern: argPath)
case .toReturn(let toPath):
@@ -200,7 +200,7 @@ private
func isExclusiveEscapeToReturn(fromArgument: Argument, fromPath: SmallProjectionPath,
toPath: SmallProjectionPath,
returnInst: ReturnInst, _ context: PassContext) -> Bool {
struct IsExclusiveReturnEscapeVisitor : EscapeInfoVisitor {
struct IsExclusiveReturnEscapeVisitor : EscapeVisitor {
let fromArgument: Argument
let fromPath: SmallProjectionPath
let toPath: SmallProjectionPath
@@ -228,14 +228,13 @@ func isExclusiveEscapeToReturn(fromArgument: Argument, fromPath: SmallProjection
}
}
let visitor = IsExclusiveReturnEscapeVisitor(fromArgument: fromArgument, fromPath: fromPath, toPath: toPath)
var walker = EscapeInfo(calleeAnalysis: context.calleeAnalysis, visitor: visitor)
return !walker.isEscaping(object: returnInst.operand, path: toPath)
return !returnInst.operand.at(toPath).isEscaping(using: visitor, context)
}
private
func isExclusiveEscapeToArgument(fromArgument: Argument, fromPath: SmallProjectionPath,
toArgumentIndex: Int, toPath: SmallProjectionPath, _ context: PassContext) -> Bool {
struct IsExclusiveArgumentEscapeVisitor : EscapeInfoVisitor {
struct IsExclusiveArgumentEscapeVisitor : EscapeVisitor {
let fromArgument: Argument
let fromPath: SmallProjectionPath
let toArgumentIndex: Int
@@ -253,8 +252,7 @@ func isExclusiveEscapeToArgument(fromArgument: Argument, fromPath: SmallProjecti
}
let visitor = IsExclusiveArgumentEscapeVisitor(fromArgument: fromArgument, fromPath: fromPath,
toArgumentIndex: toArgumentIndex, toPath: toPath)
var walker = EscapeInfo(calleeAnalysis: context.calleeAnalysis, visitor: visitor)
let toArg = fromArgument.function.arguments[toArgumentIndex]
return !walker.isEscaping(object: toArg, path: toPath)
return !toArg.at(toPath).isEscaping(using: visitor, startWalkingDown: true, context)
}

View File

@@ -75,11 +75,8 @@ func tryPromoteAlloc(_ allocRef: AllocRefInstBase,
if allocRef.isObjC || allocRef.canAllocOnStack {
return false
}
struct DefaultVisitor : EscapeInfoVisitor {}
var ei = EscapeInfo(calleeAnalysis: context.calleeAnalysis, visitor: DefaultVisitor())
// The most important check: does the object escape the current function?
if ei.isEscaping(object:allocRef) {
if allocRef.isEscaping(context) {
return false
}
@@ -196,21 +193,19 @@ func tryPromoteAlloc(_ allocRef: AllocRefInstBase,
private func getDominatingBlockOfAllUsePoints(context: PassContext,
_ value: SingleValueInstruction,
domTree: DominatorTree) -> BasicBlock {
struct Visitor : EscapeInfoVisitor {
var dominatingBlock: BasicBlock
struct FindDominatingBlock : EscapeVisitorWithResult {
var result: BasicBlock
let domTree: DominatorTree
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
let defBlock = operand.value.definingBlock
if defBlock.dominates(dominatingBlock, domTree) {
dominatingBlock = defBlock
if defBlock.dominates(result, domTree) {
result = defBlock
}
return .continueWalk
}
}
var walker = EscapeInfo(calleeAnalysis: context.calleeAnalysis, visitor: Visitor(dominatingBlock: value.block, domTree: domTree))
_ = walker.isEscaping(object: value)
return walker.visitor.dominatingBlock
return value.visit(using: FindDominatingBlock(result: value.block, domTree: domTree), context)!
}
@@ -219,11 +214,13 @@ func computeInnerAndOuterLiferanges(instruction: SingleValueInstruction, in domB
/// All uses which are dominated by the `innerInstRange`s begin-block are included
/// in both, the `innerInstRange` and the `outerBlockRange`.
/// All _not_ dominated uses are only included in the `outerBlockRange`.
struct Visitor : EscapeInfoVisitor {
struct Visitor : EscapeVisitorWithResult {
var innerRange: InstructionRange
var outerBlockRange: BasicBlockRange
let domTree: DominatorTree
var result: (InstructionRange, BasicBlockRange) { (innerRange, outerBlockRange) }
init(instruction: Instruction, domBlock: BasicBlock, domTree: DominatorTree, context: PassContext) {
innerRange = InstructionRange(begin: instruction, context)
outerBlockRange = BasicBlockRange(begin: domBlock, context)
@@ -245,7 +242,7 @@ func computeInnerAndOuterLiferanges(instruction: SingleValueInstruction, in domB
outerBlockRange.insert(defBlock)
// We need to explicitly add predecessor blocks of phi-arguments becaues they
// are not necesesarily visited by `EscapeInfo.walkDown`.
// are not necesesarily visited during the down-walk in `isEscaping()`.
// This is important for the special case where there is a back-edge from the
// inner range to the inner rage's begin-block:
//
@@ -261,16 +258,11 @@ func computeInnerAndOuterLiferanges(instruction: SingleValueInstruction, in domB
return .continueWalk
}
}
var walker = EscapeInfo(calleeAnalysis: context.calleeAnalysis,
visitor: Visitor(
instruction: instruction,
domBlock: domBlock,
domTree: domTree,
context: context
))
_ = walker.isEscaping(object: instruction)
return (walker.visitor.innerRange, walker.visitor.outerBlockRange)
return instruction.visit(using: Visitor(instruction: instruction,
domBlock: domBlock,
domTree: domTree,
context: context), context)!
}
private extension BasicBlockRange {

View File

@@ -22,12 +22,12 @@ let escapeInfoDumper = FunctionPass(name: "dump-escape-info", {
print("Escape information for \(function.name):")
struct Visitor : EscapeInfoVisitor {
var results: Set<String> = Set()
struct Visitor : EscapeVisitorWithResult {
var result: Set<String> = Set()
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
if operand.instruction is ReturnInst {
results.insert("return[\(path.projectionPath)]")
result.insert("return[\(path.projectionPath)]")
return .ignore
}
return .continueWalk
@@ -37,7 +37,7 @@ let escapeInfoDumper = FunctionPass(name: "dump-escape-info", {
guard let arg = def as? FunctionArgument else {
return .continueWalkUp
}
results.insert("arg\(arg.index)[\(path.projectionPath)]")
result.insert("arg\(arg.index)[\(path.projectionPath)]")
return .walkDown
}
}
@@ -46,19 +46,17 @@ let escapeInfoDumper = FunctionPass(name: "dump-escape-info", {
for inst in function.instructions {
if let allocRef = inst as? AllocRefInst {
var walker = EscapeInfo(calleeAnalysis: context.calleeAnalysis, visitor: Visitor())
let escapes = walker.isEscaping(object: allocRef)
let results = walker.visitor.results
let res: String
if escapes {
res = "global"
} else if results.isEmpty {
res = " - "
let resultStr: String
if let result = allocRef.visit(using: Visitor(), context) {
if result.isEmpty {
resultStr = " - "
} else {
resultStr = Array(result).sorted().joined(separator: ",")
}
} else {
res = Array(results).sorted().joined(separator: ",")
resultStr = "global"
}
print("\(res): \(allocRef)")
print("\(resultStr): \(allocRef)")
}
}
print("End function \(function.name)\n")
@@ -90,7 +88,7 @@ let addressEscapeInfoDumper = FunctionPass(name: "dump-addr-escape-info", {
}
}
struct Visitor : EscapeInfoVisitor {
struct Visitor : EscapeVisitor {
let apply: Instruction
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
let user = operand.instruction
@@ -111,13 +109,14 @@ let addressEscapeInfoDumper = FunctionPass(name: "dump-addr-escape-info", {
for apply in applies {
let path = AliasAnalysis.getPtrOrAddressPath(for: value)
var escapeInfo = EscapeInfo(calleeAnalysis: context.calleeAnalysis, visitor: Visitor(apply: apply))
let escaping = escapeInfo.isEscaping(addressesOf: value, path: path)
print(" \(escaping ? "==>" : "- ") \(apply)")
if value.at(path).isAddressEscaping(using: Visitor(apply: apply), context) {
print(" ==> \(apply)")
} else {
print(" - \(apply)")
}
}
}
var eaa = EscapeAliasAnalysis(calleeAnalysis: context.calleeAnalysis)
// test `canReferenceSameField` for each pair of `fix_lifetime`.
if !valuesToCheck.isEmpty {
for lhsIdx in 0..<(valuesToCheck.count - 1) {
@@ -127,9 +126,14 @@ let addressEscapeInfoDumper = FunctionPass(name: "dump-addr-escape-info", {
let rhs = valuesToCheck[rhsIdx]
print(lhs)
print(rhs)
if eaa.canReferenceSameField(
lhs, path: AliasAnalysis.getPtrOrAddressPath(for: lhs),
rhs, path: AliasAnalysis.getPtrOrAddressPath(for: rhs)) {
let projLhs = lhs.at(AliasAnalysis.getPtrOrAddressPath(for: lhs))
let projRhs = rhs.at(AliasAnalysis.getPtrOrAddressPath(for: rhs))
let mayAlias = projLhs.canAddressAlias(with: projRhs, context)
assert(mayAlias == projRhs.canAddressAlias(with: projLhs, context),
"canAddressAlias(with:) must be symmetric")
if mayAlias {
print("may alias")
} else {
print("no alias")

View File

@@ -12,13 +12,154 @@
import SIL
/// This protocol is used to customize `EscapeInfo` by implementing `visitUse` and
/// `visitDef` which are called for all uses and definitions ("direct" and "transitive") encountered
/// during a walk.
protocol EscapeInfoVisitor {
typealias UseResult = EscapeInfo<Self>.UseVisitResult
typealias DefResult = EscapeInfo<Self>.DefVisitResult
typealias EscapePath = EscapeInfo<Self>.EscapePath
extension ProjectedValue {
/// Returns true if the projected value escapes.
///
/// This function finds potential escape points by starting a walk at the value and
/// alternately walking in two directions:
/// * Starting at allocations, walks down from defs to uses ("Where does the value go to?")
/// * Starting at stores, walks up from uses to defs ("Were does the value come from?")
///
/// The value "escapes" if the walk reaches a point where the further flow of the value
/// cannot be tracked anymore.
/// Example:
/// \code
/// %1 = alloc_ref $X // 1. initial value: walk down to the `store`
/// %2 = alloc_stack $X // 3. walk down to %3
/// store %1 to %2 // 2. walk up to `%2`
/// %3 = load %2 // 4. continue walking down to the `return`
/// return %3 // 5. The value is escaping!
/// \endcode
///
/// The traversal stops at points where the current path doesn't match the original projection.
/// For example, let's assume this function is called on a projected value with path `s0.c1`.
/// \code
/// %value : $Struct<X> // current path == s0.c1, the initial value
/// %ref = struct_extract %value, #field0 // current path == c1
/// %addr = ref_element_addr %ref, #field2 // mismatch: `c1` != `c2` -> ignored
/// \endcode
///
/// Trivial values are ignored, even if the path matches.
///
/// The algorithm doesn't distinguish between addresses and values. Addresses are considered
/// as escaping if either:
/// * the address escapes (e.g. to an inout parameter of an unknown function)
/// * or if the value, which is stored at the address, escapes.
///
/// The provided `visitor` can be used to override the handling a certain defs and uses during
/// the walk. See `EscapeVisitor` for details.
///
/// By default, the walk starts in upward direction. This makes sense most of the time. But for
/// some use cases, e.g. to check if an argument escapes inside the function (without
/// considering potential escapes in the caller), the walk must start downwards - by setting
/// `startWalkingDown` to true.
///
func isEscaping<V: EscapeVisitor>(using visitor: V = DefaultVisitor(),
startWalkingDown: Bool = false,
_ context: PassContext) -> Bool {
var walker = EscapeWalker(visitor: visitor, analyzeAddresses: false, context)
return walker.isEscaping(self, startWalkingDown: startWalkingDown)
}
/// Returns true if the projected address value escapes.
///
/// This function is similar to the non-address version: `isEscaping() -> Bool`, but
/// the projected value (which is expected to be an address) is handled differently:
/// * Loads from the addresss are ignored. So it's really about the _address_ and
/// not the value stored at the address.
/// * Addresses with trivial types are _not_ ignored.
///
func isAddressEscaping<V: EscapeVisitor>(using visitor: V = DefaultVisitor(),
startWalkingDown: Bool = false,
_ context: PassContext) -> Bool {
var walker = EscapeWalker(visitor: visitor, analyzeAddresses: true, context)
return walker.isEscaping(self, startWalkingDown: startWalkingDown)
}
/// Returns the result of the visitor if the projected value does not escape.
///
/// This function is similar to `isEscaping() -> Bool`, but instead of returning a Bool,
/// it returns the `result` of the `visitor`, if the projected value does not escape.
/// Returns nil, if the projected value escapes.
///
func visit<V: EscapeVisitorWithResult>(using visitor: V,
startWalkingDown: Bool = false,
_ context: PassContext) -> V.Result? {
var walker = EscapeWalker(visitor: visitor, analyzeAddresses: false, context)
if walker.isEscaping(self, startWalkingDown: startWalkingDown) {
return nil
}
return walker.visitor.result
}
/// Returns the result of the visitor if the projected address value does not escape.
///
/// This function is similar to `isAddressEscaping() -> Bool`, but instead of returning a Bool,
/// it returns the `result` of the `visitor`, if the projected address does not escape.
/// Returns nil, if the projected address escapes.
///
func visitAddress<V: EscapeVisitorWithResult>(using visitor: V,
startWalkingDown: Bool = false,
_ context: PassContext) -> V.Result? {
var walker = EscapeWalker(visitor: visitor, analyzeAddresses: true, context)
if walker.isEscaping(self, startWalkingDown: startWalkingDown) {
return nil
}
return walker.visitor.result
}
/// Returns true if the address can alias with `rhs`.
///
/// Example:
/// %1 = struct_element_addr %s, #field1
/// %2 = struct_element_addr %s, #field2
///
/// `%s`.canAddressAlias(with: `%1`) -> true
/// `%s`.canAddressAlias(with: `%2`) -> true
/// `%1`.canAddressAlias(with: `%2`) -> false
///
func canAddressAlias(with rhs: ProjectedValue, _ context: PassContext) -> Bool {
// self -> rhs will succeed (= return false) if self is a non-escaping "local" object,
// but not necessarily rhs.
if !isAddressEscaping(using: EscapesToValueVisitor(target: rhs), context) {
return false
}
// The other way round: rhs -> self will succeed if rhs is a non-escaping "local" object,
// but not necessarily self.
if !rhs.isAddressEscaping(using: EscapesToValueVisitor(target: self), context) {
return false
}
return true
}
}
extension Value {
/// The un-projected version of `ProjectedValue.isEscaping()`.
func isEscaping<V: EscapeVisitor>(using visitor: V = DefaultVisitor(),
_ context: PassContext) -> Bool {
return self.at(SmallProjectionPath()).isEscaping(using: visitor, context)
}
/// The un-projected version of `ProjectedValue.visit()`.
func visit<V: EscapeVisitorWithResult>(using visitor: V,
_ context: PassContext) -> V.Result? {
return self.at(SmallProjectionPath()).visit(using: visitor, context)
}
}
private func escapePath(_ path: SmallProjectionPath) -> EscapeUtilityTypes.EscapePath {
EscapeUtilityTypes.EscapePath(projectionPath: path, followStores: false, knownType: nil)
}
/// This protocol is used to customize `ProjectedValue.isEscaping` (and similar functions)
/// by implementing `visitUse` and `visitDef` which are called for all uses and definitions
/// encountered during a walk.
protocol EscapeVisitor {
typealias UseResult = EscapeUtilityTypes.UseVisitResult
typealias DefResult = EscapeUtilityTypes.DefVisitResult
typealias EscapePath = EscapeUtilityTypes.EscapePath
/// Called during the DefUse walk for each use
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult
@@ -27,7 +168,7 @@ protocol EscapeInfoVisitor {
mutating func visitDef(def: Value, path: EscapePath) -> DefResult
}
extension EscapeInfoVisitor {
extension EscapeVisitor {
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
return .continueWalk
}
@@ -36,35 +177,52 @@ extension EscapeInfoVisitor {
}
}
/// A utility for checking if a value escapes and for finding the escape points.
///
/// The EscapeInfo starts at the initial value and alternately walks in two directions:
/// * Starting at allocations, walks down from defs to uses ("Where does the value go to?")
/// * Starting at stores, walks up from uses to defs ("Were does the value come from?")
///
/// The result of the walk indicates if the initial value "escapes" or not.
/// The value escapes if the walk reaches a point where the further flow of the
/// value cannot be tracked anymore.
/// Example:
/// \code
/// %1 = alloc_ref $X // 1. initial value: walk down to the `store`
/// %2 = alloc_stack $X // 3. walk down to %3
/// store %1 to %2 // 2. walk up to `%2`
/// %3 = load %2 // 4. continue walking down to the `return`
/// return %3 // 5. The value is escaping!
/// \endcode
///
/// The algorithm doesn't distinguish between addresses and values, i.e. loads
/// and stores are treated as simple forwarding instructions, like casts.
/// For escaping it doesn't make a difference if a value or an address pointing to
/// the value, escapes.
/// An exception are `isEscaping(address: Value)` and similar functions: they ignore
/// values which are loaded from the address in question.
struct EscapeInfo<V: EscapeInfoVisitor> {
/// A visitor which returns a `result`.
protocol EscapeVisitorWithResult : EscapeVisitor {
associatedtype Result
var result: Result { get }
}
/// Lets `ProjectedValue.isEscaping` return true if the value is "escaping" to the `target` value.
struct EscapesToValueVisitor : EscapeVisitor {
let target: ProjectedValue
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
if operand.value == target.value && path.projectionPath.mayOverlap(with: target.path) {
return .abort
}
if operand.instruction is ReturnInst {
// Anything which is returned cannot escape to an instruction inside the function.
return .ignore
}
return .continueWalk
}
}
/// Lets `ProjectedValue.isEscaping` return true if the value is "escaping" to the `target` instruction.
struct EscapesToInstructionVisitor : EscapeVisitor {
let target: Instruction
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
let user = operand.instruction
if user == target {
return .abort
}
if user is ReturnInst {
// Anything which is returned cannot escape to an instruction inside the function.
return .ignore
}
return .continueWalk
}
}
private struct DefaultVisitor : EscapeVisitor {}
struct EscapeUtilityTypes {
/// The EscapePath is updated and maintained during the up-walk and down-walk.
///
/// It's passed to the EscapeInfoVisitor's `visitUse` and `visitDef`.
/// It's passed to the EscapeVisitor's `visitUse` and `visitDef`.
struct EscapePath: SmallProjectionWalkingPath {
/// During the walk, a projection path indicates where the initial value is
/// contained in an aggregate.
@@ -155,176 +313,33 @@ struct EscapeInfo<V: EscapeInfoVisitor> {
case continueWalk
case abort
}
//===--------------------------------------------------------------------===//
// The top-level API
//===--------------------------------------------------------------------===//
init(calleeAnalysis: CalleeAnalysis, visitor: V) {
self.walker = EscapeInfoWalker(calleeAnalysis: calleeAnalysis, visitor: visitor)
}
private var walker: EscapeInfoWalker<V>
/// Returns the visitor passed as argument during initialization.
/// It is also possible to mutate the visitor to reuse it for performance. See ``EscapeAliasAnalysis/canReferenceSameField``.
var visitor: V {
get { walker.visitor }
_modify { yield &walker.visitor }
}
/// Returns true if `object`, or any sub-objects which are selected by `path`, can escape.
///
/// For example, let's assume this function is called with a struct, containing a reference,
/// and a path of `s0.c*`:
/// \code
/// %value : $Struct<X> // path == s0.c*, the initial `object`
/// %ref = struct_extract %value, #field0 // path == c*
/// %ref1 = struct_extract %value, #field1 // ignored - not selected by path
/// %addr = ref_element_addr %ref, #some_field // path is empty
/// %v = load %addr // path is empty
/// return %v // escaping!
/// \endcode
///
/// Trivial values are ignored, even if they are selected by `path`.
mutating func isEscaping(object: Value, path: SmallProjectionPath = SmallProjectionPath()) -> Bool {
walker.start(forAnalyzingAddresses: false)
defer { walker.cleanup() }
let escPath = EscapePath(projectionPath: path, followStores: false, knownType: nil)
if let p = walker.walkUpCache.needWalk(for: object, path: escPath) {
return walker.walkUp(addressOrValue: object, path: p) == .abortWalk
}
return false
}
/// Returns true if the definition of `value` is escaping.
///
/// In contrast to `isEscaping`, this function starts with a walk-down instead of a walk-up from `value`.
mutating func isEscapingWhenWalkingDown(object: Value, path: SmallProjectionPath = SmallProjectionPath()) -> Bool {
walker.start(forAnalyzingAddresses: false)
defer { walker.cleanup() }
let escPath = EscapePath(projectionPath: path, followStores: false, knownType: nil)
return walker.cachedWalkDown(addressOrValue: object, path: escPath) == .abortWalk
}
/// Returns true if any address of `value`, which is selected by `path`, can escape.
///
/// For example, let's assume this function is called with a struct, containing a reference,
/// and a path of `s0.c*.v**`:
/// \code
/// %value : $Struct<X> // path == s0.c*.v**, the initial `value`
/// %ref = struct_extract %value, #field0 // path == c*.v**
/// %selected_addr = ref_element_addr %ref, #x // path == v**, the selected address
/// apply %f(%selected_addr) // escaping!
/// \endcode
///
/// There are two differences to `isEscaping(object:)`:
/// * Loads from the selected address(es) are ignored. So it's really about the _address_ and
/// not the value stored at the address.
/// * Addresses with trivial types are _not_ ignored.
mutating
func isEscaping(addressesOf value: Value, path: SmallProjectionPath = SmallProjectionPath(.anyValueFields)) -> Bool {
walker.start(forAnalyzingAddresses: true)
defer { walker.cleanup() }
let escPath = EscapePath(projectionPath: path, followStores: false, knownType: nil)
if let p = walker.walkUpCache.needWalk(for: value, path: escPath) {
return walker.walkUp(addressOrValue: value, path: p) == .abortWalk
}
return false
}
}
/// A lightweight form of AliasAnalysis that checks whether given two addresses can alias
/// by checking that the addresses don't escape and that during a walk of one of
/// the values, a use does not result in the other value.
struct EscapeAliasAnalysis {
private struct Visitor : EscapeInfoVisitor {
// TODO: maybe we can create an empty value instead of option?
var target: Value?
func visitUse(operand: Operand, path: EscapePath) -> UseResult {
// Note: since we are checking the value of an operand, we are ignoring address
// projections with no uses. This is no problem. It just requires a fix_lifetime for
// each address to test in alias-analysis test files.
if operand.value == target! { return .abort }
if operand.instruction is ReturnInst { return .ignore }
return .continueWalk
}
}
/// EscapeWalker is both a DefUse walker and UseDef walker. It implements both, the up-, and down-walk.
fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
AddressDefUseWalker,
ValueUseDefWalker,
AddressUseDefWalker {
typealias Path = EscapeUtilityTypes.EscapePath
private var calleeAnalysis: CalleeAnalysis
private var walker: EscapeInfo<Visitor>
init(calleeAnalysis: CalleeAnalysis) {
self.calleeAnalysis = calleeAnalysis
self.walker = EscapeInfo(calleeAnalysis: calleeAnalysis, visitor: Visitor())
}
/// Returns true if the selected address(es) of `lhs`/`lhsPath` can reference the same field as
/// the selected address(es) of `rhs`/`rhsPath`.
///
/// Example:
/// %1 = struct_element_addr %s, #field1 // true for (%1, %s)
/// %2 = struct_element_addr %s, #field2 // true for (%2, %s), false for (%1,%2)
///
mutating func canReferenceSameField(_ lhs: Value, path lhsPath: SmallProjectionPath = SmallProjectionPath(.anyValueFields),
_ rhs: Value, path rhsPath: SmallProjectionPath = SmallProjectionPath(.anyValueFields)) -> Bool {
// lhs -> rhs will succeed (= return false) if lhs is a non-escaping "local" object,
// but not necessarily rhs.
walker.visitor.target = rhs
if !walker.isEscaping(addressesOf: lhs, path: lhsPath) {
return false
}
// The other way round: rhs -> lhs will succeed if rhs is a non-escaping "local" object,
// but not necessarily lhs.
walker.visitor.target = lhs
if !walker.isEscaping(addressesOf: rhs, path: rhsPath) {
return false
}
return true
}
}
/// The walker used by `EscapeInfo` to check whether a value escapes.
/// It is both a DefUse walker and UseDef walker. If during a walkDown a store or copy
/// is reached then
fileprivate struct EscapeInfoWalker<V: EscapeInfoVisitor> : ValueDefUseWalker,
AddressDefUseWalker,
ValueUseDefWalker,
AddressUseDefWalker {
typealias Path = EscapeInfo<V>.EscapePath
init(calleeAnalysis: CalleeAnalysis, visitor: V) {
self.calleeAnalysis = calleeAnalysis
init(visitor: V, analyzeAddresses: Bool, _ context: PassContext) {
self.calleeAnalysis = context.calleeAnalysis
self.visitor = visitor
}
mutating func start(forAnalyzingAddresses: Bool) {
assert(walkDownCache.isEmpty && walkUpCache.isEmpty)
analyzeAddresses = forAnalyzingAddresses
self.analyzeAddresses = analyzeAddresses
}
mutating func cleanup() {
walkDownCache.clear()
walkUpCache.clear()
mutating func isEscaping(_ projValue: ProjectedValue, startWalkingDown: Bool) -> Bool {
if startWalkingDown {
return walkDown(addressOrValue: projValue.value, path: escapePath(projValue.path)) == .abortWalk
} else {
return walkUp(addressOrValue: projValue.value, path: escapePath(projValue.path)) == .abortWalk
}
}
//===--------------------------------------------------------------------===//
// Walking down
//===--------------------------------------------------------------------===//
/// Main entry point called by ``EscapeInfo``
mutating func cachedWalkDown(addressOrValue: Value, path: Path) -> WalkResult {
if let path = walkDownCache.needWalk(for: addressOrValue, path: path) {
return walkDown(addressOrValue: addressOrValue, path: path)
} else {
return .continueWalk
}
}
mutating func walkDown(addressOrValue: Value, path: Path) -> WalkResult {
if addressOrValue.type.isAddress {
return walkDownUses(ofAddress: addressOrValue, path: path)
@@ -333,6 +348,14 @@ fileprivate struct EscapeInfoWalker<V: EscapeInfoVisitor> : ValueDefUseWalker,
}
}
mutating func cachedWalkDown(addressOrValue: Value, path: Path) -> WalkResult {
if let path = walkDownCache.needWalk(for: addressOrValue, path: path) {
return walkDown(addressOrValue: addressOrValue, path: path)
} else {
return .continueWalk
}
}
mutating func walkDown(value: Operand, path: Path) -> WalkResult {
if hasRelevantType(value.value, at: path.projectionPath) {
switch visitor.visitUse(operand: value, path: path) {
@@ -656,7 +679,6 @@ fileprivate struct EscapeInfoWalker<V: EscapeInfoVisitor> : ValueDefUseWalker,
// Walking up
//===--------------------------------------------------------------------===//
/// Main entry point called by ``EscapeInfo``
mutating func walkUp(addressOrValue: Value, path: Path) -> WalkResult {
if addressOrValue.type.isAddress {
return walkUp(address: addressOrValue, path: path)

View File

@@ -144,8 +144,6 @@ struct WalkerCache<Path : WalkingPath> {
return cache[value.hashable, default: CacheEntry()].needWalk(path: path)
}
var isEmpty: Bool { cache.isEmpty }
mutating func clear() {
inlineEntry0 = nil
inlineEntry1 = nil