From 8cf4e34cc1ce207b3b4426059f9e688deb86ebdc Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 18 Apr 2025 19:01:53 +0200 Subject: [PATCH] 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 = 1 ..< 3 } ``` gets a statically initialized global, even at Onone, with this improvement. rdar://149356742 --- .../RedundantLoadElimination.swift | 143 ++++++++++++------ .../Optimizer/Utilities/OptUtils.swift | 85 ++++++++--- .../issue-62608-conflicting-debug-info.swift | 2 +- test/SILGen/reference_bindings.swift | 4 +- .../package-cmo-serialize-tables.swift | 6 +- test/SILOptimizer/redundant_load_elim.sil | 42 +++++ .../redundant_load_elim_nontrivial_ossa.sil | 83 +++++++++- test/SILOptimizer/static_init_globals.swift | 13 ++ 8 files changed, 301 insertions(+), 77 deletions(-) create mode 100644 test/SILOptimizer/static_init_globals.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/RedundantLoadElimination.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/RedundantLoadElimination.swift index 902543bbe78..8f4447ddd81 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/RedundantLoadElimination.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/RedundantLoadElimination.swift @@ -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() - 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 - } -} diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift index 3e9465311eb..9d1ea5ead1d 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift @@ -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.. 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 } } diff --git a/test/AutoDiff/compiler_crashers_fixed/issue-62608-conflicting-debug-info.swift b/test/AutoDiff/compiler_crashers_fixed/issue-62608-conflicting-debug-info.swift index c7bb979f54d..f5f3941d5ab 100644 --- a/test/AutoDiff/compiler_crashers_fixed/issue-62608-conflicting-debug-info.swift +++ b/test/AutoDiff/compiler_crashers_fixed/issue-62608-conflicting-debug-info.swift @@ -38,7 +38,7 @@ public func pullback( // 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 diff --git a/test/SILGen/reference_bindings.swift b/test/SILGen/reference_bindings.swift index c45b51ef277..385c3ae1037 100644 --- a/test/SILGen/reference_bindings.swift +++ b/test/SILGen/reference_bindings.swift @@ -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() { diff --git a/test/SILOptimizer/package-cmo-serialize-tables.swift b/test/SILOptimizer/package-cmo-serialize-tables.swift index 47bc903d013..382ff2e8080 100644 --- a/test/SILOptimizer/package-cmo-serialize-tables.swift +++ b/test/SILOptimizer/package-cmo-serialize-tables.swift @@ -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) -> () { diff --git a/test/SILOptimizer/redundant_load_elim.sil b/test/SILOptimizer/redundant_load_elim.sil index df3b635ea51..2f5b96a9321 100644 --- a/test/SILOptimizer/redundant_load_elim.sil +++ b/test/SILOptimizer/redundant_load_elim.sil @@ -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 +} + diff --git a/test/SILOptimizer/redundant_load_elim_nontrivial_ossa.sil b/test/SILOptimizer/redundant_load_elim_nontrivial_ossa.sil index da46259fadd..4d3de14be08 100644 --- a/test/SILOptimizer/redundant_load_elim_nontrivial_ossa.sil +++ b/test/SILOptimizer/redundant_load_elim_nontrivial_ossa.sil @@ -1233,7 +1233,7 @@ bb0(%0 : $*MyArray): // 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 diff --git a/test/SILOptimizer/static_init_globals.swift b/test/SILOptimizer/static_init_globals.swift new file mode 100644 index 00000000000..7bad51165ef --- /dev/null +++ b/test/SILOptimizer/static_init_globals.swift @@ -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 = { + // 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 (%1, %3) + // CHECK-NEXT: } + public static let r: Range = 1 ..< 3 +} +