Skip to content

Commit 57a236e

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 3fd93c0 commit 57a236e

File tree

6 files changed

+591
-37
lines changed

6 files changed

+591
-37
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift

+200-6
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

+10-6
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

+24-19
Original file line numberDiff line numberDiff line change
@@ -791,10 +791,13 @@ extension InstructionRange {
791791
/// %i = some_const_initializer_insts
792792
/// store %i to %a
793793
/// ```
794+
///
795+
/// For all other instructions `handleUnknownInstruction` is called and such an instruction
796+
/// is accepted if `handleUnknownInstruction` returns true.
794797
func getGlobalInitialization(
795798
of function: Function,
796-
forStaticInitializer: Bool,
797-
_ context: some Context
799+
_ context: some Context,
800+
handleUnknownInstruction: (Instruction) -> Bool
798801
) -> (allocInst: AllocGlobalInst, storeToGlobal: StoreInst)? {
799802
guard let block = function.blocks.singleElement else {
800803
return nil
@@ -811,34 +814,36 @@ func getGlobalInitialization(
811814
is DebugStepInst,
812815
is BeginAccessInst,
813816
is EndAccessInst:
814-
break
817+
continue
815818
case let agi as AllocGlobalInst:
816-
if allocInst != nil {
817-
return nil
819+
if allocInst == nil {
820+
allocInst = agi
821+
continue
818822
}
819-
allocInst = agi
820823
case let ga as GlobalAddrInst:
821824
if let agi = allocInst, agi.global == ga.global {
822825
globalAddr = ga
823826
}
827+
continue
824828
case let si as StoreInst:
825-
if store != nil {
826-
return nil
827-
}
828-
guard let ga = globalAddr else {
829-
return nil
830-
}
831-
if si.destination != ga {
832-
return nil
829+
if store == nil,
830+
let ga = globalAddr,
831+
si.destination == ga
832+
{
833+
store = si
834+
continue
833835
}
834-
store = si
835-
case is GlobalValueInst where !forStaticInitializer:
836-
break
836+
// Note that the initializer must not contain a `global_value` because `global_value` needs to
837+
// initialize the class metadata at runtime.
837838
default:
838-
if !inst.isValidInStaticInitializerOfGlobal(context) {
839-
return nil
839+
if inst.isValidInStaticInitializerOfGlobal(context) {
840+
continue
840841
}
841842
}
843+
if handleUnknownInstruction(inst) {
844+
continue
845+
}
846+
return nil
842847
}
843848
if let store = store {
844849
return (allocInst: allocInst!, storeToGlobal: store)

include/swift/SIL/SILBuilder.h

+10-6
Original file line numberDiff line numberDiff line change
@@ -3115,12 +3115,16 @@ class SILBuilder {
31153115
C.notifyInserted(TheInst);
31163116

31173117
#ifndef NDEBUG
3118-
// If we are inserting into a specific function (rather than a block for a
3119-
// global_addr), verify that our instruction/the associated location are in
3120-
// sync. We don't care if an instruction is used in global_addr.
3121-
if (F)
3122-
TheInst->verifyDebugInfo();
3123-
TheInst->verifyOperandOwnership(&C.silConv);
3118+
// A vector instruction can only be in a global initializer. Therefore there
3119+
// is no point in verifying debug info or ownership.
3120+
if (!isa<VectorInst>(TheInst)) {
3121+
// If we are inserting into a specific function (rather than a block for a
3122+
// global_addr), verify that our instruction/the associated location are in
3123+
// sync. We don't care if an instruction is used in global_addr.
3124+
if (F)
3125+
TheInst->verifyDebugInfo();
3126+
TheInst->verifyOperandOwnership(&C.silConv);
3127+
}
31243128
#endif
31253129
}
31263130

0 commit comments

Comments
 (0)