Skip to content

Commit c567167

Browse files
committed
InitializeStaticGlobals: add a peephole to merge element stores to a single store.
Sometimes structs are not stored in one piece, but as individual elements. Merge such individual stores to a single store of the whole struct. This enables generating a statically initialized global.
1 parent a0d5234 commit c567167

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
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+
}

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+

0 commit comments

Comments
 (0)