Skip to content

Commit a478034

Browse files
authored
Merge pull request #62000 from eeckstein/side-effects-in-inliner
Use the new side effect analysis in the performance inliner
2 parents 355d162 + 0403a21 commit a478034

File tree

12 files changed

+127
-36
lines changed

12 files changed

+127
-36
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeSideEffects.swift

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,16 @@ let computeSideEffects = FunctionPass(name: "compute-side-effects", {
4444

4545
var collectedEffects = CollectedEffects(function: function, context)
4646

47-
var deadEndBlocks = DeadEndBlocks(function: function, context)
48-
defer { deadEndBlocks.deinitialize() }
49-
5047
// First step: collect effects from all instructions.
5148
//
5249
for block in function.blocks {
53-
// Effects in blocks from which the function doesn't return are not relevant for the caller.
54-
if deadEndBlocks.isDeadEnd(block: block) {
55-
continue
56-
}
5750
for inst in block.instructions {
5851
collectedEffects.addInstructionEffects(inst)
5952
}
6053
}
6154

6255
// Second step: If an argument has unknown uses, we must add all previously collected
63-
// global effects to the argument, because we don't know to wich "global" side-effect
56+
// global effects to the argument, because we don't know to which "global" side-effect
6457
// instruction the argument might have escaped.
6558
for argument in function.arguments {
6659
collectedEffects.addEffectsForEcapingArgument(argument: argument)
@@ -380,7 +373,7 @@ private struct ArgumentEscapingWalker : ValueDefUseWalker, AddressDefUseWalker {
380373
mutating func hasUnknownUses(argument: FunctionArgument) -> Bool {
381374
if argument.type.isAddress {
382375
return walkDownUses(ofAddress: argument, path: UnusedWalkingPath()) == .abortWalk
383-
} else if argument.hasTrivialType {
376+
} else if argument.hasTrivialNonPointerType {
384377
return false
385378
} else {
386379
return walkDownUses(ofValue: argument, path: UnusedWalkingPath()) == .abortWalk

SwiftCompilerSources/Sources/Optimizer/TestPasses/EscapeInfoDumper.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,24 @@ let addressEscapeInfoDumper = FunctionPass(name: "dump-addr-escape-info", {
130130
let projLhs = lhs.at(AliasAnalysis.getPtrOrAddressPath(for: lhs))
131131
let projRhs = rhs.at(AliasAnalysis.getPtrOrAddressPath(for: rhs))
132132
let mayAlias = projLhs.canAddressAlias(with: projRhs, context)
133-
assert(mayAlias == projRhs.canAddressAlias(with: projLhs, context),
134-
"canAddressAlias(with:) must be symmetric")
133+
if mayAlias != projRhs.canAddressAlias(with: projLhs, context) {
134+
fatalError("canAddressAlias(with:) must be symmetric")
135+
}
135136

137+
let addrReachable: Bool
138+
if lhs.type.isAddress && !rhs.type.isAddress {
139+
let anythingReachableFromRhs = rhs.at(SmallProjectionPath(.anything))
140+
addrReachable = projLhs.canAddressAlias(with: anythingReachableFromRhs, context)
141+
if mayAlias && !addrReachable {
142+
fatalError("mayAlias implies addrReachable")
143+
}
144+
} else {
145+
addrReachable = false
146+
}
136147
if mayAlias {
137148
print("may alias")
149+
} else if addrReachable {
150+
print("address reachable but no alias")
138151
} else {
139152
print("no alias")
140153
}

SwiftCompilerSources/Sources/SIL/Effects.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ extension Function {
128128
default:
129129
break
130130
}
131+
if isProgramTerminationPoint {
132+
// We can ignore any memory writes in a program termination point, because it's not relevant
133+
// for the caller. But we need to consider memory reads, otherwise preceeding memory writes
134+
// would be eliminated by dead-store-elimination in the caller. E.g. String initialization
135+
// for error strings which are printed by the program termination point.
136+
// Regarding ownership: a program termination point must not touch any reference counted objects.
137+
return SideEffects.GlobalEffects(memory: SideEffects.Memory(read: true))
138+
}
131139
var result = SideEffects.GlobalEffects.worstEffects
132140
switch effectAttribute {
133141
case .none:

SwiftCompilerSources/Sources/SIL/Function.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash
121121
}
122122
}
123123

124+
/// True if the callee function is annotated with @_semantics("programtermination_point").
125+
/// This means that the function terminates the program.
126+
public var isProgramTerminationPoint: Bool { hasSemanticsAttribute("programtermination_point") }
127+
124128
/// Kinds of effect attributes which can be defined for a Swift function.
125129
public enum EffectAttribute {
126130
/// No effect attribute is specified.

include/swift/SILOptimizer/Utils/PerformanceInlinerUtils.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ using namespace swift;
2828
extern llvm::cl::opt<bool> EnableSILInliningOfGenerics;
2929

3030
namespace swift {
31-
class SideEffectAnalysis;
31+
class BasicCalleeAnalysis;
3232

3333
// Controls the decision to inline functions with @_semantics, @effect and
3434
// global_init attributes.
@@ -45,7 +45,7 @@ SILFunction *getEligibleFunction(FullApplySite AI,
4545

4646
// Returns true if this is a pure call, i.e. the callee has no side-effects
4747
// and all arguments are constants.
48-
bool isPureCall(FullApplySite AI, SideEffectAnalysis *SEA);
48+
bool isPureCall(FullApplySite AI, BasicCalleeAnalysis *BCA);
4949

5050
/// Fundamental @_semantic tags provide additional semantics for optimization
5151
/// beyond what SIL analysis can discover from the function body. They should be

lib/SILOptimizer/Transforms/PerformanceInliner.cpp

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#include "swift/AST/SemanticAttrs.h"
1616
#include "swift/SIL/MemAccessUtils.h"
1717
#include "swift/SIL/OptimizationRemark.h"
18-
#include "swift/SILOptimizer/Analysis/SideEffectAnalysis.h"
18+
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
1919
#include "swift/SILOptimizer/PassManager/Passes.h"
2020
#include "swift/SILOptimizer/PassManager/Transforms.h"
2121
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
@@ -120,7 +120,7 @@ class SILPerformanceInliner {
120120

121121
DominanceAnalysis *DA;
122122
SILLoopAnalysis *LA;
123-
SideEffectAnalysis *SEA;
123+
BasicCalleeAnalysis *BCA;
124124

125125
// For keys of SILFunction and SILLoop.
126126
llvm::DenseMap<SILFunction *, ShortestPathAnalysis *> SPAs;
@@ -246,10 +246,10 @@ class SILPerformanceInliner {
246246
public:
247247
SILPerformanceInliner(StringRef PassName, SILOptFunctionBuilder &FuncBuilder,
248248
InlineSelection WhatToInline, DominanceAnalysis *DA,
249-
SILLoopAnalysis *LA, SideEffectAnalysis *SEA,
249+
SILLoopAnalysis *LA, BasicCalleeAnalysis *BCA,
250250
OptimizationMode OptMode, OptRemark::Emitter &ORE)
251251
: PassName(PassName), FuncBuilder(FuncBuilder),
252-
WhatToInline(WhatToInline), DA(DA), LA(LA), SEA(SEA), CBI(DA), ORE(ORE),
252+
WhatToInline(WhatToInline), DA(DA), LA(LA), BCA(BCA), CBI(DA), ORE(ORE),
253253
OptMode(OptMode) {}
254254

255255
bool inlineCallsIntoFunction(SILFunction *F);
@@ -352,7 +352,7 @@ bool SILPerformanceInliner::isProfitableToInline(
352352

353353
// It is always OK to inline a simple call.
354354
// TODO: May be consider also the size of the callee?
355-
if (isPureCall(AI, SEA)) {
355+
if (isPureCall(AI, BCA)) {
356356
OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, &ORE, [&]() {
357357
using namespace OptRemark;
358358
return RemarkPassed("Inline", *AI.getInstruction())
@@ -394,6 +394,7 @@ bool SILPerformanceInliner::isProfitableToInline(
394394
// We will only inline if *ALL* dynamic accesses are
395395
// known and have no nested conflict
396396
bool AllAccessesBeneficialToInline = true;
397+
bool returnsAllocation = false;
397398

398399
// Calculate the inlining cost of the callee.
399400
int CalleeCost = 0;
@@ -510,6 +511,18 @@ bool SILPerformanceInliner::isProfitableToInline(
510511
AllAccessesBeneficialToInline = false;
511512
}
512513
}
514+
} else if (auto ri = dyn_cast<ReturnInst>(&I)) {
515+
SILValue retVal = ri->getOperand();
516+
if (auto *uci = dyn_cast<UpcastInst>(retVal))
517+
retVal = uci->getOperand();
518+
519+
// Inlining functions which return an allocated object or partial_apply
520+
// most likely has a benefit in the caller, because e.g. it can enable
521+
// de-virtualization.
522+
if (isa<AllocationInst>(retVal) || isa<PartialApplyInst>(retVal)) {
523+
BlockW.updateBenefit(Benefit, RemovedCallBenefit + 10);
524+
returnsAllocation = true;
525+
}
513526
}
514527
}
515528
// Don't count costs in blocks which are dead after inlining.
@@ -577,6 +590,8 @@ bool SILPerformanceInliner::isProfitableToInline(
577590

578591
if (isClassMethodAtOsize && Benefit > OSizeClassMethodBenefit) {
579592
Benefit = OSizeClassMethodBenefit;
593+
if (returnsAllocation)
594+
Benefit += 10;
580595
}
581596

582597
// This is the final inlining decision.
@@ -1110,7 +1125,7 @@ class SILPerformanceInlinerPass : public SILFunctionTransform {
11101125
void run() override {
11111126
DominanceAnalysis *DA = PM->getAnalysis<DominanceAnalysis>();
11121127
SILLoopAnalysis *LA = PM->getAnalysis<SILLoopAnalysis>();
1113-
SideEffectAnalysis *SEA = PM->getAnalysis<SideEffectAnalysis>();
1128+
BasicCalleeAnalysis *BCA = PM->getAnalysis<BasicCalleeAnalysis>();
11141129
OptRemark::Emitter ORE(DEBUG_TYPE, *getFunction());
11151130

11161131
if (getOptions().InlineThreshold == 0) {
@@ -1122,7 +1137,7 @@ class SILPerformanceInlinerPass : public SILFunctionTransform {
11221137
SILOptFunctionBuilder FuncBuilder(*this);
11231138

11241139
SILPerformanceInliner Inliner(getID(), FuncBuilder, WhatToInline, DA, LA,
1125-
SEA, OptMode, ORE);
1140+
BCA, OptMode, ORE);
11261141

11271142
assert(getFunction()->isDefinition() &&
11281143
"Expected only functions with bodies!");

lib/SILOptimizer/Utils/PerformanceInlinerUtils.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/SILOptimizer/Analysis/ArraySemantic.h"
14-
#include "swift/SILOptimizer/Analysis/SideEffectAnalysis.h"
14+
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
1515
#include "swift/SILOptimizer/Utils/PerformanceInlinerUtils.h"
1616
#include "swift/AST/Module.h"
1717
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
@@ -933,14 +933,11 @@ static bool isConstantArg(Operand *Arg) {
933933
}
934934

935935

936-
bool swift::isPureCall(FullApplySite AI, SideEffectAnalysis *SEA) {
936+
bool swift::isPureCall(FullApplySite AI, BasicCalleeAnalysis *BCA) {
937937
// If a call has only constant arguments and the call is pure, i.e. has
938938
// no side effects, then we should always inline it.
939939
// This includes arguments which are objects initialized with constant values.
940-
FunctionSideEffects ApplyEffects;
941-
SEA->getCalleeEffects(ApplyEffects, AI);
942-
auto GE = ApplyEffects.getGlobalEffects();
943-
if (GE.mayRead() || GE.mayWrite() || GE.mayRetain() || GE.mayRelease())
940+
if (BCA->getMemoryBehavior(AI, /*observeRetains*/ true) != SILInstruction::MemoryBehavior::None)
944941
return false;
945942
// Check if all parameters are constant.
946943
auto Args = AI.getArgumentOperands().slice(AI.getNumIndirectSILResults());

test/SILOptimizer/addr_escape_info.sil

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,44 @@ bb0:
133133
return %5 : $X
134134
}
135135

136+
sil @return_x : $@convention(thin) (@guaranteed X) -> @owned X
137+
138+
// CHECK-LABEL: Address escape information for test_class_argument:
139+
// CHECK: pair 0 - 1
140+
// CHECK-NEXT: %0 = alloc_ref $X
141+
// CHECK-NEXT: %3 = ref_element_addr %2 : $X, #X.s
142+
// CHECK-NEXT: may alias
143+
// CHECK: End function test_class_argument
144+
sil @test_class_argument : $@convention(thin) () -> () {
145+
bb0:
146+
%0 = alloc_ref $X
147+
%1 = function_ref @return_x : $@convention(thin) (@guaranteed X) -> @owned X
148+
%2 = apply %1(%0) : $@convention(thin) (@guaranteed X) -> @owned X
149+
%3 = ref_element_addr %2 : $X, #X.s
150+
fix_lifetime %0 : $X
151+
fix_lifetime %3 : $*Str
152+
%12 = tuple ()
153+
return %12 : $()
154+
}
155+
156+
// CHECK-LABEL: Address escape information for test_reachable_no_alias:
157+
// CHECK: pair 0 - 1
158+
// CHECK-NEXT: %3 = ref_element_addr %2 : $X, #X.s
159+
// CHECK-NEXT: %0 = alloc_ref $XandIntClass
160+
// CHECK-NEXT: address reachable but no alias
161+
// CHECK: End function test_reachable_no_alias
162+
sil @test_reachable_no_alias : $@convention(thin) () -> () {
163+
bb0:
164+
%0 = alloc_ref $XandIntClass
165+
%1 = ref_element_addr %0 : $XandIntClass, #XandIntClass.x
166+
%2 = load %1 : $*X
167+
%3 = ref_element_addr %2 : $X, #X.s
168+
fix_lifetime %3 : $*Str
169+
fix_lifetime %0 : $XandIntClass
170+
%12 = tuple ()
171+
return %12 : $()
172+
}
173+
136174
// CHECK-LABEL: Address escape information for test_struct_with_class:
137175
// CHECK: value: %1 = struct $Container (%0 : $X)
138176
// CHECK-NEXT: ==> %7 = apply %6(%1) : $@convention(thin) (@guaranteed Container) -> ()

test/SILOptimizer/assemblyvision_remark/attributes.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ public func failMatch() {
4040

4141
@_semantics("optremark")
4242
public func allocateInlineCallee() -> Klass {
43-
return Klass() // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
43+
return Klass() // expected-remark {{"main.Klass.__allocating_init()" inlined into "main.allocateInlineCallee()"}}
4444
// expected-remark @-1 {{heap allocated ref of type 'Klass'}}
4545
}
4646

4747
@_semantics("optremark.sil-inliner")
4848
public func allocateInlineCallee2() -> Klass {
49-
return Klass() // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
49+
return Klass() // expected-remark {{"main.Klass.__allocating_init()" inlined into "main.allocateInlineCallee2()"}}
5050
}
5151

5252
// This makes sure we don't emit any remarks if we do not have semantics.
@@ -58,14 +58,14 @@ public func allocateInlineCallee3() -> Klass {
5858
@_semantics("optremark.sil-assembly-vision-remark-gen")
5959
public func mix1() -> (Klass, Klass) {
6060
let x = getGlobal()
61-
return (x, Klass()) // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
61+
return (x, Klass()) // expected-remark {{"main.Klass.__allocating_init()" inlined into "main.mix1()"}}
6262
// expected-remark @-1:16 {{heap allocated ref of type 'Klass'}}
6363
}
6464

6565
@_semantics("optremark.sil-inliner")
6666
public func mix2() -> (Klass, Klass) {
6767
let x = getGlobal()
68-
return (x, Klass()) // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
68+
return (x, Klass()) // expected-remark {{"main.Klass.__allocating_init()" inlined into "main.mix2()"}}
6969
}
7070

7171
@_semantics("optremark.sil-assembly-vision-remark-gen")
@@ -77,7 +77,7 @@ public func mix3() -> (Klass, Klass) {
7777
@_semantics("optremark")
7878
public func mix4() -> (Klass, Klass) {
7979
let x = getGlobal()
80-
return (x, Klass()) // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
80+
return (x, Klass()) // expected-remark {{"main.Klass.__allocating_init()" inlined into "main.mix4()"}}
8181
// expected-remark @-1 {{heap allocated ref of type 'Klass'}}
8282
}
8383

@@ -89,6 +89,6 @@ public func mix5() -> (Klass, Klass) {
8989
@_assemblyVision
9090
public func mix4a() -> (Klass, Klass) {
9191
let x = getGlobal()
92-
return (x, Klass()) // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
92+
return (x, Klass()) // expected-remark {{"main.Klass.__allocating_init()" inlined into "main.mix4a()"}}
9393
// expected-remark @-1 {{heap allocated ref of type 'Klass'}}
9494
}

test/SILOptimizer/inline_heuristics.sil

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: %target-sil-opt -enable-sil-verify-all %s -inline -sil-inline-generics=true -sil-partial-specialization=false -debug-only=sil-inliner 2>%t/log | %FileCheck %s
2+
// RUN: %target-sil-opt -enable-sil-verify-all %s -compute-side-effects -inline -sil-inline-generics=true -sil-partial-specialization=false -debug-only=sil-inliner 2>%t/log | %FileCheck %s
33
// RUN: %FileCheck %s --check-prefix=CHECK-LOG <%t/log
44
// RUN: %target-sil-opt -enable-sil-verify-all %s -inline -sil-inline-generics=true -sil-partial-specialization=true -generic-specializer -debug-only=sil-inliner 2>%t/log | %FileCheck %s --check-prefix=CHECK-PARTIAL-SPECIALIZATION
55
// REQUIRES: asserts
66

7+
// REQUIRES: swift_in_compiler
8+
79
// This test checks the inline heuristics based on the debug log output of
810
// the performance inliner.
911
// Checking the final sil is just a goodie.

test/SILOptimizer/inliner_coldblocks.sil

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: %target-sil-opt -enable-sil-verify-all %s -inline -sil-remarks=sil-inliner -o %t.sil 2>&1 | %FileCheck -check-prefix=REMARKS_PASSED %s
2+
// RUN: %target-sil-opt -enable-sil-verify-all %s -compute-side-effects -inline -sil-remarks=sil-inliner -o %t.sil 2>&1 | %FileCheck -check-prefix=REMARKS_PASSED %s
33
// RUN: %FileCheck %s < %t.sil
44
// RUN: %target-sil-opt -enable-sil-verify-all %s -inline -sil-remarks-missed=sil-inliner -o /dev/null 2>&1 | %FileCheck -check-prefix=REMARKS_MISSED %s
55
// RUN: %target-sil-opt -enable-sil-verify-all %s -inline -save-optimization-record-path %t.yaml -o /dev/null 2>&1 | %FileCheck -allow-empty -check-prefix=NO_REMARKS %s
@@ -9,6 +9,8 @@
99
// REMARKS_MISSED-NOT: remark:
1010
// NO_REMARKS-NOT: remark:
1111

12+
// REQUIRES: swift_in_compiler
13+
1214
sil_stage canonical
1315

1416
import Builtin
@@ -207,7 +209,7 @@ bb0:
207209
// YAML-NEXT: - Callee: '"update_global"'
208210
// YAML-NEXT: DebugLoc:
209211
// YAML: File: {{.*}}inliner_coldblocks.sil
210-
// YAML: Line: 20
212+
// YAML: Line: 22
211213
// YAML: Column: 6
212214
// YAML-NEXT: - String: ' inlined into '
213215
// YAML-NEXT: - Caller: '"regular_large_callee"'

test/SILOptimizer/side_effects.sil

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ bb0(%0 : $*X):
655655
}
656656

657657
// CHECK-LABEL: sil @call_noreturn
658-
// CHECK-NEXT: [global: ]
658+
// CHECK-NEXT: [global: read]
659659
// CHECK-NEXT: {{^[^[]}}
660660
sil @call_noreturn : $@convention(thin) () -> () {
661661
bb0:
@@ -1113,3 +1113,22 @@ bb0(%0 : $@callee_guaranteed () -> ()):
11131113
return %1 : $Builtin.Int1
11141114
}
11151115

1116+
// CHECK-LABEL: sil @test_enum_and_p2a
1117+
// CHECK-NEXT: [%0: write v**.c*.v**]
1118+
// CHECK-NEXT: [global: write,deinit_barrier]
1119+
// CHECK-NEXT: {{^[^[]}}
1120+
sil @test_enum_and_p2a : $@convention(thin) (Optional<UnsafeMutablePointer<Int>>, Int) -> () {
1121+
bb0(%0 : $Optional<UnsafeMutablePointer<Int>>, %1 : $Int):
1122+
switch_enum %0 : $Optional<UnsafeMutablePointer<Int>>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2
1123+
bb1(%2 : $UnsafeMutablePointer<Int>):
1124+
%3 = struct_extract %2 : $UnsafeMutablePointer<Int>, #UnsafeMutablePointer._rawValue
1125+
%4 = pointer_to_address %3 : $Builtin.RawPointer to [strict] $*Int
1126+
store %1 to %4 : $*Int
1127+
br bb3
1128+
bb2:
1129+
br bb3
1130+
bb3:
1131+
%r = tuple()
1132+
return %r : $()
1133+
}
1134+

0 commit comments

Comments
 (0)