Files
swift-mirror/SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift
Erik Eckstein 2dbd6cc56b SwiftCompilerSources: rework bridging
Introduce two modes of bridging:
* inline mode: this is basically how it worked so far. Using full C++ interop which allows bridging functions to be inlined.
* pure mode: bridging functions are not inlined but compiled in a cpp file. This allows to reduce the C++ interop requirements to a minimum. No std/llvm/swift headers are imported.

This change requires a major refactoring of bridging sources. The implementation of bridging functions go to two separate files: SILBridgingImpl.h and OptimizerBridgingImpl.h.
Depending on the mode, those files are either included in the corresponding header files (inline mode), or included in the c++ file (pure mode).

The mode can be selected with the BRIDGING_MODE cmake variable. By default it is set to the inline mode (= existing behavior). The pure mode is only selected in certain configurations to work around C++ interop issues:
* In debug builds, to workaround a problem with LLDB's `po` command (rdar://115770255).
* On windows to workaround a build problem.
2023-10-09 09:52:52 +02:00

267 lines
11 KiB
Swift

//===--- AliasAnalysis.swift - the alias analysis -------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import OptimizerBridging
import SIL
struct AliasAnalysis {
let bridged: BridgedAliasAnalysis
/// Returns the correct path for address-alias functions.
static func getPtrOrAddressPath(for value: Value) -> SmallProjectionPath {
let ty = value.type
if ty.isAddress {
// This is the regular case: the path selects any sub-fields of an address.
return SmallProjectionPath(.anyValueFields)
}
// Some optimizations use the address-alias APIs with non-address SIL values.
// TODO: this is non-intuitive and we should eliminate those API uses.
if ty.isClass {
// If the value is a (non-address) reference it means: all addresses within the class instance.
return SmallProjectionPath(.anyValueFields).push(.anyClassField)
}
// Any other non-address value means: all addresses of any referenced class instances within the value.
return SmallProjectionPath(.anyValueFields).push(.anyClassField).push(.anyValueFields)
}
static func register() {
BridgedAliasAnalysis.registerAnalysis(
// getMemEffectsFn
{ (bridgedCtxt: BridgedPassContext, bridgedVal: BridgedValue, bridgedInst: BridgedInstruction, complexityBudget: Int) -> BridgedMemoryBehavior in
let context = FunctionPassContext(_bridged: bridgedCtxt)
let inst = bridgedInst.instruction
let val = bridgedVal.value
let path = AliasAnalysis.getPtrOrAddressPath(for: val)
switch inst {
case let apply as ApplySite:
return getMemoryEffect(ofApply: apply, for: val, path: path, context).bridged
case let builtin as BuiltinInst:
return getMemoryEffect(ofBuiltin: builtin, for: val, path: path, context).bridged
default:
if val.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: true),
complexityBudget: complexityBudget, context) {
return .MayReadWrite
}
return .None
}
},
// isObjReleasedFn
{ (bridgedCtxt: BridgedPassContext, bridgedObj: BridgedValue, bridgedInst: BridgedInstruction, complexityBudget: Int) -> Bool in
let context = FunctionPassContext(_bridged: bridgedCtxt)
let inst = bridgedInst.instruction
let obj = bridgedObj.value
let path = SmallProjectionPath(.anyValueFields)
if let apply = inst as? ApplySite {
// Workaround for quadratic complexity in ARCSequenceOpts.
// We need to use an ever lower budget to not get into noticable compile time troubles.
let budget = complexityBudget / 10
let effect = getOwnershipEffect(of: apply, for: obj, path: path, complexityBudget: budget, context)
return effect.destroy
}
return obj.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: false),
complexityBudget: complexityBudget, context)
},
// isAddrVisibleFromObj
{ (bridgedCtxt: BridgedPassContext, bridgedAddr: BridgedValue, bridgedObj: BridgedValue, complexityBudget: Int) -> Bool in
let context = FunctionPassContext(_bridged: bridgedCtxt)
let addr = bridgedAddr.value.at(AliasAnalysis.getPtrOrAddressPath(for: bridgedAddr.value))
// This is similar to `canReferenceSameFieldFn`, except that all addresses of all objects are
// considered which are transitively visible from `bridgedObj`.
let anythingReachableFromObj = bridgedObj.value.at(SmallProjectionPath(.anything))
return addr.canAddressAlias(with: anythingReachableFromObj, complexityBudget: complexityBudget, context)
},
// canReferenceSameFieldFn
{ (bridgedCtxt: BridgedPassContext, bridgedLhs: BridgedValue, bridgedRhs: BridgedValue) -> Bool in
let context = FunctionPassContext(_bridged: bridgedCtxt)
// If `lhs` or `rhs` is not an address, but an object, it means: check for alias of any class
// field address of the object.
let lhs = bridgedLhs.value.at(AliasAnalysis.getPtrOrAddressPath(for: bridgedLhs.value))
let rhs = bridgedRhs.value.at(AliasAnalysis.getPtrOrAddressPath(for: bridgedRhs.value))
return lhs.canAddressAlias(with: rhs, context)
}
)
}
}
extension Instruction {
func mayRead(fromAddress: Value, _ aliasAnalysis: AliasAnalysis) -> Bool {
switch aliasAnalysis.bridged.getMemBehavior(bridged, fromAddress.bridged) {
case .MayRead, .MayReadWrite, .MayHaveSideEffects:
return true
default:
return false
}
}
func mayWrite(toAddress: Value, _ aliasAnalysis: AliasAnalysis) -> Bool {
switch aliasAnalysis.bridged.getMemBehavior(bridged, toAddress.bridged) {
case .MayWrite, .MayReadWrite, .MayHaveSideEffects:
return true
default:
return false
}
}
func mayReadOrWrite(address: Value, _ aliasAnalysis: AliasAnalysis) -> Bool {
switch aliasAnalysis.bridged.getMemBehavior(bridged, address.bridged) {
case .MayRead, .MayWrite, .MayReadWrite, .MayHaveSideEffects:
return true
default:
return false
}
}
}
private func getMemoryEffect(ofApply apply: ApplySite, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory {
let calleeAnalysis = context.calleeAnalysis
let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: calleeAnalysis, isAddress: true)
let memoryEffects: SideEffects.Memory
// First try to figure out to which argument(s) the address "escapes" to.
if let result = address.at(path).visit(using: visitor, context) {
// The resulting effects are the argument effects to which `address` escapes to.
memoryEffects = result.memory
} else {
// `address` has unknown escapes. So we have to take the global effects of the called function(s).
memoryEffects = calleeAnalysis.getSideEffects(ofApply: apply).memory
}
// Do some magic for `let` variables. Function calls cannot modify let variables.
// The only exception is that the let variable is directly passed to an indirect out of the
// apply.
// TODO: make this a more formal and verified approach.
if memoryEffects.write && address.accessBase.isLet && !address.isIndirectResult(of: apply) {
return SideEffects.Memory(read: memoryEffects.read, write: false)
}
return memoryEffects
}
private func getMemoryEffect(ofBuiltin builtin: BuiltinInst, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory {
switch builtin.id {
case .Once, .OnceWithContext:
if !address.at(path).isEscaping(using: AddressVisibleByBuiltinOnceVisitor(), context) {
return SideEffects.Memory()
}
let callee = builtin.operands[1].value
return context.calleeAnalysis.getSideEffects(ofCallee: callee).memory
default:
return builtin.memoryEffects
}
}
private func getOwnershipEffect(of apply: ApplySite, for value: Value, path: SmallProjectionPath,
complexityBudget: Int, _ context: FunctionPassContext) -> SideEffects.Ownership {
let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: context.calleeAnalysis, isAddress: false)
if let result = value.at(path).visit(using: visitor, complexityBudget: complexityBudget, context) {
// The resulting effects are the argument effects to which `value` escapes to.
return result.ownership
} else {
// `value` has unknown escapes. So we have to take the global effects of the called function(s).
return visitor.calleeAnalysis.getSideEffects(ofApply: apply).ownership
}
}
private struct SideEffectsVisitor : EscapeVisitorWithResult {
let apply: ApplySite
let calleeAnalysis: CalleeAnalysis
let isAddress: Bool
var result = SideEffects.GlobalEffects()
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
let user = operand.instruction
if user is ReturnInst {
// Anything which is returned cannot escape to an instruction inside the function.
return .ignore
}
if user == apply {
if let argIdx = apply.argumentIndex(of: operand) {
let e = calleeAnalysis.getSideEffects(of: apply, forArgument: argIdx, path: path.projectionPath)
result.merge(with: e)
}
}
return .continueWalk
}
var followTrivialTypes: Bool { isAddress }
var followLoads: Bool { !isAddress }
}
private struct AddressVisibleByBuiltinOnceVisitor : EscapeVisitor {
var followTrivialTypes: Bool { true }
var followLoads: Bool { false }
}
/// Lets `ProjectedValue.isEscaping` return true if the value is "escaping" to the `target` instruction.
private struct EscapesToInstructionVisitor : EscapeVisitor {
let target: Instruction
let isAddress: Bool
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
}
var followTrivialTypes: Bool { isAddress }
var followLoads: Bool { !isAddress }
}
private extension Value {
/// Returns true if this address is passed as indirect out of `apply`.
func isIndirectResult(of apply: ApplySite) -> Bool {
guard let fullApply = apply as? FullApplySite else {
return false
}
if fullApply.numIndirectResultArguments == 0 {
return false
}
var walker = IsIndirectResultWalker(apply: fullApply)
return walker.walkDownUses(ofAddress: self, path: UnusedWalkingPath()) == .abortWalk
}
}
private struct IsIndirectResultWalker: AddressDefUseWalker {
let apply: FullApplySite
mutating func leafUse(address: Operand, path: UnusedWalkingPath) -> WalkResult {
if address.instruction == apply,
let argIdx = apply.argumentIndex(of: address),
argIdx < apply.numIndirectResultArguments {
return .abortWalk
}
return .continueWalk
}
}
private extension SideEffects.Memory {
var bridged: BridgedMemoryBehavior {
switch (read, write) {
case (false, false): return .None
case (true, false): return .MayRead
case (false, true): return .MayWrite
case (true, true): return .MayReadWrite
}
}
}