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}
@@ -48,33 +50,37 @@ private func optimizeFunctionsTopDown(using worklist: inout FunctionWorklist,
4850}
4951
5052private 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+
129230fileprivate 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 {
0 commit comments