Skip to content

Commit 16ce6b8

Browse files
authored
Merge pull request #84511 from eeckstein/fix-init-static-globals
InitializeStaticGlobals : reapply support of non-loadable enums with a fix
2 parents 35fab79 + d590b94 commit 16ce6b8

File tree

3 files changed

+246
-16
lines changed

3 files changed

+246
-16
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift

Lines changed: 112 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,41 @@ 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+
if path.isEmpty, case .enumCaseWithoutPayload(let iea) = value {
237+
238+
guard case .undefined = self else {
239+
// The enum was set twice.
240+
return false
241+
}
242+
assert(index == iea.caseIndex)
243+
self = .enumCase(caseIndex: index)
244+
} else {
245+
guard let payloadType = type.getEnumCases(in: value.parentFunction)!.getPayloadType(ofCaseIndex: index) else {
246+
return false
247+
}
248+
switch self {
249+
case .undefined:
250+
// It's the first time we set the payload or a sub-field of it.
251+
var payload = GlobalInitValue.undefined
252+
if !payload.setElement(to: value, at: path, type: payloadType) {
253+
return false
254+
}
255+
self = .enumCaseWithPayload(caseIndex: index, payload: payload)
256+
case .enumCaseWithPayload(let existingIndex, var payload) where index == existingIndex:
257+
// Some sub-field of the enum-payload was already set.
258+
self = .undefined // avoid copy-on-write
259+
if !payload.setElement(to: value, at: path, type: payloadType) {
260+
return false
261+
}
262+
self = .enumCaseWithPayload(caseIndex: index, payload: payload)
263+
default:
264+
return false
265+
}
266+
}
267+
return true
268+
}
269+
208270
/// Creates SIL for this global init value in the initializer of the `global`.
209271
func materialize(into global: GlobalVariable, from function: Function, _ context: FunctionPassContext) {
210272
var cloner = Cloner(cloneToGlobal: global, context)
@@ -248,8 +310,11 @@ private indirect enum GlobalInitValue {
248310
}
249311
return builder.createVector(type: type, arguments: elementValues)
250312

251-
case .enumCase(let caseIndex, let payload):
252-
let payloadType = type.getEnumCases(in: function)!.first(where: { $0.index == caseIndex })!.payload!
313+
case .enumCase(let caseIndex):
314+
return builder.createEnum(caseIndex: caseIndex, payload: nil, enumType: type)
315+
316+
case .enumCaseWithPayload(let caseIndex, let payload):
317+
let payloadType = type.getEnumCases(in: function)!.getPayloadType(ofCaseIndex: caseIndex)!
253318
let payloadValue = payload.materializeRecursively(type: payloadType, &cloner, builder, function)
254319
return builder.createEnum(caseIndex: caseIndex, payload: payloadValue, enumType: type)
255320
}
@@ -272,7 +337,7 @@ private indirect enum GlobalInitValue {
272337
_ context: FunctionPassContext
273338
) {
274339
switch self {
275-
case .undefined:
340+
case .undefined, .enumCase:
276341
break
277342
case .constant(let value):
278343
if value.containsLoad(context) {
@@ -281,7 +346,7 @@ private indirect enum GlobalInitValue {
281346
self = .aggregate((value as! Instruction).operands.lazy.map { .constant($0.value) })
282347
resolveLoadsRecursively(from: &stackValues, &resolvedAllocStacks, context)
283348
case let ei as EnumInst:
284-
self = .enumCase(caseIndex: ei.caseIndex, payload: .constant(ei.payload!))
349+
self = .enumCaseWithPayload(caseIndex: ei.caseIndex, payload: .constant(ei.payload!))
285350
resolveLoadsRecursively(from: &stackValues, &resolvedAllocStacks, context)
286351
case let li as LoadInst:
287352
guard let allocStack = li.address as? AllocStackInst,
@@ -306,10 +371,9 @@ private indirect enum GlobalInitValue {
306371
newFields[i].resolveLoadsRecursively(from: &stackValues, &resolvedAllocStacks, context)
307372
}
308373
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)
374+
case .enumCaseWithPayload(let caseIndex, var payload):
375+
payload.resolveLoadsRecursively(from: &stackValues, &resolvedAllocStacks, context)
376+
self = .enumCaseWithPayload(caseIndex: caseIndex, payload: payload)
313377
}
314378
}
315379

@@ -321,7 +385,9 @@ private indirect enum GlobalInitValue {
321385
return value.isValidGlobalInitValue(context)
322386
case .aggregate(let fields):
323387
return fields.allSatisfy { $0.isValid(context) }
324-
case .enumCase(_, let payload):
388+
case .enumCase:
389+
return true
390+
case .enumCaseWithPayload(_, let payload):
325391
return payload.isValid(context)
326392
}
327393
}
@@ -361,7 +427,7 @@ private struct InitValueBuilder: AddressDefUseWalker {
361427
let accessPath = store.destination.lookThroughRawLayoutAddress.constantAccessPath
362428
switch accessPath.base {
363429
case .global, .stack:
364-
if !initValue.setElement(to: store.source, at: accessPath.projectionPath, type: originalAddress.type) {
430+
if !initValue.setElement(to: .value(store.source), at: accessPath.projectionPath, type: originalAddress.type) {
365431
return .abortWalk
366432
}
367433
return .continueWalk
@@ -376,13 +442,35 @@ private struct InitValueBuilder: AddressDefUseWalker {
376442
return .abortWalk
377443
}
378444
// The `nonConstAccessPath` now contains a single `.anyIndexedElement`.
379-
if !initValue.setElement(to: store.source, at: nonConstAccessPath.projectionPath, type: originalAddress.type) {
445+
if !initValue.setElement(to: .value(store.source), at: nonConstAccessPath.projectionPath, type: originalAddress.type) {
380446
return .abortWalk
381447
}
382448
return .continueWalk
383449
default:
384450
fatalError("could not compute access path")
385451
}
452+
case let injectEnum as InjectEnumAddrInst:
453+
if injectEnum.element.hasAssociatedValues {
454+
if !injectEnum.operand.value.type.isLoadable(in: injectEnum.parentFunction) {
455+
// TODO: we don't support non-loadable enum cases with payload yet, because IRGen support is missing.
456+
// e.g. `var global: Atomic<Int>? = Atomic<Int>(0)`
457+
// FixedTypeInfo (= used for non-loadable types) is missing the ability to pack a payload into an enum.
458+
return .abortWalk
459+
}
460+
return .continueWalk
461+
}
462+
let accessPath = injectEnum.enum.getAccessPath(fromInitialPath: SmallProjectionPath(.enumCase,
463+
index: injectEnum.caseIndex))
464+
switch accessPath.base {
465+
case .global, .stack:
466+
if !initValue.setElement(to: .enumCaseWithoutPayload(injectEnum), at: accessPath.projectionPath, type: originalAddress.type) {
467+
return .abortWalk
468+
}
469+
return .continueWalk
470+
default:
471+
return .abortWalk
472+
}
473+
386474
case is LoadInst, is DeallocStackInst:
387475
return .continueWalk
388476
case let bi as BuiltinInst:
@@ -477,6 +565,8 @@ private extension Function {
477565
return false
478566
case let store as StoreInst:
479567
return !store.destination.lookThroughRawLayoutAddress.isAddressOfStack(orGlobal: global)
568+
case let injectEnum as InjectEnumAddrInst:
569+
return !injectEnum.enum.isAddressOfStack(orGlobal: global)
480570
case let bi as BuiltinInst where bi.id == .PrepareInitialization:
481571
return false
482572
default:
@@ -533,3 +623,9 @@ private extension Value {
533623
return self
534624
}
535625
}
626+
627+
private extension EnumCases {
628+
func getPayloadType(ofCaseIndex caseIndex: Int) -> Type? {
629+
return first(where: { $0.index == caseIndex })!.payload
630+
}
631+
}

test/SILOptimizer/init_static_globals.sil

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ struct TwoFields {
3737
let b: Int32
3838
}
3939

40+
struct WithOptionalEnum {
41+
var e: E?
42+
}
43+
44+
enum E {
45+
case a
46+
case b
47+
}
48+
4049
@_rawLayout(like: Int32)
4150
struct Raw: ~Copyable {}
4251

@@ -168,6 +177,28 @@ sil_global [let] @graw2: $Raw
168177
// CHECK-LABEL: sil_global [let] @grawArray : $RawArray{{$}}
169178
sil_global [let] @grawArray: $RawArray
170179

180+
// CHECK-LABEL: sil_global [let] @gIndirectNone : $Optional<TwoFields> = {
181+
// CHECK-NEXT: %initval = enum $Optional<TwoFields>, #Optional.none!enumelt
182+
// CHECK-NEXT: }
183+
sil_global [let] @gIndirectNone: $Optional<TwoFields>
184+
185+
// CHECK-LABEL: sil_global [let] @gIndirectSome : $Optional<TwoFields> = {
186+
// CHECK-NEXT: %0 = integer_literal $Builtin.Int32, 11
187+
// CHECK-NEXT: %1 = struct $Int32 (%0)
188+
// CHECK-NEXT: %2 = integer_literal $Builtin.Int32, 10
189+
// CHECK-NEXT: %3 = struct $Int32 (%2)
190+
// CHECK-NEXT: %4 = struct $TwoFields (%1, %3)
191+
// CHECK-NEXT: %initval = enum $Optional<TwoFields>, #Optional.some!enumelt, %4
192+
// CHECK-NEXT: }
193+
sil_global [let] @gIndirectSome: $Optional<TwoFields>
194+
195+
// CHECK-LABEL: sil_global [let] @structWithOptionalEnum : $WithOptionalEnum = {
196+
// CHECK-NEXT: %0 = enum $E, #E.a!enumelt
197+
// CHECK-NEXT: %1 = enum $Optional<E>, #Optional.some!enumelt, %0
198+
// CHECK-NEXT: %initval = struct $WithOptionalEnum (%1)
199+
// CHECK-NEXT: }
200+
sil_global [let] @structWithOptionalEnum : $WithOptionalEnum
201+
171202
sil @unknownfunc : $@convention(thin) () -> ()
172203

173204
// CHECK-LABEL: sil [global_init_once_fn] [ossa] @globalinit_trivialglobal_func :
@@ -579,3 +610,41 @@ bb0(%0 : $Builtin.RawPointer):
579610
return %21
580611
}
581612

613+
sil [global_init_once_fn] [ossa] @globalinit_indirect_none: $@convention(c) (Builtin.RawPointer) -> () {
614+
bb0(%0 : $Builtin.RawPointer):
615+
alloc_global @gIndirectNone
616+
%2 = global_addr @gIndirectNone : $*Optional<TwoFields>
617+
inject_enum_addr %2, #Optional.none!enumelt
618+
%21 = tuple ()
619+
return %21
620+
}
621+
622+
sil [global_init_once_fn] [ossa] @globalinit_indirect_some : $@convention(c) () -> () {
623+
bb0:
624+
alloc_global @gIndirectSome
625+
%1 = global_addr @gIndirectSome : $*Optional<TwoFields>
626+
%2 = init_enum_data_addr %1, #Optional.some!enumelt
627+
%3 = integer_literal $Builtin.Int32, 10
628+
%4 = struct $Int32 (%3)
629+
%5 = struct_element_addr %2, #TwoFields.b
630+
store %4 to [trivial] %5
631+
%7 = integer_literal $Builtin.Int32, 11
632+
%8 = struct $Int32 (%7)
633+
%9 = struct_element_addr %2, #TwoFields.a
634+
store %8 to [trivial] %9
635+
%10 = tuple ()
636+
return %10 : $()
637+
}
638+
639+
sil [global_init_once_fn] @globalinit_structWithOptionalEnum : $@convention(c) (Builtin.RawPointer) -> () {
640+
bb0(%0 : $Builtin.RawPointer):
641+
alloc_global @structWithOptionalEnum
642+
%2 = global_addr @structWithOptionalEnum : $*WithOptionalEnum
643+
%3 = struct_element_addr %2, #WithOptionalEnum.e
644+
%4 = init_enum_data_addr %3, #Optional.some!enumelt
645+
inject_enum_addr %4, #E.a!enumelt
646+
inject_enum_addr %3, #Optional.some!enumelt
647+
%7 = tuple ()
648+
return %7
649+
}
650+
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)