Skip to content

Commit 143d8ff

Browse files
authored
Merge pull request #83505 from eeckstein/non-loadable-global-inits
Optimizer: support statically initialized `Atomic` and `Mutex` globals
2 parents 3d6c240 + eaf38da commit 143d8ff

23 files changed

+436
-84
lines changed

SwiftCompilerSources/Sources/AST/Declarations.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ final public class AccessorDecl: FuncDecl {}
116116

117117
final public class MacroDecl: ValueDecl {}
118118

119-
final public class EnumElementDecl: ValueDecl {}
119+
final public class EnumElementDecl: ValueDecl {
120+
public var hasAssociatedValues: Bool { bridged.EnumElementDecl_hasAssociatedValues() }
121+
}
120122

121123
final public class ExtensionDecl: Decl {}
122124

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift

Lines changed: 162 additions & 27 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
}
@@ -358,10 +426,10 @@ private struct InitValueBuilder: AddressDefUseWalker {
358426
mutating func leafUse(address: Operand, path: UnusedWalkingPath) -> WalkResult {
359427
switch address.instruction {
360428
case let store as StoreInst:
361-
let accessPath = store.destination.constantAccessPath
429+
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,17 +444,49 @@ 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
388-
case let bi as BuiltinInst where bi.id == .PrepareInitialization:
389-
return .continueWalk
478+
case let bi as BuiltinInst:
479+
switch bi.id {
480+
case .PrepareInitialization:
481+
return .continueWalk
482+
case .AddressOfRawLayout:
483+
if let addr2Ptr = bi.uses.getSingleUser(ofType: PointerToAddressInst.self) {
484+
return walkDownUses(ofAddress: addr2Ptr, path: path)
485+
}
486+
return .abortWalk
487+
default:
488+
return .abortWalk
489+
}
390490
default:
391491
return .abortWalk
392492
}
@@ -466,14 +566,9 @@ private extension Function {
466566
case let alloc as AllocGlobalInst where alloc.global == global:
467567
return false
468568
case let store as StoreInst:
469-
switch store.destination.accessBase {
470-
case .global(let g) where g == global:
471-
return false
472-
case .stack:
473-
return false
474-
default:
475-
return true
476-
}
569+
return !store.destination.lookThroughRawLayoutAddress.isAddressOfStack(orGlobal: global)
570+
case let injectEnum as InjectEnumAddrInst:
571+
return !injectEnum.enum.isAddressOfStack(orGlobal: global)
477572
case let bi as BuiltinInst where bi.id == .PrepareInitialization:
478573
return false
479574
default:
@@ -496,3 +591,43 @@ private extension Function {
496591
context.removeTriviallyDeadInstructionsIgnoringDebugUses(in: self)
497592
}
498593
}
594+
595+
private extension Value {
596+
func isAddressOfStack(orGlobal global: GlobalVariable) -> Bool {
597+
switch accessBase {
598+
case .global(let g) where g == global:
599+
return true
600+
case .stack:
601+
return true
602+
default:
603+
return false
604+
}
605+
}
606+
607+
/// Looks through a `@_rawLayout` projection, which "type casts" a raw-layout struct to it's content, which
608+
/// must match the like-type of the raw-layout, e.g.
609+
/// ```
610+
/// @_rawLayout(like: T)
611+
/// struct S {}
612+
///
613+
/// %2 = builtin "addressOfRawLayout"<S>(%1 : $*S) : $Builtin.RawPointer
614+
/// %3 = pointer_to_address %2 to $*T
615+
/// ```
616+
var lookThroughRawLayoutAddress: Value {
617+
if let ptr2Addr = self as? PointerToAddressInst,
618+
let builtin = ptr2Addr.pointer as? BuiltinInst, builtin.id == .AddressOfRawLayout,
619+
let likeType = builtin.arguments[0].type.rawLayoutSubstitutedLikeType,
620+
builtin.arguments[0].type.rawLayoutSubstitutedCountType == nil,
621+
likeType.canonical == type.canonicalType
622+
{
623+
return builtin.arguments[0]
624+
}
625+
return self
626+
}
627+
}
628+
629+
private extension EnumCases {
630+
func getPayloadType(ofCaseIndex caseIndex: Int) -> Type? {
631+
return first(where: { $0.index == caseIndex })!.payload
632+
}
633+
}

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,7 @@ extension Instruction {
391391
case let bi as BuiltinInst:
392392
switch bi.id {
393393
case .ZeroInitializer:
394-
let type = bi.type.isBuiltinVector ? bi.type.builtinVectorElementType(in: parentFunction) : bi.type
395-
return type.isBuiltinInteger || type.isBuiltinFloat
394+
return bi.arguments.count == 0
396395
case .PtrToInt:
397396
return bi.operands[0].value is StringLiteralInst
398397
case .IntToPtr:

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,7 @@ final public class ExtendLifetimeInst : Instruction, UnaryInstruction {}
673673
final public class InjectEnumAddrInst : Instruction, UnaryInstruction, EnumInstruction {
674674
public var `enum`: Value { operand.value }
675675
public var caseIndex: Int { bridged.InjectEnumAddrInst_caseIndex() }
676+
public var element: EnumElementDecl { bridged.InjectEnumAddrInst_element().getAs(EnumElementDecl.self) }
676677
}
677678

678679
//===----------------------------------------------------------------------===//

SwiftCompilerSources/Sources/SIL/Type.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ public struct Type : TypeProperties, CustomStringConvertible, NoReflectionChildr
103103
bridged.isAddressableForDeps(function.bridged)
104104
}
105105

106+
/// If this is a raw layout type, returns the substituted like-type.
107+
public var rawLayoutSubstitutedLikeType: AST.`Type`? {
108+
.init(bridgedOrNil: bridged.getRawLayoutSubstitutedLikeType())
109+
}
110+
111+
/// If this is a raw layout type, returns the substituted count-type.
112+
public var rawLayoutSubstitutedCountType: AST.`Type`? {
113+
.init(bridgedOrNil: bridged.getRawLayoutSubstitutedCountType())
114+
}
115+
106116
//===--------------------------------------------------------------------===//
107117
// Properties of lowered `SILFunctionType`s
108118
//===--------------------------------------------------------------------===//

include/swift/AST/ASTBridging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ struct BridgedDeclObj {
377377
BRIDGED_INLINE bool ProtocolDecl_requiresClass() const;
378378
BRIDGED_INLINE bool AbstractFunction_isOverridden() const;
379379
BRIDGED_INLINE bool Destructor_isIsolated() const;
380+
BRIDGED_INLINE bool EnumElementDecl_hasAssociatedValues() const;
380381
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedStringRef AccessorDecl_getKindName() const;
381382
};
382383

include/swift/AST/ASTBridgingImpl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ bool BridgedDeclObj::Destructor_isIsolated() const {
219219
return ai.isActorIsolated();
220220
}
221221

222+
bool BridgedDeclObj::EnumElementDecl_hasAssociatedValues() const {
223+
return getAs<swift::EnumElementDecl>()->hasAssociatedValues();
224+
}
225+
222226
//===----------------------------------------------------------------------===//
223227
// MARK: BridgedASTNode
224228
//===----------------------------------------------------------------------===//

include/swift/SIL/SILBridging.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ struct BridgedType {
263263
BRIDGED_INLINE bool isExactSuperclassOf(BridgedType t) const;
264264
BRIDGED_INLINE bool isMarkedAsImmortal() const;
265265
BRIDGED_INLINE bool isAddressableForDeps(BridgedFunction f) const;
266+
BRIDGED_INLINE SWIFT_IMPORT_UNSAFE BridgedASTType getRawLayoutSubstitutedLikeType() const;
267+
BRIDGED_INLINE SWIFT_IMPORT_UNSAFE BridgedASTType getRawLayoutSubstitutedCountType() const;
266268
BRIDGED_INLINE SwiftInt getCaseIdxOfEnumType(BridgedStringRef name) const;
267269
static BRIDGED_INLINE SwiftInt getNumBoxFields(BridgedCanType boxTy);
268270
static SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedType getBoxFieldType(BridgedCanType boxTy,
@@ -770,6 +772,7 @@ struct BridgedInstruction {
770772
BRIDGED_INLINE SwiftInt InitEnumDataAddrInst_caseIndex() const;
771773
BRIDGED_INLINE SwiftInt UncheckedTakeEnumDataAddrInst_caseIndex() const;
772774
BRIDGED_INLINE SwiftInt InjectEnumAddrInst_caseIndex() const;
775+
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedDeclObj InjectEnumAddrInst_element() const;
773776
BRIDGED_INLINE SwiftInt RefElementAddrInst_fieldIndex() const;
774777
BRIDGED_INLINE bool RefElementAddrInst_fieldIsLet() const;
775778
BRIDGED_INLINE bool RefElementAddrInst_isImmutable() const;

0 commit comments

Comments
 (0)