@@ -58,19 +58,21 @@ let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals"
58
58
// Merge such individual stores to a single store of the whole struct.
59
59
mergeStores ( in: function, context)
60
60
61
- // The initializer must not contain a `global_value` because `global_value` needs to
62
- // initialize the class metadata at runtime.
63
- guard let ( allocInst, storeToGlobal) = getGlobalInitialization ( of: function,
64
- forStaticInitializer: true ,
65
- context) else
66
- {
61
+ guard let ( allocInst, storeToGlobal, inlineArrays) = getGlobalInitializerInfo ( of: function, context) else {
67
62
return
68
63
}
69
64
70
65
if !allocInst. global. canBeInitializedStatically {
71
66
return
72
67
}
73
68
69
+ /// Replace inline arrays, which are allocated in stack locations with `vector` instructions.
70
+ /// Note that `vector` instructions are only allowed in global initializers. Therefore it's important
71
+ /// that the code in this global initializer is eventually completely removed after copying it to the global.
72
+ for array in inlineArrays {
73
+ lowerInlineArray ( array: array, context)
74
+ }
75
+
74
76
var cloner = StaticInitCloner ( cloneTo: allocInst. global, context)
75
77
defer { cloner. deinitialize ( ) }
76
78
@@ -87,6 +89,186 @@ let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals"
87
89
context. removeTriviallyDeadInstructionsIgnoringDebugUses ( in: function)
88
90
}
89
91
92
+ /// Gets all info about a global initializer function if it can be converted to a statically initialized global.
93
+ private func getGlobalInitializerInfo(
94
+ of function: Function ,
95
+ _ context: FunctionPassContext
96
+ ) -> ( allocInst: AllocGlobalInst , storeToGlobal: StoreInst , inlineArrays: [ InlineArray ] ) ? {
97
+
98
+ var arrayInitInstructions = InstructionSet ( context)
99
+ defer { arrayInitInstructions. deinitialize ( ) }
100
+
101
+ var inlineArrays = [ InlineArray] ( )
102
+
103
+ guard let ( allocInst, storeToGlobal) = getGlobalInitialization ( of: function, context,
104
+ handleUnknownInstruction: { inst in
105
+ if let asi = inst as? AllocStackInst {
106
+ if let array = getInlineArrayInfo ( of: asi) {
107
+ inlineArrays. append ( array)
108
+ arrayInitInstructions. insertAllAddressUses ( of: asi)
109
+ return true
110
+ }
111
+ return false
112
+ }
113
+ // Accept all instructions which are part of inline array initialization, because we'll remove them anyway.
114
+ return arrayInitInstructions. contains ( inst)
115
+ } )
116
+ else {
117
+ return nil
118
+ }
119
+
120
+ return ( allocInst, storeToGlobal, inlineArrays)
121
+ }
122
+
123
+ /// Represents an inline array which is initialized by a literal.
124
+ private struct InlineArray {
125
+ let elementType : Type
126
+
127
+ /// In case the `elementType` is a tuple, the element values are flattened,
128
+ /// i.e. `elements` contains elementcount * tupleelements values.
129
+ let elements : [ Value ]
130
+
131
+ /// The final load instruction which loads the initialized array from a temporary stack location.
132
+ let finalArrayLoad : LoadInst
133
+
134
+ /// The stack location which contains the initialized array.
135
+ var stackLoocation : AllocStackInst { finalArrayLoad. address as! AllocStackInst }
136
+ }
137
+
138
+ /// Replaces an initialized inline array (which is allocated in a temporary stack location) with a
139
+ /// `vector` instruction.
140
+ /// The stack location of the array is removed.
141
+ private func lowerInlineArray( array: InlineArray , _ context: FunctionPassContext ) {
142
+ let vector : VectorInst
143
+ let builder = Builder ( after: array. finalArrayLoad, context)
144
+ if array. elementType. isTuple {
145
+ let numTupleElements = array. elementType. tupleElements. count
146
+ assert ( array. elements. count % numTupleElements == 0 )
147
+ var tuples : [ TupleInst ] = [ ]
148
+ for tupleIdx in 0 ..< ( array. elements. count / numTupleElements) {
149
+ let range = ( tupleIdx * numTupleElements) ..< ( ( tupleIdx + 1 ) * numTupleElements)
150
+ let tuple = builder. createTuple ( type: array. elementType, elements: Array ( array. elements [ range] ) )
151
+ tuples. append ( tuple)
152
+ }
153
+ vector = builder. createVector ( type: array. elementType, arguments: tuples)
154
+ } else {
155
+ vector = builder. createVector ( type: array. elementType, arguments: array. elements)
156
+ }
157
+ array. finalArrayLoad. uses. replaceAll ( with: vector, context)
158
+ context. erase ( instructionIncludingAllUsers: array. stackLoocation)
159
+ }
160
+
161
+ /// An alloc_stack could be a temporary object which holds an initialized inline-array literal.
162
+ /// It looks like:
163
+ ///
164
+ /// %1 = alloc_stack $InlineArray<Count, ElementType>
165
+ /// %2 = unchecked_addr_cast %1 to $*ElementType // the elementStorage
166
+ /// store %firstElement to [trivial] %2
167
+ /// %4 = integer_literal $Builtin.Word, 1
168
+ /// %5 = index_addr %2, %4
169
+ /// store %secondElement to [trivial] %5
170
+ /// ...
171
+ /// %10 = load [trivial] %1 // the final arrayLoad
172
+ /// dealloc_stack %1
173
+ ///
174
+ /// Returns nil if `allocStack` is not a properly initialized inline array.
175
+ ///
176
+ private func getInlineArrayInfo( of allocStack: AllocStackInst ) -> InlineArray ? {
177
+ var arrayLoad : LoadInst ? = nil
178
+ var elementStorage : UncheckedAddrCastInst ? = nil
179
+
180
+ for use in allocStack. uses {
181
+ switch use. instruction {
182
+ case let load as LoadInst :
183
+ if arrayLoad != nil {
184
+ return nil
185
+ }
186
+ // It's guaranteed that the array load is located after all element stores.
187
+ // Otherwise it would load uninitialized memory.
188
+ arrayLoad = load
189
+ case is DeallocStackInst :
190
+ break
191
+ case let addrCastToElement as UncheckedAddrCastInst :
192
+ if elementStorage != nil {
193
+ return nil
194
+ }
195
+ elementStorage = addrCastToElement
196
+ default :
197
+ return nil
198
+ }
199
+ }
200
+ guard let arrayLoad, let elementStorage else {
201
+ return nil
202
+ }
203
+
204
+ var stores = Array < StoreInst ? > ( )
205
+ if !findArrayElementStores( toElementAddress: elementStorage, elementIndex: 0 , stores: & stores) {
206
+ return nil
207
+ }
208
+ if stores. isEmpty {
209
+ // We cannot create an empty `vector` instruction, therefore we don't support empty inline arrays.
210
+ return nil
211
+ }
212
+ // Usually there must be a store for each element. Otherwise the `arrayLoad` would load uninitialized memory.
213
+ // We still check this to not crash in some weird corner cases, like the element type is an empty tuple.
214
+ if stores. contains ( nil ) {
215
+ return nil
216
+ }
217
+
218
+ return InlineArray ( elementType: elementStorage. type. objectType,
219
+ elements: stores. map { $0!. source } ,
220
+ finalArrayLoad: arrayLoad)
221
+ }
222
+
223
+ /// Recursively traverses all uses of `elementAddr` and finds all stores to an inline array storage.
224
+ /// The element store instructions are put into `stores` - one store for each element.
225
+ /// In case the element type is a tuple, the tuples are flattened. See `InlineArray.elements`.
226
+ private func findArrayElementStores(
227
+ toElementAddress elementAddr: Value ,
228
+ elementIndex: Int ,
229
+ stores: inout [ StoreInst ? ]
230
+ ) -> Bool {
231
+ for use in elementAddr. uses {
232
+ switch use. instruction {
233
+ case let indexAddr as IndexAddrInst :
234
+ guard let indexLiteral = indexAddr. index as? IntegerLiteralInst ,
235
+ let tailIdx = indexLiteral. value else
236
+ {
237
+ return false
238
+ }
239
+ if !findArrayElementStores( toElementAddress: indexAddr, elementIndex: elementIndex + tailIdx, stores: & stores) {
240
+ return false
241
+ }
242
+ case let tea as TupleElementAddrInst :
243
+ // The array elements are tuples. There is a separate store for each tuple element.
244
+ let numTupleElements = tea. tuple. type. tupleElements. count
245
+ let tupleIdx = tea. fieldIndex
246
+ if !findArrayElementStores( toElementAddress: tea,
247
+ elementIndex: elementIndex * numTupleElements + tupleIdx,
248
+ stores: & stores) {
249
+ return false
250
+ }
251
+ case let store as StoreInst :
252
+ if store. source. type. isTuple {
253
+ // This kind of SIL is never generated because tuples are stored with separated stores to tuple_element_addr.
254
+ // Just to be on the safe side..
255
+ return false
256
+ }
257
+ if elementIndex >= stores. count {
258
+ stores += Array ( repeating: nil , count: elementIndex - stores. count + 1 )
259
+ }
260
+ if stores [ elementIndex] != nil {
261
+ // An element is stored twice.
262
+ return false
263
+ }
264
+ stores [ elementIndex] = store
265
+ default :
266
+ return false
267
+ }
268
+ }
269
+ return true
270
+ }
271
+
90
272
/// Merges stores to individual struct fields to a single store of the whole struct.
91
273
///
92
274
/// store %element1 to %element1Addr
@@ -172,3 +354,15 @@ private func merge(elementStores: [StoreInst], lastStore: StoreInst, _ context:
172
354
}
173
355
}
174
356
}
357
+
358
+ private extension InstructionSet {
359
+ mutating func insertAllAddressUses( of value: Value ) {
360
+ for use in value. uses {
361
+ if insert ( use. instruction) {
362
+ for result in use. instruction. results where result. type. isAddress {
363
+ insertAllAddressUses ( of: result)
364
+ }
365
+ }
366
+ }
367
+ }
368
+ }
0 commit comments