1212
1313import SIL
1414
15- /// Outlines COW objects from functions into statically initialized global variables.
16- /// This is currently only done for Arrays.
15+ /// Outlines class objects from functions into statically initialized global variables.
16+ /// This is currently done for Arrays and for global let variables.
17+ ///
1718/// If a function constructs an Array literal with constant elements (done by storing
1819/// the element values into the array buffer), a new global variable is created which
1920/// contains the constant elements in its static initializer.
@@ -26,23 +27,43 @@ import SIL
2627/// ```
2728/// is turned into
2829/// ```
29- /// private let outlinedVariable_from_arrayLookup = [10, 11, 12] // statically initialized
30+ /// private let outlinedVariable = [10, 11, 12] // statically initialized and allocated in the data section
3031///
3132/// public func arrayLookup(_ i: Int) -> Int {
32- /// return outlinedVariable_from_arrayLookup [i]
33+ /// return outlinedVariable [i]
3334/// }
3435/// ```
3536///
36- /// As a second optimization, if the array is a string literal which is a parameter to the
37+ /// Similar with global let variables:
38+ /// ```
39+ /// let c = SomeClass()
40+ /// ```
41+ /// is turned into
42+ /// ```
43+ /// private let outlinedVariable = SomeClass() // statically initialized and allocated in the data section
44+ ///
45+ /// let c = outlinedVariable
46+ /// ```
47+ ///
48+ /// As a second optimization, if an array is a string literal which is a parameter to the
3749/// `_findStringSwitchCase` library function and the array has many elements (> 16), the
3850/// call is redirected to `_findStringSwitchCaseWithCache`. This function builds a cache
3951/// (e.g. a Dictionary) and stores it into a global variable.
4052/// Then subsequent calls to this function can do a fast lookup using the cache.
4153///
4254let objectOutliner = FunctionPass ( name: " object-outliner " ) {
4355 ( function: Function , context: FunctionPassContext ) in
56+
57+ if function. hasOwnership && !function. isSwift51RuntimeAvailable {
58+ // Since Swift 5.1 global objects have immortal ref counts. And that's required for ownership.
59+ return
60+ }
61+
4462 for inst in function. instructions {
4563 if let ari = inst as? AllocRefInstBase {
64+ if !context. continueWithNextSubpassRun ( for: inst) {
65+ return
66+ }
4667 if let globalValue = optimizeObjectAllocation ( allocRef: ari, context) {
4768 optimizeFindStringCall ( stringArray: globalValue, context)
4869 }
@@ -55,14 +76,11 @@ private func optimizeObjectAllocation(allocRef: AllocRefInstBase, _ context: Fun
5576 return nil
5677 }
5778
58- // The presence of an end_cow_mutation guarantees that the originally initialized
59- // object is not mutated (because it must be copied before mutation).
60- guard let endCOW = findEndCOWMutation ( of: allocRef) ,
61- !endCOW. doKeepUnique else {
79+ guard let endOfInitInst = findEndOfInitialization ( of: allocRef) else {
6280 return nil
6381 }
6482
65- guard let ( storesToClassFields, storesToTailElements) = getInitialization ( of: allocRef) else {
83+ guard let ( storesToClassFields, storesToTailElements) = getInitialization ( of: allocRef, ignore : endOfInitInst ) else {
6684 return nil
6785 }
6886
@@ -77,32 +95,42 @@ private func optimizeObjectAllocation(allocRef: AllocRefInstBase, _ context: Fun
7795 return replace ( object: allocRef, with: outlinedGlobal, context)
7896}
7997
80- private func findEndCOWMutation( of object: Value ) -> EndCOWMutationInst ? {
98+ // The end-of-initialization is either an end_cow_mutation, because it guarantees that the originally initialized
99+ // object is not mutated (it must be copied before mutation).
100+ // Or it is the store to a global let variable in the global's initializer function.
101+ private func findEndOfInitialization( of object: Value ) -> Instruction ? {
81102 for use in object. uses {
82- switch use. instruction {
83- case let uci as UpcastInst :
84- if let ecm = findEndCOWMutation ( of: uci) {
85- return ecm
86- }
87- case let urci as UncheckedRefCastInst :
88- if let ecm = findEndCOWMutation ( of: urci) {
89- return ecm
90- }
91- case let mv as MoveValueInst :
92- if let ecm = findEndCOWMutation ( of: mv) {
103+ let user = use. instruction
104+ switch user {
105+ case is UpcastInst ,
106+ is UncheckedRefCastInst ,
107+ is MoveValueInst ,
108+ is EndInitLetRefInst :
109+ if let ecm = findEndOfInitialization ( of: user as! SingleValueInstruction ) {
93110 return ecm
94111 }
95112 case let ecm as EndCOWMutationInst :
113+ if ecm. doKeepUnique {
114+ return nil
115+ }
96116 return ecm
117+ case let store as StoreInst :
118+ if let ga = store. destination as? GlobalAddrInst ,
119+ ga. global. isLet,
120+ ga. parentFunction. initializedGlobal == ga. global
121+ {
122+ return store
123+ }
97124 default :
98125 break
99126 }
100127 }
101128 return nil
102129}
103130
104- private func getInitialization( of allocRef: AllocRefInstBase ) -> ( storesToClassFields: [ StoreInst ] ,
105- storesToTailElements: [ StoreInst ] ) ? {
131+ private func getInitialization( of allocRef: AllocRefInstBase , ignore ignoreInst: Instruction )
132+ -> ( storesToClassFields: [ StoreInst ] , storesToTailElements: [ StoreInst ] ) ?
133+ {
106134 guard let numTailElements = allocRef. numTailElements else {
107135 return nil
108136 }
@@ -115,9 +143,10 @@ private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassF
115143 // store %0 to %3
116144 // %4 = tuple_element_addr %2, 1
117145 // store %1 to %4
118- var tailStores = Array < StoreInst ? > ( repeating: nil , count: numTailElements * allocRef. numStoresPerTailElement)
146+ let tailCount = numTailElements != 0 ? numTailElements * allocRef. numStoresPerTailElement : 0
147+ var tailStores = Array < StoreInst ? > ( repeating: nil , count: tailCount)
119148
120- if !findInitStores( of: allocRef, & fieldStores, & tailStores) {
149+ if !findInitStores( of: allocRef, & fieldStores, & tailStores, ignore : ignoreInst ) {
121150 return nil
122151 }
123152
@@ -130,19 +159,17 @@ private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassF
130159
131160private func findInitStores( of object: Value ,
132161 _ fieldStores: inout [ StoreInst ? ] ,
133- _ tailStores: inout [ StoreInst ? ] ) -> Bool {
162+ _ tailStores: inout [ StoreInst ? ] ,
163+ ignore ignoreInst: Instruction ) -> Bool {
134164 for use in object. uses {
135- switch use. instruction {
136- case let uci as UpcastInst :
137- if !findInitStores( of: uci, & fieldStores, & tailStores) {
138- return false
139- }
140- case let urci as UncheckedRefCastInst :
141- if !findInitStores( of: urci, & fieldStores, & tailStores) {
142- return false
143- }
144- case let mvi as MoveValueInst :
145- if !findInitStores( of: mvi, & fieldStores, & tailStores) {
165+ let user = use. instruction
166+ switch user {
167+ case is UpcastInst ,
168+ is UncheckedRefCastInst ,
169+ is MoveValueInst ,
170+ is EndInitLetRefInst ,
171+ is BeginBorrowInst :
172+ if !findInitStores( of: user as! SingleValueInstruction , & fieldStores, & tailStores, ignore: ignoreInst) {
146173 return false
147174 }
148175 case let rea as RefElementAddrInst :
@@ -153,6 +180,9 @@ private func findInitStores(of object: Value,
153180 if !findStores( toTailAddress: rta, tailElementIndex: 0 , stores: & tailStores) {
154181 return false
155182 }
183+ case ignoreInst,
184+ is EndBorrowInst :
185+ break
156186 default :
157187 if !isValidUseOfObject( use) {
158188 return false
@@ -243,8 +273,7 @@ private func isValidUseOfObject(_ use: Operand) -> Bool {
243273 is DeallocStackRefInst ,
244274 is StrongRetainInst ,
245275 is StrongReleaseInst ,
246- is FixLifetimeInst ,
247- is EndCOWMutationInst :
276+ is FixLifetimeInst :
248277 return true
249278
250279 case let mdi as MarkDependenceInst :
@@ -314,23 +343,24 @@ private func constructObject(of allocRef: AllocRefInstBase,
314343 }
315344 let globalBuilder = Builder ( staticInitializerOf: global, context)
316345
317- // Create the initializers for the tail elements.
318- let numTailTupleElems = allocRef. numStoresPerTailElement
319- if numTailTupleElems > 1 {
320- // The elements are tuples: combine numTailTupleElems elements to a single tuple instruction.
321- for elementIdx in 0 ..< allocRef. numTailElements! {
322- var tupleElems = [ Value] ( )
323- for tupleIdx in 0 ..< numTailTupleElems {
324- let store = storesToTailElements [ elementIdx * numTailTupleElems + tupleIdx]
325- tupleElems. append ( cloner. clone ( store. source as! SingleValueInstruction ) )
346+ if !storesToTailElements. isEmpty {
347+ // Create the initializers for the tail elements.
348+ let numTailTupleElems = allocRef. numStoresPerTailElement
349+ if numTailTupleElems > 1 {
350+ // The elements are tuples: combine numTailTupleElems elements to a single tuple instruction.
351+ for elementIdx in 0 ..< allocRef. numTailElements! {
352+ let tupleElems = ( 0 ..< numTailTupleElems) . map { tupleIdx in
353+ let store = storesToTailElements [ elementIdx * numTailTupleElems + tupleIdx]
354+ return cloner. clone ( store. source as! SingleValueInstruction )
355+ }
356+ let tuple = globalBuilder. createTuple ( type: allocRef. tailAllocatedTypes [ 0 ] , elements: tupleElems)
357+ objectArgs. append ( tuple)
358+ }
359+ } else {
360+ // The non-tuple element case.
361+ for store in storesToTailElements {
362+ objectArgs. append ( cloner. clone ( store. source as! SingleValueInstruction ) )
326363 }
327- let tuple = globalBuilder. createTuple ( type: allocRef. tailAllocatedTypes [ 0 ] , elements: tupleElems)
328- objectArgs. append ( tuple)
329- }
330- } else {
331- // The non-tuple element case.
332- for store in storesToTailElements {
333- objectArgs. append ( cloner. clone ( store. source as! SingleValueInstruction ) )
334364 }
335365 }
336366 globalBuilder. createObject ( type: allocRef. type, arguments: objectArgs, numBaseElements: storesToClassFields. count)
@@ -349,7 +379,9 @@ private func replace(object allocRef: AllocRefInstBase,
349379 // Replace the alloc_ref by global_value + strong_retain instructions.
350380 let builder = Builder ( before: allocRef, context)
351381 let globalValue = builder. createGlobalValue ( global: global, isBare: false )
352- builder. createStrongRetain ( operand: globalValue)
382+ if !allocRef. parentFunction. hasOwnership {
383+ builder. createStrongRetain ( operand: globalValue)
384+ }
353385
354386 rewriteUses ( of: allocRef, context)
355387 allocRef. uses. replaceAll ( with: globalValue, context)
@@ -367,13 +399,16 @@ private func rewriteUses(of startValue: Value, _ context: FunctionPassContext) {
367399 case let beginDealloc as BeginDeallocRefInst :
368400 worklist. pushIfNotVisited ( usersOf: beginDealloc)
369401 let builder = Builder ( before: beginDealloc, context)
370- builder. createStrongRelease ( operand: beginDealloc. reference)
402+ if !beginDealloc. parentFunction. hasOwnership {
403+ builder. createStrongRelease ( operand: beginDealloc. reference)
404+ }
371405 beginDealloc. uses. replaceAll ( with: beginDealloc. reference, context)
372406 context. erase ( instruction: beginDealloc)
373- case let endMutation as EndCOWMutationInst :
374- worklist. pushIfNotVisited ( usersOf: endMutation)
375- endMutation. uses. replaceAll ( with: endMutation. instance, context)
376- context. erase ( instruction: endMutation)
407+ case is EndCOWMutationInst , is EndInitLetRefInst , is MoveValueInst :
408+ let svi = inst as! SingleValueInstruction
409+ worklist. pushIfNotVisited ( usersOf: svi)
410+ svi. uses. replaceAll ( with: svi. operands [ 0 ] . value, context)
411+ context. erase ( instruction: svi)
377412 case let upCast as UpcastInst :
378413 worklist. pushIfNotVisited ( usersOf: upCast)
379414 case let refCast as UncheckedRefCastInst :
@@ -407,6 +442,11 @@ private extension AllocRefInstBase {
407442 }
408443
409444 var numTailElements : Int ? {
445+
446+ if tailAllocatedCounts. count == 0 {
447+ return 0
448+ }
449+
410450 // We only support a single tail allocated array.
411451 // Stdlib's tail allocated arrays don't have any side-effects in the constructor if the element type is trivial.
412452 // TODO: also exclude custom tail allocated arrays which might have side-effects in the destructor.
0 commit comments