Skip to content

Commit 145f12f

Browse files
committed
Allow using structs with trivial initializers in globals that require static initialization (e.g. @_section attribute)
Before this change, if a global variable is required to be statically initialized (e.g. due to @_section attribute), we don't allow its type to be a struct, only a scalar type works. This change improves on that by teaching MandatoryPerformanceOptimizations pass to inline struct initializer calls into initializer of globals, as long as they are simple enough so that we can be sure that we don't trigger recursive/infinite inlining.
1 parent 438c067 commit 145f12f

File tree

11 files changed

+283
-32
lines changed

11 files changed

+283
-32
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/SimplificationPasses.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ protocol LateOnoneSimplifyable : Instruction {
3737
let ononeSimplificationPass = FunctionPass(name: "onone-simplification") {
3838
(function: Function, context: FunctionPassContext) in
3939

40-
runSimplification(on: function, context, preserveDebugInfo: true) {
40+
_ = runSimplification(on: function, context, preserveDebugInfo: true) {
4141
if let i = $0 as? OnoneSimplifyable {
4242
i.simplify($1)
4343
}
@@ -47,7 +47,7 @@ let ononeSimplificationPass = FunctionPass(name: "onone-simplification") {
4747
let simplificationPass = FunctionPass(name: "simplification") {
4848
(function: Function, context: FunctionPassContext) in
4949

50-
runSimplification(on: function, context, preserveDebugInfo: false) {
50+
_ = runSimplification(on: function, context, preserveDebugInfo: false) {
5151
if let i = $0 as? Simplifyable {
5252
i.simplify($1)
5353
}
@@ -57,7 +57,7 @@ let simplificationPass = FunctionPass(name: "simplification") {
5757
let lateOnoneSimplificationPass = FunctionPass(name: "late-onone-simplification") {
5858
(function: Function, context: FunctionPassContext) in
5959

60-
runSimplification(on: function, context, preserveDebugInfo: true) {
60+
_ = runSimplification(on: function, context, preserveDebugInfo: true) {
6161
if let i = $0 as? LateOnoneSimplifyable {
6262
i.simplifyLate($1)
6363
} else if let i = $0 as? OnoneSimplifyable {
@@ -73,13 +73,15 @@ let lateOnoneSimplificationPass = FunctionPass(name: "late-onone-simplification"
7373

7474
func runSimplification(on function: Function, _ context: FunctionPassContext,
7575
preserveDebugInfo: Bool,
76-
_ simplify: (Instruction, SimplifyContext) -> ()) {
76+
_ simplify: (Instruction, SimplifyContext) -> ()) -> Bool {
7777
var worklist = InstructionWorklist(context)
7878
defer { worklist.deinitialize() }
7979

80+
var changed = false
8081
let simplifyCtxt = context.createSimplifyContext(preserveDebugInfo: preserveDebugInfo,
8182
notifyInstructionChanged: {
8283
worklist.pushIfNotVisited($0)
84+
changed = true
8385
})
8486

8587
// Push in reverse order so that popping from the tail of the worklist visits instruction in forward order again.
@@ -97,7 +99,7 @@ func runSimplification(on function: Function, _ context: FunctionPassContext,
9799
continue
98100
}
99101
if !context.continueWithNextSubpassRun(for: instruction) {
100-
return
102+
return changed
101103
}
102104
simplify(instruction, simplifyCtxt)
103105
}
@@ -110,6 +112,8 @@ func runSimplification(on function: Function, _ context: FunctionPassContext,
110112
if context.needFixStackNesting {
111113
function.fixStackNesting(context)
112114
}
115+
116+
return changed
113117
}
114118

115119
private func cleanupDeadInstructions(in function: Function,

SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift

Lines changed: 132 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212

1313
import SIL
1414

15-
/// Performs mandatory optimizations for performance-annotated functions.
15+
/// Performs mandatory optimizations for performance-annotated functions, and global
16+
/// variable initializers that are required to be statically initialized.
1617
///
1718
/// Optimizations include:
1819
/// * de-virtualization
@@ -22,14 +23,15 @@ import SIL
2223
/// * dead alloc elimination
2324
/// * instruction simplification
2425
///
25-
/// The pass starts with performance-annotated functions and transitively handles
26+
/// The pass starts with performance-annotated functions / globals and transitively handles
2627
/// called functions.
2728
///
2829
let mandatoryPerformanceOptimizations = ModulePass(name: "mandatory-performance-optimizations") {
2930
(moduleContext: ModulePassContext) in
3031

3132
var worklist = FunctionWorklist()
3233
worklist.addAllPerformanceAnnotatedFunctions(of: moduleContext)
34+
worklist.addAllAnnotatedGlobalInitOnceFunctions(of: moduleContext)
3335

3436
optimizeFunctionsTopDown(using: &worklist, moduleContext)
3537
}
@@ -48,33 +50,37 @@ private func optimizeFunctionsTopDown(using worklist: inout FunctionWorklist,
4850
}
4951

5052
private func optimize(function: Function, _ context: FunctionPassContext) {
51-
runSimplification(on: function, context, preserveDebugInfo: true) { instruction, simplifyCtxt in
52-
if let i = instruction as? OnoneSimplifyable {
53-
i.simplify(simplifyCtxt)
54-
if instruction.isDeleted {
55-
return
53+
var alreadyInlinedFunctions: [SmallProjectionPath:Set<Function>] = [:]
54+
55+
var changed = true
56+
while changed {
57+
changed = runSimplification(on: function, context, preserveDebugInfo: true) { instruction, simplifyCtxt in
58+
if let i = instruction as? OnoneSimplifyable {
59+
i.simplify(simplifyCtxt)
60+
if instruction.isDeleted {
61+
return
62+
}
63+
}
64+
switch instruction {
65+
case let apply as FullApplySite:
66+
inlineAndDevirtualize(apply: apply, alreadyInlinedFunctions: &alreadyInlinedFunctions, context, simplifyCtxt)
67+
default:
68+
break
5669
}
5770
}
58-
switch instruction {
59-
case let apply as FullApplySite:
60-
inlineAndDevirtualize(apply: apply, context, simplifyCtxt)
61-
default:
62-
break
63-
}
64-
}
6571

66-
_ = context.specializeApplies(in: function, isMandatory: true)
72+
_ = context.specializeApplies(in: function, isMandatory: true)
6773

68-
removeUnusedMetatypeInstructions(in: function, context)
74+
removeUnusedMetatypeInstructions(in: function, context)
6975

70-
// If this is a just specialized function, try to optimize copy_addr, etc.
71-
if context.optimizeMemoryAccesses(in: function) {
76+
// If this is a just specialized function, try to optimize copy_addr, etc.
77+
changed = context.optimizeMemoryAccesses(in: function) || changed
7278
_ = context.eliminateDeadAllocations(in: function)
7379
}
7480
}
7581

76-
private func inlineAndDevirtualize(apply: FullApplySite, _ context: FunctionPassContext, _ simplifyCtxt: SimplifyContext) {
77-
82+
private func inlineAndDevirtualize(apply: FullApplySite, alreadyInlinedFunctions: inout [SmallProjectionPath:Set<Function>],
83+
_ context: FunctionPassContext, _ simplifyCtxt: SimplifyContext) {
7884
if simplifyCtxt.tryDevirtualize(apply: apply, isMandatory: true) != nil {
7985
return
8086
}
@@ -88,7 +94,7 @@ private func inlineAndDevirtualize(apply: FullApplySite, _ context: FunctionPass
8894
return
8995
}
9096

91-
if shouldInline(apply: apply, callee: callee) {
97+
if shouldInline(apply: apply, callee: callee, alreadyInlinedFunctions: &alreadyInlinedFunctions) {
9298
simplifyCtxt.inlineFunction(apply: apply, mandatoryInline: true)
9399

94100
// In OSSA `partial_apply [on_stack]`s are represented as owned values rather than stack locations.
@@ -110,7 +116,7 @@ private func removeUnusedMetatypeInstructions(in function: Function, _ context:
110116
}
111117
}
112118

113-
private func shouldInline(apply: FullApplySite, callee: Function) -> Bool {
119+
private func shouldInline(apply: FullApplySite, callee: Function, alreadyInlinedFunctions: inout [SmallProjectionPath:Set<Function>]) -> Bool {
114120
if callee.isTransparent {
115121
return true
116122
}
@@ -123,9 +129,104 @@ private func shouldInline(apply: FullApplySite, callee: Function) -> Bool {
123129
// Force inlining them in global initializers so that it's possible to statically initialize the global.
124130
return true
125131
}
132+
if apply.parentFunction.isGlobalInitOnceFunction,
133+
let global = apply.parentFunction.getInitializedGlobal(),
134+
global.mustBeInitializedStatically,
135+
let applyInst = apply as? ApplyInst,
136+
let projectionPath = applyInst.isStored(to: global),
137+
!alreadyInlinedFunctions[projectionPath, default: Set()].contains(callee) {
138+
alreadyInlinedFunctions[projectionPath, default: Set()].insert(callee)
139+
return true
140+
}
126141
return false
127142
}
128143

144+
private extension Value {
145+
/// Analyzes the def-use chain of an apply instruction, and looks for a single chain that leads to a store instruction
146+
/// that initializes a part of a global variable or the entire variable:
147+
///
148+
/// Example:
149+
/// %g = global_addr @global
150+
/// ...
151+
/// %f = function_ref @func
152+
/// %apply = apply %f(...)
153+
/// store %apply to %g <--- is a store to the global trivially (the apply result is immediately going into a store)
154+
///
155+
/// Example:
156+
/// %apply = apply %f(...)
157+
/// %apply2 = apply %f2(%apply)
158+
/// store %apply2 to %g <--- is a store to the global (the apply result has a single chain into the store)
159+
///
160+
/// Example:
161+
/// %a = apply %f(...)
162+
/// %s = struct $MyStruct (%a, %b)
163+
/// store %s to %g <--- is a partial store to the global (returned SmallProjectionPath is MyStruct.s0)
164+
///
165+
/// Example:
166+
/// %a = apply %f(...)
167+
/// %as = struct $AStruct (%other, %a)
168+
/// %bs = struct $BStruct (%as, %bother)
169+
/// store %bs to %g <--- is a partial store to the global (returned SmallProjectionPath is MyStruct.s0.s1)
170+
///
171+
/// Returns nil if we cannot find a singular def-use use chain (e.g. because a value has more than one user)
172+
/// leading to a store to the specified global variable.
173+
func isStored(to global: GlobalVariable) -> SmallProjectionPath? {
174+
var singleUseValue: any Value = self
175+
var path = SmallProjectionPath()
176+
while true {
177+
guard let use = singleUseValue.uses.singleUse else {
178+
return nil
179+
}
180+
181+
switch use.instruction {
182+
case is StructInst:
183+
path = path.push(.structField, index: use.index)
184+
break
185+
case is TupleInst:
186+
path = path.push(.tupleField, index: use.index)
187+
break
188+
case is EnumInst:
189+
path = path.push(.enumCase, index: use.index)
190+
break
191+
case let si as StoreInst:
192+
guard let storeDestination = si.destination as? GlobalAddrInst else {
193+
return nil
194+
}
195+
196+
guard storeDestination.global == global else {
197+
return nil
198+
}
199+
200+
return path
201+
default:
202+
break
203+
}
204+
205+
guard let nextInstruction = use.instruction as? SingleValueInstruction else {
206+
return nil
207+
}
208+
209+
singleUseValue = nextInstruction
210+
}
211+
}
212+
}
213+
214+
private extension Function {
215+
/// Analyzes the global initializer function and returns global it initializes (from `alloc_global` instruction).
216+
func getInitializedGlobal() -> GlobalVariable? {
217+
for inst in self.entryBlock.instructions {
218+
switch inst {
219+
case let agi as AllocGlobalInst:
220+
return agi.global
221+
default:
222+
break
223+
}
224+
}
225+
226+
return nil
227+
}
228+
}
229+
129230
fileprivate struct FunctionWorklist {
130231
private(set) var functions = Array<Function>()
131232
private var pushedFunctions = Set<Function>()
@@ -146,6 +247,15 @@ fileprivate struct FunctionWorklist {
146247
}
147248
}
148249

250+
mutating func addAllAnnotatedGlobalInitOnceFunctions(of moduleContext: ModulePassContext) {
251+
for f in moduleContext.functions where f.isGlobalInitOnceFunction {
252+
if let global = f.getInitializedGlobal(),
253+
global.mustBeInitializedStatically {
254+
pushIfNotVisited(f)
255+
}
256+
}
257+
}
258+
149259
mutating func add(calleesOf function: Function) {
150260
for inst in function.instructions {
151261
switch inst {

SwiftCompilerSources/Sources/SIL/GlobalVariable.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ final public class GlobalVariable : CustomStringConvertible, HasShortDescription
5353
return bridged.canBeInitializedStatically()
5454
}
5555

56+
public var mustBeInitializedStatically: Bool {
57+
return bridged.mustBeInitializedStatically()
58+
}
59+
5660
public static func ==(lhs: GlobalVariable, rhs: GlobalVariable) -> Bool {
5761
lhs === rhs
5862
}

include/swift/AST/DiagnosticsSIL.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ ERROR(performance_callee_unavailable,none,
342342
"called function is not available in this module and can have unpredictable performance", ())
343343
ERROR(bad_attr_on_non_const_global,none,
344344
"global variable must be a compile-time constant to use %0 attribute", (StringRef))
345+
ERROR(global_must_be_compile_time_const,none,
346+
"global variable must be a compile-time constant", ())
345347
NOTE(performance_called_from,none,
346348
"called from here", ())
347349

include/swift/SIL/SILBridging.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ struct BridgedGlobalVar {
369369
inline OptionalBridgedInstruction getStaticInitializerValue() const;
370370

371371
bool canBeInitializedStatically() const;
372+
373+
bool mustBeInitializedStatically() const;
372374
};
373375

374376
struct OptionalBridgedGlobalVar {

include/swift/SIL/SILGlobalVariable.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ class SILGlobalVariable
173173
/// static initializer.
174174
SILInstruction *getStaticInitializerValue();
175175

176+
bool mustBeInitializedStatically() const;
177+
176178
/// Returns true if the global is a statically initialized heap object.
177179
bool isInitializedObject() {
178180
return dyn_cast_or_null<ObjectInst>(getStaticInitializerValue()) != nullptr;

lib/SIL/IR/SILGlobalVariable.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ SILInstruction *SILGlobalVariable::getStaticInitializerValue() {
8181
return &StaticInitializerBlock.back();
8282
}
8383

84+
bool SILGlobalVariable::mustBeInitializedStatically() const {
85+
if (getSectionAttr())
86+
return true;
87+
88+
auto *decl = getDecl();
89+
if (decl && isDefinition() && decl->getAttrs().hasAttribute<SILGenNameAttr>())
90+
return true;
91+
92+
return false;
93+
}
94+
8495
/// Return whether this variable corresponds to a Clang node.
8596
bool SILGlobalVariable::hasClangNode() const {
8697
return (VDecl ? VDecl->hasClangNode() : false);

lib/SIL/Utils/SILBridging.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ bool BridgedGlobalVar::canBeInitializedStatically() const {
208208
return tl.isLoadable();
209209
}
210210

211+
bool BridgedGlobalVar::mustBeInitializedStatically() const {
212+
SILGlobalVariable *global = getGlobal();
213+
return global->mustBeInitializedStatically();
214+
}
215+
211216
//===----------------------------------------------------------------------===//
212217
// SILVTable
213218
//===----------------------------------------------------------------------===//

lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -483,18 +483,20 @@ class PerformanceDiagnosticsPass : public SILModuleTransform {
483483

484484
// Check that @_section, @_silgen_name is only on constant globals
485485
for (SILGlobalVariable &g : module->getSILGlobals()) {
486-
if (!g.getStaticInitializerValue()) {
486+
if (!g.getStaticInitializerValue() && g.mustBeInitializedStatically()) {
487+
auto *decl = g.getDecl();
487488
if (g.getSectionAttr()) {
488489
module->getASTContext().Diags.diagnose(
489490
g.getDecl()->getLoc(), diag::bad_attr_on_non_const_global,
490491
"@_section");
491-
}
492-
493-
auto *decl = g.getDecl();
494-
if (decl && g.isDefinition() && decl->getAttrs().hasAttribute<SILGenNameAttr>()) {
492+
} else if (decl && g.isDefinition() &&
493+
decl->getAttrs().hasAttribute<SILGenNameAttr>()) {
495494
module->getASTContext().Diags.diagnose(
496495
g.getDecl()->getLoc(), diag::bad_attr_on_non_const_global,
497496
"@_silgen_name");
497+
} else {
498+
module->getASTContext().Diags.diagnose(
499+
g.getDecl()->getLoc(), diag::global_must_be_compile_time_const);
498500
}
499501
}
500502
}

0 commit comments

Comments
 (0)