Skip to content

Commit 7bb9aa7

Browse files
authored
Merge pull request #34812 from gottesmm/pr-b1ab03067e8684c69df8ad52319746f5b9d145e0
[semantic-arc] Eliminate simple recursive copy without borrows involved.
2 parents 546039f + 1fd4003 commit 7bb9aa7

File tree

3 files changed

+274
-0
lines changed

3 files changed

+274
-0
lines changed

lib/SILOptimizer/SemanticARC/CopyValueOpts.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "OwnershipPhiOperand.h"
2020
#include "SemanticARCOptVisitor.h"
21+
#include "swift/SIL/LinearLifetimeChecker.h"
2122

2223
using namespace swift;
2324
using namespace swift::semanticarc;
@@ -450,6 +451,65 @@ bool SemanticARCOptVisitor::tryJoiningCopyValueLiveRangeWithOperand(
450451
return false;
451452
}
452453

454+
/// Given an owned value that is completely enclosed within its parent owned
455+
/// value and is not consumed, eliminate the copy.
456+
bool SemanticARCOptVisitor::tryPerformOwnedCopyValueOptimization(
457+
CopyValueInst *cvi) {
458+
if (ctx.onlyGuaranteedOpts)
459+
return false;
460+
461+
auto originalValue = cvi->getOperand();
462+
if (originalValue.getOwnershipKind() != OwnershipKind::Owned)
463+
return false;
464+
465+
// TODO: Add support for forwarding insts here.
466+
SmallVector<DestroyValueInst *, 8> destroyingUses;
467+
SmallVector<Operand *, 32> allCopyUses;
468+
for (auto *use : cvi->getUses()) {
469+
// First just stash our use so we have /all uses/.
470+
allCopyUses.push_back(use);
471+
472+
// Then if we are not a lifetime ending use, just continue.
473+
if (!use->isLifetimeEnding()) {
474+
continue;
475+
}
476+
477+
// Otherwise, if we have a destroy value lifetime ending use, stash that.
478+
if (auto *dvi = dyn_cast<DestroyValueInst>(use->getUser())) {
479+
destroyingUses.push_back(dvi);
480+
continue;
481+
}
482+
483+
// Otherwise, just bail for now.
484+
return false;
485+
}
486+
487+
// NOTE: We do not actually care if the parent's lifetime ends with
488+
// destroy_values. All we care is that it is lifetime ending and the use isn't
489+
// a forwarding instruction.
490+
SmallVector<Operand *, 8> parentLifetimeEndingUses;
491+
for (auto *origValueUse : originalValue->getUses())
492+
if (origValueUse->isLifetimeEnding() &&
493+
!isa<OwnershipForwardingInst>(origValueUse->getUser()))
494+
parentLifetimeEndingUses.push_back(origValueUse);
495+
496+
// Ok, we have an owned value. If we do not have any non-destroying consuming
497+
// uses, see if all of our uses (ignoring destroying uses) are within our
498+
// parent owned value's lifetime.
499+
SmallPtrSet<SILBasicBlock *, 8> visitedBlocks;
500+
LinearLifetimeChecker checker(visitedBlocks, ctx.getDeadEndBlocks());
501+
if (!checker.validateLifetime(originalValue, parentLifetimeEndingUses,
502+
allCopyUses))
503+
return false;
504+
505+
// Ok, we can perform our transform. Eliminate all of our destroy value insts,
506+
// and then RAUW our copy value with our parent value.
507+
while (!destroyingUses.empty())
508+
eraseInstruction(destroyingUses.pop_back_val());
509+
eraseAndRAUWSingleValueInstruction(cvi, cvi->getOperand());
510+
return true;
511+
}
512+
453513
//===----------------------------------------------------------------------===//
454514
// Top Level Entrypoint
455515
//===----------------------------------------------------------------------===//
@@ -472,5 +532,9 @@ bool SemanticARCOptVisitor::visitCopyValueInst(CopyValueInst *cvi) {
472532
return true;
473533
}
474534

535+
if (tryPerformOwnedCopyValueOptimization(cvi)) {
536+
return true;
537+
}
538+
475539
return false;
476540
}

lib/SILOptimizer/SemanticARC/SemanticARCOptVisitor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ struct LLVM_LIBRARY_VISIBILITY SemanticARCOptVisitor
196196
bool performGuaranteedCopyValueOptimization(CopyValueInst *cvi);
197197
bool eliminateDeadLiveRangeCopyValue(CopyValueInst *cvi);
198198
bool tryJoiningCopyValueLiveRangeWithOperand(CopyValueInst *cvi);
199+
bool tryPerformOwnedCopyValueOptimization(CopyValueInst *cvi);
199200
};
200201

201202
} // namespace semanticarc

test/SILOptimizer/semantic-arc-opts.sil

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,23 @@ sil @owned_user : $@convention(thin) (@owned Builtin.NativeObject) -> ()
2121
sil @get_owned_obj : $@convention(thin) () -> @owned Builtin.NativeObject
2222
sil @unreachable_guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> MyNever
2323
sil @inout_user : $@convention(thin) (@inout FakeOptional<NativeObjectPair>) -> ()
24+
sil @get_native_object : $@convention(thin) () -> @owned Builtin.NativeObject
2425

2526
struct NativeObjectPair {
2627
var obj1 : Builtin.NativeObject
2728
var obj2 : Builtin.NativeObject
2829
}
2930

31+
sil @get_object_pair : $@convention(thin) () -> @owned NativeObjectPair
32+
3033
struct FakeOptionalNativeObjectPairPair {
3134
var pair1 : FakeOptional<NativeObjectPair>
3235
var pair2 : FakeOptional<NativeObjectPair>
3336
}
3437
sil @inout_user2 : $@convention(thin) (@inout FakeOptionalNativeObjectPairPair) -> ()
3538

3639
sil @get_nativeobject_pair : $@convention(thin) () -> @owned NativeObjectPair
40+
sil @consume_nativeobject_pair : $@convention(thin) (@owned NativeObjectPair) -> ()
3741

3842
protocol MyFakeAnyObject : Klass {
3943
func myFakeMethod()
@@ -2868,3 +2872,208 @@ bb3(%result : @owned $FakeOptional<Builtin.NativeObject>):
28682872
dealloc_stack %allocStack : $*Builtin.NativeObject
28692873
return %result : $FakeOptional<Builtin.NativeObject>
28702874
}
2875+
2876+
// CHECK-LABEL: sil [ossa] @simple_recursive_copy_case : $@convention(thin) () -> () {
2877+
// CHECK-NOT: copy_value
2878+
// CHECK: } // end sil function 'simple_recursive_copy_case'
2879+
sil [ossa] @simple_recursive_copy_case : $@convention(thin) () -> () {
2880+
bb0:
2881+
%f = function_ref @get_object_pair : $@convention(thin) () -> @owned NativeObjectPair
2882+
%pair = apply %f() : $@convention(thin) () -> @owned NativeObjectPair
2883+
%1 = copy_value %pair : $NativeObjectPair
2884+
%2 = begin_borrow %1 : $NativeObjectPair
2885+
%3 = struct_extract %2 : $NativeObjectPair, #NativeObjectPair.obj1
2886+
%gUserFun = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2887+
apply %gUserFun(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2888+
end_borrow %2 : $NativeObjectPair
2889+
destroy_value %1 : $NativeObjectPair
2890+
destroy_value %pair : $NativeObjectPair
2891+
%9999 = tuple()
2892+
return %9999 : $()
2893+
}
2894+
2895+
// CHECK-LABEL: sil [ossa] @simple_recursive_copy_case_2 : $@convention(thin) () -> () {
2896+
// CHECK-NOT: copy_value
2897+
// CHECK: } // end sil function 'simple_recursive_copy_case_2'
2898+
sil [ossa] @simple_recursive_copy_case_2 : $@convention(thin) () -> () {
2899+
bb0:
2900+
%f = function_ref @get_object_pair : $@convention(thin) () -> @owned NativeObjectPair
2901+
%pair = apply %f() : $@convention(thin) () -> @owned NativeObjectPair
2902+
%1 = copy_value %pair : $NativeObjectPair
2903+
%2 = begin_borrow %1 : $NativeObjectPair
2904+
destroy_value %pair : $NativeObjectPair
2905+
%3 = struct_extract %2 : $NativeObjectPair, #NativeObjectPair.obj1
2906+
%gUserFun = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2907+
apply %gUserFun(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2908+
end_borrow %2 : $NativeObjectPair
2909+
destroy_value %1 : $NativeObjectPair
2910+
%9999 = tuple()
2911+
return %9999 : $()
2912+
}
2913+
2914+
// We fail in this case since the lifetime of %pair ends too early and our
2915+
// joined lifetime analysis is too simplistic to handle this case.
2916+
//
2917+
// CHECK-LABEL: sil [ossa] @simple_recursive_copy_case_3 : $@convention(thin) () -> () {
2918+
// CHECK: copy_value
2919+
// CHECK: } // end sil function 'simple_recursive_copy_case_3'
2920+
sil [ossa] @simple_recursive_copy_case_3 : $@convention(thin) () -> () {
2921+
bb0:
2922+
%f = function_ref @get_object_pair : $@convention(thin) () -> @owned NativeObjectPair
2923+
%pair = apply %f() : $@convention(thin) () -> @owned NativeObjectPair
2924+
%1 = copy_value %pair : $NativeObjectPair
2925+
%2 = begin_borrow %1 : $NativeObjectPair
2926+
%3 = struct_extract %2 : $NativeObjectPair, #NativeObjectPair.obj1
2927+
%gUserFun = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2928+
apply %gUserFun(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2929+
end_borrow %2 : $NativeObjectPair
2930+
%consumeFunc = function_ref @consume_nativeobject_pair : $@convention(thin) (@owned NativeObjectPair) -> ()
2931+
apply %consumeFunc(%pair) : $@convention(thin) (@owned NativeObjectPair) -> ()
2932+
cond_br undef, bb1, bb2
2933+
2934+
bb1:
2935+
(%1a, %1b) = destructure_struct %1 : $NativeObjectPair
2936+
%ownedUser = function_ref @owned_user : $@convention(thin) (@owned Builtin.NativeObject) -> ()
2937+
apply %ownedUser(%1a) : $@convention(thin) (@owned Builtin.NativeObject) -> ()
2938+
apply %ownedUser(%1b) : $@convention(thin) (@owned Builtin.NativeObject) -> ()
2939+
br bb3
2940+
2941+
bb2:
2942+
destroy_value %1 : $NativeObjectPair
2943+
br bb3
2944+
2945+
bb3:
2946+
%9999 = tuple()
2947+
return %9999 : $()
2948+
}
2949+
2950+
// This case fails due to the destructure of our parent object even though the
2951+
// lifetimes line up. We don't support destructures here yet.
2952+
//
2953+
// TODO: Handle this!
2954+
//
2955+
// CHECK-LABEL: sil [ossa] @simple_recursive_copy_case_4 : $@convention(thin) () -> () {
2956+
// CHECK: copy_value
2957+
// CHECK: } // end sil function 'simple_recursive_copy_case_4'
2958+
sil [ossa] @simple_recursive_copy_case_4 : $@convention(thin) () -> () {
2959+
bb0:
2960+
%f = function_ref @get_object_pair : $@convention(thin) () -> @owned NativeObjectPair
2961+
%pair = apply %f() : $@convention(thin) () -> @owned NativeObjectPair
2962+
%1 = copy_value %pair : $NativeObjectPair
2963+
%2 = begin_borrow %1 : $NativeObjectPair
2964+
%3 = struct_extract %2 : $NativeObjectPair, #NativeObjectPair.obj1
2965+
%gUserFun = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2966+
apply %gUserFun(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2967+
end_borrow %2 : $NativeObjectPair
2968+
%consumeFunc = function_ref @consume_nativeobject_pair : $@convention(thin) (@owned NativeObjectPair) -> ()
2969+
destroy_value %1 : $NativeObjectPair
2970+
(%pair1, %pair2) = destructure_struct %pair : $NativeObjectPair
2971+
%ownedUser = function_ref @owned_user : $@convention(thin) (@owned Builtin.NativeObject) -> ()
2972+
apply %ownedUser(%pair1) : $@convention(thin) (@owned Builtin.NativeObject) -> ()
2973+
apply %ownedUser(%pair2) : $@convention(thin) (@owned Builtin.NativeObject) -> ()
2974+
%9999 = tuple()
2975+
return %9999 : $()
2976+
}
2977+
2978+
// This case fails due to the destructure of our parent object even though the
2979+
// lifetimes line up. We don't support destructures here yet.
2980+
//
2981+
// TODO: Handle this!
2982+
//
2983+
// CHECK-LABEL: sil [ossa] @simple_recursive_copy_case_5 : $@convention(thin) () -> () {
2984+
// CHECK: copy_value
2985+
// CHECK: } // end sil function 'simple_recursive_copy_case_5'
2986+
sil [ossa] @simple_recursive_copy_case_5 : $@convention(thin) () -> () {
2987+
bb0:
2988+
%f = function_ref @get_object_pair : $@convention(thin) () -> @owned NativeObjectPair
2989+
%pair = apply %f() : $@convention(thin) () -> @owned NativeObjectPair
2990+
%1 = copy_value %pair : $NativeObjectPair
2991+
%2 = begin_borrow %1 : $NativeObjectPair
2992+
%3 = struct_extract %2 : $NativeObjectPair, #NativeObjectPair.obj1
2993+
%gUserFun = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2994+
apply %gUserFun(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
2995+
end_borrow %2 : $NativeObjectPair
2996+
%consumeFunc = function_ref @consume_nativeobject_pair : $@convention(thin) (@owned NativeObjectPair) -> ()
2997+
(%pair1, %pair2) = destructure_struct %pair : $NativeObjectPair
2998+
%ownedUser = function_ref @owned_user : $@convention(thin) (@owned Builtin.NativeObject) -> ()
2999+
destroy_value %1 : $NativeObjectPair
3000+
apply %ownedUser(%pair1) : $@convention(thin) (@owned Builtin.NativeObject) -> ()
3001+
apply %ownedUser(%pair2) : $@convention(thin) (@owned Builtin.NativeObject) -> ()
3002+
%9999 = tuple()
3003+
return %9999 : $()
3004+
}
3005+
3006+
// Make sure we do not eliminate copies where only the destroy_value is outside
3007+
// of the lifetime of the parent value, but a begin_borrow extends the lifetime
3008+
// of the value.
3009+
//
3010+
// CHECK-LABEL: sil [ossa] @simple_recursive_copy_case_destroying_use_out_of_lifetime : $@convention(thin) () -> () {
3011+
// CHECK: copy_value
3012+
// CHECK: } // end sil function 'simple_recursive_copy_case_destroying_use_out_of_lifetime'
3013+
sil [ossa] @simple_recursive_copy_case_destroying_use_out_of_lifetime : $@convention(thin) () -> () {
3014+
bb0:
3015+
%f = function_ref @get_object_pair : $@convention(thin) () -> @owned NativeObjectPair
3016+
%pair = apply %f() : $@convention(thin) () -> @owned NativeObjectPair
3017+
%pairBorrow = begin_borrow %pair : $NativeObjectPair
3018+
%3 = struct_extract %pairBorrow : $NativeObjectPair, #NativeObjectPair.obj1
3019+
%gUserFun = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
3020+
apply %gUserFun(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
3021+
end_borrow %pairBorrow : $NativeObjectPair
3022+
cond_br undef, bb1, bb2
3023+
3024+
bb1:
3025+
%1 = copy_value %pair : $NativeObjectPair
3026+
%2 = begin_borrow %1 : $NativeObjectPair
3027+
destroy_value %pair : $NativeObjectPair
3028+
%3a = struct_extract %2 : $NativeObjectPair, #NativeObjectPair.obj1
3029+
apply %gUserFun(%3a) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
3030+
end_borrow %2 : $NativeObjectPair
3031+
destroy_value %1 : $NativeObjectPair
3032+
br bb3
3033+
3034+
bb2:
3035+
destroy_value %pair : $NativeObjectPair
3036+
br bb3
3037+
3038+
bb3:
3039+
%9999 = tuple()
3040+
return %9999 : $()
3041+
}
3042+
3043+
// Second version of the test that consumes the pair in case we make the
3044+
// lifetime joining smart enough to handle the original case.
3045+
//
3046+
// CHECK-LABEL: sil [ossa] @simple_recursive_copy_case_destroying_use_out_of_lifetime_2 : $@convention(thin) () -> () {
3047+
// CHECK: copy_value
3048+
// CHECK: } // end sil function 'simple_recursive_copy_case_destroying_use_out_of_lifetime_2'
3049+
sil [ossa] @simple_recursive_copy_case_destroying_use_out_of_lifetime_2 : $@convention(thin) () -> () {
3050+
bb0:
3051+
%f = function_ref @get_object_pair : $@convention(thin) () -> @owned NativeObjectPair
3052+
%pair = apply %f() : $@convention(thin) () -> @owned NativeObjectPair
3053+
%pairBorrow = begin_borrow %pair : $NativeObjectPair
3054+
%3 = struct_extract %pairBorrow : $NativeObjectPair, #NativeObjectPair.obj1
3055+
%gUserFun = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
3056+
apply %gUserFun(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
3057+
end_borrow %pairBorrow : $NativeObjectPair
3058+
cond_br undef, bb1, bb2
3059+
3060+
bb1:
3061+
%1 = copy_value %pair : $NativeObjectPair
3062+
%2 = begin_borrow %1 : $NativeObjectPair
3063+
destroy_value %pair : $NativeObjectPair
3064+
%3a = struct_extract %2 : $NativeObjectPair, #NativeObjectPair.obj1
3065+
apply %gUserFun(%3a) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
3066+
end_borrow %2 : $NativeObjectPair
3067+
destroy_value %1 : $NativeObjectPair
3068+
br bb3
3069+
3070+
bb2:
3071+
%consumePair = function_ref @consume_nativeobject_pair : $@convention(thin) (@owned NativeObjectPair) -> ()
3072+
apply %consumePair(%pair) : $@convention(thin) (@owned NativeObjectPair) -> ()
3073+
br bb3
3074+
3075+
bb3:
3076+
%9999 = tuple()
3077+
return %9999 : $()
3078+
}
3079+

0 commit comments

Comments
 (0)