-
Notifications
You must be signed in to change notification settings - Fork 824
Add partial support for -fwasm-exceptions in Asyncify (#5343) #5475
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
be0a16d
5057cef
a0e114b
372442e
1378904
3ca7b8a
b60b75d
fc47f68
8d37605
4175a77
7fd9579
af58be1
e9b062f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -96,6 +96,13 @@ | |
| // Overall, this should allow good performance with small overhead that is | ||
| // mostly noticed at rewind time. | ||
| // | ||
| // Exceptions handling (-fwasm-exceptions) is partially supported, everything | ||
| // except for handling unwinding from within a catch block. If assertions mode | ||
| // is enabled then this pass will check for that problem, and if so, throw an | ||
| // unreachable exception. (If "ignore unwind from catch" mode is enabled then | ||
| // Asyncify will silently skip any unwind call from within catch blocks, see | ||
| // below.) | ||
| // | ||
| // After this pass is run a new i32 global "__asyncify_state" is added, which | ||
| // has the following values: | ||
| // | ||
|
|
@@ -239,6 +246,12 @@ | |
| // an unwind/rewind in an invalid place (this can be helpful for manual | ||
| // tweaking of the only-list / remove-list, see later). | ||
| // | ||
| // --pass-arg=asyncify-ignore-unwind-from-catch | ||
| // | ||
| // When an unwind operation is triggered from inside a wasm-exceptions | ||
| // catch block, which is not supported, silently ignore it rather than | ||
| // fail during rewinding later. (This is unsafe in general.) | ||
| // | ||
| // --pass-arg=asyncify-verbose | ||
| // | ||
| // Logs out instrumentation decisions to the console. This can help figure | ||
|
|
@@ -1138,6 +1151,18 @@ struct AsyncifyFlow : public Pass { | |
| // here as well. | ||
| results.push_back(makeCallSupport(curr)); | ||
| continue; | ||
| } else if (auto* try_ = curr->dynCast<Try>()) { | ||
| if (item.phase == Work::Scan) { | ||
| work.push_back(Work{curr, Work::Finish}); | ||
| work.push_back(Work{try_->body, Work::Scan}); | ||
| // catchBodies are ignored because we assume that pause/resume will | ||
| // not happen inside them | ||
| continue; | ||
| } | ||
| try_->body = results.back(); | ||
| results.pop_back(); | ||
| results.push_back(try_); | ||
| continue; | ||
| } | ||
| // We must handle all control flow above, and all things that can change | ||
| // the state, so there should be nothing that can reach here - add it | ||
|
|
@@ -1316,6 +1341,81 @@ struct AsyncifyAssertInNonInstrumented : public Pass { | |
| Module* module; | ||
| }; | ||
|
|
||
| struct AsyncifyAssertUnwindCorrectness : Pass { | ||
| ModuleAnalyzer* analyzer; | ||
| Module* module; | ||
|
|
||
| AsyncifyAssertUnwindCorrectness(ModuleAnalyzer* analyzer, Module* module) { | ||
| this->analyzer = analyzer; | ||
| this->module = module; | ||
| } | ||
|
|
||
| std::unique_ptr<Pass> create() override { | ||
| return std::make_unique<AsyncifyAssertUnwindCorrectness>(analyzer, module); | ||
| } | ||
|
|
||
| bool isFunctionParallel() override { return true; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a convention we prefer this at the top of the class, before all other internals. |
||
|
|
||
| void runOnFunction(Module*, Function* function) override { | ||
| auto builder = std::make_unique<Builder>(*module); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Builders are very cheap to construct - please create one in |
||
|
|
||
| struct UnwindWalker : WalkerPass<ExpressionStackWalker<UnwindWalker>> { | ||
| Function* function; | ||
| Builder* builder; | ||
|
|
||
| // Adds a check for Call that is inside a Catch block (we do not handle unwinding there). | ||
| void checkCallInsideCatch(Call* call) { | ||
| auto check = builder->makeIf( | ||
| builder->makeBinary(NeInt32, | ||
| builder->makeGlobalGet(ASYNCIFY_STATE, Type::i32), | ||
| builder->makeConst(int32_t(State::Normal))), | ||
| builder->makeUnreachable()); | ||
| if (call->type.isConcrete()) { | ||
| auto temp = builder->addVar(function, call->type); | ||
| replaceCurrent(builder->makeBlock( | ||
| { | ||
| builder->makeLocalSet(temp, call), | ||
| check, | ||
| builder->makeLocalGet(temp, call->type), | ||
| }, | ||
| call->type)); | ||
| } else { | ||
| replaceCurrent(builder->makeBlock( | ||
| { | ||
| call, | ||
| check, | ||
| }, | ||
| call->type)); | ||
| } | ||
| } | ||
|
|
||
| void visitCall(Call* curr) { | ||
| assert(!expressionStack.empty()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A |
||
| // Go up the stack and see if we are in a Catch. | ||
| Index i = expressionStack.size() - 1; | ||
caiiiycuk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| while (i > 0) { | ||
| auto* expr = expressionStack[i]; | ||
| if (Try* aTry = expr->template dynCast<Try>()) { | ||
| // check if curr is inside body of aTry (which is safe), | ||
| // otherwise do replace a call | ||
| assert(i + 1 < expressionStack.size()); | ||
| if (expressionStack[i + 1] != aTry->body) { | ||
| replaceCallWithCheck(curr); | ||
| } | ||
| break; | ||
| } | ||
| i--; | ||
| } | ||
| }; | ||
| }; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should also handle CallIndirect and CallRef here. |
||
|
|
||
| UnwindWalker walker; | ||
| walker.function = function; | ||
| walker.builder = builder.get(); | ||
| walker.walk(function->body); | ||
| } | ||
| }; | ||
|
|
||
| // Instrument local saving/restoring. | ||
| struct AsyncifyLocals : public WalkerPass<PostWalker<AsyncifyLocals>> { | ||
| bool isFunctionParallel() override { return true; } | ||
|
|
@@ -1759,8 +1859,12 @@ struct Asyncify : public Pass { | |
| // Add asserts in non-instrumented code. Note we do not use an | ||
| // instrumented pass runner here as we do want to run on all functions. | ||
| PassRunner runner(module); | ||
| runner.add(std::make_unique<AsyncifyAssertInNonInstrumented>( | ||
| &analyzer, pointerType, asyncifyMemory)); | ||
| if (asserts) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is already inside an |
||
| runner.add(std::make_unique<AsyncifyAssertInNonInstrumented>( | ||
| &analyzer, pointerType, asyncifyMemory)); | ||
| runner.add( | ||
| std::make_unique<AsyncifyAssertUnwindCorrectness>(&analyzer, module)); | ||
| } | ||
| runner.setIsNested(true); | ||
| runner.setValidateGlobally(false); | ||
| runner.run(); | ||
|
|
@@ -1786,7 +1890,7 @@ struct Asyncify : public Pass { | |
| } | ||
| // Finally, add function support (that should not have been seen by | ||
| // the previous passes). | ||
| addFunctions(module); | ||
| addFunctions(module, asserts); | ||
| } | ||
|
|
||
| private: | ||
|
|
@@ -1814,14 +1918,14 @@ struct Asyncify : public Pass { | |
| module->addGlobal(std::move(asyncifyData)); | ||
| } | ||
|
|
||
| void addFunctions(Module* module) { | ||
| void addFunctions(Module* module, bool asserts) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New param seems unused? |
||
| Builder builder(*module); | ||
| auto makeFunction = [&](Name name, bool setData, State state) { | ||
| auto* body = builder.makeBlock(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why move this? |
||
| std::vector<Type> params; | ||
| if (setData) { | ||
| params.push_back(pointerType); | ||
| } | ||
| auto* body = builder.makeBlock(); | ||
| body->list.push_back(builder.makeGlobalSet( | ||
| ASYNCIFY_STATE, builder.makeConst(int32_t(state)))); | ||
| if (setData) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.