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:
Erik Eckstein
2025-04-18 19:01:53 +02:00
parent bc0760083d
commit 8cf4e34cc1
8 changed files with 301 additions and 77 deletions

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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() {

View File

@@ -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) -> () {

View File

@@ -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
}

View File

@@ -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

View 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
}