mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
RedundantLoadElimination: support replacing a redundant copy_addr with a store
For example:
```
%0 = load %1
copy_addr %1 to %2
```
->
```
%0 = load %1
store %0 to %2
```
This is important for MandatoryRedundantLoadElimination to be able to create statically initialized globals in the mandatory pipeline.
For example:
```
public struct MyStruct {
public static let r: Range<Int> = 1 ..< 3
}
```
gets a statically initialized global, even at Onone, with this improvement.
rdar://149356742
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
import SIL
|
import SIL
|
||||||
|
|
||||||
/// Replaces redundant load instructions with already available values.
|
/// Replaces redundant `load` or `copy_addr` instructions with already available values.
|
||||||
///
|
///
|
||||||
/// A load is redundant if the loaded value is already available at that point.
|
/// A load is redundant if the loaded value is already available at that point.
|
||||||
/// This can be via a preceding store to the same address:
|
/// This can be via a preceding store to the same address:
|
||||||
@@ -52,6 +52,9 @@ import SIL
|
|||||||
/// %f2 = load %fa2
|
/// %f2 = load %fa2
|
||||||
/// %2 = struct (%f1, %f2)
|
/// %2 = struct (%f1, %f2)
|
||||||
///
|
///
|
||||||
|
/// This works in a similar fashion for `copy_addr`. If the source value of the `copy_addr` is
|
||||||
|
/// already available, the `copy_addr` is replaced by a `store` of the available value.
|
||||||
|
///
|
||||||
/// The algorithm is a data flow analysis which starts at the original load and searches
|
/// The algorithm is a data flow analysis which starts at the original load and searches
|
||||||
/// for preceding stores or loads by following the control flow in backward direction.
|
/// for preceding stores or loads by following the control flow in backward direction.
|
||||||
/// The preceding stores and loads provide the "available values" with which the original
|
/// The preceding stores and loads provide the "available values" with which the original
|
||||||
@@ -99,7 +102,7 @@ func eliminateRedundantLoads(in function: Function,
|
|||||||
while let i = inst {
|
while let i = inst {
|
||||||
defer { inst = i.previous }
|
defer { inst = i.previous }
|
||||||
|
|
||||||
if let load = inst as? LoadInst {
|
if let load = inst as? LoadingInstruction {
|
||||||
if !context.continueWithNextSubpassRun(for: load) {
|
if !context.continueWithNextSubpassRun(for: load) {
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
@@ -116,7 +119,59 @@ func eliminateRedundantLoads(in function: Function,
|
|||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
private func tryEliminate(load: LoadInst, complexityBudget: inout Int, _ context: FunctionPassContext) -> Bool {
|
/// Either a `load` or a `copy_addr` (which is equivalent to a load+store).
|
||||||
|
private protocol LoadingInstruction: Instruction {
|
||||||
|
var address: Value { get }
|
||||||
|
var type: Type { get }
|
||||||
|
var ownership: Ownership { get }
|
||||||
|
var loadOwnership: LoadInst.LoadOwnership { get }
|
||||||
|
var canLoadValue: Bool { get }
|
||||||
|
func trySplit(_ context: FunctionPassContext) -> Bool
|
||||||
|
func materializeLoadForReplacement(_ context: FunctionPassContext) -> LoadInst
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LoadInst : LoadingInstruction {
|
||||||
|
// We know that the type is loadable because - well - this is a load.
|
||||||
|
var canLoadValue: Bool { true }
|
||||||
|
|
||||||
|
// Nothing to materialize, because this is already a `load`.
|
||||||
|
func materializeLoadForReplacement(_ context: FunctionPassContext) -> LoadInst { return self }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CopyAddrInst : LoadingInstruction {
|
||||||
|
var address: Value { source }
|
||||||
|
var type: Type { address.type.objectType }
|
||||||
|
var typeIsLoadable: Bool { type.isLoadable(in: parentFunction) }
|
||||||
|
|
||||||
|
var ownership: Ownership {
|
||||||
|
if !parentFunction.hasOwnership || type.isTrivial(in: parentFunction) {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
// Regardless of if the copy is taking or copying, the loaded value is an owned value.
|
||||||
|
return .owned
|
||||||
|
}
|
||||||
|
|
||||||
|
var canLoadValue: Bool {
|
||||||
|
if !source.type.isLoadable(in: parentFunction) {
|
||||||
|
// Although the original load's type is loadable (obviously), it can be projected-out
|
||||||
|
// from the copy_addr's type which might be not loadable.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !parentFunction.hasOwnership {
|
||||||
|
if !isTakeOfSrc || !isInitializationOfDest {
|
||||||
|
// For simplicity, bail if we would have to insert compensating retains and releases.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func materializeLoadForReplacement(_ context: FunctionPassContext) -> LoadInst {
|
||||||
|
return replaceWithLoadAndStore(context).load
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tryEliminate(load: LoadingInstruction, complexityBudget: inout Int, _ context: FunctionPassContext) -> Bool {
|
||||||
switch load.isRedundant(complexityBudget: &complexityBudget, context) {
|
switch load.isRedundant(complexityBudget: &complexityBudget, context) {
|
||||||
case .notRedundant:
|
case .notRedundant:
|
||||||
return false
|
return false
|
||||||
@@ -136,9 +191,12 @@ private func tryEliminate(load: LoadInst, complexityBudget: inout Int, _ context
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension LoadInst {
|
private extension LoadingInstruction {
|
||||||
|
|
||||||
func isEligibleForElimination(in variant: RedundantLoadEliminationVariant, _ context: FunctionPassContext) -> Bool {
|
func isEligibleForElimination(in variant: RedundantLoadEliminationVariant, _ context: FunctionPassContext) -> Bool {
|
||||||
|
if !canLoadValue {
|
||||||
|
return false
|
||||||
|
}
|
||||||
switch variant {
|
switch variant {
|
||||||
case .mandatory, .mandatoryInGlobalInit:
|
case .mandatory, .mandatoryInGlobalInit:
|
||||||
if loadOwnership == .take {
|
if loadOwnership == .take {
|
||||||
@@ -171,20 +229,6 @@ private extension LoadInst {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DataflowResult {
|
|
||||||
case notRedundant
|
|
||||||
case redundant([AvailableValue])
|
|
||||||
case maybePartiallyRedundant(AccessPath)
|
|
||||||
|
|
||||||
init(notRedundantWith subPath: AccessPath?) {
|
|
||||||
if let subPath = subPath {
|
|
||||||
self = .maybePartiallyRedundant(subPath)
|
|
||||||
} else {
|
|
||||||
self = .notRedundant
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isRedundant(complexityBudget: inout Int, _ context: FunctionPassContext) -> DataflowResult {
|
func isRedundant(complexityBudget: inout Int, _ context: FunctionPassContext) -> DataflowResult {
|
||||||
return isRedundant(at: address.constantAccessPath, complexityBudget: &complexityBudget, context)
|
return isRedundant(at: address.constantAccessPath, complexityBudget: &complexityBudget, context)
|
||||||
}
|
}
|
||||||
@@ -285,7 +329,7 @@ private extension LoadInst {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func replace(load: LoadInst, with availableValues: [AvailableValue], _ context: FunctionPassContext) {
|
private func replace(load: LoadingInstruction, with availableValues: [AvailableValue], _ context: FunctionPassContext) {
|
||||||
var ssaUpdater = SSAUpdater(function: load.parentFunction,
|
var ssaUpdater = SSAUpdater(function: load.parentFunction,
|
||||||
type: load.type, ownership: load.ownership, context)
|
type: load.type, ownership: load.ownership, context)
|
||||||
|
|
||||||
@@ -318,14 +362,16 @@ private func replace(load: LoadInst, with availableValues: [AvailableValue], _ c
|
|||||||
newValue = ssaUpdater.getValue(inMiddleOf: load.parentBlock)
|
newValue = ssaUpdater.getValue(inMiddleOf: load.parentBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure to keep dependencies valid after replacing the load
|
let originalLoad = load.materializeLoadForReplacement(context)
|
||||||
insertMarkDependencies(for: load, context)
|
|
||||||
|
|
||||||
load.replace(with: newValue, context)
|
// Make sure to keep dependencies valid after replacing the load
|
||||||
|
insertMarkDependencies(for: originalLoad, context)
|
||||||
|
|
||||||
|
originalLoad.replace(with: newValue, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func provideValue(
|
private func provideValue(
|
||||||
for load: LoadInst,
|
for load: LoadingInstruction,
|
||||||
from availableValue: AvailableValue,
|
from availableValue: AvailableValue,
|
||||||
_ context: FunctionPassContext
|
_ context: FunctionPassContext
|
||||||
) -> Value {
|
) -> Value {
|
||||||
@@ -341,9 +387,9 @@ private func provideValue(
|
|||||||
builder: availableValue.getBuilderForProjections(context))
|
builder: availableValue.getBuilderForProjections(context))
|
||||||
case .take:
|
case .take:
|
||||||
if projectionPath.isEmpty {
|
if projectionPath.isEmpty {
|
||||||
return shrinkMemoryLifetime(from: load, to: availableValue, context)
|
return shrinkMemoryLifetime(to: availableValue, context)
|
||||||
} else {
|
} else {
|
||||||
return shrinkMemoryLifetimeAndSplit(from: load, to: availableValue, projectionPath: projectionPath, context)
|
return shrinkMemoryLifetimeAndSplit(to: availableValue, projectionPath: projectionPath, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,7 +438,7 @@ private struct MarkDependenceInserter : AddressUseDefWalker {
|
|||||||
/// ...
|
/// ...
|
||||||
/// // replace %2 with %1
|
/// // replace %2 with %1
|
||||||
///
|
///
|
||||||
private func shrinkMemoryLifetime(from load: LoadInst, to availableValue: AvailableValue, _ context: FunctionPassContext) -> Value {
|
private func shrinkMemoryLifetime(to availableValue: AvailableValue, _ context: FunctionPassContext) -> Value {
|
||||||
switch availableValue {
|
switch availableValue {
|
||||||
case .viaLoad(let availableLoad):
|
case .viaLoad(let availableLoad):
|
||||||
assert(availableLoad.loadOwnership == .copy)
|
assert(availableLoad.loadOwnership == .copy)
|
||||||
@@ -442,7 +488,7 @@ private func shrinkMemoryLifetime(from load: LoadInst, to availableValue: Availa
|
|||||||
/// ...
|
/// ...
|
||||||
/// // replace %3 with %1
|
/// // replace %3 with %1
|
||||||
///
|
///
|
||||||
private func shrinkMemoryLifetimeAndSplit(from load: LoadInst, to availableValue: AvailableValue, projectionPath: SmallProjectionPath, _ context: FunctionPassContext) -> Value {
|
private func shrinkMemoryLifetimeAndSplit(to availableValue: AvailableValue, projectionPath: SmallProjectionPath, _ context: FunctionPassContext) -> Value {
|
||||||
switch availableValue {
|
switch availableValue {
|
||||||
case .viaLoad(let availableLoad):
|
case .viaLoad(let availableLoad):
|
||||||
assert(availableLoad.loadOwnership == .copy)
|
assert(availableLoad.loadOwnership == .copy)
|
||||||
@@ -462,6 +508,20 @@ private func shrinkMemoryLifetimeAndSplit(from load: LoadInst, to availableValue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum DataflowResult {
|
||||||
|
case notRedundant
|
||||||
|
case redundant([AvailableValue])
|
||||||
|
case maybePartiallyRedundant(AccessPath)
|
||||||
|
|
||||||
|
init(notRedundantWith subPath: AccessPath?) {
|
||||||
|
if let subPath = subPath {
|
||||||
|
self = .maybePartiallyRedundant(subPath)
|
||||||
|
} else {
|
||||||
|
self = .notRedundant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Either a `load` or `store` which is preceding the original load and provides the loaded value.
|
/// Either a `load` or `store` which is preceding the original load and provides the loaded value.
|
||||||
private enum AvailableValue {
|
private enum AvailableValue {
|
||||||
case viaLoad(LoadInst)
|
case viaLoad(LoadInst)
|
||||||
@@ -505,7 +565,7 @@ private extension Array where Element == AvailableValue {
|
|||||||
func replaceCopyAddrsWithLoadsAndStores(_ context: FunctionPassContext) -> [AvailableValue] {
|
func replaceCopyAddrsWithLoadsAndStores(_ context: FunctionPassContext) -> [AvailableValue] {
|
||||||
return map {
|
return map {
|
||||||
if case .viaCopyAddr(let copyAddr) = $0 {
|
if case .viaCopyAddr(let copyAddr) = $0 {
|
||||||
return .viaStore(copyAddr.replaceWithLoadAndStore(context))
|
return .viaStore(copyAddr.replaceWithLoadAndStore(context).store)
|
||||||
} else {
|
} else {
|
||||||
return $0
|
return $0
|
||||||
}
|
}
|
||||||
@@ -514,7 +574,7 @@ private extension Array where Element == AvailableValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct InstructionScanner {
|
private struct InstructionScanner {
|
||||||
private let load: LoadInst
|
private let load: LoadingInstruction
|
||||||
private let accessPath: AccessPath
|
private let accessPath: AccessPath
|
||||||
private let storageDefBlock: BasicBlock?
|
private let storageDefBlock: BasicBlock?
|
||||||
private let aliasAnalysis: AliasAnalysis
|
private let aliasAnalysis: AliasAnalysis
|
||||||
@@ -522,7 +582,7 @@ private struct InstructionScanner {
|
|||||||
private(set) var potentiallyRedundantSubpath: AccessPath? = nil
|
private(set) var potentiallyRedundantSubpath: AccessPath? = nil
|
||||||
private(set) var availableValues = Array<AvailableValue>()
|
private(set) var availableValues = Array<AvailableValue>()
|
||||||
|
|
||||||
init(load: LoadInst, accessPath: AccessPath, _ aliasAnalysis: AliasAnalysis) {
|
init(load: LoadingInstruction, accessPath: AccessPath, _ aliasAnalysis: AliasAnalysis) {
|
||||||
self.load = load
|
self.load = load
|
||||||
self.accessPath = accessPath
|
self.accessPath = accessPath
|
||||||
self.storageDefBlock = accessPath.base.reference?.referenceRoot.parentBlock
|
self.storageDefBlock = accessPath.base.reference?.referenceRoot.parentBlock
|
||||||
@@ -616,7 +676,7 @@ private struct InstructionScanner {
|
|||||||
potentiallyRedundantSubpath = precedingStorePath
|
potentiallyRedundantSubpath = precedingStorePath
|
||||||
}
|
}
|
||||||
|
|
||||||
case let preceedingCopy as CopyAddrInst where preceedingCopy.canProvideValue:
|
case let preceedingCopy as CopyAddrInst where preceedingCopy.canLoadValue:
|
||||||
let copyPath = preceedingCopy.destination.constantAccessPath
|
let copyPath = preceedingCopy.destination.constantAccessPath
|
||||||
if copyPath.getMaterializableProjection(to: accessPath) != nil {
|
if copyPath.getMaterializableProjection(to: accessPath) != nil {
|
||||||
availableValues.append(.viaCopyAddr(preceedingCopy))
|
availableValues.append(.viaCopyAddr(preceedingCopy))
|
||||||
@@ -712,20 +772,3 @@ private struct Liverange {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension CopyAddrInst {
|
|
||||||
var canProvideValue: Bool {
|
|
||||||
if !source.type.isLoadable(in: parentFunction) {
|
|
||||||
// Although the original load's type is loadable (obviously), it can be projected-out
|
|
||||||
// from the copy_addr's type which might be not loadable.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !parentFunction.hasOwnership {
|
|
||||||
if !isTakeOfSrc || !isInitializationOfDest {
|
|
||||||
// For simplicity, bail if we would have to insert compensating retains and releases.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -941,27 +941,74 @@ extension CheckedCastAddrBranchInst {
|
|||||||
|
|
||||||
extension CopyAddrInst {
|
extension CopyAddrInst {
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func replaceWithLoadAndStore(_ context: some MutatingContext) -> StoreInst {
|
func trySplit(_ context: FunctionPassContext) -> Bool {
|
||||||
let loadOwnership: LoadInst.LoadOwnership
|
let builder = Builder(before: self, context)
|
||||||
let storeOwnership: StoreInst.StoreOwnership
|
if source.type.isStruct {
|
||||||
if parentFunction.hasOwnership {
|
if (source.type.nominal as! StructDecl).hasUnreferenceableStorage {
|
||||||
if source.type.isTrivial(in: parentFunction) {
|
return false
|
||||||
loadOwnership = .trivial
|
|
||||||
storeOwnership = .trivial
|
|
||||||
} else {
|
|
||||||
loadOwnership = isTakeOfSrc ? .take : .copy
|
|
||||||
storeOwnership = isInitializationOfDest ? .initialize : .assign
|
|
||||||
}
|
}
|
||||||
} else {
|
guard let fields = source.type.getNominalFields(in: parentFunction) else {
|
||||||
loadOwnership = .unqualified
|
return false
|
||||||
storeOwnership = .unqualified
|
}
|
||||||
|
for idx in 0..<fields.count {
|
||||||
|
let srcFieldAddr = builder.createStructElementAddr(structAddress: source, fieldIndex: idx)
|
||||||
|
let destFieldAddr = builder.createStructElementAddr(structAddress: destination, fieldIndex: idx)
|
||||||
|
builder.createCopyAddr(from: srcFieldAddr, to: destFieldAddr,
|
||||||
|
takeSource: isTake(for: srcFieldAddr), initializeDest: isInitializationOfDest)
|
||||||
|
}
|
||||||
|
context.erase(instruction: self)
|
||||||
|
return true
|
||||||
|
} else if source.type.isTuple {
|
||||||
|
let builder = Builder(before: self, context)
|
||||||
|
for idx in 0..<source.type.tupleElements.count {
|
||||||
|
let srcFieldAddr = builder.createTupleElementAddr(tupleAddress: source, elementIndex: idx)
|
||||||
|
let destFieldAddr = builder.createTupleElementAddr(tupleAddress: destination, elementIndex: idx)
|
||||||
|
builder.createCopyAddr(from: srcFieldAddr, to: destFieldAddr,
|
||||||
|
takeSource: isTake(for: srcFieldAddr), initializeDest: isInitializationOfDest)
|
||||||
|
}
|
||||||
|
context.erase(instruction: self)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func isTake(for fieldValue: Value) -> Bool {
|
||||||
|
return isTakeOfSrc && !fieldValue.type.objectType.isTrivial(in: parentFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func replaceWithLoadAndStore(_ context: some MutatingContext) -> (load: LoadInst, store: StoreInst) {
|
||||||
let builder = Builder(before: self, context)
|
let builder = Builder(before: self, context)
|
||||||
let value = builder.createLoad(fromAddress: source, ownership: loadOwnership)
|
let load = builder.createLoad(fromAddress: source, ownership: loadOwnership)
|
||||||
let store = builder.createStore(source: value, destination: destination, ownership: storeOwnership)
|
let store = builder.createStore(source: load, destination: destination, ownership: storeOwnership)
|
||||||
context.erase(instruction: self)
|
context.erase(instruction: self)
|
||||||
return store
|
return (load, store)
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadOwnership: LoadInst.LoadOwnership {
|
||||||
|
if !parentFunction.hasOwnership {
|
||||||
|
return .unqualified
|
||||||
|
}
|
||||||
|
if type.isTrivial(in: parentFunction) {
|
||||||
|
return .trivial
|
||||||
|
}
|
||||||
|
if isTakeOfSrc {
|
||||||
|
return .take
|
||||||
|
}
|
||||||
|
return .copy
|
||||||
|
}
|
||||||
|
|
||||||
|
var storeOwnership: StoreInst.StoreOwnership {
|
||||||
|
if !parentFunction.hasOwnership {
|
||||||
|
return .unqualified
|
||||||
|
}
|
||||||
|
if type.isTrivial(in: parentFunction) {
|
||||||
|
return .trivial
|
||||||
|
}
|
||||||
|
if isInitializationOfDest {
|
||||||
|
return .initialize
|
||||||
|
}
|
||||||
|
return .assign
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public func pullback<T>(
|
|||||||
|
|
||||||
// CHECK-LABEL: sil private @$s4main19testUpdateByCallingyyKF8fOfArrayL_5arraySdSaySdG_tFySdzcfU_TJpSUpSr :
|
// CHECK-LABEL: sil private @$s4main19testUpdateByCallingyyKF8fOfArrayL_5arraySdSaySdG_tFySdzcfU_TJpSUpSr :
|
||||||
// CHECK: alloc_stack $Double, var, name "derivative of 'element' in scope at {{.*}} (scope #3)"
|
// CHECK: alloc_stack $Double, var, name "derivative of 'element' in scope at {{.*}} (scope #3)"
|
||||||
// CHECK: debug_value %{{.*}} : $Builtin.FPIEEE64, var, (name "derivative of 'element' in scope at {{.*}} (scope #1)"
|
// CHECK: debug_value %{{.*}} : $Builtin.FPIEEE64, var, (name "derivative of 'element' in scope at {{.*}} (scope #{{.*}})"
|
||||||
|
|
||||||
public extension Array where Element: Differentiable {
|
public extension Array where Element: Differentiable {
|
||||||
@inlinable
|
@inlinable
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ func doSomething() {}
|
|||||||
// SIL: [[BOX:%.*]] = alloc_stack [var_decl] $Int, var, name "x"
|
// SIL: [[BOX:%.*]] = alloc_stack [var_decl] $Int, var, name "x"
|
||||||
// SIL: [[INOUT_BOX:%.*]] = alloc_stack [var_decl] $Int, var, name "x2"
|
// SIL: [[INOUT_BOX:%.*]] = alloc_stack [var_decl] $Int, var, name "x2"
|
||||||
// SIL: [[ACCESS:%.*]] = begin_access [modify] [static] [[BOX]]
|
// SIL: [[ACCESS:%.*]] = begin_access [modify] [static] [[BOX]]
|
||||||
// SIL: copy_addr [take] [[ACCESS]] to [init] [[INOUT_BOX]]
|
// SIL: store {{%.*}} to [[INOUT_BOX]]
|
||||||
// SIL: [[FUNC:%.*]] = function_ref @$s18reference_bindings11doSomethingyyF : $@convention(thin) () -> ()
|
// SIL: [[FUNC:%.*]] = function_ref @$s18reference_bindings11doSomethingyyF : $@convention(thin) () -> ()
|
||||||
// SIL: apply [[FUNC]]()
|
// SIL: apply [[FUNC]]()
|
||||||
// SIL: copy_addr [take] [[INOUT_BOX]] to [init] [[ACCESS]]
|
// SIL: store {{%.*}} to [[ACCESS]]
|
||||||
// SIL: end_access [[ACCESS]]
|
// SIL: end_access [[ACCESS]]
|
||||||
// SIL: } // end sil function '$s18reference_bindings13testBindToVaryyF'
|
// SIL: } // end sil function '$s18reference_bindings13testBindToVaryyF'
|
||||||
func testBindToVar() {
|
func testBindToVar() {
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ public struct PubStruct: PubProto {
|
|||||||
// protocol witness for static PubProto.root.getter in conformance PubStruct
|
// protocol witness for static PubProto.root.getter in conformance PubStruct
|
||||||
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PubStructVAA0B5ProtoA2aDP4roots6UInt16VvgZTW : $@convention(witness_method: PubProto) (@thick PubStruct.Type) -> UInt16 {
|
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PubStructVAA0B5ProtoA2aDP4roots6UInt16VvgZTW : $@convention(witness_method: PubProto) (@thick PubStruct.Type) -> UInt16 {
|
||||||
// CHECK-DAG: function_ref @$s3Lib9PubStructV4roots6UInt16VvgZ : $@convention(method) (@thin PubStruct.Type) -> UInt16
|
// CHECK-DAG: function_ref @$s3Lib9PubStructV4roots6UInt16VvgZ : $@convention(method) (@thin PubStruct.Type) -> UInt16
|
||||||
// CHECK-DAG: sil [canonical] @$s3Lib9PubStructV4roots6UInt16VvgZ : $@convention(method) (@thin PubStruct.Type) -> UInt16
|
// CHECK-DAG: sil [serialized_for_package] [canonical] [ossa] @$s3Lib9PubStructV4roots6UInt16VvgZ : $@convention(method) (@thin PubStruct.Type) -> UInt16
|
||||||
public static let root: UInt16 = 1 << 0
|
public static let root: UInt16 = 1 << 0
|
||||||
|
|
||||||
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PubStructVAA0B5ProtoA2aDP3envs6UInt16VvgTW : $@convention(witness_method: PubProto) (@in_guaranteed PubStruct) -> UInt16 {
|
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PubStructVAA0B5ProtoA2aDP3envs6UInt16VvgTW : $@convention(witness_method: PubProto) (@in_guaranteed PubStruct) -> UInt16 {
|
||||||
@@ -365,7 +365,7 @@ package class PkgKlassZ: PkgProto {
|
|||||||
// protocol witness for static PkgProto.root.getter in conformance PkgKlassZ
|
// protocol witness for static PkgProto.root.getter in conformance PkgKlassZ
|
||||||
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PkgKlassZCAA0B5ProtoA2aDP4roots6UInt16VvgZTW : $@convention(witness_method: PkgProto) (@thick PkgKlassZ.Type) -> UInt16 {
|
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PkgKlassZCAA0B5ProtoA2aDP4roots6UInt16VvgZTW : $@convention(witness_method: PkgProto) (@thick PkgKlassZ.Type) -> UInt16 {
|
||||||
// CHECK-DAG: function_ref @$s3Lib9PkgKlassZC4roots6UInt16VvgZ : $@convention(method) (@thick PkgKlassZ.Type) -> UInt16
|
// CHECK-DAG: function_ref @$s3Lib9PkgKlassZC4roots6UInt16VvgZ : $@convention(method) (@thick PkgKlassZ.Type) -> UInt16
|
||||||
// CHECK-DAG: sil package_external [canonical] @$s3Lib9PkgKlassZC4roots6UInt16VvgZ : $@convention(method) (@thick PkgKlassZ.Type) -> UInt16
|
// CHECK-DAG: sil package [serialized_for_package] [canonical] [ossa] @$s3Lib9PkgKlassZC4roots6UInt16VvgZ : $@convention(method) (@thick PkgKlassZ.Type) -> UInt16
|
||||||
package static let root: UInt16 = 1 << 0
|
package static let root: UInt16 = 1 << 0
|
||||||
|
|
||||||
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PkgKlassZCAA0B5ProtoA2aDP3envs6UInt16VvgTW : $@convention(witness_method: PkgProto) (@in_guaranteed PkgKlassZ) -> UInt16 {
|
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PkgKlassZCAA0B5ProtoA2aDP3envs6UInt16VvgTW : $@convention(witness_method: PkgProto) (@in_guaranteed PkgKlassZ) -> UInt16 {
|
||||||
@@ -396,7 +396,7 @@ package struct PkgStruct: PkgProto { /// NOTE: witness thunks get `shared` linka
|
|||||||
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PkgStructVAA0B5ProtoA2aDP4roots6UInt16VvgZTW : $@convention(witness_method: PkgProto) (@thick PkgStruct.Type) -> UInt16 {
|
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PkgStructVAA0B5ProtoA2aDP4roots6UInt16VvgZTW : $@convention(witness_method: PkgProto) (@thick PkgStruct.Type) -> UInt16 {
|
||||||
// CHECK-DAG: function_ref @$s3Lib9PkgStructV4roots6UInt16VvgZ : $@convention(method) (@thin PkgStruct.Type)
|
// CHECK-DAG: function_ref @$s3Lib9PkgStructV4roots6UInt16VvgZ : $@convention(method) (@thin PkgStruct.Type)
|
||||||
// static PkgStruct.root.getter
|
// static PkgStruct.root.getter
|
||||||
// CHECK-DAG: sil package_external [canonical] @$s3Lib9PkgStructV4roots6UInt16VvgZ : $@convention(method) (@thin PkgStruct.Type) -> UInt16
|
// CHECK-DAG: sil package [serialized_for_package] [canonical] [ossa] @$s3Lib9PkgStructV4roots6UInt16VvgZ : $@convention(method) (@thin PkgStruct.Type) -> UInt16
|
||||||
package static let root: UInt16 = 1 << 0
|
package static let root: UInt16 = 1 << 0
|
||||||
|
|
||||||
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PkgStructVAA0B5ProtoA2aDP3envs6UInt16VvsTW : $@convention(witness_method: PkgProto) (UInt16, @inout PkgStruct) -> () {
|
// CHECK-DAG: sil shared [transparent] [serialized] [thunk] [canonical] [ossa] @$s3Lib9PkgStructVAA0B5ProtoA2aDP3envs6UInt16VvsTW : $@convention(witness_method: PkgProto) (UInt16, @inout PkgStruct) -> () {
|
||||||
|
|||||||
@@ -1386,3 +1386,45 @@ bb0(%0 : $*B, %1 : $*B):
|
|||||||
return %3
|
return %3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil @copyaddr_after_store :
|
||||||
|
// CHECK: [[S:%.*]] = alloc_stack $B
|
||||||
|
// CHECK: store %1 to [[S]]
|
||||||
|
// CHECK: store %1 to %0
|
||||||
|
// CHECK-LABEL: } // end sil function 'copyaddr_after_store'
|
||||||
|
sil @copyaddr_after_store : $@convention(thin) (@owned B) -> @out B {
|
||||||
|
bb0(%0 : $*B, %1 : $B):
|
||||||
|
%2 = alloc_stack $B
|
||||||
|
store %1 to %2
|
||||||
|
copy_addr [take] %2 to [init] %0
|
||||||
|
dealloc_stack %2
|
||||||
|
%r = tuple ()
|
||||||
|
return %r
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil @dont_remove_copying_copyaddr :
|
||||||
|
// CHECK: copy_addr
|
||||||
|
// CHECK-LABEL: } // end sil function 'dont_remove_copying_copyaddr'
|
||||||
|
sil @dont_remove_copying_copyaddr : $@convention(thin) (@owned B) -> @out B {
|
||||||
|
bb0(%0 : $*B, %1 : $B):
|
||||||
|
%2 = alloc_stack $B
|
||||||
|
store %1 to %2
|
||||||
|
copy_addr %2 to [init] %0
|
||||||
|
destroy_addr %2
|
||||||
|
dealloc_stack %2
|
||||||
|
%r = tuple ()
|
||||||
|
return %r
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil @dont_remove_assigning_copyaddr :
|
||||||
|
// CHECK: copy_addr
|
||||||
|
// CHECK-LABEL: } // end sil function 'dont_remove_assigning_copyaddr'
|
||||||
|
sil @dont_remove_assigning_copyaddr : $@convention(thin) (@inout B, @owned B) -> () {
|
||||||
|
bb0(%0 : $*B, %1 : $B):
|
||||||
|
%2 = alloc_stack $B
|
||||||
|
store %1 to %2
|
||||||
|
copy_addr [take] %2 to %0
|
||||||
|
dealloc_stack %2
|
||||||
|
%r = tuple ()
|
||||||
|
return %r
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1233,7 +1233,7 @@ bb0(%0 : $*MyArray<MyStruct>):
|
|||||||
|
|
||||||
// CHECK-LABEL: @load_trivial_from_store :
|
// CHECK-LABEL: @load_trivial_from_store :
|
||||||
// CHECK: [[B:%.*]] = begin_borrow %0
|
// CHECK: [[B:%.*]] = begin_borrow %0
|
||||||
// CHECK: [[I:%.*]] = struct_extract %2 : $Agg3, #Agg3.i
|
// CHECK: [[I:%.*]] = struct_extract [[B]] : $Agg3, #Agg3.i
|
||||||
// CHECK: end_borrow [[B]]
|
// CHECK: end_borrow [[B]]
|
||||||
// CHECK: store %0 to [assign] %1
|
// CHECK: store %0 to [assign] %1
|
||||||
// CHECK: return [[I]]
|
// CHECK: return [[I]]
|
||||||
@@ -1248,7 +1248,7 @@ bb0(%0 : @owned $Agg3, %1 : $*Agg3):
|
|||||||
|
|
||||||
// CHECK-LABEL: @load_copy_from_store :
|
// CHECK-LABEL: @load_copy_from_store :
|
||||||
// CHECK: [[B:%.*]] = begin_borrow %0
|
// CHECK: [[B:%.*]] = begin_borrow %0
|
||||||
// CHECK: [[K:%.*]] = struct_extract %2 : $Agg3, #Agg3.k
|
// CHECK: [[K:%.*]] = struct_extract [[B]] : $Agg3, #Agg3.k
|
||||||
// CHECK: [[C:%.*]] = copy_value [[K]]
|
// CHECK: [[C:%.*]] = copy_value [[K]]
|
||||||
// CHECK: end_borrow [[B]]
|
// CHECK: end_borrow [[B]]
|
||||||
// CHECK: store %0 to [assign] %1
|
// CHECK: store %0 to [assign] %1
|
||||||
@@ -1261,6 +1261,65 @@ bb0(%0 : @owned $Agg3, %1 : $*Agg3):
|
|||||||
%4 = load [copy] %3 : $*Klass
|
%4 = load [copy] %3 : $*Klass
|
||||||
return %4 : $Klass
|
return %4 : $Klass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @copyaddr_trivial_from_store :
|
||||||
|
// CHECK: [[B:%.*]] = begin_borrow %1
|
||||||
|
// CHECK: [[I:%.*]] = struct_extract [[B]] : $Agg3, #Agg3.i
|
||||||
|
// CHECK: end_borrow [[B]]
|
||||||
|
// CHECK: store %1 to [assign] %2
|
||||||
|
// CHECK: store [[I]] to [trivial] %0
|
||||||
|
// CHECK: } // end sil function 'copyaddr_trivial_from_store'
|
||||||
|
sil [ossa] @copyaddr_trivial_from_store : $@convention(thin) (@owned Agg3, @inout Agg3) -> @out Int {
|
||||||
|
bb0(%0 : $*Int, %1 : @owned $Agg3, %2 : $*Agg3):
|
||||||
|
store %1 to [assign] %2
|
||||||
|
%3 = struct_element_addr %2, #Agg3.i
|
||||||
|
copy_addr %3 to %0
|
||||||
|
%r = tuple ()
|
||||||
|
return %r
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @copyaddr_copy_from_store :
|
||||||
|
// CHECK: [[B:%.*]] = begin_borrow %1
|
||||||
|
// CHECK: [[K:%.*]] = struct_extract [[B]] : $Agg3, #Agg3.k
|
||||||
|
// CHECK: [[C:%.*]] = copy_value [[K]]
|
||||||
|
// CHECK: end_borrow [[B]]
|
||||||
|
// CHECK: store %1 to [assign] %2
|
||||||
|
// CHECK: store [[C]] to [init] %0
|
||||||
|
// CHECK: } // end sil function 'copyaddr_copy_from_store'
|
||||||
|
sil [ossa] @copyaddr_copy_from_store : $@convention(thin) (@owned Agg3, @inout Agg3) -> @out Klass {
|
||||||
|
bb0(%0 : $*Klass, %1 : @owned $Agg3, %2 : $*Agg3):
|
||||||
|
store %1 to [assign] %2
|
||||||
|
%3 = struct_element_addr %2, #Agg3.k
|
||||||
|
copy_addr %3 to [init] %0
|
||||||
|
%r = tuple ()
|
||||||
|
return %r
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil [ossa] @split_copyaddr :
|
||||||
|
// CHECK: [[A:%.*]] = tuple_element_addr %0 : $*(Agg3, Klass), 0
|
||||||
|
// CHECK: [[KA:%.*]] = struct_element_addr %11 : $*Agg3, #Agg3.k
|
||||||
|
// CHECK: store %1 to [init] [[KA]] : $*Klass
|
||||||
|
// CHECK: [[KI:%.*]] = struct_element_addr %11 : $*Agg3, #Agg3.i
|
||||||
|
// CHECK: store %2 to [trivial] [[KI]] : $*Int
|
||||||
|
// CHECK: [[I:%.*]] = tuple_element_addr %0 : $*(Agg3, Klass), 1
|
||||||
|
// CHECK: store %3 to [init] [[I]] : $*Klass
|
||||||
|
// CHECK: } // end sil function 'split_copyaddr'
|
||||||
|
sil [ossa] @split_copyaddr : $@convention(thin) (@owned Klass, Int, @owned Klass) -> @out (Agg3, Klass) {
|
||||||
|
bb0(%0 : $*(Agg3, Klass), %1 : @owned $Klass, %2 : $Int, %3 : @owned $Klass):
|
||||||
|
%4 = alloc_stack $(Agg3, Klass)
|
||||||
|
%5 = tuple_element_addr %4, 0
|
||||||
|
%6 = tuple_element_addr %4, 1
|
||||||
|
%7 = struct_element_addr %5, #Agg3.k
|
||||||
|
%8 = struct_element_addr %5, #Agg3.i
|
||||||
|
store %1 to [init] %7
|
||||||
|
store %2 to [trivial] %8
|
||||||
|
store %3 to [init] %6
|
||||||
|
copy_addr [take] %4 to [init] %0
|
||||||
|
dealloc_stack %4
|
||||||
|
%r = tuple ()
|
||||||
|
return %r
|
||||||
|
}
|
||||||
|
|
||||||
// CHECK-LABEL: @load_take_from_store_assign :
|
// CHECK-LABEL: @load_take_from_store_assign :
|
||||||
// CHECK: [[DS:%.*]] = destructure_struct %0
|
// CHECK: [[DS:%.*]] = destructure_struct %0
|
||||||
// CHECK: [[TADDR:%.*]] = struct_element_addr %1 : $*Agg2, #Agg2.t
|
// CHECK: [[TADDR:%.*]] = struct_element_addr %1 : $*Agg2, #Agg2.t
|
||||||
@@ -1281,6 +1340,26 @@ bb0(%0 : @owned $Agg2, %1 : $*Agg2, %2 : @owned $Klass):
|
|||||||
return %6 : $Klass
|
return %6 : $Klass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @copyaddr_take_from_store_assign :
|
||||||
|
// CHECK: [[DS:%.*]] = destructure_struct %1
|
||||||
|
// CHECK: [[TADDR:%.*]] = struct_element_addr %2 : $*Agg2, #Agg2.t
|
||||||
|
// CHECK: ([[T0:%.*]], [[T1:%.*]]) = destructure_tuple [[DS]]
|
||||||
|
// CHECK: [[TA0:%.*]] = tuple_element_addr [[TADDR]] : $*(Klass, Klass), 0
|
||||||
|
// CHECK: destroy_addr [[TA0]]
|
||||||
|
// CHECK: [[TA1:%.*]] = tuple_element_addr [[TADDR]] : $*(Klass, Klass), 1
|
||||||
|
// CHECK: store [[T0]] to [init] %0
|
||||||
|
// CHECK: } // end sil function 'copyaddr_take_from_store_assign'
|
||||||
|
sil [ossa] @copyaddr_take_from_store_assign : $@convention(thin) (@owned Agg2, @inout Agg2, @owned Klass) -> @out Klass {
|
||||||
|
bb0(%0: $*Klass, %1 : @owned $Agg2, %2 : $*Agg2, %3 : @owned $Klass):
|
||||||
|
store %1 to [assign] %2
|
||||||
|
%4 = struct_element_addr %2, #Agg2.t
|
||||||
|
%5 = tuple_element_addr %4, 0
|
||||||
|
copy_addr [take] %5 to [init] %0
|
||||||
|
store %3 to [init] %5
|
||||||
|
%r = tuple ()
|
||||||
|
return %r
|
||||||
|
}
|
||||||
|
|
||||||
// CHECK-LABEL: @load_take_from_store_init :
|
// CHECK-LABEL: @load_take_from_store_init :
|
||||||
// CHECK: [[DS:%.*]] = destructure_struct %0
|
// CHECK: [[DS:%.*]] = destructure_struct %0
|
||||||
// CHECK: [[TADDR:%.*]] = struct_element_addr %1 : $*Agg2, #Agg2.t
|
// CHECK: [[TADDR:%.*]] = struct_element_addr %1 : $*Agg2, #Agg2.t
|
||||||
|
|||||||
13
test/SILOptimizer/static_init_globals.swift
Normal file
13
test/SILOptimizer/static_init_globals.swift
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// RUN: %target-swift-frontend %s -module-name=test -emit-sil | %FileCheck %s
|
||||||
|
|
||||||
|
public struct MyStruct {
|
||||||
|
// CHECK-LABEL: sil_global [let] @$s4test8MyStructV1rSnySiGvpZ : $Range<Int> = {
|
||||||
|
// CHECK-NEXT: %0 = integer_literal $Builtin.Int{{[0-9]+}}, 1
|
||||||
|
// CHECK-NEXT: %1 = struct $Int (%0)
|
||||||
|
// CHECK-NEXT: %2 = integer_literal $Builtin.Int{{[0-9]+}}, 3
|
||||||
|
// CHECK-NEXT: %3 = struct $Int (%2)
|
||||||
|
// CHECK-NEXT: %initval = struct $Range<Int> (%1, %3)
|
||||||
|
// CHECK-NEXT: }
|
||||||
|
public static let r: Range<Int> = 1 ..< 3
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user