Skip to content

Commit 6877778

Browse files
authored
Merge pull request #65820 from eeckstein/performance-annotations
Fix several problems with performance annotations
2 parents 3e5347c + 13108d6 commit 6877778

File tree

65 files changed

+1526
-784
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1526
-784
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ swift_compiler_sources(Optimizer
1414
InitializeStaticGlobals.swift
1515
ObjCBridgingOptimization.swift
1616
MergeCondFails.swift
17+
NamedReturnValueOptimization.swift
1718
ReleaseDevirtualizer.swift
1819
SimplificationPasses.swift
1920
StackPromotion.swift
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//===--- NamedReturnValueOptimization.swift --------------------------------==//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
/// Removes a `copy_addr` to an indirect out argument by replacing the source of the copy
16+
/// (which must be an `alloc_stack`) with the out argument itself.
17+
///
18+
/// The following SIL pattern will be optimized:
19+
///
20+
/// sil @foo : $@convention(thin) <T> () -> @out T {
21+
/// bb0(%0 : $*T):
22+
/// %2 = alloc_stack $T
23+
/// ...
24+
/// copy_addr %some_value to [init] %2 // or any other writes to %2
25+
/// ...
26+
/// bbN:
27+
/// copy_addr [take] %2 to [init] %0 : $*T // the only use of %0
28+
/// ... // no writes
29+
/// return
30+
///
31+
/// to:
32+
///
33+
/// sil @foo : $@convention(thin) <T> (@out T) -> () {
34+
/// bb0(%0 : $*T):
35+
/// %2 = alloc_stack $T // is dead now
36+
/// ...
37+
/// copy_addr %some_value to [init] %0
38+
/// ...
39+
/// bbN:
40+
/// ...
41+
/// return
42+
///
43+
/// This optimization can be done because we know that:
44+
/// * The out argument dominates all uses of the copy_addr's source (because it's a function argument).
45+
/// * It's not aliased (by definition). We can't allow aliases to be accessed between the initialization and the return.
46+
///
47+
/// This pass shouldn't run before serialization. It might prevent predictable memory optimizations
48+
/// in a caller after inlining, because the memory location (the out argument = an alloc_stack in the caller)
49+
/// might be written multiple times after this optimization.
50+
///
51+
let namedReturnValueOptimization = FunctionPass(name: "named-return-value-optimization") {
52+
(function: Function, context: FunctionPassContext) in
53+
54+
for outArg in function.arguments[0..<function.numIndirectResultArguments] {
55+
if let copyToArg = findCopyForNRVO(for: outArg) {
56+
performNRVO(with: copyToArg, context)
57+
}
58+
}
59+
}
60+
61+
/// Returns a copy_addr which copies from an alloc_stack to the `outArg` at the end of the function.
62+
///
63+
private func findCopyForNRVO(for outArg: FunctionArgument) -> CopyAddrInst? {
64+
guard let singleArgUse = outArg.uses.singleNonDebugUse,
65+
let copyToArg = singleArgUse.instruction as? CopyAddrInst else {
66+
return nil
67+
}
68+
69+
assert(singleArgUse == copyToArg.destinationOperand,
70+
"single use of out-argument cannot be the source of a copy")
71+
72+
// Don't perform NRVO unless the copy is a [take]. This is the easiest way
73+
// to determine that the local variable has ownership of its value and ensures
74+
// that removing a copy is a reference count neutral operation. For example,
75+
// this copy can't be trivially eliminated without adding a retain.
76+
// sil @f : $@convention(thin) (@guaranteed T) -> @out T
77+
// bb0(%in : $*T, %out : $T):
78+
// %local = alloc_stack $T
79+
// store %in to %local : $*T
80+
// copy_addr %local to [init] %out : $*T
81+
if !copyToArg.isTakeOfSrc {
82+
return nil
83+
}
84+
85+
guard let sourceStackAlloc = copyToArg.source as? AllocStackInst else {
86+
return nil
87+
}
88+
89+
// NRVO for alloc_stack [dynamic_lifetime] will invalidate OSSA invariants.
90+
if sourceStackAlloc.hasDynamicLifetime && copyToArg.parentFunction.hasOwnership {
91+
return nil
92+
}
93+
94+
if !(copyToArg.parentBlock.terminator is ReturnInst) {
95+
return nil
96+
}
97+
98+
// This check is overly conservative, because we only need to check if the source
99+
// of the copy is not written to. But the copy to the out argument is usually the last
100+
// instruction of the function, so it doesn't matter.
101+
if isAnyInstructionWritingToMemory(after: copyToArg) {
102+
return nil
103+
}
104+
105+
return copyToArg
106+
}
107+
108+
private func performNRVO(with copy: CopyAddrInst, _ context: FunctionPassContext) {
109+
copy.source.uses.replaceAllExceptDealloc(with: copy.destination, context)
110+
assert(copy.source == copy.destination)
111+
context.erase(instruction: copy)
112+
}
113+
114+
private func isAnyInstructionWritingToMemory(after: Instruction) -> Bool {
115+
var followingInst = after.next
116+
while let fi = followingInst {
117+
if fi.mayWriteToMemory && !(fi is DeallocStackInst) {
118+
return true
119+
}
120+
followingInst = fi.next
121+
}
122+
return false
123+
}
124+
125+
private extension UseList {
126+
func replaceAllExceptDealloc(with replacement: Value, _ context: some MutatingContext) {
127+
for use in self where !(use.instruction is Deallocation) {
128+
use.set(to: replacement, context)
129+
}
130+
}
131+
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/SimplificationPasses.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ let lateOnoneSimplificationPass = FunctionPass(name: "late-onone-simplification"
7171
//===--------------------------------------------------------------------===//
7272

7373

74-
private func runSimplification(on function: Function, _ context: FunctionPassContext,
75-
preserveDebugInfo: Bool,
76-
_ simplify: (Instruction, SimplifyContext) -> ()) {
74+
func runSimplification(on function: Function, _ context: FunctionPassContext,
75+
preserveDebugInfo: Bool,
76+
_ simplify: (Instruction, SimplifyContext) -> ()) {
7777
var worklist = InstructionWorklist(context)
7878
defer { worklist.deinitialize() }
7979

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ swift_compiler_sources(Optimizer
1717
SimplifyDestructure.swift
1818
SimplifyGlobalValue.swift
1919
SimplifyLoad.swift
20+
SimplifyPartialApply.swift
2021
SimplifyStrongRetainRelease.swift
2122
SimplifyStructExtract.swift
2223
SimplifyTupleExtract.swift

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyApply.swift

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,48 +14,18 @@ import SIL
1414

1515
extension ApplyInst : OnoneSimplifyable {
1616
func simplify(_ context: SimplifyContext) {
17-
tryReplaceTrivialApplyOfPartialApply(context)
17+
_ = context.tryDevirtualize(apply: self, isMandatory: false)
1818
}
1919
}
2020

21-
private extension ApplyInst {
22-
func tryReplaceTrivialApplyOfPartialApply(_ context: SimplifyContext) {
23-
guard let pa = callee as? PartialApplyInst else {
24-
return
25-
}
26-
27-
if pa.referencedFunction == nil {
28-
return
29-
}
30-
31-
// Currently we don't handle generic closures. For Onone this is good enough.
32-
// TODO: handle it once we replace the SILCombine simplification with this.
33-
if !allArgumentsAreTrivial(arguments) {
34-
return
35-
}
36-
37-
if !allArgumentsAreTrivial(pa.arguments) {
38-
return
39-
}
40-
41-
if !substitutionMap.isEmpty {
42-
return
43-
}
44-
45-
let allArgs = Array<Value>(arguments) + Array<Value>(pa.arguments)
46-
let builder = Builder(before: self, context)
47-
let newApply = builder.createApply(function: pa.callee, pa.substitutionMap, arguments: allArgs,
48-
isNonThrowing: isNonThrowing, isNonAsync: isNonAsync,
49-
specializationInfo: specializationInfo)
50-
uses.replaceAll(with: newApply, context)
51-
context.erase(instruction: self)
52-
53-
if context.tryDeleteDeadClosure(closure: pa) {
54-
context.notifyInvalidatedStackNesting()
55-
}
21+
extension TryApplyInst : OnoneSimplifyable {
22+
func simplify(_ context: SimplifyContext) {
23+
_ = context.tryDevirtualize(apply: self, isMandatory: false)
5624
}
5725
}
5826

59-
private func allArgumentsAreTrivial(_ args: LazyMapSequence<OperandArray, Value>) -> Bool {
60-
return !args.contains { !$0.hasTrivialType }
27+
extension BeginApplyInst : OnoneSimplifyable {
28+
func simplify(_ context: SimplifyContext) {
29+
_ = context.tryDevirtualize(apply: self, isMandatory: false)
30+
}
6131
}

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ extension BuiltinInst : OnoneSimplifyable {
2424
optimizeIsSameMetatype(context)
2525
case .Once:
2626
optimizeBuiltinOnce(context)
27+
case .CanBeObjCClass:
28+
optimizeCanBeClass(context)
29+
case .AssertConf:
30+
optimizeAssertConfig(context)
2731
default:
2832
if let literal = constantFold(context) {
2933
uses.replaceAll(with: literal, context)
@@ -90,6 +94,43 @@ private extension BuiltinInst {
9094
}
9195
return nil
9296
}
97+
98+
func optimizeCanBeClass(_ context: SimplifyContext) {
99+
guard let ty = substitutionMap.replacementTypes[0] else {
100+
return
101+
}
102+
let literal: IntegerLiteralInst
103+
switch ty.canBeClass {
104+
case .IsNot:
105+
let builder = Builder(before: self, context)
106+
literal = builder.createIntegerLiteral(0, type: type)
107+
case .Is:
108+
let builder = Builder(before: self, context)
109+
literal = builder.createIntegerLiteral(1, type: type)
110+
case .CanBe:
111+
return
112+
default:
113+
fatalError()
114+
}
115+
uses.replaceAll(with: literal, context)
116+
context.erase(instruction: self)
117+
}
118+
119+
func optimizeAssertConfig(_ context: SimplifyContext) {
120+
let literal: IntegerLiteralInst
121+
switch context.options.assertConfiguration {
122+
case .enabled:
123+
let builder = Builder(before: self, context)
124+
literal = builder.createIntegerLiteral(1, type: type)
125+
case .disabled:
126+
let builder = Builder(before: self, context)
127+
literal = builder.createIntegerLiteral(0, type: type)
128+
default:
129+
return
130+
}
131+
uses.replaceAll(with: literal, context)
132+
context.erase(instruction: self)
133+
}
93134
}
94135

95136
private func typesOfValuesAreEqual(_ lhs: Value, _ rhs: Value, in function: Function) -> Bool? {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//===--- SimplifyPartialApply.swift ---------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
extension PartialApplyInst : OnoneSimplifyable {
16+
func simplify(_ context: SimplifyContext) {
17+
let optimizedApplyOfPartialApply = context.tryOptimizeApplyOfPartialApply(closure: self)
18+
if optimizedApplyOfPartialApply {
19+
context.notifyInvalidatedStackNesting()
20+
}
21+
22+
if context.preserveDebugInfo && uses.contains(where: { $0.instruction is DebugValueInst }) {
23+
return
24+
}
25+
26+
// Try to delete the partial_apply.
27+
// In case it became dead because of tryOptimizeApplyOfPartialApply, we don't
28+
// need to copy all arguments again (to extend their lifetimes), because it
29+
// was already done in tryOptimizeApplyOfPartialApply.
30+
if context.tryDeleteDeadClosure(closure: self, needKeepArgsAlive: !optimizedApplyOfPartialApply) {
31+
context.notifyInvalidatedStackNesting()
32+
}
33+
}
34+
}

SwiftCompilerSources/Sources/Optimizer/ModulePasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
swift_compiler_sources(Optimizer
10+
MandatoryPerformanceOptimizations.swift
1011
ReadOnlyGlobalVariables.swift
1112
StackProtection.swift
1213
)

0 commit comments

Comments
 (0)