@@ -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