@@ -58,19 +58,21 @@ let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals"
5858 // Merge such individual stores to a single store of the whole struct.
5959 mergeStores ( in: function, context)
6060
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 {
6762 return
6863 }
6964
7065 if !allocInst. global. canBeInitializedStatically {
7166 return
7267 }
7368
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+
7476 var cloner = StaticInitCloner ( cloneTo: allocInst. global, context)
7577 defer { cloner. deinitialize ( ) }
7678
@@ -87,6 +89,186 @@ let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals"
8789 context. removeTriviallyDeadInstructionsIgnoringDebugUses ( in: function)
8890}
8991
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+
90272/// Merges stores to individual struct fields to a single store of the whole struct.
91273///
92274/// store %element1 to %element1Addr
@@ -172,3 +354,15 @@ private func merge(elementStores: [StoreInst], lastStore: StoreInst, _ context:
172354 }
173355 }
174356}
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