mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[SIL] Only visit final on-stack pai lifetime ends.
In OSSA, the `partial_apply [on_stack]` instruction produces a value with odd characteristics that correspond to the fact that it is lowered to a stack-allocating instruction. Among these characteristics is the fact that copies of such values aren't load bearing. When visiting the lifetime-ending uses of a `partial_apply [on_stack]` the lifetime ending uses of (transitive) copies of the partial_apply must be considered as well. Otherwise, the borrow scope it defins may be incorrectly short: ``` %closure = partial_apply %fn(%value) // borrows %value %closure2 = copy_value %closure destroy_value %closure // does _not_ end borrow of %value! ... destroy_value %closure2 // ends borrow of %value ... destroy_value %value ``` Furthermore, _only_ the final such destroys actually count as the real lifetime ends. At least one client (OME) relies on `visitOnStackLifetimeEnds` visiting at most a single lifetime end on any path. Rewrite the utility to use PrunedLiveness, tracking only destroys of copies and forwards. The final destroys are the destroys on the boundary. rdar://142636711
This commit is contained in:
@@ -15,14 +15,17 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "swift/SIL/SILInstruction.h"
|
||||
#include "swift/Basic/Assertions.h"
|
||||
#include "swift/Basic/AssertImplements.h"
|
||||
#include "swift/Basic/Assertions.h"
|
||||
#include "swift/Basic/Unicode.h"
|
||||
#include "swift/Basic/type_traits.h"
|
||||
#include "swift/SIL/ApplySite.h"
|
||||
#include "swift/SIL/DynamicCasts.h"
|
||||
#include "swift/SIL/InstWrappers.h"
|
||||
#include "swift/SIL/InstructionUtils.h"
|
||||
#include "swift/SIL/NodeDatastructures.h"
|
||||
#include "swift/SIL/OwnershipUtils.h"
|
||||
#include "swift/SIL/PrunedLiveness.h"
|
||||
#include "swift/SIL/SILBuilder.h"
|
||||
#include "swift/SIL/SILCloner.h"
|
||||
#include "swift/SIL/SILDebugScope.h"
|
||||
@@ -1869,6 +1872,38 @@ visitRecursivelyLifetimeEndingUses(
|
||||
return true;
|
||||
}
|
||||
|
||||
static SILValue lookThroughOwnershipAndForwardingInsts(SILValue value) {
|
||||
auto current = value;
|
||||
while (true) {
|
||||
if (auto *inst = current->getDefiningInstruction()) {
|
||||
switch (inst->getKind()) {
|
||||
case SILInstructionKind::MoveValueInst:
|
||||
case SILInstructionKind::CopyValueInst:
|
||||
case SILInstructionKind::BeginBorrowInst:
|
||||
current = inst->getOperand(0);
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
auto forward = ForwardingOperation(inst);
|
||||
Operand *op = nullptr;
|
||||
if (forward && (op = forward.getSingleForwardingOperand())) {
|
||||
current = op->get();
|
||||
continue;
|
||||
}
|
||||
} else if (auto *result = SILArgument::isTerminatorResult(current)) {
|
||||
auto *op = result->forwardedTerminatorResultOperand();
|
||||
if (!op) {
|
||||
break;
|
||||
}
|
||||
current = op->get();
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
bool
|
||||
PartialApplyInst::visitOnStackLifetimeEnds(
|
||||
llvm::function_ref<bool (Operand *)> func) const {
|
||||
@@ -1877,29 +1912,74 @@ PartialApplyInst::visitOnStackLifetimeEnds(
|
||||
&& "only meaningful for OSSA stack closures");
|
||||
bool noUsers = true;
|
||||
|
||||
auto visitUnknownUse = [](Operand *unknownUse) {
|
||||
// There shouldn't be any dead-end consumptions of a nonescaping
|
||||
// partial_apply that don't forward it along, aside from destroy_value.
|
||||
//
|
||||
// On-stack partial_apply cannot be cloned, so it should never be used by a
|
||||
// BranchInst.
|
||||
//
|
||||
// This is a fatal error because it performs SIL verification that is not
|
||||
// separately checked in the verifier. It is the only check that verifies
|
||||
// the structural requirements of on-stack partial_apply uses.
|
||||
llvm::errs() << "partial_apply [on_stack] use:\n";
|
||||
auto *user = unknownUse->getUser();
|
||||
user->printInContext(llvm::errs());
|
||||
if (isa<BranchInst>(user)) {
|
||||
llvm::report_fatal_error("partial_apply [on_stack] cannot be cloned");
|
||||
auto *function = getFunction();
|
||||
|
||||
SmallVector<SILBasicBlock *, 32> discoveredBlocks;
|
||||
SSAPrunedLiveness liveness(function, &discoveredBlocks);
|
||||
liveness.initializeDef(this);
|
||||
|
||||
StackList<SILValue> values(function);
|
||||
values.push_back(this);
|
||||
|
||||
while (!values.empty()) {
|
||||
auto value = values.pop_back_val();
|
||||
for (auto *use : value->getUses()) {
|
||||
if (!use->isConsuming()) {
|
||||
if (auto *cvi = dyn_cast<CopyValueInst>(use->getUser())) {
|
||||
values.push_back(cvi);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
noUsers = false;
|
||||
if (isa<DestroyValueInst>(use->getUser())) {
|
||||
liveness.updateForUse(use->getUser(), /*lifetimeEnding=*/true);
|
||||
continue;
|
||||
}
|
||||
auto forward = ForwardingOperand(use);
|
||||
if (!forward) {
|
||||
// There shouldn't be any non-forwarding consumptions of a nonescaping
|
||||
// partial_apply that don't forward it along, aside from destroy_value.
|
||||
//
|
||||
// On-stack partial_apply cannot be cloned, so it should never be used
|
||||
// by a BranchInst.
|
||||
//
|
||||
// This is a fatal error because it performs SIL verification that is
|
||||
// not separately checked in the verifier. It is the only check that
|
||||
// verifies the structural requirements of on-stack partial_apply uses.
|
||||
if (lookThroughOwnershipAndForwardingInsts(use->get()) !=
|
||||
SILValue(this)) {
|
||||
// Consumes of values which aren't "essentially" the
|
||||
// partial_apply [on_stack]
|
||||
// are okay. For example, a not-on_stack partial_apply that captures
|
||||
// it.
|
||||
continue;
|
||||
}
|
||||
llvm::errs() << "partial_apply [on_stack] use:\n";
|
||||
auto *user = use->getUser();
|
||||
user->printInContext(llvm::errs());
|
||||
if (isa<BranchInst>(user)) {
|
||||
llvm::report_fatal_error("partial_apply [on_stack] cannot be cloned");
|
||||
}
|
||||
llvm::report_fatal_error("partial_apply [on_stack] must be directly "
|
||||
"forwarded to a destroy_value");
|
||||
}
|
||||
forward.visitForwardedValues([&values](auto value) {
|
||||
values.push_back(value);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
PrunedLivenessBoundary boundary;
|
||||
liveness.computeBoundary(boundary);
|
||||
|
||||
for (auto *inst : boundary.lastUsers) {
|
||||
// Only destroy_values were added to liveness, so only destroy_values can be
|
||||
// the last users.
|
||||
auto *dvi = cast<DestroyValueInst>(inst);
|
||||
auto keepGoing = func(&dvi->getOperandRef());
|
||||
if (!keepGoing) {
|
||||
return false;
|
||||
}
|
||||
llvm::report_fatal_error("partial_apply [on_stack] must be directly "
|
||||
"forwarded to a destroy_value");
|
||||
return false;
|
||||
};
|
||||
if (!visitRecursivelyLifetimeEndingUses(this, noUsers, func,
|
||||
visitUnknownUse)) {
|
||||
return false;
|
||||
}
|
||||
return !noUsers;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ sil @borrowC : $@convention(thin) (@guaranteed C) -> ()
|
||||
// CHECK: destroy_value [[C]]
|
||||
// CHECK-LABEL: } // end sil function 'copied_partial_apply'
|
||||
// CHECK: Operand.
|
||||
// CHECK: Owner: destroy_value [[PA]]
|
||||
// CHECK: Owner: destroy_value [[PA2]]
|
||||
// CHECK: returned: true
|
||||
// CHECK-LABEL: end running test {{.*}} on copied_partial_apply
|
||||
sil [ossa] @copied_partial_apply : $@convention(thin) (@owned C) -> () {
|
||||
|
||||
@@ -162,9 +162,9 @@ exit(%phi : @owned $C, %typhi : $S):
|
||||
sil @empty : $@convention(thin) () -> () {
|
||||
[global: ]
|
||||
bb0:
|
||||
%0 = tuple ()
|
||||
return %0 : $()
|
||||
}
|
||||
%0 = tuple ()
|
||||
return %0 : $()
|
||||
}
|
||||
|
||||
// Even though the apply of %empty is not a deinit barrier, verify that the
|
||||
// destroy is not hoisted, because MoS is move-only.
|
||||
@@ -568,16 +568,16 @@ entry(%c1 : @owned $C):
|
||||
// CHECK: bb0([[C1:%[^,]+]] : @owned $C):
|
||||
// CHECK: [[TAKE_C:%[^,]+]] = function_ref @takeC
|
||||
// CHECK: [[BARRIER:%[^,]+]] = function_ref @barrier
|
||||
// CHECK: cond_br undef, [[LEFT]], [[RIGHT]]
|
||||
// CHECK: [[LEFT]]:
|
||||
// CHECK: cond_br undef, [[LEFT]], [[RIGHT]]
|
||||
// CHECK: [[LEFT]]:
|
||||
// CHECK: [[M:%[^,]+]] = move_value [[C1]]
|
||||
// CHECK: apply [[TAKE_C]]([[M]])
|
||||
// CHECK: br [[EXIT]]
|
||||
// CHECK: [[RIGHT]]:
|
||||
// CHECK: br [[EXIT]]
|
||||
// CHECK: [[RIGHT]]:
|
||||
// CHECK: apply [[BARRIER]]()
|
||||
// CHECK: destroy_value [[C1]]
|
||||
// CHECK: br [[EXIT]]
|
||||
// CHECK: [[EXIT]]:
|
||||
// CHECK: br [[EXIT]]
|
||||
// CHECK: [[EXIT]]:
|
||||
// CHECK: apply [[BARRIER]]()
|
||||
// CHECK-LABEL: } // end sil function 'lexical_end_at_end_2'
|
||||
// CHECK-LABEL: end running test {{.*}} on lexical_end_at_end_2: canonicalize_ossa_lifetime
|
||||
@@ -884,3 +884,37 @@ die:
|
||||
apply undef(%reload) : $@convention(thin) (@guaranteed C) -> ()
|
||||
unreachable
|
||||
}
|
||||
|
||||
// The destroy of a value must not be hoisted over a destroy of a copy of a
|
||||
// partial_apply [on_stack] which captures the value.
|
||||
// CHECK-LABEL: begin running test {{.*}} on destroy_after_pa_copy_destroy
|
||||
// CHECK-LABEL: sil [ossa] @destroy_after_pa_copy_destroy : {{.*}} {
|
||||
// CHECK: bb0([[A:%[^,]+]] :
|
||||
// CHECK: [[C:%[^,]+]] = move_value [[A]]
|
||||
// CHECK: [[BORROW_C:%[^,]+]] = function_ref @borrowC
|
||||
// CHECK: [[PA:%[^,]+]] = partial_apply [callee_guaranteed] [on_stack] [[BORROW_C]]([[C]])
|
||||
// CHECK: [[PA2:%[^,]+]] = copy_value [[PA]]
|
||||
// CHECK: destroy_value [[PA]]
|
||||
// CHECK: destroy_value [[PA2]]
|
||||
// CHECK: destroy_value [[C]]
|
||||
// CHECK: apply undef()
|
||||
// CHECK-LABEL: } // end sil function 'destroy_after_pa_copy_destroy'
|
||||
// CHECK-LABEL: end running test {{.*}} on destroy_after_pa_copy_destroy
|
||||
sil [ossa] @destroy_after_pa_copy_destroy : $@convention(thin) (@owned C) -> () {
|
||||
entry(%a: @owned $C):
|
||||
// To defeat deinit barriers (apply undef).
|
||||
%c = move_value %a
|
||||
specify_test "canonicalize_ossa_lifetime true false true %c"
|
||||
|
||||
%callee = function_ref @borrowC : $@convention(thin) (@guaranteed C) -> ()
|
||||
%pa = partial_apply [callee_guaranteed] [on_stack] %callee(%c) : $@convention(thin) (@guaranteed C) -> ()
|
||||
%pa2 = copy_value %pa
|
||||
destroy_value %pa
|
||||
|
||||
destroy_value %pa2
|
||||
// To defeat ignoredByDestroyHoisting.
|
||||
apply undef() : $@convention(thin) () -> ()
|
||||
destroy_value %c
|
||||
%retval = tuple ()
|
||||
return %retval
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user