Skip to content

Commit eaf38da

Browse files
committed
InitializeStaticGlobals: support non-loadable enums
TODO: we don't support non-loadable enum cases with payload, yet, because IRGen support is missing.
1 parent c849c7c commit eaf38da

File tree

3 files changed

+220
-16
lines changed

3 files changed

+220
-16
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift

Lines changed: 114 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,11 @@ private indirect enum GlobalInitValue {
8080
// For example, a struct or vector which is initialized by storing its elements.
8181
case aggregate([GlobalInitValue])
8282

83-
// An enum with a payload which is not a SIL "constant".
84-
case enumCase(caseIndex: Int, payload: GlobalInitValue)
83+
// An enum case without a payload of an address-only enum.
84+
case enumCase(caseIndex: Int)
85+
86+
// An enum case which is not a SIL "constant", e.g. because it's address-only
87+
case enumCaseWithPayload(caseIndex: Int, payload: GlobalInitValue)
8588

8689
init?(of globalInitFunction: Function, _ context: FunctionPassContext) {
8790
self = .undefined
@@ -133,20 +136,44 @@ private indirect enum GlobalInitValue {
133136
self = builder.initValue
134137
}
135138

139+
enum InitValue {
140+
// The common case
141+
case value(Value)
142+
143+
// For payload-less cases of address-only enums. Such cases are initialized purely with an `inject_enum_addr`,
144+
// and we don't have a `Value` which represents the resulting enum(-case).
145+
case enumCaseWithoutPayload(InjectEnumAddrInst)
146+
147+
var parentFunction: Function {
148+
switch self {
149+
case .value(let value): return value.parentFunction
150+
case .enumCaseWithoutPayload(let iea): return iea.parentFunction
151+
}
152+
}
153+
}
154+
136155
// Sets an element in the constant tree.
137156
// Returns true if this was successful. One reason for being not successful is if a certain
138157
// element is set twice, i.e. does not have a single defined value.
139-
mutating func setElement(to value: Value, at path: SmallProjectionPath, type: Type) -> Bool {
158+
mutating func setElement(to value: InitValue, at path: SmallProjectionPath, type: Type) -> Bool {
140159
let (kind, index, subPath) = path.pop()
141160
switch kind {
142161
case .root:
143162
guard case .undefined = self else {
144163
// The element was set twice.
145164
return false
146165
}
147-
self = .constant(value)
166+
switch value {
167+
case .value(let value):
168+
self = .constant(value)
169+
case .enumCaseWithoutPayload:
170+
fatalError("should have been handled in the .enumCase of the SmallProjectionPath below")
171+
}
148172
return true
149173

174+
case .enumCase:
175+
return setEnumCase(to: value, at: subPath, index: index, type: type)
176+
150177
case .structField:
151178
guard let structFields = type.getNominalFields(in: value.parentFunction) else {
152179
return false
@@ -186,7 +213,7 @@ private indirect enum GlobalInitValue {
186213
}
187214

188215
private mutating func setField(
189-
to value: Value, at path: SmallProjectionPath,
216+
to value: InitValue, at path: SmallProjectionPath,
190217
index: Int, type: Type, numFields: Int
191218
) -> Bool {
192219
if case .undefined = self {
@@ -205,6 +232,43 @@ private indirect enum GlobalInitValue {
205232
return false
206233
}
207234

235+
private mutating func setEnumCase(to value: InitValue, at path: SmallProjectionPath, index: Int, type: Type) -> Bool {
236+
switch value {
237+
238+
case .enumCaseWithoutPayload(let iea):
239+
guard case .undefined = self else {
240+
// The enum was set twice.
241+
return false
242+
}
243+
assert(index == iea.caseIndex)
244+
self = .enumCase(caseIndex: index)
245+
246+
case .value:
247+
guard let payloadType = type.getEnumCases(in: value.parentFunction)!.getPayloadType(ofCaseIndex: index) else {
248+
return false
249+
}
250+
switch self {
251+
case .undefined:
252+
// It's the first time we set the payload or a sub-field of it.
253+
var payload = GlobalInitValue.undefined
254+
if !payload.setElement(to: value, at: path, type: payloadType) {
255+
return false
256+
}
257+
self = .enumCaseWithPayload(caseIndex: index, payload: payload)
258+
case .enumCaseWithPayload(let existingIndex, var payload) where index == existingIndex:
259+
// Some sub-field of the enum-payload was already set.
260+
self = .undefined // avoid copy-on-write
261+
if !payload.setElement(to: value, at: path, type: payloadType) {
262+
return false
263+
}
264+
self = .enumCaseWithPayload(caseIndex: index, payload: payload)
265+
default:
266+
return false
267+
}
268+
}
269+
return true
270+
}
271+
208272
/// Creates SIL for this global init value in the initializer of the `global`.
209273
func materialize(into global: GlobalVariable, from function: Function, _ context: FunctionPassContext) {
210274
var cloner = StaticInitCloner(cloneTo: global, context)
@@ -248,8 +312,11 @@ private indirect enum GlobalInitValue {
248312
}
249313
return builder.createVector(type: type, arguments: elementValues)
250314

251-
case .enumCase(let caseIndex, let payload):
252-
let payloadType = type.getEnumCases(in: function)!.first(where: { $0.index == caseIndex })!.payload!
315+
case .enumCase(let caseIndex):
316+
return builder.createEnum(caseIndex: caseIndex, payload: nil, enumType: type)
317+
318+
case .enumCaseWithPayload(let caseIndex, let payload):
319+
let payloadType = type.getEnumCases(in: function)!.getPayloadType(ofCaseIndex: caseIndex)!
253320
let payloadValue = payload.materializeRecursively(type: payloadType, &cloner, builder, function)
254321
return builder.createEnum(caseIndex: caseIndex, payload: payloadValue, enumType: type)
255322
}
@@ -272,7 +339,7 @@ private indirect enum GlobalInitValue {
272339
_ context: FunctionPassContext
273340
) {
274341
switch self {
275-
case .undefined:
342+
case .undefined, .enumCase:
276343
break
277344
case .constant(let value):
278345
if value.containsLoad(context) {
@@ -281,7 +348,7 @@ private indirect enum GlobalInitValue {
281348
self = .aggregate((value as! Instruction).operands.lazy.map { .constant($0.value) })
282349
resolveLoadsRecursively(from: &stackValues, &resolvedAllocStacks, context)
283350
case let ei as EnumInst:
284-
self = .enumCase(caseIndex: ei.caseIndex, payload: .constant(ei.payload!))
351+
self = .enumCaseWithPayload(caseIndex: ei.caseIndex, payload: .constant(ei.payload!))
285352
resolveLoadsRecursively(from: &stackValues, &resolvedAllocStacks, context)
286353
case let li as LoadInst:
287354
guard let allocStack = li.address as? AllocStackInst,
@@ -306,10 +373,9 @@ private indirect enum GlobalInitValue {
306373
newFields[i].resolveLoadsRecursively(from: &stackValues, &resolvedAllocStacks, context)
307374
}
308375
self = .aggregate(newFields)
309-
case .enumCase(let caseIndex, let payload):
310-
var newPayload = payload
311-
newPayload.resolveLoadsRecursively(from: &stackValues, &resolvedAllocStacks, context)
312-
self = .enumCase(caseIndex: caseIndex, payload: newPayload)
376+
case .enumCaseWithPayload(let caseIndex, var payload):
377+
payload.resolveLoadsRecursively(from: &stackValues, &resolvedAllocStacks, context)
378+
self = .enumCaseWithPayload(caseIndex: caseIndex, payload: payload)
313379
}
314380
}
315381

@@ -321,7 +387,9 @@ private indirect enum GlobalInitValue {
321387
return value.isValidGlobalInitValue(context)
322388
case .aggregate(let fields):
323389
return fields.allSatisfy { $0.isValid(context) }
324-
case .enumCase(_, let payload):
390+
case .enumCase:
391+
return true
392+
case .enumCaseWithPayload(_, let payload):
325393
return payload.isValid(context)
326394
}
327395
}
@@ -361,7 +429,7 @@ private struct InitValueBuilder: AddressDefUseWalker {
361429
let accessPath = store.destination.lookThroughRawLayoutAddress.constantAccessPath
362430
switch accessPath.base {
363431
case .global, .stack:
364-
if !initValue.setElement(to: store.source, at: accessPath.projectionPath, type: originalAddress.type) {
432+
if !initValue.setElement(to: .value(store.source), at: accessPath.projectionPath, type: originalAddress.type) {
365433
return .abortWalk
366434
}
367435
return .continueWalk
@@ -376,13 +444,35 @@ private struct InitValueBuilder: AddressDefUseWalker {
376444
return .abortWalk
377445
}
378446
// The `nonConstAccessPath` now contains a single `.anyIndexedElement`.
379-
if !initValue.setElement(to: store.source, at: nonConstAccessPath.projectionPath, type: originalAddress.type) {
447+
if !initValue.setElement(to: .value(store.source), at: nonConstAccessPath.projectionPath, type: originalAddress.type) {
380448
return .abortWalk
381449
}
382450
return .continueWalk
383451
default:
384452
fatalError("could not compute access path")
385453
}
454+
case let injectEnum as InjectEnumAddrInst:
455+
if injectEnum.element.hasAssociatedValues {
456+
if !injectEnum.operand.value.type.isLoadable(in: injectEnum.parentFunction) {
457+
// TODO: we don't support non-loadable enum cases with payload yet, because IRGen support is missing.
458+
// e.g. `var global: Atomic<Int>? = Atomic<Int>(0)`
459+
// FixedTypeInfo (= used for non-loadable types) is missing the ability to pack a payload into an enum.
460+
return .abortWalk
461+
}
462+
return .continueWalk
463+
}
464+
let accessPath = injectEnum.enum.getAccessPath(fromInitialPath: SmallProjectionPath(.enumCase,
465+
index: injectEnum.caseIndex))
466+
switch accessPath.base {
467+
case .global, .stack:
468+
if !initValue.setElement(to: .enumCaseWithoutPayload(injectEnum), at: accessPath.projectionPath, type: originalAddress.type) {
469+
return .abortWalk
470+
}
471+
return .continueWalk
472+
default:
473+
return .abortWalk
474+
}
475+
386476
case is LoadInst, is DeallocStackInst:
387477
return .continueWalk
388478
case let bi as BuiltinInst:
@@ -477,6 +567,8 @@ private extension Function {
477567
return false
478568
case let store as StoreInst:
479569
return !store.destination.lookThroughRawLayoutAddress.isAddressOfStack(orGlobal: global)
570+
case let injectEnum as InjectEnumAddrInst:
571+
return !injectEnum.enum.isAddressOfStack(orGlobal: global)
480572
case let bi as BuiltinInst where bi.id == .PrepareInitialization:
481573
return false
482574
default:
@@ -533,3 +625,9 @@ private extension Value {
533625
return self
534626
}
535627
}
628+
629+
private extension EnumCases {
630+
func getPayloadType(ofCaseIndex caseIndex: Int) -> Type? {
631+
return first(where: { $0.index == caseIndex })!.payload
632+
}
633+
}

test/SILOptimizer/init_static_globals.sil

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,21 @@ sil_global [let] @graw2: $Raw
168168
// CHECK-LABEL: sil_global [let] @grawArray : $RawArray{{$}}
169169
sil_global [let] @grawArray: $RawArray
170170

171+
// CHECK-LABEL: sil_global [let] @gIndirectNone : $Optional<TwoFields> = {
172+
// CHECK-NEXT: %initval = enum $Optional<TwoFields>, #Optional.none!enumelt
173+
// CHECK-NEXT: }
174+
sil_global [let] @gIndirectNone: $Optional<TwoFields>
175+
176+
// CHECK-LABEL: sil_global [let] @gIndirectSome : $Optional<TwoFields> = {
177+
// CHECK-NEXT: %0 = integer_literal $Builtin.Int32, 11
178+
// CHECK-NEXT: %1 = struct $Int32 (%0)
179+
// CHECK-NEXT: %2 = integer_literal $Builtin.Int32, 10
180+
// CHECK-NEXT: %3 = struct $Int32 (%2)
181+
// CHECK-NEXT: %4 = struct $TwoFields (%1, %3)
182+
// CHECK-NEXT: %initval = enum $Optional<TwoFields>, #Optional.some!enumelt, %4
183+
// CHECK-NEXT: }
184+
sil_global [let] @gIndirectSome: $Optional<TwoFields>
185+
171186
sil @unknownfunc : $@convention(thin) () -> ()
172187

173188
// CHECK-LABEL: sil [global_init_once_fn] [ossa] @globalinit_trivialglobal_func :
@@ -579,3 +594,29 @@ bb0(%0 : $Builtin.RawPointer):
579594
return %21
580595
}
581596

597+
sil [global_init_once_fn] [ossa] @globalinit_indirect_none: $@convention(c) (Builtin.RawPointer) -> () {
598+
bb0(%0 : $Builtin.RawPointer):
599+
alloc_global @gIndirectNone
600+
%2 = global_addr @gIndirectNone : $*Optional<TwoFields>
601+
inject_enum_addr %2, #Optional.none!enumelt
602+
%21 = tuple ()
603+
return %21
604+
}
605+
606+
sil [global_init_once_fn] [ossa] @globalinit_indirect_some : $@convention(c) () -> () {
607+
bb0:
608+
alloc_global @gIndirectSome
609+
%1 = global_addr @gIndirectSome : $*Optional<TwoFields>
610+
%2 = init_enum_data_addr %1, #Optional.some!enumelt
611+
%3 = integer_literal $Builtin.Int32, 10
612+
%4 = struct $Int32 (%3)
613+
%5 = struct_element_addr %2, #TwoFields.b
614+
store %4 to [trivial] %5
615+
%7 = integer_literal $Builtin.Int32, 11
616+
%8 = struct $Int32 (%7)
617+
%9 = struct_element_addr %2, #TwoFields.a
618+
store %8 to [trivial] %9
619+
%10 = tuple ()
620+
return %10 : $()
621+
}
622+
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: %target-build-swift -parse-as-library -Xfrontend -disable-availability-checking -O %s -module-name=test -emit-sil | %FileCheck %s
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %target-build-swift -parse-as-library -Xfrontend -disable-availability-checking -O -module-name=test %s -o %t/a.out
5+
// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT
6+
7+
// REQUIRES: executable_test,swift_stdlib_no_asserts,optimized_stdlib
8+
// REQUIRES: synchronization
9+
// UNSUPPORTED: back_deployment_runtime
10+
11+
import Synchronization
12+
13+
14+
struct TwoAtomics: ~Copyable {
15+
let a: Atomic<UInt8>
16+
let b: Atomic<UInt8>
17+
}
18+
19+
// CHECK-LABEL: sil_global hidden @$s4test7atomic1AA10TwoAtomicsVSgvp : $Optional<TwoAtomics> = {
20+
var atomic1: TwoAtomics? = nil
21+
22+
// CHECK-LABEL: sil_global hidden @$s4test7atomic2AA10TwoAtomicsVvp : $TwoAtomics = {
23+
let atomic2: TwoAtomics = TwoAtomics(a: Atomic(29), b: Atomic(30))
24+
25+
// TODO: this is not initialized statically, because missing IRGen support
26+
var atomic3: TwoAtomics? = TwoAtomics(a: Atomic(27), b: Atomic(28))
27+
28+
// CHECK-LABEL: sil_global hidden @$s4test5mutex15Synchronization5MutexVySiGvp : $Mutex<Int> = {
29+
let mutex = Mutex<Int>(123)
30+
31+
@main
32+
struct Main {
33+
static func main() {
34+
35+
precondition(atomic1 == nil)
36+
37+
// CHECK-OUTPUT: atomic2: 29 30
38+
print("atomic2:", atomic2.a.load(ordering: .relaxed), atomic2.b.load(ordering: .relaxed))
39+
40+
// CHECK-OUTPUT: atomic3: 27 28
41+
switch atomic3 {
42+
case .some(let a):
43+
print("atomic3:", a.a.load(ordering: .relaxed), a.b.load(ordering: .relaxed))
44+
case .none:
45+
break
46+
}
47+
48+
atomic1 = TwoAtomics(a: Atomic(1), b: Atomic(2))
49+
50+
// CHECK-OUTPUT: atomic2: 29 30
51+
print("atomic2:", atomic2.a.load(ordering: .relaxed), atomic2.b.load(ordering: .relaxed))
52+
53+
54+
mutex.withLock {
55+
$0 = $0 + 1
56+
}
57+
58+
// CHECK-OUTPUT: mutex: 124
59+
mutex.withLock {
60+
print("mutex:", $0)
61+
}
62+
}
63+
}
64+
65+

0 commit comments

Comments
 (0)