From 5a73063e3747fef9204928e0b9562dfdaf1f93e5 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Mon, 1 Oct 2018 20:18:27 -0700 Subject: [PATCH 1/2] [semantic-arc-opts] Eliminate all copy_value from guaranteed arguments that have all uses that can accept values with guaranteed ownership. This takes advantage of my restricting Semantic ARC Opts to run only on the stdlib since we know it passes the ownership verifier. Using that I reimplemented this optimization in a more robust, elegant, general way. Specifically, we now will eliminate any SILGen copy_value on a guaranteed argument all of whose uses are either destroy_values or instructions that can accept guaranteed parameters. To be clear: This means that the value must be consumed in the same function only be destroy_value. Since we know that the lifetime of the guaranteed parameter will be larger than any owned value created/destroyed in the body, we do not need to check that the copy_value's destroy_value set is joint post-dominated by the set of end_borrows. That will have to be added to turn this into a general optimization. --- .../Mandatory/SemanticARCOpts.cpp | 283 ++++++++++-------- test/SILOptimizer/semantic-arc-opts.sil | 78 +++++ 2 files changed, 243 insertions(+), 118 deletions(-) diff --git a/lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp b/lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp index 77d86c830f9bc..7fcb27934b9cd 100644 --- a/lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp +++ b/lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp @@ -15,6 +15,8 @@ #include "swift/SIL/OwnershipUtils.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILInstruction.h" +#include "swift/SIL/SILVisitor.h" +#include "swift/Basic/STLExtras.h" #include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" @@ -26,123 +28,143 @@ using namespace swift; STATISTIC(NumEliminatedInsts, "number of removed instructions"); -static bool optimizeGuaranteedArgument(SILArgument *Arg, - OwnershipChecker &Checker) { - bool MadeChange = false; +namespace { - // Gather all copy_value users of Arg. - llvm::SmallVector Worklist; - for (auto *Op : Arg->getUses()) { - if (auto *CVI = dyn_cast(Op->getUser())) { - Worklist.push_back(CVI); - } - } +struct SemanticARCOptVisitor + : SILInstructionVisitor { + bool visitSILInstruction(SILInstruction *i) { return false; } + bool visitCopyValueInst(CopyValueInst *cvi); + bool visitBeginBorrowInst(BeginBorrowInst *bbi); +}; + +} // end anonymous namespace - // Then until we run out of copies... - while (!Worklist.empty()) { - auto *CVI = Worklist.pop_back_val(); - - // Quickly see if copy has only one use and that use is a destroy_value. In - // such a case, we can always eliminate both the copy and the destroy. - if (auto *Op = CVI->getSingleUse()) { - if (auto *DVI = dyn_cast(Op->getUser())) { - DVI->eraseFromParent(); - CVI->eraseFromParent(); - NumEliminatedInsts += 2; +bool SemanticARCOptVisitor::visitBeginBorrowInst(BeginBorrowInst *bbi) { + auto kind = bbi->getOperand().getOwnershipKind(); + SmallVector endBorrows; + for (auto *op : bbi->getUses()) { + auto *user = op->getUser(); + switch (user->getKind()) { + case SILInstructionKind::EndBorrowInst: + endBorrows.push_back(cast(user)); + break; + default: + // Make sure that this operand can accept our arguments kind. + auto map = op->getOwnershipKindMap(); + if (map.canAcceptKind(kind)) continue; - } + return false; } + } - // Ok, now run the checker on the copy value. If it fails, then we just - // continue. - if (!Checker.checkValue(CVI)) - continue; - - // Otherwise, lets do a quick check on what the checker thinks the lifetime - // ending and non-lifetime ending users. To be conservative, we bail unless - // each lifetime ending use is a destroy_value and if each non-lifetime - // ending use is one of the following instructions: - // - // 1. copy_value. - // 2. begin_borrow. - // 3. end_borrow. - if (!all_of(Checker.lifetimeEndingUsers, [](SILInstruction *I) -> bool { - return isa(I); - })) - continue; - - // Extra copy values that we should visit recursively. - llvm::SmallVector NewCopyInsts; - llvm::SmallVector NewBorrowInsts; - if (!all_of(Checker.regularUsers, [&](SILInstruction *I) -> bool { - if (auto *CVI = dyn_cast(I)) { - NewCopyInsts.push_back(CVI); - return true; - } - - if (!isa(I) && !isa(I)) - return false; - - NewBorrowInsts.push_back(I); - return true; - })) - continue; - - // Ok! we can remove the copy_value, destroy_values! - MadeChange = true; - CVI->replaceAllUsesWith(CVI->getOperand()); - CVI->eraseFromParent(); + // At this point, we know that the begin_borrow's operand can be + // used as an argument to all non-end borrow uses. Eliminate the + // begin borrow and end borrows. + while (!endBorrows.empty()) { + auto *ebi = endBorrows.pop_back_val(); + ebi->eraseFromParent(); ++NumEliminatedInsts; + } + bbi->replaceAllUsesWith(bbi->getOperand()); + bbi->eraseFromParent(); + ++NumEliminatedInsts; + return true; +} - while (!Checker.lifetimeEndingUsers.empty()) { - Checker.lifetimeEndingUsers.pop_back_val()->eraseFromParent(); - ++NumEliminatedInsts; - } - - // Then add the copy_values that were users of our original copy value to - // the worklist. - while (!NewCopyInsts.empty()) { - Worklist.push_back(NewCopyInsts.pop_back_val()); - } - - // Then remove any begin/end borrow that we found. These are unneeded since - // the lifetime guarantee from the argument exists above and beyond said - // scope. - while (!NewBorrowInsts.empty()) { - SILInstruction *I = NewBorrowInsts.pop_back_val(); - if (auto *BBI = dyn_cast(I)) { - // Any copy_value that is used by the begin borrow is added to the - // worklist. - for (auto *BBIUse : BBI->getUses()) { - if (auto *BBIUseCopyValue = - dyn_cast(BBIUse->getUser())) { - Worklist.push_back(BBIUseCopyValue); - } - } +/// TODO: Add support for begin_borrow, load_borrow. +static bool canHandleOperand(SILValue operand) { + if (auto *arg = dyn_cast(operand)) { + return arg->getOwnershipKind() == ValueOwnershipKind::Guaranteed; + } - // First go through and eliminate all end borrows. - SmallVector endBorrows; - copy(BBI->getEndBorrows(), std::back_inserter(endBorrows)); - while (!endBorrows.empty()) { - endBorrows.pop_back_val()->eraseFromParent(); - ++NumEliminatedInsts; - } + return false; +} - // Then eliminate BBI itself. - BBI->replaceAllUsesWith(BBI->getOperand()); - BBI->eraseFromParent(); - ++NumEliminatedInsts; +static bool performGuaranteedCopyValueOptimization(CopyValueInst *cvi) { + // Whitelist the operands that we know how to support and make sure + // our operand is actually guaranteed. + if (!canHandleOperand(cvi->getOperand())) + return false; + + // Then go over all of our uses. Find our destroying instructions + // and make sure all of them are destroy_value. For our + // non-destroying instructions, make sure that they accept a + // guaranteed value. After that, make sure that our destroys are + // within the lifetime of our borrowed values. + SmallVector destroys; + for (auto *op : cvi->getUses()) { + // We know that a copy_value produces an @owned value. Look + // through all of our uses and classify them as either + // invalidating or not invalidating. Make sure that all of the + // invalidating ones are destroy_value since otherwise the + // live_range is not complete. + auto map = op->getOwnershipKindMap(); + auto constraint = map.getLifetimeConstraint(ValueOwnershipKind::Owned); + switch (constraint) { + case UseLifetimeConstraint::MustBeInvalidated: + // And we have a destroy_value, track it and continue. + if (auto *dvi = dyn_cast(op->getUser())) { + destroys.push_back(dvi); continue; } + // Otherwise, we found a non-destroy value invalidating owned + // user... This is not an unnecessary live range. + return false; + case UseLifetimeConstraint::MustBeLive: + // Ok, this constraint can take something owned as live. Lets + // see if it can also take something that is guaranteed. If it + // can not, then we bail. + if (!map.canAcceptKind(ValueOwnershipKind::Guaranteed)) { + return false; + } - // This is not necessary, but it does add a check. - auto *EBI = cast(I); - EBI->eraseFromParent(); - ++NumEliminatedInsts; + // Otherwise, continue. + continue; } } - return MadeChange; + // If we reached this point, then we know that all of our users can + // accept a guaranteed value and our owned value is destroyed only + // by destroy_value. Check if all of our destroys are joint + // post-dominated by the end_borrow set. If they do not, then the + // copy_value is lifetime extending the guaranteed value, we can not + // eliminate it. + // + // TODO: When we support begin_borrow/load_borrow a linear linfetime + // check will be needed here. + assert(isa(cvi->getOperand()) && + "This code must be updated for begin_borrow/load_borrow?!"); + + // Otherwise, we know that our copy_value/destroy_values are all + // completely within the guaranteed value scope. + while (!destroys.empty()) { + auto *dvi = destroys.pop_back_val(); + dvi->eraseFromParent(); + ++NumEliminatedInsts; + } + cvi->replaceAllUsesWith(cvi->getOperand()); + cvi->eraseFromParent(); + ++NumEliminatedInsts; + return true; +} + +bool SemanticARCOptVisitor::visitCopyValueInst(CopyValueInst *cvi) { + // If our copy value inst has a single destroy value user, eliminate + // it. + if (auto *op = cvi->getSingleUse()) { + if (auto *dvi = dyn_cast(op->getUser())) { + dvi->eraseFromParent(); + cvi->eraseFromParent(); + NumEliminatedInsts += 2; + return true; + } + } + + // Then try to perform the guaranteed copy value optimization. + if (performGuaranteedCopyValueOptimization(cvi)) + return true; + + return false; } //===----------------------------------------------------------------------===// @@ -156,31 +178,56 @@ namespace { // configuration. struct SemanticARCOpts : SILFunctionTransform { void run() override { - bool MadeChange = false; - SILFunction *F = getFunction(); - if (!F->getModule().isStdlibModule()) { + SILFunction &f = *getFunction(); + // Do not run the semantic arc opts unless we are running on the + // standard library. This is because this is the only module that + // passes the ownership verifier. + if (!f.getModule().isStdlibModule()) return; - } - DeadEndBlocks DEBlocks(F); - OwnershipChecker Checker{{}, {}, {}, {}, F->getModule(), DEBlocks}; - - // First as a special case, handle guaranteed SIL function arguments. + // Iterate over all of the arguments, performing small peephole + // ARC optimizations. // - // The reason that this is special is that we do not need to consider the - // end of the borrow scope since the end of the function is the end of the - // borrow scope. - for (auto *Arg : F->getArguments()) { - if (Arg->getOwnershipKind() != ValueOwnershipKind::Guaranteed) + // FIXME: Should we iterate or use a RPOT order here? + bool madeChange = false; + for (auto &bb : f) { + auto ii = bb.rend(); + auto start = bb.rbegin(); + + // If the bb is empty, continue. + if (start == ii) continue; - MadeChange |= optimizeGuaranteedArgument(Arg, Checker); + + // Go to the first instruction to process. + --ii; + + // Then until we process the first instruction of the block... + while (ii != start) { + // Move the iterator before ii. + auto tmp = std::next(ii); + + // Then try to optimize. If we succeeded, then we deleted + // ii. Move ii from the next value back onto the instruction + // after ii's old value in the block instruction list and then + // process that. + if (SemanticARCOptVisitor().visit(&*ii)) { + madeChange = true; + ii = std::prev(tmp); + continue; + } + + // Otherwise, we didn't delete ii. Just visit the next instruction. + --ii; + } + + // Finally visit the first instruction of the block. + madeChange |= SemanticARCOptVisitor().visit(&*ii); } - if (MadeChange) { + if (madeChange) { invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); } } - }; } // end anonymous namespace diff --git a/test/SILOptimizer/semantic-arc-opts.sil b/test/SILOptimizer/semantic-arc-opts.sil index 7e54ab93bf8ca..534d7c91d5d8a 100644 --- a/test/SILOptimizer/semantic-arc-opts.sil +++ b/test/SILOptimizer/semantic-arc-opts.sil @@ -10,6 +10,11 @@ import Builtin sil @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () +struct NativeObjectPair { + var obj1 : Builtin.NativeObject + var obj2 : Builtin.NativeObject +} + /////////// // Tests // /////////// @@ -94,3 +99,76 @@ bb0(%0 : @guaranteed $Builtin.NativeObject): %9999 = tuple() return %9999 : $() } + +// CHECK-LABEL: sil @struct_extract_guaranteed_use : $@convention(thin) (@guaranteed NativeObjectPair) -> () { +// CHECK: bb0([[ARG:%.*]] : @guaranteed $NativeObjectPair): +// CHECK-NOT: copy_value +// CHECK-NOT: begin_borrow +// CHECK: [[FIELD:%.*]] = struct_extract [[ARG]] +// CHECK: apply {{%.*}}([[FIELD]]) : +// CHECK-NEXT: tuple +// CHECK-NEXT: return +// CHECK: } // end sil function 'struct_extract_guaranteed_use' +sil @struct_extract_guaranteed_use : $@convention(thin) (@guaranteed NativeObjectPair) -> () { +bb0(%0 : @guaranteed $NativeObjectPair): + %1 = copy_value %0 : $NativeObjectPair + %2 = begin_borrow %1 : $NativeObjectPair + %3 = struct_extract %2 : $NativeObjectPair, #NativeObjectPair.obj1 + %4 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () + apply %4(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () + end_borrow %2 : $NativeObjectPair + destroy_value %1 : $NativeObjectPair + %9999 = tuple() + return %9999 : $() +} + +// CHECK-LABEL: sil @struct_extract_copy_guaranteed_use : $@convention(thin) (@guaranteed NativeObjectPair) -> () { +// CHECK: bb0([[ARG:%.*]] : @guaranteed $NativeObjectPair): +// CHECK: [[FIELD:%.*]] = struct_extract [[ARG]] +// CHECK: apply {{%.*}}([[FIELD]]) +// CHECK-NOT: destroy_value +// CHECK: } // end sil function 'struct_extract_copy_guaranteed_use' +sil @struct_extract_copy_guaranteed_use : $@convention(thin) (@guaranteed NativeObjectPair) -> () { +bb0(%0 : @guaranteed $NativeObjectPair): + %1 = struct_extract %0 : $NativeObjectPair, #NativeObjectPair.obj1 + %2 = copy_value %1 : $Builtin.NativeObject + %3 = begin_borrow %2 : $Builtin.NativeObject + %4 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () + apply %4(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () + end_borrow %3 : $Builtin.NativeObject + destroy_value %2 : $Builtin.NativeObject + %9999 = tuple() + return %9999 : $() +} + +// For now we do not support recreating forwarding instructions since we do not +// dynamically recompute ownership. +// CHECK-LABEL: sil @do_not_process_forwarding_uses : $@convention(thin) (@guaranteed NativeObjectPair) -> () { +// CHECK: copy_value +// CHECK: } // end sil function 'do_not_process_forwarding_uses' +sil @do_not_process_forwarding_uses : $@convention(thin) (@guaranteed NativeObjectPair) -> () { +bb0(%0 : @guaranteed $NativeObjectPair): + %1 = copy_value %0 : $NativeObjectPair + (%2, %3) = destructure_struct %1 : $NativeObjectPair + %4 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () + apply %4(%2) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () + apply %4(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () + destroy_value %2 : $Builtin.NativeObject + destroy_value %3 : $Builtin.NativeObject + %9999 = tuple() + return %9999 : $() +} + +// CHECK-LABEL: sil @do_not_process_forwarding_uses_2 : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () { +// CHECK: copy_value +// CHECK: } // end sil function 'do_not_process_forwarding_uses_2' +sil @do_not_process_forwarding_uses_2 : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () { +bb0(%0 : @guaranteed $Builtin.NativeObject): + %1 = copy_value %0 : $Builtin.NativeObject + %2 = unchecked_ref_cast %1 : $Builtin.NativeObject to $Builtin.NativeObject + %4 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () + apply %4(%2) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () + destroy_value %2 : $Builtin.NativeObject + %9999 = tuple() + return %9999 : $() +} From 099cae5692c5bdb8410df1a83af4915fc414b5ee Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 3 Oct 2018 13:08:03 -0700 Subject: [PATCH 2/2] [semantic-arc-opts] Handle arg extract + copy + borrow + use. Specifically, now the optimizer can take the following code: sil @foo: $@convention(thin) (@guaranteed NativeObjectPair) -> () { bb0(%0 : @guaranteed $NativeObjectPair): %1 = struct_extract %0 : $NativeObjectPair, #NativeObjectPair.obj1 %2 = copy_value %1 : $Builtin.NativeObject %3 = begin_borrow %2 : $Builtin.NativeObject %foo = function_ref ... apply %foo(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () end_borrow %3 : $Builtin.NativeObject destroy_value %2 : $Builtin.NativeObject ... } and eliminate all ownership instructions. I.e.: sil @foo: $@convention(thin) (@guaranteed NativeObjectPair) -> () { bb0(%0 : @guaranteed $NativeObjectPair): %1 = struct_extract %0 : $NativeObjectPair, #NativeObjectPair.obj1 %foo = function_ref .... apply %foo(%1) ... } Before this the optimizer could only eliminate 200 insts in the stdlib. With this change, we now can eliminate ~7000. --- include/swift/SIL/OwnershipUtils.h | 5 ++- lib/SIL/OwnershipUtils.cpp | 36 +++++++++++++++++++ .../Mandatory/SemanticARCOpts.cpp | 21 +++++------ test/SILOptimizer/semantic-arc-opts.sil | 6 ++-- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/include/swift/SIL/OwnershipUtils.h b/include/swift/SIL/OwnershipUtils.h index 7c474a59606e4..7a601696e0367 100644 --- a/include/swift/SIL/OwnershipUtils.h +++ b/include/swift/SIL/OwnershipUtils.h @@ -114,9 +114,12 @@ bool isGuaranteedForwardingValueKind(SILNodeKind kind); bool isGuaranteedForwardingValue(SILValue value); +bool isOwnershipForwardingInst(SILInstruction *i); + bool isGuaranteedForwardingInst(SILInstruction *i); -bool isOwnershipForwardingInst(SILInstruction *i); +bool getUnderlyingBorrowIntroducers(SILValue inputValue, + SmallVectorImpl &out); } // namespace swift diff --git a/lib/SIL/OwnershipUtils.cpp b/lib/SIL/OwnershipUtils.cpp index a3d61097a49e7..174fd358c5c4b 100644 --- a/lib/SIL/OwnershipUtils.cpp +++ b/lib/SIL/OwnershipUtils.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "swift/SIL/OwnershipUtils.h" +#include "swift/SIL/SILArgument.h" #include "swift/SIL/SILInstruction.h" using namespace swift; @@ -75,3 +76,38 @@ bool swift::isGuaranteedForwardingInst(SILInstruction *i) { bool swift::isOwnershipForwardingInst(SILInstruction *i) { return isOwnershipForwardingValueKind(SILNodeKind(i->getKind())); } + +bool swift::getUnderlyingBorrowIntroducers(SILValue inputValue, + SmallVectorImpl &out) { + SmallVector worklist; + worklist.emplace_back(inputValue); + + while (!worklist.empty()) { + SILValue v = worklist.pop_back_val(); + + // First check if v is an introducer. If so, stash it and continue. + if (isa(v) || isa(v) || + isa(v)) { + out.push_back(v); + continue; + } + + // Otherwise if v is an ownership forwarding value, add its defining + // instruction + if (isGuaranteedForwardingValue(v)) { + auto *i = v->getDefiningInstruction(); + assert(i); + transform(i->getAllOperands(), std::back_inserter(worklist), + [](const Operand &op) -> SILValue { return op.get(); }); + continue; + } + + // If v produces any ownership, then we can ignore it. Otherwise, we need to + // return false since this is an introducer we do not understand. + if (v.getOwnershipKind() != ValueOwnershipKind::Any && + v.getOwnershipKind() != ValueOwnershipKind::Trivial) + return false; + } + + return true; +} diff --git a/lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp b/lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp index 7fcb27934b9cd..44c332e73fce5 100644 --- a/lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp +++ b/lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp @@ -11,12 +11,12 @@ //===----------------------------------------------------------------------===// #define DEBUG_TYPE "sil-semantic-arc-opts" +#include "swift/Basic/STLExtras.h" #include "swift/SIL/BasicBlockUtils.h" #include "swift/SIL/OwnershipUtils.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILInstruction.h" #include "swift/SIL/SILVisitor.h" -#include "swift/Basic/STLExtras.h" #include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" @@ -71,19 +71,20 @@ bool SemanticARCOptVisitor::visitBeginBorrowInst(BeginBorrowInst *bbi) { return true; } -/// TODO: Add support for begin_borrow, load_borrow. -static bool canHandleOperand(SILValue operand) { - if (auto *arg = dyn_cast(operand)) { - return arg->getOwnershipKind() == ValueOwnershipKind::Guaranteed; - } +static bool canHandleOperand(SILValue operand, SmallVectorImpl &out) { + if (!getUnderlyingBorrowIntroducers(operand, out)) + return false; - return false; + /// TODO: Add support for begin_borrow, load_borrow. + return all_of(out, [](SILValue v) { return isa(v); }); } static bool performGuaranteedCopyValueOptimization(CopyValueInst *cvi) { + SmallVector borrowIntroducers; + // Whitelist the operands that we know how to support and make sure // our operand is actually guaranteed. - if (!canHandleOperand(cvi->getOperand())) + if (!canHandleOperand(cvi->getOperand(), borrowIntroducers)) return false; // Then go over all of our uses. Find our destroying instructions @@ -132,8 +133,8 @@ static bool performGuaranteedCopyValueOptimization(CopyValueInst *cvi) { // // TODO: When we support begin_borrow/load_borrow a linear linfetime // check will be needed here. - assert(isa(cvi->getOperand()) && - "This code must be updated for begin_borrow/load_borrow?!"); + assert(all_of(borrowIntroducers, + [](SILValue v) { return isa(v); })); // Otherwise, we know that our copy_value/destroy_values are all // completely within the guaranteed value scope. diff --git a/test/SILOptimizer/semantic-arc-opts.sil b/test/SILOptimizer/semantic-arc-opts.sil index 534d7c91d5d8a..5436b1e5e387d 100644 --- a/test/SILOptimizer/semantic-arc-opts.sil +++ b/test/SILOptimizer/semantic-arc-opts.sil @@ -100,7 +100,7 @@ bb0(%0 : @guaranteed $Builtin.NativeObject): return %9999 : $() } -// CHECK-LABEL: sil @struct_extract_guaranteed_use : $@convention(thin) (@guaranteed NativeObjectPair) -> () { +// CHECK-LABEL: sil @copy_struct_extract_guaranteed_use : $@convention(thin) (@guaranteed NativeObjectPair) -> () { // CHECK: bb0([[ARG:%.*]] : @guaranteed $NativeObjectPair): // CHECK-NOT: copy_value // CHECK-NOT: begin_borrow @@ -108,8 +108,8 @@ bb0(%0 : @guaranteed $Builtin.NativeObject): // CHECK: apply {{%.*}}([[FIELD]]) : // CHECK-NEXT: tuple // CHECK-NEXT: return -// CHECK: } // end sil function 'struct_extract_guaranteed_use' -sil @struct_extract_guaranteed_use : $@convention(thin) (@guaranteed NativeObjectPair) -> () { +// CHECK: } // end sil function 'copy_struct_extract_guaranteed_use' +sil @copy_struct_extract_guaranteed_use : $@convention(thin) (@guaranteed NativeObjectPair) -> () { bb0(%0 : @guaranteed $NativeObjectPair): %1 = copy_value %0 : $NativeObjectPair %2 = begin_borrow %1 : $NativeObjectPair