9696// Overall, this should allow good performance with small overhead that is
9797// mostly noticed at rewind time.
9898//
99+ // Exceptions handling (-fwasm-exceptions) is partially supported. Asyncify
100+ // can't start unwind operation when a catch block is in the stack trace.
101+ // If assertions mode is enabled then pass will check if unwind called from
102+ // within catch block or not, and if so throw an unreachable exception.
103+ // If "ignore unwind from catch" mode is enable then Asyncify will skip
104+ // any unwind call from within catch block.
105+ //
99106// After this pass is run a new i32 global "__asyncify_state" is added, which
100107// has the following values:
101108//
239246// an unwind/rewind in an invalid place (this can be helpful for manual
240247// tweaking of the only-list / remove-list, see later).
241248//
249+ // --pass-arg=asyncify-ignore-unwind-from-catch
250+ //
251+ // This enables additional check to be performed before unwinding. In
252+ // cases where the unwind operation is triggered from the catch block,
253+ // it will be silently ignored (-fwasm-exceptions support)
254+ //
242255// --pass-arg=asyncify-verbose
243256//
244257// Logs out instrumentation decisions to the console. This can help figure
317330
318331#include " asmjs/shared-constants.h"
319332#include " cfg/liveness-traversal.h"
333+ #include " ir/branch-utils.h"
320334#include " ir/effects.h"
335+ #include " ir/eh-utils.h"
321336#include " ir/find_all.h"
322337#include " ir/linear-execution.h"
323338#include " ir/literal-utils.h"
324339#include " ir/memory-utils.h"
325340#include " ir/module-utils.h"
326341#include " ir/names.h"
342+ #include " ir/parents.h"
327343#include " ir/utils.h"
328344#include " pass.h"
329345#include " passes/pass-utils.h"
@@ -338,6 +354,8 @@ namespace {
338354
339355static const Name ASYNCIFY_STATE = " __asyncify_state" ;
340356static const Name ASYNCIFY_GET_STATE = " asyncify_get_state" ;
357+ static const Name ASYNCIFY_CATCH_COUNTER = " __asyncify_catch_counter" ;
358+ static const Name ASYNCIFY_GET_CATCH_COUNTER = " asyncify_get_catch_counter" ;
341359static const Name ASYNCIFY_DATA = " __asyncify_data" ;
342360static const Name ASYNCIFY_START_UNWIND = " asyncify_start_unwind" ;
343361static const Name ASYNCIFY_STOP_UNWIND = " asyncify_stop_unwind" ;
@@ -1138,6 +1156,18 @@ struct AsyncifyFlow : public Pass {
11381156 // here as well.
11391157 results.push_back (makeCallSupport (curr));
11401158 continue ;
1159+ } else if (auto * try_ = curr->dynCast <Try>()) {
1160+ if (item.phase == Work::Scan) {
1161+ work.push_back (Work{curr, Work::Finish});
1162+ work.push_back (Work{try_->body , Work::Scan});
1163+ // catchBodies are ignored because we assume that pause/resume will
1164+ // not happen inside them
1165+ continue ;
1166+ }
1167+ try_->body = results.back ();
1168+ results.pop_back ();
1169+ results.push_back (try_);
1170+ continue ;
11411171 }
11421172 // We must handle all control flow above, and all things that can change
11431173 // the state, so there should be nothing that can reach here - add it
@@ -1218,6 +1248,202 @@ struct AsyncifyFlow : public Pass {
12181248 }
12191249};
12201250
1251+ // Add catch block counters to verify that unwind is not called from catch
1252+ // block.
1253+ struct AsyncifyAddCatchCounters : public Pass {
1254+ bool isFunctionParallel () override { return true ; }
1255+
1256+ std::unique_ptr<Pass> create () override {
1257+ return std::make_unique<AsyncifyAddCatchCounters>();
1258+ }
1259+
1260+ void runOnFunction (Module* module_, Function* func) override {
1261+ class CountersBuilder : public Builder {
1262+ public:
1263+ CountersBuilder (Module& wasm) : Builder(wasm) {}
1264+ Expression* makeInc (int amount = 1 ) {
1265+ return makeGlobalSet (
1266+ ASYNCIFY_CATCH_COUNTER,
1267+ makeBinary (AddInt32,
1268+ makeGlobalGet (ASYNCIFY_CATCH_COUNTER, Type::i32 ),
1269+ makeConst (int32_t (amount))));
1270+ }
1271+ Expression* makeDec (int amount = 1 ) {
1272+ return makeGlobalSet (
1273+ ASYNCIFY_CATCH_COUNTER,
1274+ makeBinary (SubInt32,
1275+ makeGlobalGet (ASYNCIFY_CATCH_COUNTER, Type::i32 ),
1276+ makeConst (int32_t (amount))));
1277+ }
1278+ };
1279+
1280+ // with this walker we will handle those changes of counter:
1281+ // - entering top-level catch (= pop) +1
1282+ // - entering nested catch (= pop) 0 (ignored)
1283+ //
1284+ // - return inside top-level/nested catch -1
1285+ // - return outside top-level/nested catch 0 (ignored)
1286+ //
1287+ // - break target outside of top-level catch -1
1288+ // - break target inside of top-level catch 0 (ignored)
1289+ // - break outside top-level/nested catch 0 (ignored)
1290+ //
1291+ // - exiting from top-level catch -1
1292+ // - exiting from nested catch 0 (ignored)
1293+ struct AddCountersWalker : public PostWalker <AddCountersWalker> {
1294+ Function* func;
1295+ CountersBuilder* builder;
1296+ BranchUtils::BranchTargets* branchTargets;
1297+ Parents* parents;
1298+ int finallyNum = 0 ;
1299+ int popNum = 0 ;
1300+
1301+ int getCatchCount (Expression* expression) {
1302+ int catchCount = 0 ;
1303+ while (expression != func->body ) {
1304+ auto parent = parents->getParent (expression);
1305+ if (auto * try_ = parent->dynCast <Try>()) {
1306+ if (try_->body != expression) {
1307+ catchCount++;
1308+ }
1309+ }
1310+ expression = parent;
1311+ }
1312+
1313+ return catchCount;
1314+ }
1315+
1316+ // Each catch block except catch_all should have pop instruction
1317+ // We increment counter each time when we enter top-level catch block
1318+ void visitPop (Pop* pop) {
1319+ if (getCatchCount (pop) == 1 ) {
1320+ auto name =
1321+ func->name .toString () + " -pop-" + std::to_string (++popNum);
1322+ replaceCurrent (
1323+ builder->makeBlock (name, {pop, builder->makeInc ()}, Type::none));
1324+ }
1325+ }
1326+ void visitLocalSet (LocalSet* set) {
1327+ auto block = set->value ->dynCast <Block>(); // from visitPop above
1328+ if (block && block->name .hasSubstring (" -pop-" )) {
1329+ auto pop = block->list [0 ]->dynCast <Pop>();
1330+ assert (pop && getCatchCount (pop) == 1 );
1331+ set->value = pop;
1332+ replaceCurrent (builder->makeBlock (
1333+ block->name , {set, builder->makeInc ()}, Type::none));
1334+ }
1335+ }
1336+
1337+ // When return happens we decrement counter on 1, because we account
1338+ // only top-level catch blocks
1339+ // catch
1340+ // +1
1341+ // catch
1342+ // ;; not counted
1343+ // -1
1344+ // return
1345+ // ...
1346+ void visitReturn (Return* ret) {
1347+ if (getCatchCount (ret) > 0 ) {
1348+ replaceCurrent (builder->makeSequence (builder->makeDec (), ret));
1349+ }
1350+ }
1351+
1352+ // When break happens we decrement counter only if it goes out
1353+ // from top-level catch block
1354+ void visitBreak (Break* br) {
1355+ Expression* target = branchTargets->getTarget (br->name );
1356+ assert (target != nullptr );
1357+ if (getCatchCount (br) > 0 && getCatchCount (target) == 0 ) {
1358+ if (br->condition == nullptr ) {
1359+ replaceCurrent (builder->makeSequence (builder->makeDec (), br));
1360+ } else if (br->value == nullptr ) {
1361+ auto decIf =
1362+ builder->makeIf (br->condition ,
1363+ builder->makeSequence (builder->makeDec (), br),
1364+ nullptr );
1365+ br->condition = nullptr ;
1366+ replaceCurrent (decIf);
1367+ } else {
1368+ Index newLocal = builder->addVar (func, br->value ->type );
1369+ auto setLocal = builder->makeLocalSet (newLocal, br->value );
1370+ auto getLocal = builder->makeLocalGet (newLocal, br->value ->type );
1371+ auto condition = br->condition ;
1372+ br->condition = nullptr ;
1373+ br->value = getLocal;
1374+ auto decIf =
1375+ builder->makeIf (condition,
1376+ builder->makeSequence (builder->makeDec (), br),
1377+ getLocal);
1378+ replaceCurrent (builder->makeSequence (setLocal, decIf));
1379+ }
1380+ }
1381+ }
1382+
1383+ // Replacing each top-level catch block with try/catch_all(finally) and
1384+ // increase counter for catch_all blocks (not handled by visitPop); dec
1385+ // counter at the end of catch block try ({fn}-finally-{label})
1386+ // +1
1387+ // {catch body}
1388+ // -1
1389+ // catch_all
1390+ // -1
1391+ // rethrow {fn}-finally-{label}
1392+ void visitTry (Try* curr) {
1393+ if (getCatchCount (curr) == 0 ) {
1394+ for (size_t i = 0 ; i < curr->catchBodies .size (); ++i) {
1395+ curr->catchBodies [i] = addCatchCounters (
1396+ curr->catchBodies [i], i == curr->catchTags .size ());
1397+ }
1398+ }
1399+ }
1400+ Expression* addCatchCounters (Expression* expression, bool catchAll) {
1401+ auto block = expression->dynCast <Block>();
1402+ if (block == nullptr ) {
1403+ block = builder->makeBlock (expression);
1404+ }
1405+
1406+ // catch_all case is not covered by visitPop
1407+ if (catchAll) {
1408+ block->list .insertAt (0 , builder->makeInc ());
1409+ }
1410+
1411+ // dec counters at the end of catch
1412+ if (block->type == Type::none) {
1413+ auto last = block->list [block->list .size () - 1 ];
1414+ if (!last->dynCast <Return>()) {
1415+ block->list .push_back (builder->makeDec ());
1416+ block->finalize ();
1417+ }
1418+ }
1419+
1420+ auto name =
1421+ func->name .toString () + " -finally-" + std::to_string (++finallyNum);
1422+ return builder->makeTry (
1423+ name,
1424+ block,
1425+ {},
1426+ {builder->makeSequence (builder->makeDec (),
1427+ builder->makeRethrow (name))},
1428+ block->type );
1429+ }
1430+ };
1431+
1432+ Parents parents (func->body );
1433+ CountersBuilder builder (*module_);
1434+ BranchUtils::BranchTargets branchTargets (func->body );
1435+
1436+ AddCountersWalker addCountersWalker;
1437+ addCountersWalker.func = func;
1438+ addCountersWalker.builder = &builder;
1439+ addCountersWalker.branchTargets = &branchTargets;
1440+ addCountersWalker.parents = &parents;
1441+ addCountersWalker.walk (func->body );
1442+
1443+ EHUtils::handleBlockNestedPops (func, *module_);
1444+ }
1445+ };
1446+
12211447// Add asserts in non-instrumented code.
12221448struct AsyncifyAssertInNonInstrumented : public Pass {
12231449 bool isFunctionParallel () override { return true ; }
@@ -1650,6 +1876,8 @@ struct Asyncify : public Pass {
16501876 auto relocatable = hasArgument (" asyncify-relocatable" );
16511877 auto secondaryMemory = hasArgument (" asyncify-in-secondary-memory" );
16521878 auto propagateAddList = hasArgument (" asyncify-propagate-addlist" );
1879+ auto ignoreCatchUnwind = hasArgument (" asyncify-ignore-unwind-from-catch" );
1880+ auto addAsyncifyCounters = asserts || ignoreCatchUnwind;
16531881
16541882 // Ensure there is a memory, as we need it.
16551883
@@ -1714,7 +1942,7 @@ struct Asyncify : public Pass {
17141942 verbose);
17151943
17161944 // Add necessary globals before we emit code to use them.
1717- addGlobals (module , relocatable);
1945+ addGlobals (module , relocatable, addAsyncifyCounters );
17181946
17191947 // Compute the set of functions we will instrument. All of the passes we run
17201948 // below only need to run there.
@@ -1755,12 +1983,17 @@ struct Asyncify : public Pass {
17551983 runner.setValidateGlobally (false );
17561984 runner.run ();
17571985 }
1758- if (asserts) {
1986+ if (asserts || addAsyncifyCounters ) {
17591987 // Add asserts in non-instrumented code. Note we do not use an
17601988 // instrumented pass runner here as we do want to run on all functions.
17611989 PassRunner runner (module );
1762- runner.add (std::make_unique<AsyncifyAssertInNonInstrumented>(
1763- &analyzer, pointerType, asyncifyMemory));
1990+ if (addAsyncifyCounters) {
1991+ runner.add (std::make_unique<AsyncifyAddCatchCounters>());
1992+ }
1993+ if (asserts) {
1994+ runner.add (std::make_unique<AsyncifyAssertInNonInstrumented>(
1995+ &analyzer, pointerType, asyncifyMemory));
1996+ }
17641997 runner.setIsNested (true );
17651998 runner.setValidateGlobally (false );
17661999 runner.run ();
@@ -1786,11 +2019,11 @@ struct Asyncify : public Pass {
17862019 }
17872020 // Finally, add function support (that should not have been seen by
17882021 // the previous passes).
1789- addFunctions (module );
2022+ addFunctions (module , asserts, ignoreCatchUnwind );
17902023 }
17912024
17922025private:
1793- void addGlobals (Module* module , bool imported) {
2026+ void addGlobals (Module* module , bool imported, bool addAsyncifyCounters ) {
17942027 Builder builder (*module );
17952028
17962029 auto asyncifyState = builder.makeGlobal (ASYNCIFY_STATE,
@@ -1803,6 +2036,19 @@ struct Asyncify : public Pass {
18032036 }
18042037 module ->addGlobal (std::move (asyncifyState));
18052038
2039+ if (addAsyncifyCounters) {
2040+ auto asyncifyCatchCounter =
2041+ builder.makeGlobal (ASYNCIFY_CATCH_COUNTER,
2042+ Type::i32 ,
2043+ builder.makeConst (int32_t (0 )),
2044+ Builder::Mutable);
2045+ if (imported) {
2046+ asyncifyCatchCounter->module = ENV;
2047+ asyncifyCatchCounter->base = ASYNCIFY_CATCH_COUNTER;
2048+ }
2049+ module ->addGlobal (std::move (asyncifyCatchCounter));
2050+ }
2051+
18062052 auto asyncifyData = builder.makeGlobal (ASYNCIFY_DATA,
18072053 pointerType,
18082054 builder.makeConst (pointerType),
@@ -1814,14 +2060,24 @@ struct Asyncify : public Pass {
18142060 module ->addGlobal (std::move (asyncifyData));
18152061 }
18162062
1817- void addFunctions (Module* module ) {
2063+ void addFunctions (Module* module , bool asserts, bool ignoreCatchUnwind ) {
18182064 Builder builder (*module );
18192065 auto makeFunction = [&](Name name, bool setData, State state) {
2066+ auto * body = builder.makeBlock ();
2067+ if (name == ASYNCIFY_START_UNWIND && (asserts || ignoreCatchUnwind)) {
2068+ auto * check = builder.makeIf (
2069+ builder.makeBinary (
2070+ NeInt32,
2071+ builder.makeGlobalGet (ASYNCIFY_CATCH_COUNTER, Type::i32 ),
2072+ builder.makeConst (int32_t (0 ))),
2073+ ignoreCatchUnwind ? (Expression*)builder.makeReturn ()
2074+ : (Expression*)builder.makeUnreachable ());
2075+ body->list .push_back (check);
2076+ }
18202077 std::vector<Type> params;
18212078 if (setData) {
18222079 params.push_back (pointerType);
18232080 }
1824- auto * body = builder.makeBlock ();
18252081 body->list .push_back (builder.makeGlobalSet (
18262082 ASYNCIFY_STATE, builder.makeConst (int32_t (state))));
18272083 if (setData) {
@@ -1869,6 +2125,17 @@ struct Asyncify : public Pass {
18692125 builder.makeGlobalGet (ASYNCIFY_STATE, Type::i32 )));
18702126 module ->addExport (builder.makeExport (
18712127 ASYNCIFY_GET_STATE, ASYNCIFY_GET_STATE, ExternalKind::Function));
2128+
2129+ if (asserts || ignoreCatchUnwind) {
2130+ module ->addFunction (builder.makeFunction (
2131+ ASYNCIFY_GET_CATCH_COUNTER,
2132+ Signature (Type::none, Type::i32 ),
2133+ {},
2134+ builder.makeGlobalGet (ASYNCIFY_CATCH_COUNTER, Type::i32 )));
2135+ module ->addExport (builder.makeExport (ASYNCIFY_GET_CATCH_COUNTER,
2136+ ASYNCIFY_GET_CATCH_COUNTER,
2137+ ExternalKind::Function));
2138+ }
18722139 }
18732140
18742141 Name createSecondaryMemory (Module* module , Address secondaryMemorySize) {
0 commit comments