Skip to content

Commit c7ab760

Browse files
authored
Merge pull request #62247 from eeckstein/fix-perf-annotation-optimization
Add and improve some "mandatory" peephole optimization to fix a performance diagnostic
2 parents 9f54289 + d80d7dd commit c7ab760

8 files changed

+119
-19
lines changed

lib/SILOptimizer/Transforms/AllocBoxToStack.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ static bool useCaptured(Operand *UI) {
7070
// These instructions do not cause the address to escape.
7171
if (isa<DebugValueInst>(User)
7272
|| isa<StrongReleaseInst>(User) || isa<StrongRetainInst>(User)
73-
|| isa<DestroyValueInst>(User))
73+
|| isa<DestroyValueInst>(User)
74+
|| isa<EndBorrowInst>(User))
7475
return false;
7576

7677
if (auto *Store = dyn_cast<StoreInst>(User)) {
@@ -254,8 +255,9 @@ static bool partialApplyEscapes(SILValue V, bool examineApply) {
254255
// If we have a copy_value, the copy value does not cause an escape, but its
255256
// uses might do so... so add the copy_value's uses to the worklist and
256257
// continue.
257-
if (auto CVI = dyn_cast<CopyValueInst>(User)) {
258-
llvm::copy(CVI->getUses(), std::back_inserter(Worklist));
258+
if (isa<CopyValueInst>(User) || isa<BeginBorrowInst>(User)) {
259+
llvm::copy(cast<SingleValueInstruction>(User)->getUses(),
260+
std::back_inserter(Worklist));
259261
continue;
260262
}
261263

lib/SILOptimizer/Transforms/GenericSpecializer.cpp

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
2929
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
3030
#include "swift/SILOptimizer/Utils/SILInliner.h"
31+
#include "swift/SILOptimizer/Utils/StackNesting.h"
3132
#include "llvm/ADT/SmallVector.h"
3233

3334
using namespace swift;
@@ -195,10 +196,12 @@ class MandatoryGenericSpecializer : public SILModuleTransform {
195196

196197
void run() override;
197198

198-
bool optimize(SILFunction *func, ClassHierarchyAnalysis *cha);
199+
bool optimize(SILFunction *func, ClassHierarchyAnalysis *cha,
200+
bool &invalidatedStackNesting);
199201

200202
bool optimizeInst(SILInstruction *inst, SILOptFunctionBuilder &funcBuilder,
201-
InstructionDeleter &deleter, ClassHierarchyAnalysis *cha);
203+
InstructionDeleter &deleter, ClassHierarchyAnalysis *cha,
204+
bool &invalidatedStackNesting);
202205
};
203206

204207

@@ -220,7 +223,7 @@ void MandatoryGenericSpecializer::run() {
220223
visited.insert(&function);
221224
}
222225
}
223-
226+
224227
while (!workList.empty()) {
225228
SILFunction *func = workList.pop_back_val();
226229
module->linkFunction(func, SILModule::LinkingMode::LinkAll);
@@ -229,20 +232,26 @@ void MandatoryGenericSpecializer::run() {
229232

230233
// Perform generic specialization and other related optimization.
231234

235+
bool invalidatedStackNesting = false;
236+
232237
// To avoid phase ordering problems of the involved optimizations, iterate
233238
// until we reach a fixed point.
234239
// This should always happen, but to be on the safe side, limit the number
235240
// of iterations to 10 (which is more than enough - usually the loop runs
236241
// 1 to 3 times).
237242
for (int i = 0; i < 10; i++) {
238-
bool changed = optimize(func, cha);
243+
bool changed = optimize(func, cha, invalidatedStackNesting);
239244
if (changed) {
240245
invalidateAnalysis(func, SILAnalysis::InvalidationKind::FunctionBody);
241246
} else {
242247
break;
243248
}
244249
}
245250

251+
if (invalidatedStackNesting) {
252+
StackNesting::fixNesting(func);
253+
}
254+
246255
// Continue specializing called functions.
247256
for (SILBasicBlock &block : *func) {
248257
for (SILInstruction &inst : block) {
@@ -260,7 +269,8 @@ void MandatoryGenericSpecializer::run() {
260269
/// Specialize generic calls in \p func and do some other related optimizations:
261270
/// devirtualization and constant-folding of the Builtin.canBeClass.
262271
bool MandatoryGenericSpecializer::optimize(SILFunction *func,
263-
ClassHierarchyAnalysis *cha) {
272+
ClassHierarchyAnalysis *cha,
273+
bool &invalidatedStackNesting) {
264274
bool changed = false;
265275
SILOptFunctionBuilder funcBuilder(*this);
266276
InstructionDeleter deleter;
@@ -282,7 +292,7 @@ bool MandatoryGenericSpecializer::optimize(SILFunction *func,
282292
continue;
283293

284294
for (SILInstruction *inst : deleter.updatingReverseRange(&block)) {
285-
changed |= optimizeInst(inst, funcBuilder, deleter, cha);
295+
changed |= optimizeInst(inst, funcBuilder, deleter, cha, invalidatedStackNesting);
286296
}
287297
}
288298
deleter.cleanupDeadInstructions();
@@ -295,7 +305,8 @@ bool MandatoryGenericSpecializer::optimize(SILFunction *func,
295305

296306
bool MandatoryGenericSpecializer::
297307
optimizeInst(SILInstruction *inst, SILOptFunctionBuilder &funcBuilder,
298-
InstructionDeleter &deleter, ClassHierarchyAnalysis *cha) {
308+
InstructionDeleter &deleter, ClassHierarchyAnalysis *cha,
309+
bool &invalidatedStackNesting) {
299310
if (auto as = ApplySite::isa(inst)) {
300311

301312
bool changed = false;
@@ -307,10 +318,22 @@ optimizeInst(SILInstruction *inst, SILOptFunctionBuilder &funcBuilder,
307318
as = newAS;
308319
}
309320

310-
auto fas = FullApplySite::isa(as.getInstruction());
311-
if (!fas)
321+
if (auto *pai = dyn_cast<PartialApplyInst>(as)) {
322+
SILBuilderContext builderCtxt(funcBuilder.getModule());
323+
if (tryOptimizeApplyOfPartialApply(pai, builderCtxt, deleter.getCallbacks())) {
324+
// Try to delete the partial_apply.
325+
// We don't need to copy all arguments again (to extend their lifetimes),
326+
// because it was already done in tryOptimizeApplyOfPartialApply.
327+
tryDeleteDeadClosure(pai, deleter.getCallbacks(), /*needKeepArgsAlive=*/ false);
328+
invalidatedStackNesting = true;
329+
return true;
330+
}
312331
return changed;
313-
332+
}
333+
334+
auto fas = FullApplySite::isa(as.getInstruction());
335+
assert(fas);
336+
314337
SILFunction *callee = fas.getReferencedFunctionOrNull();
315338
if (!callee)
316339
return changed;

lib/SILOptimizer/Utils/PartialApplyCombiner.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ bool PartialApplyCombiner::combine() {
240240
auto *user = use->getUser();
241241

242242
// Recurse through copy_value
243-
if (auto *cvi = dyn_cast<CopyValueInst>(user)) {
244-
for (auto *copyUse : cvi->getUses())
243+
if (isa<CopyValueInst>(user) || isa<BeginBorrowInst>(user)) {
244+
for (auto *copyUse : cast<SingleValueInstruction>(user)->getUses())
245245
worklist.push_back(copyUse);
246246
continue;
247247
}

test/IRGen/alloc_box.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
func f() -> Bool? { return nil }
44

5+
var gb = false
6+
var gc: () -> () = {}
7+
58
({
69
guard var b = f() else { return }
710
let c = { b = true }
8-
_ = (b, c)
11+
gb = b
12+
gc = c
913
})()
1014

11-
// CHECK-LABEL: @"$s9alloc_boxyyXEfU_"
15+
// CHECK-LABEL: @"$s9alloc_boxyyXEfU0_"
1216
// CHECK-NOT: call void @swift_setDeallocating
1317
// CHECK: call void @swift_deallocUninitializedObject
1418
// CHECK-NOT: call void @swift_setDeallocating

test/SILOptimizer/allocbox_to_stack_ownership.sil

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,38 @@ bb0(%0 : $Int):
469469
unreachable
470470
}
471471

472+
// CHECK-LABEL: sil [ossa] @test_copy_and_borrow_of_closure
473+
// CHECK-NOT: alloc_box
474+
// CHECK: alloc_stack
475+
// CHECK-NOT: alloc_box
476+
// CHECK: } // end sil function 'test_copy_and_borrow_of_closure'
477+
sil [ossa] @test_copy_and_borrow_of_closure : $@convention(thin) (Int) -> () {
478+
bb0(%0 : $Int):
479+
// This is not destroyed, but the unreachable makes the verifier not trip.
480+
%1 = alloc_box $<τ_0_0> { var τ_0_0 } <Int>
481+
%1a = project_box %1 : $<τ_0_0> { var τ_0_0 } <Int>, 0
482+
store %0 to [trivial] %1a : $*Int
483+
%3 = function_ref @closure3 : $@convention(thin) (@owned <τ_0_0> { var τ_0_0 } <Int>) -> ()
484+
%4 = copy_value %1 : $<τ_0_0> { var τ_0_0 } <Int>
485+
%5 = partial_apply %3(%4) : $@convention(thin) (@owned <τ_0_0> { var τ_0_0 } <Int>) -> ()
486+
%6 = begin_borrow %5 : $@callee_owned () -> ()
487+
%7 = copy_value %6 : $@callee_owned () -> ()
488+
apply %7() : $@callee_owned () -> ()
489+
end_borrow %6 : $@callee_owned () -> ()
490+
destroy_value %5 : $@callee_owned () -> ()
491+
destroy_value %1 : $<τ_0_0> { var τ_0_0 } <Int>
492+
%r = tuple ()
493+
return %r : $()
494+
}
495+
496+
sil [ossa] @closure3 : $@convention(thin) (@owned <τ_0_0> { var τ_0_0 } <Int>) -> () {
497+
bb0(%0 : @owned $<τ_0_0> { var τ_0_0 } <Int>):
498+
%1 = project_box %0 : $<τ_0_0> { var τ_0_0 } <Int>, 0
499+
destroy_value %0 : $<τ_0_0> { var τ_0_0 } <Int> // id: %7
500+
%r = tuple ()
501+
return %r : $()
502+
}
503+
472504
sil [ossa] @closureWithBoxArg : $@convention(thin) (@guaranteed { var SomeClass }) -> () {
473505
bb0(%0 : @guaranteed ${ var SomeClass }):
474506
%r = tuple ()

test/SILOptimizer/move_function_kills_copyable_addressonly_vars.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,15 +574,25 @@ public func castAccess2<T : P>(_ x : __owned T) {
574574
// Partial Apply Tests //
575575
/////////////////////////
576576

577+
public func nonEscapingpartialApplyTest<T : P>(_ x: __owned T) {
578+
var x2 = x // expected-error {{'x2' used after being moved}}
579+
x2 = x
580+
let _ = _move x2 // expected-note {{move here}}
581+
let f = { // expected-note {{use here}}
582+
print(x2)
583+
}
584+
f()
585+
}
586+
577587
// This makes sure we always fail if we are asked to check in a partial apply.
578-
public func partialApplyTest<T : P>(_ x: __owned T) {
588+
public func partialApplyTest<T : P>(_ x: __owned T) -> () -> () {
579589
var x2 = x
580590
x2 = x
581591
let _ = _move x2 // expected-error {{move applied to value that the compiler does not support checking}}
582592
let f = {
583593
print(x2)
584594
}
585-
f()
595+
return f
586596
}
587597

588598
////////////////////////

test/SILOptimizer/performance-annotations.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,13 @@ func goodClosure2() {
213213
})
214214
}
215215

216+
@_noAllocation
217+
func closueWhichModifiesLocalVar() -> Int {
218+
var x = 42
219+
let localNonEscapingClosure = {
220+
x += 1
221+
}
222+
localNonEscapingClosure()
223+
return x
224+
}
225+

test/SILOptimizer/sil_combine_apply_ossa.sil

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,3 +1152,22 @@ bb3:
11521152
%rv = tuple()
11531153
return %rv : $()
11541154
}
1155+
1156+
// CHECK-LABEL: sil [ossa] @test_copy_and_borrow_of_closure
1157+
// CHECK-NOT: partial_apply
1158+
// CHECK-LABEL: } // end sil function 'test_copy_and_borrow_of_closure'
1159+
sil [ossa] @test_copy_and_borrow_of_closure : $@convention(thin) (Int) -> () {
1160+
bb0(%0 : $Int):
1161+
%3 = function_ref @closure2 : $@convention(thin) (Int) -> ()
1162+
%5 = partial_apply %3(%0) : $@convention(thin) (Int) -> ()
1163+
%6 = begin_borrow [lexical] %5 : $@callee_owned () -> ()
1164+
%7 = copy_value %6 : $@callee_owned () -> ()
1165+
apply %7() : $@callee_owned () -> ()
1166+
end_borrow %6 : $@callee_owned () -> ()
1167+
destroy_value %5 : $@callee_owned () -> ()
1168+
%r = tuple ()
1169+
return %r : $()
1170+
}
1171+
1172+
sil [ossa] @closure2 : $@convention(thin) (Int) -> ()
1173+

0 commit comments

Comments
 (0)