Skip to content

Commit f3f4698

Browse files
committed
Optimize resume of non-suspending continuations
Add a `visitResume` in OptimizeInstructions that does the normal optimizations on null continuations and then tries to turn resumes into calls. We can do this when we are resuming a freshly allocated continuation created with a reference to a known function that GlobalEffects tells us will not suspend.
1 parent 9a36dad commit f3f4698

2 files changed

Lines changed: 1220 additions & 0 deletions

File tree

src/passes/OptimizeInstructions.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,88 @@ struct OptimizeInstructions
14611461
}
14621462
}
14631463

1464+
void visitResume(Resume* curr) {
1465+
skipNonNullCast(curr->cont, curr);
1466+
if (trapOnNull(curr, curr->cont)) {
1467+
return;
1468+
}
1469+
if (curr->type == Type::unreachable) {
1470+
return;
1471+
}
1472+
1473+
// If this resume operates on a freshly-created continuation of an exact
1474+
// function that is known never to suspend, we can turn the resumption into
1475+
// a direct call, avoiding the continuation allocation and handler overhead.
1476+
1477+
// Continuations are single-shot, so resuming a continuation that has
1478+
// already been consumed will trap. If traps are assumed never to happen, we
1479+
// can assume this continuation was not consumed on another path and look
1480+
// through tees and conditional branches. Otherwise, avoid looking through
1481+
// them to ensure the continuation cannot be consumed elsewhere.
1482+
auto behavior = getPassOptions().trapsNeverHappen
1483+
? Properties::FallthroughBehavior::AllowTeeBrIf
1484+
: Properties::FallthroughBehavior::NoTeeBrIf;
1485+
1486+
auto* contExpr = Properties::getFallthrough(
1487+
curr->cont, getPassOptions(), *getModule(), behavior);
1488+
auto* contNew = contExpr->dynCast<ContNew>();
1489+
if (!contNew) {
1490+
return;
1491+
}
1492+
if (contNew->func->type == Type::unreachable) {
1493+
return;
1494+
}
1495+
1496+
auto* funcExpr = Properties::getFallthrough(
1497+
contNew->func, getPassOptions(), *getModule(), behavior);
1498+
auto* refFunc = funcExpr->dynCast<RefFunc>();
1499+
if (!refFunc) {
1500+
return;
1501+
}
1502+
1503+
auto* target = getModule()->getFunctionOrNull(refFunc->func);
1504+
if (!target || target->imported()) {
1505+
return;
1506+
}
1507+
1508+
if (!target->effects || target->effects->suspends()) {
1509+
return;
1510+
}
1511+
1512+
Builder builder(*getModule());
1513+
1514+
// If the continuation expression has no side effects, we can eliminate it
1515+
// entirely and replace the resume with a direct call.
1516+
if (!effects(curr->cont).hasSideEffects()) {
1517+
replaceCurrent(
1518+
builder.makeCall(target->name, curr->operands, target->getResults()));
1519+
return;
1520+
}
1521+
1522+
// The continuation expression has side effects. In Wasm, resume operands
1523+
// are evaluated before the continuation expression. If there are no
1524+
// operands, evaluate the continuation (dropped) and then call.
1525+
if (curr->operands.empty()) {
1526+
replaceCurrent(builder.makeSequence(
1527+
builder.makeDrop(curr->cont),
1528+
builder.makeCall(target->name, {}, target->getResults())));
1529+
return;
1530+
}
1531+
1532+
// In the presence of operands, execute the code in curr->cont after the
1533+
// operands and before the call happens by spilling the last operand to a
1534+
// temporary local.
1535+
auto* lastOperand = curr->operands.back();
1536+
auto lastOperandType = lastOperand->type;
1537+
Index tempLocal = builder.addVar(getFunction(), lastOperandType);
1538+
auto* set = builder.makeLocalSet(tempLocal, lastOperand);
1539+
auto* drop = builder.makeDrop(curr->cont);
1540+
auto* get = builder.makeLocalGet(tempLocal, lastOperandType);
1541+
curr->operands.back() = builder.makeBlock({set, drop, get});
1542+
replaceCurrent(
1543+
builder.makeCall(target->name, curr->operands, target->getResults()));
1544+
}
1545+
14641546
// Note on removing casts (which the following utilities, skipNonNullCast and
14651547
// skipCast do): removing a cast is potentially dangerous, as it removes
14661548
// information from the IR. For example:

0 commit comments

Comments
 (0)