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
|
||||
|
||||
/// 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.
|
||||
/// This can be via a preceding store to the same address:
|
||||
@@ -52,6 +52,9 @@ import SIL
|
||||
/// %f2 = load %fa2
|
||||
/// %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
|
||||
/// 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
|
||||
@@ -99,7 +102,7 @@ func eliminateRedundantLoads(in function: Function,
|
||||
while let i = inst {
|
||||
defer { inst = i.previous }
|
||||
|
||||
if let load = inst as? LoadInst {
|
||||
if let load = inst as? LoadingInstruction {
|
||||
if !context.continueWithNextSubpassRun(for: load) {
|
||||
return changed
|
||||
}
|
||||
@@ -116,7 +119,59 @@ func eliminateRedundantLoads(in function: Function,
|
||||
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) {
|
||||
case .notRedundant:
|
||||
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 {
|
||||
if !canLoadValue {
|
||||
return false
|
||||
}
|
||||
switch variant {
|
||||
case .mandatory, .mandatoryInGlobalInit:
|
||||
if loadOwnership == .take {
|
||||
@@ -171,20 +229,6 @@ private extension LoadInst {
|
||||
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 {
|
||||
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,
|
||||
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)
|
||||
}
|
||||
|
||||
// Make sure to keep dependencies valid after replacing the load
|
||||
insertMarkDependencies(for: load, context)
|
||||
let originalLoad = load.materializeLoadForReplacement(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(
|
||||
for load: LoadInst,
|
||||
for load: LoadingInstruction,
|
||||
from availableValue: AvailableValue,
|
||||
_ context: FunctionPassContext
|
||||
) -> Value {
|
||||
@@ -341,9 +387,9 @@ private func provideValue(
|
||||
builder: availableValue.getBuilderForProjections(context))
|
||||
case .take:
|
||||
if projectionPath.isEmpty {
|
||||
return shrinkMemoryLifetime(from: load, to: availableValue, context)
|
||||
return shrinkMemoryLifetime(to: availableValue, context)
|
||||
} else {
|
||||
return shrinkMemoryLifetimeAndSplit(from: load, to: availableValue, projectionPath: projectionPath, context)
|
||||
return shrinkMemoryLifetimeAndSplit(to: availableValue, projectionPath: projectionPath, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,7 +412,7 @@ private func insertMarkDependencies(for load: LoadInst, _ context: FunctionPassC
|
||||
private struct MarkDependenceInserter : AddressUseDefWalker {
|
||||
let load: LoadInst
|
||||
let context: FunctionPassContext
|
||||
|
||||
|
||||
mutating func walkUp(address: Value, path: UnusedWalkingPath) -> WalkResult {
|
||||
if let mdi = address as? MarkDependenceInst {
|
||||
let builder = Builder(after: load, context)
|
||||
@@ -375,7 +421,7 @@ private struct MarkDependenceInserter : AddressUseDefWalker {
|
||||
}
|
||||
return walkUpDefault(address: address, path: path)
|
||||
}
|
||||
|
||||
|
||||
mutating func rootDef(address: Value, path: UnusedWalkingPath) -> WalkResult {
|
||||
return .continueWalk
|
||||
}
|
||||
@@ -392,7 +438,7 @@ private struct MarkDependenceInserter : AddressUseDefWalker {
|
||||
/// ...
|
||||
/// // 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 {
|
||||
case .viaLoad(let availableLoad):
|
||||
assert(availableLoad.loadOwnership == .copy)
|
||||
@@ -442,7 +488,7 @@ private func shrinkMemoryLifetime(from load: LoadInst, to availableValue: Availa
|
||||
/// ...
|
||||
/// // 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 {
|
||||
case .viaLoad(let availableLoad):
|
||||
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.
|
||||
private enum AvailableValue {
|
||||
case viaLoad(LoadInst)
|
||||
@@ -505,7 +565,7 @@ private extension Array where Element == AvailableValue {
|
||||
func replaceCopyAddrsWithLoadsAndStores(_ context: FunctionPassContext) -> [AvailableValue] {
|
||||
return map {
|
||||
if case .viaCopyAddr(let copyAddr) = $0 {
|
||||
return .viaStore(copyAddr.replaceWithLoadAndStore(context))
|
||||
return .viaStore(copyAddr.replaceWithLoadAndStore(context).store)
|
||||
} else {
|
||||
return $0
|
||||
}
|
||||
@@ -514,7 +574,7 @@ private extension Array where Element == AvailableValue {
|
||||
}
|
||||
|
||||
private struct InstructionScanner {
|
||||
private let load: LoadInst
|
||||
private let load: LoadingInstruction
|
||||
private let accessPath: AccessPath
|
||||
private let storageDefBlock: BasicBlock?
|
||||
private let aliasAnalysis: AliasAnalysis
|
||||
@@ -522,7 +582,7 @@ private struct InstructionScanner {
|
||||
private(set) var potentiallyRedundantSubpath: AccessPath? = nil
|
||||
private(set) var availableValues = Array<AvailableValue>()
|
||||
|
||||
init(load: LoadInst, accessPath: AccessPath, _ aliasAnalysis: AliasAnalysis) {
|
||||
init(load: LoadingInstruction, accessPath: AccessPath, _ aliasAnalysis: AliasAnalysis) {
|
||||
self.load = load
|
||||
self.accessPath = accessPath
|
||||
self.storageDefBlock = accessPath.base.reference?.referenceRoot.parentBlock
|
||||
@@ -616,7 +676,7 @@ private struct InstructionScanner {
|
||||
potentiallyRedundantSubpath = precedingStorePath
|
||||
}
|
||||
|
||||
case let preceedingCopy as CopyAddrInst where preceedingCopy.canProvideValue:
|
||||
case let preceedingCopy as CopyAddrInst where preceedingCopy.canLoadValue:
|
||||
let copyPath = preceedingCopy.destination.constantAccessPath
|
||||
if copyPath.getMaterializableProjection(to: accessPath) != nil {
|
||||
availableValues.append(.viaCopyAddr(preceedingCopy))
|
||||
@@ -712,20 +772,3 @@ private struct Liverange {
|
||||
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 {
|
||||
@discardableResult
|
||||
func replaceWithLoadAndStore(_ context: some MutatingContext) -> StoreInst {
|
||||
let loadOwnership: LoadInst.LoadOwnership
|
||||
let storeOwnership: StoreInst.StoreOwnership
|
||||
if parentFunction.hasOwnership {
|
||||
if source.type.isTrivial(in: parentFunction) {
|
||||
loadOwnership = .trivial
|
||||
storeOwnership = .trivial
|
||||
} else {
|
||||
loadOwnership = isTakeOfSrc ? .take : .copy
|
||||
storeOwnership = isInitializationOfDest ? .initialize : .assign
|
||||
}
|
||||
} else {
|
||||
loadOwnership = .unqualified
|
||||
storeOwnership = .unqualified
|
||||
}
|
||||
|
||||
func trySplit(_ context: FunctionPassContext) -> Bool {
|
||||
let builder = Builder(before: self, context)
|
||||
let value = builder.createLoad(fromAddress: source, ownership: loadOwnership)
|
||||
let store = builder.createStore(source: value, destination: destination, ownership: storeOwnership)
|
||||
if source.type.isStruct {
|
||||
if (source.type.nominal as! StructDecl).hasUnreferenceableStorage {
|
||||
return false
|
||||
}
|
||||
guard let fields = source.type.getNominalFields(in: parentFunction) else {
|
||||
return false
|
||||
}
|
||||
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 load = builder.createLoad(fromAddress: source, ownership: loadOwnership)
|
||||
let store = builder.createStore(source: load, destination: destination, ownership: storeOwnership)
|
||||
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: 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 {
|
||||
@inlinable
|
||||
|
||||
@@ -35,10 +35,10 @@ func doSomething() {}
|
||||
// SIL: [[BOX:%.*]] = alloc_stack [var_decl] $Int, var, name "x"
|
||||
// SIL: [[INOUT_BOX:%.*]] = alloc_stack [var_decl] $Int, var, name "x2"
|
||||
// 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: apply [[FUNC]]()
|
||||
// SIL: copy_addr [take] [[INOUT_BOX]] to [init] [[ACCESS]]
|
||||
// SIL: store {{%.*}} to [[ACCESS]]
|
||||
// SIL: end_access [[ACCESS]]
|
||||
// SIL: } // end sil function '$s18reference_bindings13testBindToVaryyF'
|
||||
func testBindToVar() {
|
||||
|
||||
@@ -275,7 +275,7 @@ public struct PubStruct: PubProto {
|
||||
// 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: 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
|
||||
|
||||
// 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
|
||||
// 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: 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
|
||||
|
||||
// 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: function_ref @$s3Lib9PkgStructV4roots6UInt16VvgZ : $@convention(method) (@thin PkgStruct.Type)
|
||||
// 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
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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: [[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: store %0 to [assign] %1
|
||||
// CHECK: return [[I]]
|
||||
@@ -1248,7 +1248,7 @@ bb0(%0 : @owned $Agg3, %1 : $*Agg3):
|
||||
|
||||
// CHECK-LABEL: @load_copy_from_store :
|
||||
// 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: end_borrow [[B]]
|
||||
// CHECK: store %0 to [assign] %1
|
||||
@@ -1261,6 +1261,65 @@ bb0(%0 : @owned $Agg3, %1 : $*Agg3):
|
||||
%4 = load [copy] %3 : $*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: [[DS:%.*]] = destructure_struct %0
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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: [[DS:%.*]] = destructure_struct %0
|
||||
// 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