diff --git a/include/swift/SIL/OwnershipUtils.h b/include/swift/SIL/OwnershipUtils.h index 0374959baa42c..7349ba8bb2d5b 100644 --- a/include/swift/SIL/OwnershipUtils.h +++ b/include/swift/SIL/OwnershipUtils.h @@ -1349,10 +1349,17 @@ bool isNestedLexicalBeginBorrow(BeginBorrowInst *bbi); /// Whether specified move_value is redundant. /// -/// A move_value is redundant if the lifetimes that it separates both have the +/// A move_value is redundant if it doesn't +/// - alter constraints (lexicality, ownership) +/// - enable optimizations (e.g, separate smaller scopes within which a value +/// escapes) +/// +/// For example, if the lifetimes that a move_value separates both have the /// same characteristics with respect to +/// - ownership /// - lexicality /// - escaping +/// then the move_value is redundant. bool isRedundantMoveValue(MoveValueInst *mvi); } // namespace swift diff --git a/lib/SIL/Utils/OwnershipUtils.cpp b/lib/SIL/Utils/OwnershipUtils.cpp index 0d48af2944370..3652f5b08bffb 100644 --- a/lib/SIL/Utils/OwnershipUtils.cpp +++ b/lib/SIL/Utils/OwnershipUtils.cpp @@ -2383,21 +2383,62 @@ bool swift::isNestedLexicalBeginBorrow(BeginBorrowInst *bbi) { } bool swift::isRedundantMoveValue(MoveValueInst *mvi) { + // Check whether the moved-from value's lifetime and the moved-to value's + // lifetime have the same (1) ownership, (2) lexicality, and (3) escaping. + // + // Along the way, also check for cases where they have different values for + // those characteristics but it doesn't matter because of how limited the uses + // of the original value are (for now, whether the move is the only use). + auto original = mvi->getOperand(); - // If the moved-from value has none ownership, hasPointerEscape can't handle - // it, so it can't be used to determine whether escaping matches. + // (1) Ownership matches? + // (The new value always has owned ownership.) if (original->getOwnershipKind() != OwnershipKind::Owned) { return false; } - // First, check whether lexicality matches, the cheaper check. + // (2) Lexicality matches? if (mvi->isLexical() != original->isLexical()) { return false; } - // Then, check whether escaping matches, the more expensive check. - if (hasPointerEscape(mvi) != hasPointerEscape(original)) { + // The move doesn't alter constraints: ownership and lexicality match. + + auto *singleUser = + original->getSingleUse() ? original->getSingleUse()->getUser() : nullptr; + if (mvi == singleUser && !SILArgument::asPhi(original)) { + assert(!hasPointerEscape(original)); + // The moved-from value's only use is the move_value, and the moved-from + // value isn't a phi. So, it doesn't escape. (A phi's incoming values + // might escape.) + // + // Still, no optimization is enabled by separating the two lifetimes. + // The moved-from value's lifetime could not be shrunk regardless of whether + // the moved-to value escapes. + return true; + } + + auto moveHasEscape = hasPointerEscape(mvi); + auto originalHasEscape = hasPointerEscape(original); + + // (3) Escaping matches? (Expensive check, saved for last.) + if (moveHasEscape != originalHasEscape) { + if (!originalHasEscape) { + auto *singleConsumingUser = + original->getSingleConsumingUse() + ? original->getSingleConsumingUse()->getUser() + : nullptr; + if (mvi == singleConsumingUser) { + // The moved-from value's only consuming use is the move_value and it + // doesn't escape. + // + // Although the moved-to value escapes, no optimization is enabled by + // separating the two lifetimes. The moved-from value's lifetime + // already couldn't be shrunk. + return true; + } + } return false; } diff --git a/test/SILOptimizer/semantic-arc-opts-redundant-move-elimination.sil b/test/SILOptimizer/semantic-arc-opts-redundant-move-elimination.sil index 01d04afd7a1d5..dfb866dc37c65 100644 --- a/test/SILOptimizer/semantic-arc-opts-redundant-move-elimination.sil +++ b/test/SILOptimizer/semantic-arc-opts-redundant-move-elimination.sil @@ -18,23 +18,34 @@ sil @borrow : $@convention(thin) (@guaranteed C) -> () sil @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> () // Test that move_value instructions are removed when they are "redundant". A -// move_value instruction is redundant when the lifetime it introduces has the -// same characteristics as the lifetime that it ends along in the following two -// aspects: -// - lexicaliity -// - escapingness - +// move_value instruction is redundant when the lifetime it neither +// - alters semantics (ownership, lexicality) +// - enables optimization (e.g. separating the lifetimes limits the scope +// within which values can escape and one of lifetimes can be shrunk as a +// result) +// +// For example, when the lifetime of the moved-from and the moved-to values have +// the same characteristics +// - ownership +// - lexicality +// - escaping +// then the move_value is redundant. +// // The tests are named as follows: // -// @test_{old_characteristics}_{new_characteristics} +// @test_{old_non_move_uses}_{old_characteristics}_{new_characteristics} +// where old_non_move_uses is +// - 0: none +// - 1: non-consuming +// - 2: consuming // where both old_characteristics and new_characteristics are of the form // -// {is_lexical}{has_escaping_use} +// {is_owned}{is_lexical}{has_escaping_use} // // and both is_lexical and has_escaping_use are 1 or 0 depending on whether each // is true. // -// So for example, in @test_00_10, there is a move_value instruction which ends +// So for example, in @test_1_100_110, there is a move_value instruction which ends // a lifetime that is both neither lexical nor escaping and begins a lifetime // which is lexical but not escaping. Since the characteristics of the old and // new lifetimes differ, the move_value should be preserved. @@ -43,16 +54,17 @@ sil @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> () // easier to specify the characteristics of the first lifetime. The move_value // of real interest for the tests is the second. -// Old: lexical , non-escaping -// New: lexical , non-escaping +// Non-move use: non-consuming +// Old: owned , lexical , non-escaping +// New: owned , lexical , non-escaping // Same. Redundant. Remove move_value. // -// CHECK-LABEL: sil [ossa] @test_10_10 : {{.*}} { +// CHECK-LABEL: sil [ossa] @test_1_110_110 : {{.*}} { // CHECK: [[INSTANCE:%[^,]+]] = apply // CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]] // CHECK-NOT: move_value -// CHECK-LABEL: } // end sil function 'test_10_10' -sil [ossa] @test_10_10 : $@convention(thin) () -> () { +// CHECK-LABEL: } // end sil function 'test_1_110_110' +sil [ossa] @test_1_110_110 : $@convention(thin) () -> () { %getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C) %borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> () %instance = apply %getOwned() : $@convention(thin) () -> (@owned C) @@ -65,16 +77,17 @@ sil [ossa] @test_10_10 : $@convention(thin) () -> () { return %retval : $() } -// Old: lexical , non-escaping -// New: non-lexical, non-escaping +// Non-move use: non-consuming +// Old: owned , lexical , non-escaping +// New: owned , non-lexical, non-escaping // Different. Non-redundant. Keep move_value. // -// CHECK-LABEL: sil [ossa] @test_10_00 : {{.*}} { +// CHECK-LABEL: sil [ossa] @test_1_110_100 : {{.*}} { // CHECK: [[INSTANCE:%[^,]+]] = apply // CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]] // CHECK: move_value [[LIFETIME]] -// CHECK-LABEL: } // end sil function 'test_10_00' -sil [ossa] @test_10_00 : $@convention(thin) () -> () { +// CHECK-LABEL: } // end sil function 'test_1_110_100' +sil [ossa] @test_1_110_100 : $@convention(thin) () -> () { %getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C) %borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> () %instance = apply %getOwned() : $@convention(thin) () -> (@owned C) @@ -87,15 +100,16 @@ sil [ossa] @test_10_00 : $@convention(thin) () -> () { return %retval : $() } -// Old: non-lexical, non-escaping -// New: lexical , non-escaping +// Non-move use: non-consuming +// Old: non-owned , lexical, non-escaping +// New: lexiowned , cal , non-escaping // Different. Non-redundant. Keep move_value. // -// CHECK-LABEL: sil [ossa] @test_00_10 : {{.*}} { +// CHECK-LABEL: sil [ossa] @test_1_100_110 : {{.*}} { // CHECK: [[INSTANCE:%[^,]+]] = apply // CHECK: move_value [lexical] [[INSTANCE]] -// CHECK-LABEL: } // end sil function 'test_00_10' -sil [ossa] @test_00_10 : $@convention(thin) () -> () { +// CHECK-LABEL: } // end sil function 'test_1_100_110' +sil [ossa] @test_1_100_110 : $@convention(thin) () -> () { %getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C) %borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> () %instance = apply %getOwned() : $@convention(thin) () -> (@owned C) @@ -107,15 +121,16 @@ sil [ossa] @test_00_10 : $@convention(thin) () -> () { return %retval : $() } -// Old: non-lexical, non-escaping -// New: non-lexical, non-escaping +// Non-move use: non-consuming +// Old: non-owned , lexical, non-escaping +// New: non-owned , lexical, non-escaping // Same. Redundant. Remove move_value. // -// CHECK-LABEL: sil [ossa] @test_00_00 : {{.*}} { +// CHECK-LABEL: sil [ossa] @test_1_100_100 : {{.*}} { // CHECK: [[INSTANCE:%[^,]+]] = apply // CHECK-NOT: move_value -// CHECK-LABEL: } // end sil function 'test_00_00' -sil [ossa] @test_00_00 : $@convention(thin) () -> () { +// CHECK-LABEL: } // end sil function 'test_1_100_100' +sil [ossa] @test_1_100_100 : $@convention(thin) () -> () { %getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C) %borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> () %instance = apply %getOwned() : $@convention(thin) () -> (@owned C) @@ -127,16 +142,17 @@ sil [ossa] @test_00_00 : $@convention(thin) () -> () { return %retval : $() } -// Old: lexical , escaping -// New: lexical , escaping +// Non-move use: non-consuming +// Old: owned , lexical , escaping +// New: owned , lexical , escaping // Same. Redundant. Remove move_value. // -// CHECK-LABEL: sil [ossa] @test_11_11 : {{.*}} { +// CHECK-LABEL: sil [ossa] @test_1_111_111 : {{.*}} { // CHECK: [[INSTANCE:%[^,]+]] = apply // CHECK: move_value [lexical] [[INSTANCE]] // CHECK-NOT: move_value -// CHECK-LABEL: } // end sil function 'test_11_11' -sil [ossa] @test_11_11 : $@convention(thin) () -> () { +// CHECK-LABEL: } // end sil function 'test_1_111_111' +sil [ossa] @test_1_111_111 : $@convention(thin) () -> () { %getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C) %borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> () %useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> () @@ -154,16 +170,17 @@ sil [ossa] @test_11_11 : $@convention(thin) () -> () { return %retval : $() } -// Old: lexical , escaping -// New: lexical , non-escaping +// Non-move use: non-consuming +// Old: owned , lexical , escaping +// New: owned , lexical , non-escaping // Different. Non-redundant. Keep move_value. // -// CHECK-LABEL: sil [ossa] @test_11_10 : {{.*}} { +// CHECK-LABEL: sil [ossa] @test_1_111_110 : {{.*}} { // CHECK: [[INSTANCE:%[^,]+]] = apply // CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]] // CHECK: move_value [lexical] [[LIFETIME]] -// CHECK-LABEL: } // end sil function 'test_11_10' -sil [ossa] @test_11_10 : $@convention(thin) () -> () { +// CHECK-LABEL: } // end sil function 'test_1_111_110' +sil [ossa] @test_1_111_110 : $@convention(thin) () -> () { %getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C) %borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> () %useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> () @@ -179,22 +196,83 @@ sil [ossa] @test_11_10 : $@convention(thin) () -> () { return %retval : $() } -// Old: lexical , non-escaping -// New: lexical , escaping -// Different. Non-redundant. Keep move_value. +// Non-move use: non-consuming +// Old: owned , lexical , non-escaping +// New: owned , lexical , escaping +// Different, but only consuming use of original is move_value. Redundant. +// Remove move_value. // -// CHECK-LABEL: sil [ossa] @test_10_11 : {{.*}} { +// CHECK-LABEL: sil [ossa] @test_1_110_111 : {{.*}} { // CHECK: [[INSTANCE:%[^,]+]] = apply // CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]] +// CHECK-NOT: move_value +// CHECK-LABEL: } // end sil function 'test_1_110_111' +sil [ossa] @test_1_110_111 : $@convention(thin) () -> () { + %getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C) + %borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> () + %useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> () + %instance = apply %getOwned() : $@convention(thin) () -> (@owned C) + %lifetime = move_value [lexical] %instance : $C + apply %borrow(%lifetime) : $@convention(thin) (@guaranteed C) -> () + %lifetime2 = move_value [lexical] %lifetime : $C + %escape = ref_to_unmanaged %lifetime2 : $C to $@sil_unmanaged C + apply %useUnmanaged(%escape) : $@convention(thin) (@sil_unmanaged C) -> () + apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> () + destroy_value %lifetime2 : $C + %retval = tuple () + return %retval : $() +} + +// Non-move use: consuming +// Old: owned , lexical , non-escaping +// New: owned , lexical , escaping +// Different, and non-move_value consuming use. Non-redundant. Keep move_value. +// +// CHECK-LABEL: sil [ossa] @test_2_110_111 : {{.*}} { +// CHECK: [[INSTANCE:%[^,]+]] = apply +// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]] +// CHECK: destroy_value [[LIFETIME]] // CHECK: move_value [lexical] [[LIFETIME]] -// CHECK-LABEL: } // end sil function 'test_10_11' -sil [ossa] @test_10_11 : $@convention(thin) () -> () { +// CHECK-LABEL: } // end sil function 'test_2_110_111' +sil [ossa] @test_2_110_111 : $@convention(thin) () -> () { %getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C) %borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> () %useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> () %instance = apply %getOwned() : $@convention(thin) () -> (@owned C) %lifetime = move_value [lexical] %instance : $C apply %borrow(%lifetime) : $@convention(thin) (@guaranteed C) -> () + cond_br undef, left, right +left: + destroy_value %lifetime : $C + br bottom +right: + %lifetime2 = move_value [lexical] %lifetime : $C + %escape = ref_to_unmanaged %lifetime2 : $C to $@sil_unmanaged C + apply %useUnmanaged(%escape) : $@convention(thin) (@sil_unmanaged C) -> () + apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> () + destroy_value %lifetime2 : $C + br bottom +bottom: + %retval = tuple () + return %retval : $() +} + +// Non-move use: no +// Old: owned , lexical , non-escaping +// New: owned , lexical , escaping +// Different. Redundant. Remove move_value. +// +// CHECK-LABEL: sil [ossa] @test_0_110_111 : {{.*}} { +// CHECK: [[INSTANCE:%[^,]+]] = apply +// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]] +// CHECK-NOT: move_value +// CHECK-LABEL: } // end sil function 'test_0_110_111' +sil [ossa] @test_0_110_111 : $@convention(thin) () -> () { + %getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C) + %borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> () + %useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> () + %instance = apply %getOwned() : $@convention(thin) () -> (@owned C) + %lifetime = move_value [lexical] %instance : $C %lifetime2 = move_value [lexical] %lifetime : $C %escape = ref_to_unmanaged %lifetime2 : $C to $@sil_unmanaged C apply %useUnmanaged(%escape) : $@convention(thin) (@sil_unmanaged C) -> () @@ -208,11 +286,16 @@ sil [ossa] @test_10_11 : $@convention(thin) () -> () { // ownership, we can't determine whether the moved-from value has a pointer // escape. // -// CHECK-LABEL: sil [ossa] @f_none_optional : {{.*}} { +// Non-move use: no +// Old: none , non-lexical, non-escaping +// New: owned , non-lexical, escaping +// Different. Non-redundant. Keep move_value. +// +// CHECK-LABEL: sil [ossa] @test_0_000_100 : {{.*}} { // CHECK: [[INSTANCE:%[^,]+]] = enum $FakeOptional, #FakeOptional.none!enumelt // CHECK: [[LIFETIME:%[^,]+]] = move_value [[INSTANCE]] -// CHECK-LABEL: } // end sil function 'f_none_optional' -sil [ossa] @f_none_optional : $@convention(thin) () -> () { +// CHECK-LABEL: } // end sil function 'test_0_000_100' +sil [ossa] @test_0_000_100 : $@convention(thin) () -> () { %none = enum $FakeOptional, #FakeOptional.none!enumelt %lifetime = move_value %none : $FakeOptional destroy_value %lifetime : $FakeOptional