Skip to content

Commit e802649

Browse files
committed
InitializeStaticGlobals: support statically initializing globals which are or contain inline arrays
For example: ``` struct S { static let slab: Slab = [1, 2, 3, 4] } ``` rdar://143005996
1 parent ef62366 commit e802649

File tree

6 files changed

+592
-37
lines changed

6 files changed

+592
-37
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift

Lines changed: 200 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,10 @@ private func getInitializerFromInitFunction(of globalAddr: GlobalAddrInst, _ con
272272
}
273273
let initFn = initFnRef.referencedFunction
274274
context.notifyDependency(onBodyOf: initFn)
275-
guard let (_, storeToGlobal) = getGlobalInitialization(of: initFn, forStaticInitializer: false, context) else {
275+
guard let (_, storeToGlobal) = getGlobalInitialization(of: initFn, context, handleUnknownInstruction: {
276+
// Accept `global_value` because the class header can be initialized at runtime by the `global_value` instruction.
277+
return $0 is GlobalValueInst
278+
}) else {
276279
return nil
277280
}
278281
return storeToGlobal.source
@@ -305,10 +308,6 @@ private func transitivelyErase(load: LoadInst, _ context: SimplifyContext) {
305308

306309
private extension Value {
307310
func canBeCopied(into function: Function, _ context: SimplifyContext) -> Bool {
308-
if !function.isAnySerialized {
309-
return true
310-
}
311-
312311
// Can't use `ValueSet` because the this value is inside a global initializer and
313312
// not inside a function.
314313
var worklist = Stack<Value>(context)
@@ -320,8 +319,13 @@ private extension Value {
320319
handled.insert(ObjectIdentifier(self))
321320

322321
while let value = worklist.pop() {
322+
if value is VectorInst {
323+
return false
324+
}
323325
if let fri = value as? FunctionRefInst {
324-
if !fri.referencedFunction.hasValidLinkageForFragileRef(function.serializedKind) {
326+
if function.isAnySerialized,
327+
!fri.referencedFunction.hasValidLinkageForFragileRef(function.serializedKind)
328+
{
325329
return false
326330
}
327331
}

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -792,10 +792,13 @@ extension InstructionRange {
792792
/// %i = some_const_initializer_insts
793793
/// store %i to %a
794794
/// ```
795+
///
796+
/// For all other instructions `handleUnknownInstruction` is called and such an instruction
797+
/// is accepted if `handleUnknownInstruction` returns true.
795798
func getGlobalInitialization(
796799
of function: Function,
797-
forStaticInitializer: Bool,
798-
_ context: some Context
800+
_ context: some Context,
801+
handleUnknownInstruction: (Instruction) -> Bool
799802
) -> (allocInst: AllocGlobalInst, storeToGlobal: StoreInst)? {
800803
guard let block = function.blocks.singleElement else {
801804
return nil
@@ -812,34 +815,36 @@ func getGlobalInitialization(
812815
is DebugStepInst,
813816
is BeginAccessInst,
814817
is EndAccessInst:
815-
break
818+
continue
816819
case let agi as AllocGlobalInst:
817-
if allocInst != nil {
818-
return nil
820+
if allocInst == nil {
821+
allocInst = agi
822+
continue
819823
}
820-
allocInst = agi
821824
case let ga as GlobalAddrInst:
822825
if let agi = allocInst, agi.global == ga.global {
823826
globalAddr = ga
824827
}
828+
continue
825829
case let si as StoreInst:
826-
if store != nil {
827-
return nil
828-
}
829-
guard let ga = globalAddr else {
830-
return nil
831-
}
832-
if si.destination != ga {
833-
return nil
830+
if store == nil,
831+
let ga = globalAddr,
832+
si.destination == ga
833+
{
834+
store = si
835+
continue
834836
}
835-
store = si
836-
case is GlobalValueInst where !forStaticInitializer:
837-
break
837+
// Note that the initializer must not contain a `global_value` because `global_value` needs to
838+
// initialize the class metadata at runtime.
838839
default:
839-
if !inst.isValidInStaticInitializerOfGlobal(context) {
840-
return nil
840+
if inst.isValidInStaticInitializerOfGlobal(context) {
841+
continue
841842
}
842843
}
844+
if handleUnknownInstruction(inst) {
845+
continue
846+
}
847+
return nil
843848
}
844849
if let store = store {
845850
return (allocInst: allocInst!, storeToGlobal: store)

include/swift/SIL/SILBuilder.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,12 +3125,16 @@ class SILBuilder {
31253125
C.notifyInserted(TheInst);
31263126

31273127
#ifndef NDEBUG
3128-
// If we are inserting into a specific function (rather than a block for a
3129-
// global_addr), verify that our instruction/the associated location are in
3130-
// sync. We don't care if an instruction is used in global_addr.
3131-
if (F)
3132-
TheInst->verifyDebugInfo();
3133-
TheInst->verifyOperandOwnership(&C.silConv);
3128+
// A vector instruction can only be in a global initializer. Therefore there
3129+
// is no point in verifying debug info or ownership.
3130+
if (!isa<VectorInst>(TheInst)) {
3131+
// If we are inserting into a specific function (rather than a block for a
3132+
// global_addr), verify that our instruction/the associated location are in
3133+
// sync. We don't care if an instruction is used in global_addr.
3134+
if (F)
3135+
TheInst->verifyDebugInfo();
3136+
TheInst->verifyOperandOwnership(&C.silConv);
3137+
}
31343138
#endif
31353139
}
31363140

0 commit comments

Comments
 (0)