Skip to content

Commit 6425565

Browse files
committed
MandatoryPerformanceOptimizations: refine the inlining decisions in global initializers
We inline a function (e.g. a struct initializer) into a global init function if the result is part of the initialized global. Now, also handle functions with indirect return values. Such function can result from not-reabstracted generic specializations. Handle cases where the result is stored into a temporary alloc_stack or directly stored to (a part) of the global variable.
1 parent 393711d commit 6425565

File tree

3 files changed

+118
-17
lines changed

3 files changed

+118
-17
lines changed

SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,66 @@ private func shouldInline(apply: FullApplySite, callee: Function, alreadyInlined
134134
// Force inlining them in global initializers so that it's possible to statically initialize the global.
135135
return true
136136
}
137-
if apply.parentFunction.isGlobalInitOnceFunction,
138-
let global = apply.parentFunction.getInitializedGlobal(),
139-
global.mustBeInitializedStatically,
140-
let applyInst = apply as? ApplyInst,
141-
let projectionPath = applyInst.isStored(to: global),
142-
alreadyInlinedFunctions.insert(PathFunctionTuple(path: projectionPath, function: callee)).inserted {
137+
138+
if apply.substitutionMap.isEmpty,
139+
let pathIntoGlobal = apply.resultIsUsedInGlobalInitialization(),
140+
alreadyInlinedFunctions.insert(PathFunctionTuple(path: pathIntoGlobal, function: callee)).inserted {
143141
return true
144142
}
143+
145144
return false
146145
}
147146

147+
private extension FullApplySite {
148+
func resultIsUsedInGlobalInitialization() -> SmallProjectionPath? {
149+
guard parentFunction.isGlobalInitOnceFunction,
150+
let global = parentFunction.getInitializedGlobal() else {
151+
return nil
152+
}
153+
154+
switch numIndirectResultArguments {
155+
case 0:
156+
return singleDirectResult?.isStored(to: global)
157+
case 1:
158+
let resultAccessPath = arguments[0].accessPath
159+
switch resultAccessPath.base {
160+
case .global(let resultGlobal) where resultGlobal == global:
161+
return resultAccessPath.materializableProjectionPath
162+
case .stack(let allocStack) where resultAccessPath.projectionPath.isEmpty:
163+
return allocStack.getStoredValue(by: self)?.isStored(to: global)
164+
default:
165+
return nil
166+
}
167+
default:
168+
return nil
169+
}
170+
}
171+
}
172+
173+
private extension AllocStackInst {
174+
func getStoredValue(by storingInstruction: Instruction) -> Value? {
175+
// If the only use (beside `storingInstruction`) is a load, it's the value which is
176+
// stored by `storingInstruction`.
177+
var loadedValue: Value? = nil
178+
for use in self.uses {
179+
switch use.instruction {
180+
case is DeallocStackInst:
181+
break
182+
case let load as LoadInst:
183+
if loadedValue != nil {
184+
return nil
185+
}
186+
loadedValue = load
187+
default:
188+
if use.instruction != storingInstruction {
189+
return nil
190+
}
191+
}
192+
}
193+
return loadedValue
194+
}
195+
}
196+
148197
private extension Value {
149198
/// Analyzes the def-use chain of an apply instruction, and looks for a single chain that leads to a store instruction
150199
/// that initializes a part of a global variable or the entire variable:
@@ -193,15 +242,13 @@ private extension Value {
193242
path = path.push(.enumCase, index: ei.caseIndex)
194243
break
195244
case let si as StoreInst:
196-
guard let storeDestination = si.destination as? GlobalAddrInst else {
197-
return nil
198-
}
199-
200-
guard storeDestination.global == global else {
245+
let accessPath = si.destination.getAccessPath(fromInitialPath: path)
246+
switch accessPath.base {
247+
case .global(let storedGlobal) where storedGlobal == global:
248+
return accessPath.materializableProjectionPath
249+
default:
201250
return nil
202251
}
203-
204-
return path
205252
default:
206253
return nil
207254
}

SwiftCompilerSources/Sources/Optimizer/Utilities/AccessUtils.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,13 @@ struct AccessPath : CustomStringConvertible {
293293
return getProjection(to: other) != nil
294294
}
295295

296+
var materializableProjectionPath: SmallProjectionPath? {
297+
if projectionPath.isMaterializable {
298+
return projectionPath
299+
}
300+
return nil
301+
}
302+
296303
/// Returns the projection path to `other` if this access path is equal or contains `other`.
297304
///
298305
/// For example,

test/SILOptimizer/performance-annotations.swift

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -experimental-performance-annotations -emit-sil %s -o /dev/null -verify
1+
// RUN: %target-swift-frontend -parse-as-library -emit-sil %s -o /dev/null -verify
22
// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib
33
// REQUIRES: swift_in_compiler
44

@@ -11,7 +11,7 @@ open class Cl {
1111
final func finalMethod() {}
1212
}
1313

14-
func initFunc() -> Int { return 3 }
14+
func initFunc() -> Int { return Int.random(in: 0..<10) }
1515

1616
struct Str : P {
1717
let x: Int
@@ -163,8 +163,8 @@ class H {
163163
}
164164

165165
struct MyStruct {
166-
static var v: Int = { // expected-note {{called from here}}
167-
return H().hash // expected-error {{Using type 'H' can cause metadata allocation or locks}}
166+
static var v: Int = { // expected-error {{Using type 'H' can cause metadata allocation or locks}}
167+
return H().hash
168168
}()
169169
}
170170

@@ -330,3 +330,50 @@ public func testClosurePassing(a: inout Y) -> Int {
330330
return a.Xsort()
331331
}
332332

333+
struct LargeGenericStruct<T> {
334+
var a: T
335+
var b: T
336+
var c: T
337+
var d: T
338+
var e: T
339+
var f: T
340+
var g: T
341+
var h: T
342+
}
343+
344+
var largeGeneric = LargeGenericStruct<Int>(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8)
345+
346+
@_noLocks
347+
func testLargeGenericStruct() -> LargeGenericStruct<Int> {
348+
return largeGeneric
349+
}
350+
351+
struct ContainsLargeGenericStruct {
352+
var s: LargeGenericStruct<Int>
353+
}
354+
355+
var clgs = ContainsLargeGenericStruct(s: LargeGenericStruct<Int>(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8))
356+
357+
@_noLocks
358+
func testClgs() -> ContainsLargeGenericStruct {
359+
return clgs
360+
}
361+
362+
struct NestedGenericStruct<T> {
363+
var a: T
364+
var b: T
365+
var c: LargeGenericStruct<T>
366+
var d: T
367+
var e: T
368+
var f: T
369+
var g: T
370+
var h: T
371+
}
372+
373+
var nestedGeneric = NestedGenericStruct(a: 1, b: 2, c: LargeGenericStruct<Int>(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8), d: 4, e: 5, f: 6, g: 7, h: 8)
374+
375+
@_noLocks
376+
func testNestedGenericStruct() -> NestedGenericStruct<Int> {
377+
return nestedGeneric
378+
}
379+

0 commit comments

Comments
 (0)