Skip to content

Commit 792bca7

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
1 parent c0f4b7b commit 792bca7

File tree

3 files changed

+190
-60
lines changed

3 files changed

+190
-60
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift

Lines changed: 82 additions & 56 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,14 +27,25 @@ 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.
@@ -55,14 +67,11 @@ private func optimizeObjectAllocation(allocRef: AllocRefInstBase, _ context: Fun
5567
return nil
5668
}
5769

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 {
70+
guard let endOfInitInst = findEndOfInitialization(of: allocRef) else {
6271
return nil
6372
}
6473

65-
guard let (storesToClassFields, storesToTailElements) = getInitialization(of: allocRef) else {
74+
guard let (storesToClassFields, storesToTailElements) = getInitialization(of: allocRef, ignore: endOfInitInst) else {
6675
return nil
6776
}
6877

@@ -77,32 +86,39 @@ private func optimizeObjectAllocation(allocRef: AllocRefInstBase, _ context: Fun
7786
return replace(object: allocRef, with: outlinedGlobal, context)
7887
}
7988

80-
private func findEndCOWMutation(of object: Value) -> EndCOWMutationInst? {
89+
// The end-of-initialization is either an end_cow_mutation, because it guarantees that the originally initialized
90+
// object is not mutated (it must be copied before mutation).
91+
// Or it is the store to a global let variable in the global's initializer function.
92+
private func findEndOfInitialization(of object: Value) -> Instruction? {
8193
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) {
94+
let user = use.instruction
95+
switch user {
96+
case is UpcastInst,
97+
is UncheckedRefCastInst,
98+
is MoveValueInst,
99+
is EndInitLetRefInst:
100+
if let ecm = findEndOfInitialization(of: user as! SingleValueInstruction) {
93101
return ecm
94102
}
95103
case let ecm as EndCOWMutationInst:
96104
return ecm
105+
case let store as StoreInst:
106+
if let ga = store.destination as? GlobalAddrInst,
107+
ga.global.isLet,
108+
ga.parentFunction.initializedGlobal == ga.global
109+
{
110+
return store
111+
}
97112
default:
98113
break
99114
}
100115
}
101116
return nil
102117
}
103118

104-
private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassFields: [StoreInst],
105-
storesToTailElements: [StoreInst])? {
119+
private func getInitialization(of allocRef: AllocRefInstBase, ignore ignoreInst: Instruction)
120+
-> (storesToClassFields: [StoreInst], storesToTailElements: [StoreInst])?
121+
{
106122
guard let numTailElements = allocRef.numTailElements else {
107123
return nil
108124
}
@@ -115,9 +131,10 @@ private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassF
115131
// store %0 to %3
116132
// %4 = tuple_element_addr %2, 1
117133
// store %1 to %4
118-
var tailStores = Array<StoreInst?>(repeating: nil, count: numTailElements * allocRef.numStoresPerTailElement)
134+
let tailCount = numTailElements != 0 ? numTailElements * allocRef.numStoresPerTailElement : 0
135+
var tailStores = Array<StoreInst?>(repeating: nil, count: tailCount)
119136

120-
if !findInitStores(of: allocRef, &fieldStores, &tailStores) {
137+
if !findInitStores(of: allocRef, &fieldStores, &tailStores, ignore: ignoreInst) {
121138
return nil
122139
}
123140

@@ -130,19 +147,16 @@ private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassF
130147

131148
private func findInitStores(of object: Value,
132149
_ fieldStores: inout [StoreInst?],
133-
_ tailStores: inout [StoreInst?]) -> Bool {
150+
_ tailStores: inout [StoreInst?],
151+
ignore ignoreInst: Instruction) -> Bool {
134152
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) {
153+
let user = use.instruction
154+
switch user {
155+
case is UpcastInst,
156+
is UncheckedRefCastInst,
157+
is MoveValueInst,
158+
is EndInitLetRefInst:
159+
if !findInitStores(of: user as! SingleValueInstruction, &fieldStores, &tailStores, ignore: ignoreInst) {
146160
return false
147161
}
148162
case let rea as RefElementAddrInst:
@@ -153,6 +167,8 @@ private func findInitStores(of object: Value,
153167
if !findStores(toTailAddress: rta, tailElementIndex: 0, stores: &tailStores) {
154168
return false
155169
}
170+
case ignoreInst:
171+
break
156172
default:
157173
if !isValidUseOfObject(use) {
158174
return false
@@ -243,8 +259,7 @@ private func isValidUseOfObject(_ use: Operand) -> Bool {
243259
is DeallocStackRefInst,
244260
is StrongRetainInst,
245261
is StrongReleaseInst,
246-
is FixLifetimeInst,
247-
is EndCOWMutationInst:
262+
is FixLifetimeInst:
248263
return true
249264

250265
case let mdi as MarkDependenceInst:
@@ -314,23 +329,25 @@ private func constructObject(of allocRef: AllocRefInstBase,
314329
}
315330
let globalBuilder = Builder(staticInitializerOf: global, context)
316331

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))
332+
if !storesToTailElements.isEmpty {
333+
// Create the initializers for the tail elements.
334+
let numTailTupleElems = allocRef.numStoresPerTailElement
335+
if numTailTupleElems > 1 {
336+
// The elements are tuples: combine numTailTupleElems elements to a single tuple instruction.
337+
for elementIdx in 0..<allocRef.numTailElements! {
338+
var tupleElems = [Value]()
339+
for tupleIdx in 0..<numTailTupleElems {
340+
let store = storesToTailElements[elementIdx * numTailTupleElems + tupleIdx]
341+
tupleElems.append(cloner.clone(store.source as! SingleValueInstruction))
342+
}
343+
let tuple = globalBuilder.createTuple(type: allocRef.tailAllocatedTypes[0], elements: tupleElems)
344+
objectArgs.append(tuple)
345+
}
346+
} else {
347+
// The non-tuple element case.
348+
for store in storesToTailElements {
349+
objectArgs.append(cloner.clone(store.source as! SingleValueInstruction))
326350
}
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))
334351
}
335352
}
336353
globalBuilder.createObject(type: allocRef.type, arguments: objectArgs, numBaseElements: storesToClassFields.count)
@@ -374,6 +391,10 @@ private func rewriteUses(of startValue: Value, _ context: FunctionPassContext) {
374391
worklist.pushIfNotVisited(usersOf: endMutation)
375392
endMutation.uses.replaceAll(with: endMutation.instance, context)
376393
context.erase(instruction: endMutation)
394+
case let eilr as EndInitLetRefInst:
395+
worklist.pushIfNotVisited(usersOf: eilr)
396+
eilr.uses.replaceAll(with: eilr.operand.value, context)
397+
context.erase(instruction: eilr)
377398
case let upCast as UpcastInst:
378399
worklist.pushIfNotVisited(usersOf: upCast)
379400
case let refCast as UncheckedRefCastInst:
@@ -407,6 +428,11 @@ private extension AllocRefInstBase {
407428
}
408429

409430
var numTailElements: Int? {
431+
432+
if tailAllocatedCounts.count == 0 {
433+
return 0
434+
}
435+
410436
// We only support a single tail allocated array.
411437
// Stdlib's tail allocated arrays don't have any side-effects in the constructor if the element type is trivial.
412438
// TODO: also exclude custom tail allocated arrays which might have side-effects in the destructor.

test/SILOptimizer/objectoutliner.sil

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ sil_global [let] @g1 : $Int32 = {
1818
%initval = struct $Int32 (%0 : $Builtin.Int32)
1919
}
2020

21+
sil_global [let] @gobj : $Obj
22+
sil_global @gobj_var : $Obj
23+
2124
// CHECK-LABEL: sil_global private @outline_global_simpleTv_ : $Obj = {
2225
// CHECK-NEXT: %0 = integer_literal $Builtin.Int64, 1
2326
// CHECK-NEXT: %1 = struct $Int64 (%0 : $Builtin.Int64)
@@ -61,6 +64,12 @@ sil_global [let] @g1 : $Int32 = {
6164
// CHECK-NEXT: %initval = object $Obj (%1 : $Int64, [tail_elems] %4 : $UnsafePointer<Int32>)
6265
// CHECK-NEXT: }
6366

67+
// CHECK-LABEL: sil_global private @init_gobjTv_ : $Obj = {
68+
// CHECK-NEXT: %0 = integer_literal $Builtin.Int64, 27
69+
// CHECK-NEXT: %1 = struct $Int64 (%0 : $Builtin.Int64)
70+
// CHECK-NEXT: %initval = object $Obj (%1 : $Int64)
71+
// CHECK-NEXT: }
72+
6473
// CHECK-LABEL: sil @outline_global_simple
6574
// CHECK: [[G:%[0-9]+]] = global_value @outline_global_simpleTv_ : $Obj
6675
// CHECK: strong_retain [[G]] : $Obj
@@ -197,19 +206,18 @@ bb0:
197206
return %r : $()
198207
}
199208

200-
// CHECK-LABEL: sil @dont_outline_without_tail_elems
209+
// CHECK-LABEL: sil @dont_outline_without_end_cow_mutation
201210
// CHECK: alloc_ref
202211
// CHECK: store
203212
// CHECK: return
204-
sil @dont_outline_without_tail_elems : $@convention(thin) () -> () {
213+
sil @dont_outline_without_end_cow_mutation : $@convention(thin) () -> () {
205214
bb0:
206215
%1 = integer_literal $Builtin.Int64, 1
207216
%4 = struct $Int64 (%1 : $Builtin.Int64)
208217
%7 = alloc_ref $Obj
209218
%9 = ref_element_addr %7 : $Obj, #Obj.value
210219
store %4 to %9 : $*Int64
211-
%10 = end_cow_mutation %7 : $Obj
212-
strong_release %10 : $Obj
220+
strong_release %7 : $Obj
213221
%r = tuple ()
214222
return %r : $()
215223
}
@@ -448,3 +456,44 @@ bb0:
448456
return %r : $()
449457
}
450458

459+
// CHECK-LABEL: sil [global_init_once_fn] @init_gobj :
460+
// CHECK: [[A:%.*]] = global_addr @gobj : $*Obj
461+
// CHECK: [[G:%[0-9]+]] = global_value @init_gobjTv_ : $Obj
462+
// CHECK: strong_retain [[G]] : $Obj
463+
// CHECK: store [[G]] to [[A]]
464+
// CHECK: } // end sil function 'init_gobj'
465+
sil [global_init_once_fn] @init_gobj : $@convention(c) (Builtin.RawPointer) -> () {
466+
bb0(%0 : $Builtin.RawPointer):
467+
alloc_global @gobj
468+
%2 = global_addr @gobj : $*Obj
469+
%3 = integer_literal $Builtin.Int64, 27
470+
%4 = struct $Int64 (%3 : $Builtin.Int64)
471+
%5 = alloc_ref $Obj
472+
%8 = end_init_let_ref %5 : $Obj
473+
%9 = ref_element_addr %8 : $Obj, #Obj.value
474+
store %4 to %9 : $*Int64
475+
store %8 to %2 : $*Obj
476+
%12 = tuple ()
477+
return %12 : $()
478+
}
479+
480+
// CHECK-LABEL: sil [global_init_once_fn] @init_gobj_var :
481+
// CHECK: alloc_ref
482+
// CHECK-NOT: global_value
483+
// CHECK: } // end sil function 'init_gobj_var'
484+
sil [global_init_once_fn] @init_gobj_var : $@convention(c) (Builtin.RawPointer) -> () {
485+
bb0(%0 : $Builtin.RawPointer):
486+
alloc_global @gobj_var
487+
%2 = global_addr @gobj_var : $*Obj
488+
%3 = integer_literal $Builtin.Int64, 27
489+
%4 = struct $Int64 (%3 : $Builtin.Int64)
490+
%5 = alloc_ref $Obj
491+
%8 = end_init_let_ref %5 : $Obj
492+
%9 = ref_element_addr %8 : $Obj, #Obj.value
493+
store %4 to %9 : $*Int64
494+
store %8 to %2 : $*Obj
495+
%12 = tuple ()
496+
return %12 : $()
497+
}
498+
499+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// RUN: %target-swift-frontend -parse-as-library %s -O -sil-verify-all -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test -emit-sil | %FileCheck %s
2+
// RUN: %target-swift-frontend -parse-as-library %s -O -sil-verify-all -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test -emit-ir | %FileCheck %s -check-prefix=CHECK-LLVM
3+
4+
// Also do an end-to-end test to check all components, including IRGen.
5+
// RUN: %empty-directory(%t)
6+
// RUN: %target-build-swift -parse-as-library -O -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test %s -o %t/a.out
7+
// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT
8+
// REQUIRES: executable_test,swift_stdlib_no_asserts,optimized_stdlib
9+
// REQUIRES: CPU=arm64 || CPU=x86_64
10+
11+
12+
public class C {
13+
var x: Int
14+
15+
init(x: Int) {
16+
self.x = x
17+
}
18+
}
19+
20+
public class D: C {}
21+
22+
// CHECK-LABEL: sil_global private @$s4test1c_WZTv_ : $C = {
23+
// CHECK-NEXT: %0 = integer_literal $Builtin.Int64, 27 // user: %1
24+
// CHECK-NEXT: %1 = struct $Int (%0 : $Builtin.Int64) // user: %2
25+
// CHECK-NEXT: %initval = object $C (%1 : $Int)
26+
// CHECK-NEXT: }
27+
28+
// CHECK-LLVM-LABEL: @"$s4test1c_WZTv_" ={{.*}} global %{{[a-zA-Z_0-9]*}}c { [1 x i64] zeroinitializer, %{{[a-zA-Z_0-9]*}} <{ %swift.refcounted zeroinitializer, %TSi <{ i64 27 }> }
29+
30+
public let c = C(x: 27)
31+
32+
33+
// CHECK-LABEL: sil [noinline] @$s4test6testitAA1CCyF : $@convention(thin) () -> @owned C {
34+
// CHECK: [[C:%.*]] = global_value @$s4test1c_WZTv_ : $C
35+
// CHECK-NEXT: return [[C]]
36+
// CHECK: } // end sil function '$s4test6testitAA1CCyF'
37+
38+
// CHECK-LLVM-LABEL: define {{.*}} @"$s4test6testitAA1CCyF"
39+
// CHECK-LLVM: [[C:%.*]] = tail call ptr @swift_initStaticObject({{.*}} getelementptr {{.*}}, ptr @"$s4test1c_WZTv_", i64 0, i32 1))
40+
// CHECK-LLVM-NEXT: ret ptr [[C]]
41+
// CHECK-LLVM: }
42+
@inline(never)
43+
public func testit() -> C {
44+
return c
45+
}
46+
47+
@main struct Main {
48+
static func main() {
49+
// CHECK-OUTPUT: c.x=27
50+
print("c.x=\(testit().x)")
51+
// CHECK-OUTPUT: c=test.C
52+
print("c=\(testit())")
53+
}
54+
}
55+

0 commit comments

Comments
 (0)