1212
1313import 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///
2829let 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+
5057private 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+
129234fileprivate 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 {
0 commit comments