Skip to content

Commit 2961caf

Browse files
authored
Merge pull request #66844 from kubamracek/static-init-structs
Allow using structs with trivial initializers in globals that require static initialization (e.g. @_section attribute)
2 parents 83a7e05 + 2bf728d commit 2961caf

File tree

12 files changed

+332
-32
lines changed

12 files changed

+332
-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: 136 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
}
@@ -47,34 +49,43 @@ private func optimizeFunctionsTopDown(using worklist: inout FunctionWorklist,
4749
}
4850
}
4951

52+
fileprivate struct PathFunctionTuple: Hashable {
53+
var path: SmallProjectionPath
54+
var function: Function
55+
}
56+
5057
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
58+
var alreadyInlinedFunctions: Set<PathFunctionTuple> = Set()
59+
60+
var changed = true
61+
while changed {
62+
changed = runSimplification(on: function, context, preserveDebugInfo: true) { instruction, simplifyCtxt in
63+
if let i = instruction as? OnoneSimplifyable {
64+
i.simplify(simplifyCtxt)
65+
if instruction.isDeleted {
66+
return
67+
}
68+
}
69+
switch instruction {
70+
case let apply as FullApplySite:
71+
inlineAndDevirtualize(apply: apply, alreadyInlinedFunctions: &alreadyInlinedFunctions, context, simplifyCtxt)
72+
default:
73+
break
5674
}
5775
}
58-
switch instruction {
59-
case let apply as FullApplySite:
60-
inlineAndDevirtualize(apply: apply, context, simplifyCtxt)
61-
default:
62-
break
63-
}
64-
}
6576

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

68-
removeUnusedMetatypeInstructions(in: function, context)
79+
removeUnusedMetatypeInstructions(in: function, context)
6980

70-
// If this is a just specialized function, try to optimize copy_addr, etc.
71-
if context.optimizeMemoryAccesses(in: function) {
81+
// If this is a just specialized function, try to optimize copy_addr, etc.
82+
changed = context.optimizeMemoryAccesses(in: function) || changed
7283
_ = context.eliminateDeadAllocations(in: function)
7384
}
7485
}
7586

76-
private func inlineAndDevirtualize(apply: FullApplySite, _ context: FunctionPassContext, _ simplifyCtxt: SimplifyContext) {
77-
87+
private func inlineAndDevirtualize(apply: FullApplySite, alreadyInlinedFunctions: inout Set<PathFunctionTuple>,
88+
_ context: FunctionPassContext, _ simplifyCtxt: SimplifyContext) {
7889
if simplifyCtxt.tryDevirtualize(apply: apply, isMandatory: true) != nil {
7990
return
8091
}
@@ -88,7 +99,7 @@ private func inlineAndDevirtualize(apply: FullApplySite, _ context: FunctionPass
8899
return
89100
}
90101

91-
if shouldInline(apply: apply, callee: callee) {
102+
if shouldInline(apply: apply, callee: callee, alreadyInlinedFunctions: &alreadyInlinedFunctions) {
92103
simplifyCtxt.inlineFunction(apply: apply, mandatoryInline: true)
93104

94105
// In OSSA `partial_apply [on_stack]`s are represented as owned values rather than stack locations.
@@ -110,7 +121,7 @@ private func removeUnusedMetatypeInstructions(in function: Function, _ context:
110121
}
111122
}
112123

113-
private func shouldInline(apply: FullApplySite, callee: Function) -> Bool {
124+
private func shouldInline(apply: FullApplySite, callee: Function, alreadyInlinedFunctions: inout Set<PathFunctionTuple>) -> Bool {
114125
if callee.isTransparent {
115126
return true
116127
}
@@ -123,9 +134,103 @@ private func shouldInline(apply: FullApplySite, callee: Function) -> Bool {
123134
// Force inlining them in global initializers so that it's possible to statically initialize the global.
124135
return true
125136
}
137+
if apply.parentFunction.isGlobalInitOnceFunction,
138+
let global = apply.parentFunction.getInitializedGlobal(),
139+
global.mustBeInitializedStatically,
140+
let applyInst = apply as? ApplyInst,
141+
let projectionPath = applyInst.isStored(to: global),
142+
alreadyInlinedFunctions.insert(PathFunctionTuple(path: projectionPath, function: callee)).inserted {
143+
return true
144+
}
126145
return false
127146
}
128147

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

254+
mutating func addAllAnnotatedGlobalInitOnceFunctions(of moduleContext: ModulePassContext) {
255+
for f in moduleContext.functions where f.isGlobalInitOnceFunction {
256+
if let global = f.getInitializedGlobal(),
257+
global.mustBeInitializedStatically {
258+
pushIfNotVisited(f)
259+
}
260+
}
261+
}
262+
149263
mutating func add(calleesOf function: Function) {
150264
for inst in function.instructions {
151265
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)