|
96 | 96 | // Overall, this should allow good performance with small overhead that is |
97 | 97 | // mostly noticed at rewind time. |
98 | 98 | // |
| 99 | +// Exceptions handling (-fwasm-exceptions) is partially supported, everything |
| 100 | +// except for handling unwinding from within a catch block. If assertions mode |
| 101 | +// is enabled then this pass will check for that problem, and if so, throw an |
| 102 | +// unreachable exception. (If "ignore unwind from catch" mode is enabled then |
| 103 | +// Asyncify will silently skip any unwind call from within catch blocks, see |
| 104 | +// below.) |
| 105 | +// |
99 | 106 | // After this pass is run a new i32 global "__asyncify_state" is added, which |
100 | 107 | // has the following values: |
101 | 108 | // |
|
239 | 246 | // an unwind/rewind in an invalid place (this can be helpful for manual |
240 | 247 | // tweaking of the only-list / remove-list, see later). |
241 | 248 | // |
| 249 | +// --pass-arg=asyncify-ignore-unwind-from-catch |
| 250 | +// |
| 251 | +// When an unwind operation is triggered from inside a wasm-exceptions |
| 252 | +// catch block, which is not supported, silently ignore it rather than |
| 253 | +// fail during rewinding later. (This is unsafe in general.) |
| 254 | +// |
242 | 255 | // --pass-arg=asyncify-verbose |
243 | 256 | // |
244 | 257 | // Logs out instrumentation decisions to the console. This can help figure |
@@ -1138,6 +1151,18 @@ struct AsyncifyFlow : public Pass { |
1138 | 1151 | // here as well. |
1139 | 1152 | results.push_back(makeCallSupport(curr)); |
1140 | 1153 | continue; |
| 1154 | + } else if (auto* try_ = curr->dynCast<Try>()) { |
| 1155 | + if (item.phase == Work::Scan) { |
| 1156 | + work.push_back(Work{curr, Work::Finish}); |
| 1157 | + work.push_back(Work{try_->body, Work::Scan}); |
| 1158 | + // catchBodies are ignored because we assume that pause/resume will |
| 1159 | + // not happen inside them |
| 1160 | + continue; |
| 1161 | + } |
| 1162 | + try_->body = results.back(); |
| 1163 | + results.pop_back(); |
| 1164 | + results.push_back(try_); |
| 1165 | + continue; |
1141 | 1166 | } |
1142 | 1167 | // We must handle all control flow above, and all things that can change |
1143 | 1168 | // the state, so there should be nothing that can reach here - add it |
@@ -1316,6 +1341,93 @@ struct AsyncifyAssertInNonInstrumented : public Pass { |
1316 | 1341 | Module* module; |
1317 | 1342 | }; |
1318 | 1343 |
|
| 1344 | +struct AsyncifyUnwindWalker |
| 1345 | + : WalkerPass<ExpressionStackWalker<AsyncifyUnwindWalker>> { |
| 1346 | + Function* function; |
| 1347 | + Module* module; |
| 1348 | + |
| 1349 | + // Adds a check for Call that is inside a Catch block (we do not handle |
| 1350 | + // unwinding there). |
| 1351 | + template<typename T> void replaceCallWithCheck(T* call) { |
| 1352 | + auto builder = std::make_unique<Builder>(*module); |
| 1353 | + auto check = builder->makeIf( |
| 1354 | + builder->makeBinary(NeInt32, |
| 1355 | + builder->makeGlobalGet(ASYNCIFY_STATE, Type::i32), |
| 1356 | + builder->makeConst(int32_t(State::Normal))), |
| 1357 | + builder->makeUnreachable()); |
| 1358 | + if (call->type.isConcrete()) { |
| 1359 | + auto temp = builder->addVar(function, call->type); |
| 1360 | + replaceCurrent(builder->makeBlock( |
| 1361 | + { |
| 1362 | + builder->makeLocalSet(temp, call), |
| 1363 | + check, |
| 1364 | + builder->makeLocalGet(temp, call->type), |
| 1365 | + }, |
| 1366 | + call->type)); |
| 1367 | + } else { |
| 1368 | + replaceCurrent(builder->makeBlock( |
| 1369 | + { |
| 1370 | + call, |
| 1371 | + check, |
| 1372 | + }, |
| 1373 | + call->type)); |
| 1374 | + } |
| 1375 | + } |
| 1376 | + |
| 1377 | + template<typename T> void visitCallLike(T* curr) { |
| 1378 | + assert(!expressionStack.empty()); |
| 1379 | + // A return_call (curr->isReturn) can be ignored here: It returns first, |
| 1380 | + // leaving the Catch, before calling. |
| 1381 | + if (curr->isReturn) { |
| 1382 | + return; |
| 1383 | + } |
| 1384 | + // Go up the stack and see if we are in a Catch. |
| 1385 | + Index i = expressionStack.size() - 1; |
| 1386 | + while (i > 0) { |
| 1387 | + auto* expr = expressionStack[i]; |
| 1388 | + if (Try* aTry = expr->template dynCast<Try>()) { |
| 1389 | + // check if curr is inside body of aTry (which is safe), |
| 1390 | + // otherwise do replace a call |
| 1391 | + assert(i + 1 < expressionStack.size()); |
| 1392 | + if (expressionStack[i + 1] != aTry->body) { |
| 1393 | + replaceCallWithCheck(curr); |
| 1394 | + } |
| 1395 | + break; |
| 1396 | + } |
| 1397 | + i--; |
| 1398 | + } |
| 1399 | + } |
| 1400 | + |
| 1401 | + void visitCall(Call* curr) { visitCallLike(curr); } |
| 1402 | + |
| 1403 | + void visitCallRef(CallRef* curr) { visitCallLike(curr); } |
| 1404 | + |
| 1405 | + void visitCallIndirect(CallIndirect* curr) { visitCallLike(curr); } |
| 1406 | +}; |
| 1407 | + |
| 1408 | +struct AsyncifyAssertUnwindCorrectness : Pass { |
| 1409 | + bool isFunctionParallel() override { return true; } |
| 1410 | + |
| 1411 | + ModuleAnalyzer* analyzer; |
| 1412 | + Module* module; |
| 1413 | + |
| 1414 | + AsyncifyAssertUnwindCorrectness(ModuleAnalyzer* analyzer, Module* module) { |
| 1415 | + this->analyzer = analyzer; |
| 1416 | + this->module = module; |
| 1417 | + } |
| 1418 | + |
| 1419 | + std::unique_ptr<Pass> create() override { |
| 1420 | + return std::make_unique<AsyncifyAssertUnwindCorrectness>(analyzer, module); |
| 1421 | + } |
| 1422 | + |
| 1423 | + void runOnFunction(Module* module_, Function* function) override { |
| 1424 | + AsyncifyUnwindWalker walker; |
| 1425 | + walker.function = function; |
| 1426 | + walker.module = module_; |
| 1427 | + walker.walk(function->body); |
| 1428 | + } |
| 1429 | +}; |
| 1430 | + |
1319 | 1431 | // Instrument local saving/restoring. |
1320 | 1432 | struct AsyncifyLocals : public WalkerPass<PostWalker<AsyncifyLocals>> { |
1321 | 1433 | bool isFunctionParallel() override { return true; } |
@@ -1761,6 +1873,8 @@ struct Asyncify : public Pass { |
1761 | 1873 | PassRunner runner(module); |
1762 | 1874 | runner.add(std::make_unique<AsyncifyAssertInNonInstrumented>( |
1763 | 1875 | &analyzer, pointerType, asyncifyMemory)); |
| 1876 | + runner.add( |
| 1877 | + std::make_unique<AsyncifyAssertUnwindCorrectness>(&analyzer, module)); |
1764 | 1878 | runner.setIsNested(true); |
1765 | 1879 | runner.setValidateGlobally(false); |
1766 | 1880 | runner.run(); |
|
0 commit comments