Skip to content

Commit 9865e82

Browse files
authored
Merge pull request swiftlang#67515 from eeckstein/static-globals-improvements
Enable large generic structs in statically initialized global variables
2 parents 82c172c + 6425565 commit 9865e82

File tree

7 files changed

+269
-18
lines changed

7 files changed

+269
-18
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals"
4848
return
4949
}
5050

51+
// Sometimes structs are not stored in one piece, but as individual elements.
52+
// Merge such individual stores to a single store of the whole struct.
53+
mergeStores(in: function, context)
54+
5155
guard let (allocInst, storeToGlobal) = getGlobalInitialization(of: function) else {
5256
return
5357
}
@@ -137,3 +141,80 @@ private extension Function {
137141
return block
138142
}
139143
}
144+
145+
/// Merges stores to individual struct fields to a single store of the whole struct.
146+
///
147+
/// store %element1 to %element1Addr
148+
/// store %element2 to %element2Addr
149+
/// ->
150+
/// %s = struct $S (%element1, %element2)
151+
/// store %s to @structAddr
152+
private func mergeStores(in function: Function, _ context: FunctionPassContext) {
153+
for inst in function.instructions {
154+
if let store = inst as? StoreInst {
155+
if let elementStores = getSequenceOfElementStores(firstStore: store) {
156+
merge(elementStores: elementStores, context)
157+
}
158+
}
159+
}
160+
}
161+
162+
/// Returns a sequence of individual stores to elements of a struct.
163+
///
164+
/// %addr1 = struct_element_addr %structAddr, #field1
165+
/// store %element1 to %addr1
166+
/// // ...
167+
/// %addr_n = struct_element_addr %structAddr, #field_n
168+
/// store %element_n to %addr_n
169+
///
170+
private func getSequenceOfElementStores(firstStore: StoreInst) -> [StoreInst]? {
171+
guard let elementAddr = firstStore.destination as? StructElementAddrInst else {
172+
return nil
173+
}
174+
let structAddr = elementAddr.struct
175+
let numElements = structAddr.type.getNominalFields(in: firstStore.parentFunction).count
176+
var elementStores = Array<StoreInst?>(repeating: nil, count: numElements)
177+
var numStoresFound = 0
178+
179+
for inst in InstructionList(first: firstStore) {
180+
switch inst {
181+
case let store as StoreInst:
182+
guard store.storeOwnership == .trivial,
183+
let sea = store.destination as? StructElementAddrInst,
184+
sea.struct == structAddr,
185+
// Multiple stores to the same element?
186+
elementStores[sea.fieldIndex] == nil else {
187+
return nil
188+
}
189+
190+
elementStores[sea.fieldIndex] = store
191+
numStoresFound += 1
192+
if numStoresFound == numElements {
193+
// If we saw `numElements` distinct stores, it implies that all elements in `elementStores` are not nil.
194+
return elementStores.map { $0! }
195+
}
196+
default:
197+
if inst.mayReadOrWriteMemory {
198+
return nil
199+
}
200+
}
201+
}
202+
return nil
203+
}
204+
205+
private func merge(elementStores: [StoreInst], _ context: FunctionPassContext) {
206+
let lastStore = elementStores.last!
207+
let builder = Builder(after: lastStore, context)
208+
209+
let structAddr = (lastStore.destination as! StructElementAddrInst).struct
210+
let str = builder.createStruct(type: structAddr.type.objectType, elements: elementStores.map { $0.source })
211+
builder.createStore(source: str, destination: structAddr, ownership: lastStore.storeOwnership)
212+
213+
for store in elementStores {
214+
let destAddr = store.destination as! StructElementAddrInst
215+
context.erase(instruction: store)
216+
if destAddr.uses.isEmpty {
217+
context.erase(instruction: destAddr)
218+
}
219+
}
220+
}

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private extension BuiltinInst {
8181
// because memory effects are not computed in the Onone pipeline, yet.
8282
// This is no problem because the callee (usually a global init function )is mostly very small,
8383
// or contains the side-effect instruction `alloc_global` right at the beginning.
84-
if callee.instructions.contains(where: { $0.mayReadOrWriteMemory || $0.hasUnspecifiedSideEffects }) {
84+
if callee.instructions.contains(where: hasSideEffectForBuiltinOnce) {
8585
return
8686
}
8787
context.erase(instruction: self)
@@ -133,6 +133,16 @@ private extension BuiltinInst {
133133
}
134134
}
135135

136+
private func hasSideEffectForBuiltinOnce(_ instruction: Instruction) -> Bool {
137+
switch instruction {
138+
case is DebugStepInst, is DebugValueInst:
139+
return false
140+
default:
141+
return instruction.mayReadOrWriteMemory ||
142+
instruction.hasUnspecifiedSideEffects
143+
}
144+
}
145+
136146
private func typesOfValuesAreEqual(_ lhs: Value, _ rhs: Value, in function: Function) -> Bool? {
137147
if lhs == rhs {
138148
return true

SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,66 @@ private func shouldInline(apply: FullApplySite, callee: Function, alreadyInlined
134134
// Force inlining them in global initializers so that it's possible to statically initialize the global.
135135
return true
136136
}
137-
if apply.parentFunction.isGlobalInitOnceFunction,
138-
let global = apply.parentFunction.getInitializedGlobal(),
139-
global.mustBeInitializedStatically,
140-
let applyInst = apply as? ApplyInst,
141-
let projectionPath = applyInst.isStored(to: global),
142-
alreadyInlinedFunctions.insert(PathFunctionTuple(path: projectionPath, function: callee)).inserted {
137+
138+
if apply.substitutionMap.isEmpty,
139+
let pathIntoGlobal = apply.resultIsUsedInGlobalInitialization(),
140+
alreadyInlinedFunctions.insert(PathFunctionTuple(path: pathIntoGlobal, function: callee)).inserted {
143141
return true
144142
}
143+
145144
return false
146145
}
147146

147+
private extension FullApplySite {
148+
func resultIsUsedInGlobalInitialization() -> SmallProjectionPath? {
149+
guard parentFunction.isGlobalInitOnceFunction,
150+
let global = parentFunction.getInitializedGlobal() else {
151+
return nil
152+
}
153+
154+
switch numIndirectResultArguments {
155+
case 0:
156+
return singleDirectResult?.isStored(to: global)
157+
case 1:
158+
let resultAccessPath = arguments[0].accessPath
159+
switch resultAccessPath.base {
160+
case .global(let resultGlobal) where resultGlobal == global:
161+
return resultAccessPath.materializableProjectionPath
162+
case .stack(let allocStack) where resultAccessPath.projectionPath.isEmpty:
163+
return allocStack.getStoredValue(by: self)?.isStored(to: global)
164+
default:
165+
return nil
166+
}
167+
default:
168+
return nil
169+
}
170+
}
171+
}
172+
173+
private extension AllocStackInst {
174+
func getStoredValue(by storingInstruction: Instruction) -> Value? {
175+
// If the only use (beside `storingInstruction`) is a load, it's the value which is
176+
// stored by `storingInstruction`.
177+
var loadedValue: Value? = nil
178+
for use in self.uses {
179+
switch use.instruction {
180+
case is DeallocStackInst:
181+
break
182+
case let load as LoadInst:
183+
if loadedValue != nil {
184+
return nil
185+
}
186+
loadedValue = load
187+
default:
188+
if use.instruction != storingInstruction {
189+
return nil
190+
}
191+
}
192+
}
193+
return loadedValue
194+
}
195+
}
196+
148197
private extension Value {
149198
/// Analyzes the def-use chain of an apply instruction, and looks for a single chain that leads to a store instruction
150199
/// that initializes a part of a global variable or the entire variable:
@@ -193,15 +242,13 @@ private extension Value {
193242
path = path.push(.enumCase, index: ei.caseIndex)
194243
break
195244
case let si as StoreInst:
196-
guard let storeDestination = si.destination as? GlobalAddrInst else {
197-
return nil
198-
}
199-
200-
guard storeDestination.global == global else {
245+
let accessPath = si.destination.getAccessPath(fromInitialPath: path)
246+
switch accessPath.base {
247+
case .global(let storedGlobal) where storedGlobal == global:
248+
return accessPath.materializableProjectionPath
249+
default:
201250
return nil
202251
}
203-
204-
return path
205252
default:
206253
return nil
207254
}

SwiftCompilerSources/Sources/Optimizer/Utilities/AccessUtils.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,13 @@ struct AccessPath : CustomStringConvertible {
293293
return getProjection(to: other) != nil
294294
}
295295

296+
var materializableProjectionPath: SmallProjectionPath? {
297+
if projectionPath.isMaterializable {
298+
return projectionPath
299+
}
300+
return nil
301+
}
302+
296303
/// Returns the projection path to `other` if this access path is equal or contains `other`.
297304
///
298305
/// For example,

test/SILOptimizer/init_static_globals.sil

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ struct GenericStruct<T> {
3131
var x: T
3232
}
3333

34+
struct TwoFields {
35+
let a: Int32
36+
let b: Int32
37+
}
38+
3439
let nontrivialglobal: TClass
3540

3641
// CHECK-LABEL: sil_global hidden [let] @$trivialglobal : $TStruct = {
@@ -74,6 +79,17 @@ sil_global private @g3_token : $Builtin.Word
7479
sil_global [let] @g4 : $Optional<UnsafeMutablePointer<Int>>
7580
sil_global private @g4_token : $Builtin.Word
7681

82+
// CHECK-LABEL: sil_global [let] @g5 : $TwoFields = {
83+
// CHECK-NEXT: %0 = integer_literal $Builtin.Int32, 11
84+
// CHECK-NEXT: %1 = struct $Int32 (%0 : $Builtin.Int32)
85+
// CHECK-NEXT: %2 = integer_literal $Builtin.Int32, 10
86+
// CHECK-NEXT: %3 = struct $Int32 (%2 : $Builtin.Int32)
87+
// CHECK-NEXT: %initval = struct $TwoFields (%1 : $Int32, %3 : $Int32)
88+
// CHECK-NEXT: }
89+
sil_global [let] @g5 : $TwoFields
90+
91+
sil_global [let] @g6 : $TwoFields
92+
sil_global [let] @g7 : $TwoFields
7793

7894
// CHECK-LABEL: sil [global_init_once_fn] [ossa] @globalinit_trivialglobal_func :
7995
// CHECK-NOT: alloc_global
@@ -189,3 +205,44 @@ bb0:
189205
%10 = tuple ()
190206
return %10 : $()
191207
}
208+
209+
// CHECK-LABEL: sil [global_init_once_fn] [ossa] @globalinit_separate_stores :
210+
// CHECK-NOT: alloc_global
211+
// CHECK-NOT: store
212+
// CHECK: } // end sil function 'globalinit_separate_stores'
213+
sil [global_init_once_fn] [ossa] @globalinit_separate_stores : $@convention(c) () -> () {
214+
bb0:
215+
alloc_global @g5
216+
%1 = global_addr @g5 : $*TwoFields
217+
%2 = integer_literal $Builtin.Int32, 10
218+
%3 = struct $Int32 (%2 : $Builtin.Int32)
219+
%4 = struct_element_addr %1 : $*TwoFields, #TwoFields.b
220+
store %3 to [trivial] %4 : $*Int32
221+
%6 = integer_literal $Builtin.Int32, 11
222+
%7 = struct $Int32 (%6 : $Builtin.Int32)
223+
%8 = struct_element_addr %1 : $*TwoFields, #TwoFields.a
224+
store %7 to [trivial] %8 : $*Int32
225+
%10 = tuple ()
226+
return %10 : $()
227+
}
228+
229+
// CHECK-LABEL: sil [global_init_once_fn] [ossa] @globalinit_wrong_separate_stores :
230+
// CHECK: alloc_global
231+
// CHECK: store
232+
// CHECK: store
233+
// CHECK: } // end sil function 'globalinit_wrong_separate_stores'
234+
sil [global_init_once_fn] [ossa] @globalinit_wrong_separate_stores : $@convention(c) () -> () {
235+
bb0:
236+
alloc_global @g5
237+
%1 = global_addr @g5 : $*TwoFields
238+
%2 = integer_literal $Builtin.Int32, 10
239+
%3 = struct $Int32 (%2 : $Builtin.Int32)
240+
%4 = struct_element_addr %1 : $*TwoFields, #TwoFields.b
241+
store %3 to [trivial] %4 : $*Int32
242+
%6 = integer_literal $Builtin.Int32, 11
243+
%7 = struct $Int32 (%6 : $Builtin.Int32)
244+
store %7 to [trivial] %4 : $*Int32
245+
%10 = tuple ()
246+
return %10 : $()
247+
}
248+

test/SILOptimizer/performance-annotations.swift

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -experimental-performance-annotations -emit-sil %s -o /dev/null -verify
1+
// RUN: %target-swift-frontend -parse-as-library -emit-sil %s -o /dev/null -verify
22
// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib
33
// REQUIRES: swift_in_compiler
44

@@ -11,7 +11,7 @@ open class Cl {
1111
final func finalMethod() {}
1212
}
1313

14-
func initFunc() -> Int { return 3 }
14+
func initFunc() -> Int { return Int.random(in: 0..<10) }
1515

1616
struct Str : P {
1717
let x: Int
@@ -163,8 +163,8 @@ class H {
163163
}
164164

165165
struct MyStruct {
166-
static var v: Int = { // expected-note {{called from here}}
167-
return H().hash // expected-error {{Using type 'H' can cause metadata allocation or locks}}
166+
static var v: Int = { // expected-error {{Using type 'H' can cause metadata allocation or locks}}
167+
return H().hash
168168
}()
169169
}
170170

@@ -330,3 +330,50 @@ public func testClosurePassing(a: inout Y) -> Int {
330330
return a.Xsort()
331331
}
332332

333+
struct LargeGenericStruct<T> {
334+
var a: T
335+
var b: T
336+
var c: T
337+
var d: T
338+
var e: T
339+
var f: T
340+
var g: T
341+
var h: T
342+
}
343+
344+
var largeGeneric = LargeGenericStruct<Int>(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8)
345+
346+
@_noLocks
347+
func testLargeGenericStruct() -> LargeGenericStruct<Int> {
348+
return largeGeneric
349+
}
350+
351+
struct ContainsLargeGenericStruct {
352+
var s: LargeGenericStruct<Int>
353+
}
354+
355+
var clgs = ContainsLargeGenericStruct(s: LargeGenericStruct<Int>(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8))
356+
357+
@_noLocks
358+
func testClgs() -> ContainsLargeGenericStruct {
359+
return clgs
360+
}
361+
362+
struct NestedGenericStruct<T> {
363+
var a: T
364+
var b: T
365+
var c: LargeGenericStruct<T>
366+
var d: T
367+
var e: T
368+
var f: T
369+
var g: T
370+
var h: T
371+
}
372+
373+
var nestedGeneric = NestedGenericStruct(a: 1, b: 2, c: LargeGenericStruct<Int>(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8), d: 4, e: 5, f: 6, g: 7, h: 8)
374+
375+
@_noLocks
376+
func testNestedGenericStruct() -> NestedGenericStruct<Int> {
377+
return nestedGeneric
378+
}
379+

test/SILOptimizer/simplify_builtin.sil

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ bb0:
274274
%1 = global_addr @g : $*Int32
275275
%2 = integer_literal $Builtin.Int32, 10
276276
%3 = struct $Int32 (%2 : $Builtin.Int32)
277+
debug_step
278+
debug_value %1 : $*Int32
277279
%6 = tuple ()
278280
return %6 : $()
279281
}

0 commit comments

Comments
 (0)