[allocbox-to-stack] When we promote a heap -> stack of noncopyable type, move mark_must_check onto alloc_stack and always

ensure that we use consumable_to_assign.

What this patch is does is add an extra phase after alloc_box runs where we look
at uses of the alloc_stack and if we see any mark_must_check of any kind, we
delete them and rewrite a single mark_must_check [consumable_and_assignable] on
the alloc_stack and make all uses of the alloc_stack go through the
mark_must_check.

This has two effects:

1. In a subsequent PR when I add noncopyable semantics for escaping closures,
this will cause allocbox to stack to convert such boxes from having escaping
semantics to having non-escaping semantics. Escaping semantics means that we
always reproject out from the box and use mark_must_check
[assignable_but_not_consumable] (since we can't consume from the box, but can
assign to it). In contrast, non-escaping semantics means that the box becomes an
alloc_stack and we use the traditional var checker semantics. NOTE: We can do
this for lets represented as addresses and vars since the typechecker will
validate that the let is never actually written to even if at the SIL level we
would allow that.

2. In cases where we are implementing simple mark_must_check
[consumable_and_assignable] on one of the project_box and capture the box, we
used to have a problem where the direct box uses would be on the alloc_stack and
not go through the mark_must_check. Now, all uses after allocbox_to_stack occur
go through the mark_must_check. This is why I was able to remove instances of
the "compiler does not understand this pattern" errors... since the compiler
with this change can now understand them.
This commit is contained in:
Michael Gottesman
2023-02-17 11:55:10 -08:00
parent 5bc3b997fa
commit 969d776c15
5 changed files with 171 additions and 111 deletions

View File

@@ -18,6 +18,7 @@
#include "swift/SIL/ApplySite.h"
#include "swift/SIL/BasicBlockDatastructures.h"
#include "swift/SIL/Dominance.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILCloner.h"
@@ -500,25 +501,70 @@ struct AllocBoxToStackState {
};
} // anonymous namespace
static void replaceProjectBoxUsers(SILValue HeapBox, SILValue StackBox) {
SmallVector<Operand *, 8> Worklist(HeapBox->use_begin(), HeapBox->use_end());
while (!Worklist.empty()) {
auto *Op = Worklist.pop_back_val();
if (auto *PBI = dyn_cast<ProjectBoxInst>(Op->getUser())) {
static void replaceProjectBoxUsers(SILValue heapBox, SILValue stackBox) {
StackList<Operand *> worklist(heapBox->getFunction());
for (auto *use : heapBox->getUses())
worklist.push_back(use);
while (!worklist.empty()) {
auto *nextUse = worklist.pop_back_val();
if (auto *pbi = dyn_cast<ProjectBoxInst>(nextUse->getUser())) {
// This may result in an alloc_stack being used by begin_access [dynamic].
PBI->replaceAllUsesWith(StackBox);
pbi->replaceAllUsesWith(stackBox);
pbi->eraseFromParent();
continue;
}
auto *User = Op->getUser();
if (isa<MarkUninitializedInst>(User) || isa<CopyValueInst>(User) ||
isa<BeginBorrowInst>(User)) {
llvm::copy(cast<SingleValueInstruction>(User)->getUses(),
std::back_inserter(Worklist));
auto *user = nextUse->getUser();
if (isa<MarkUninitializedInst>(user) || isa<CopyValueInst>(user) ||
isa<BeginBorrowInst>(user)) {
for (auto *use : cast<SingleValueInstruction>(user)->getUses()) {
worklist.push_back(use);
}
}
}
}
static void hoistMarkMustCheckInsts(SingleValueInstruction *stackBox) {
StackList<Operand *> worklist(stackBox->getFunction());
for (auto *use : stackBox->getUses()) {
worklist.push_back(use);
}
bool foundTarget = false;
while (!worklist.empty()) {
auto *nextUse = worklist.pop_back_val();
auto *nextUser = nextUse->getUser();
if (isa<BeginBorrowInst>(nextUser) || isa<BeginAccessInst>(nextUser) ||
isa<CopyValueInst>(nextUser) || isa<MarkUninitializedInst>(nextUser)) {
for (auto result : nextUser->getResults()) {
for (auto *use : result->getUses())
worklist.push_back(use);
}
}
if (auto *mmci = dyn_cast<MarkMustCheckInst>(nextUser)) {
foundTarget = true;
mmci->replaceAllUsesWith(mmci->getOperand());
mmci->eraseFromParent();
}
}
if (!foundTarget)
return;
SILBuilderWithScope builder(stackBox);
builder.insertAfter(stackBox, [&](SILBuilder &b) {
auto *undef = SILUndef::get(stackBox->getType(), stackBox->getModule());
auto *mmci = b.createMarkMustCheckInst(
stackBox->getLoc(), undef,
MarkMustCheckInst::CheckKind::ConsumableAndAssignable);
stackBox->replaceAllUsesWith(mmci);
mmci->setOperand(stackBox);
});
}
/// rewriteAllocBoxAsAllocStack - Replace uses of the alloc_box with a
/// new alloc_stack, but do not delete the alloc_box yet.
static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
@@ -574,7 +620,7 @@ static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
ABI->hasDynamicLifetime(), isLexical());
// Transfer a mark_uninitialized if we have one.
SILValue StackBox = ASI;
SingleValueInstruction *StackBox = ASI;
if (Kind) {
StackBox =
Builder.createMarkUninitialized(ASI->getLoc(), ASI, Kind.value());
@@ -584,6 +630,12 @@ static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
// the address of the stack location.
replaceProjectBoxUsers(HeapBox, StackBox);
// Then hoist any mark_must_check [assignable_but_not_consumable] to the
// alloc_stack and convert them to [consumable_but_not_assignable]. This is
// because we are semantically converting from escaping semantics to
// non-escaping semantics.
hoistMarkMustCheckInsts(StackBox);
assert(ABI->getBoxType()->getLayout()->getFields().size() == 1
&& "promoting multi-field box not implemented");
auto &Lowering = ABI->getFunction()->getTypeLowering(