Skip to content

Commit 772d059

Browse files
committed
ObjectOutliner: support outlining of classes in global let variables
``` let c = SomeClass() ``` is turned into ``` private let outlinedVariable = SomeClass() // statically initialized and allocated in the data section let c = outlinedVariable ``` rdar://111021230 rdar://115502043 Also, make the ObjectOutliner work for OSSA. Though, it currently doesn't run in the OSSA pipeline.
1 parent 2a169cd commit 772d059

File tree

4 files changed

+250
-66
lines changed

4 files changed

+250
-66
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift

Lines changed: 102 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212

1313
import 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
///
4254
let 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

131160
private 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.

lib/SIL/IR/ValueOwnership.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ CONSTANT_OWNERSHIP_INST(Owned, WeakCopyValue)
7171

7272
CONSTANT_OWNERSHIP_INST(Guaranteed, BeginBorrow)
7373
CONSTANT_OWNERSHIP_INST(Guaranteed, LoadBorrow)
74+
CONSTANT_OWNERSHIP_INST(None, GlobalValue)
7475
CONSTANT_OWNERSHIP_INST(Owned, AllocBox)
7576
CONSTANT_OWNERSHIP_INST(Owned, AllocExistentialBox)
7677
CONSTANT_OWNERSHIP_INST(Owned, AllocRef)
@@ -85,7 +86,6 @@ CONSTANT_OWNERSHIP_INST(Owned, EndInitLetRef)
8586
CONSTANT_OWNERSHIP_INST(Owned, BeginDeallocRef)
8687
CONSTANT_OWNERSHIP_INST(Owned, KeyPath)
8788
CONSTANT_OWNERSHIP_INST(Owned, InitExistentialValue)
88-
CONSTANT_OWNERSHIP_INST(Owned, GlobalValue) // TODO: is this correct?
8989

9090
// One would think that these /should/ be unowned. In truth they are owned since
9191
// objc metatypes do not go through the retain/release fast path. In their

0 commit comments

Comments
 (0)