12
12
13
13
import SIL
14
14
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
+ ///
17
18
/// If a function constructs an Array literal with constant elements (done by storing
18
19
/// the element values into the array buffer), a new global variable is created which
19
20
/// contains the constant elements in its static initializer.
@@ -26,23 +27,43 @@ import SIL
26
27
/// ```
27
28
/// is turned into
28
29
/// ```
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
30
31
///
31
32
/// public func arrayLookup(_ i: Int) -> Int {
32
- /// return outlinedVariable_from_arrayLookup [i]
33
+ /// return outlinedVariable [i]
33
34
/// }
34
35
/// ```
35
36
///
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
37
49
/// `_findStringSwitchCase` library function and the array has many elements (> 16), the
38
50
/// call is redirected to `_findStringSwitchCaseWithCache`. This function builds a cache
39
51
/// (e.g. a Dictionary) and stores it into a global variable.
40
52
/// Then subsequent calls to this function can do a fast lookup using the cache.
41
53
///
42
54
let objectOutliner = FunctionPass ( name: " object-outliner " ) {
43
55
( 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
+
44
62
for inst in function. instructions {
45
63
if let ari = inst as? AllocRefInstBase {
64
+ if !context. continueWithNextSubpassRun ( for: inst) {
65
+ return
66
+ }
46
67
if let globalValue = optimizeObjectAllocation ( allocRef: ari, context) {
47
68
optimizeFindStringCall ( stringArray: globalValue, context)
48
69
}
@@ -55,14 +76,11 @@ private func optimizeObjectAllocation(allocRef: AllocRefInstBase, _ context: Fun
55
76
return nil
56
77
}
57
78
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 {
62
80
return nil
63
81
}
64
82
65
- guard let ( storesToClassFields, storesToTailElements) = getInitialization ( of: allocRef) else {
83
+ guard let ( storesToClassFields, storesToTailElements) = getInitialization ( of: allocRef, ignore : endOfInitInst ) else {
66
84
return nil
67
85
}
68
86
@@ -77,32 +95,42 @@ private func optimizeObjectAllocation(allocRef: AllocRefInstBase, _ context: Fun
77
95
return replace ( object: allocRef, with: outlinedGlobal, context)
78
96
}
79
97
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 ? {
81
102
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 ) {
93
110
return ecm
94
111
}
95
112
case let ecm as EndCOWMutationInst :
113
+ if ecm. doKeepUnique {
114
+ return nil
115
+ }
96
116
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
+ }
97
124
default :
98
125
break
99
126
}
100
127
}
101
128
return nil
102
129
}
103
130
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
+ {
106
134
guard let numTailElements = allocRef. numTailElements else {
107
135
return nil
108
136
}
@@ -115,9 +143,10 @@ private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassF
115
143
// store %0 to %3
116
144
// %4 = tuple_element_addr %2, 1
117
145
// 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)
119
148
120
- if !findInitStores( of: allocRef, & fieldStores, & tailStores) {
149
+ if !findInitStores( of: allocRef, & fieldStores, & tailStores, ignore : ignoreInst ) {
121
150
return nil
122
151
}
123
152
@@ -130,19 +159,17 @@ private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassF
130
159
131
160
private func findInitStores( of object: Value ,
132
161
_ fieldStores: inout [ StoreInst ? ] ,
133
- _ tailStores: inout [ StoreInst ? ] ) -> Bool {
162
+ _ tailStores: inout [ StoreInst ? ] ,
163
+ ignore ignoreInst: Instruction ) -> Bool {
134
164
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) {
146
173
return false
147
174
}
148
175
case let rea as RefElementAddrInst :
@@ -153,6 +180,9 @@ private func findInitStores(of object: Value,
153
180
if !findStores( toTailAddress: rta, tailElementIndex: 0 , stores: & tailStores) {
154
181
return false
155
182
}
183
+ case ignoreInst,
184
+ is EndBorrowInst :
185
+ break
156
186
default :
157
187
if !isValidUseOfObject( use) {
158
188
return false
@@ -243,8 +273,7 @@ private func isValidUseOfObject(_ use: Operand) -> Bool {
243
273
is DeallocStackRefInst ,
244
274
is StrongRetainInst ,
245
275
is StrongReleaseInst ,
246
- is FixLifetimeInst ,
247
- is EndCOWMutationInst :
276
+ is FixLifetimeInst :
248
277
return true
249
278
250
279
case let mdi as MarkDependenceInst :
@@ -314,23 +343,24 @@ private func constructObject(of allocRef: AllocRefInstBase,
314
343
}
315
344
let globalBuilder = Builder ( staticInitializerOf: global, context)
316
345
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 ) )
326
363
}
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 ) )
334
364
}
335
365
}
336
366
globalBuilder. createObject ( type: allocRef. type, arguments: objectArgs, numBaseElements: storesToClassFields. count)
@@ -349,7 +379,9 @@ private func replace(object allocRef: AllocRefInstBase,
349
379
// Replace the alloc_ref by global_value + strong_retain instructions.
350
380
let builder = Builder ( before: allocRef, context)
351
381
let globalValue = builder. createGlobalValue ( global: global, isBare: false )
352
- builder. createStrongRetain ( operand: globalValue)
382
+ if !allocRef. parentFunction. hasOwnership {
383
+ builder. createStrongRetain ( operand: globalValue)
384
+ }
353
385
354
386
rewriteUses ( of: allocRef, context)
355
387
allocRef. uses. replaceAll ( with: globalValue, context)
@@ -367,13 +399,16 @@ private func rewriteUses(of startValue: Value, _ context: FunctionPassContext) {
367
399
case let beginDealloc as BeginDeallocRefInst :
368
400
worklist. pushIfNotVisited ( usersOf: beginDealloc)
369
401
let builder = Builder ( before: beginDealloc, context)
370
- builder. createStrongRelease ( operand: beginDealloc. reference)
402
+ if !beginDealloc. parentFunction. hasOwnership {
403
+ builder. createStrongRelease ( operand: beginDealloc. reference)
404
+ }
371
405
beginDealloc. uses. replaceAll ( with: beginDealloc. reference, context)
372
406
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)
377
412
case let upCast as UpcastInst :
378
413
worklist. pushIfNotVisited ( usersOf: upCast)
379
414
case let refCast as UncheckedRefCastInst :
@@ -407,6 +442,11 @@ private extension AllocRefInstBase {
407
442
}
408
443
409
444
var numTailElements : Int ? {
445
+
446
+ if tailAllocatedCounts. count == 0 {
447
+ return 0
448
+ }
449
+
410
450
// We only support a single tail allocated array.
411
451
// Stdlib's tail allocated arrays don't have any side-effects in the constructor if the element type is trivial.
412
452
// TODO: also exclude custom tail allocated arrays which might have side-effects in the destructor.
0 commit comments