Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions src/passes/Asyncify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
//
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1316,6 +1341,93 @@ struct AsyncifyAssertInNonInstrumented : public Pass {
Module* module;
};

struct AsyncifyUnwindWalker
: WalkerPass<ExpressionStackWalker<AsyncifyUnwindWalker>> {
Function* function;
Module* module;

// Adds a check for Call that is inside a Catch block (we do not handle
// unwinding there).
template<typename T> void replaceCallWithCheck(T* call) {
auto builder = std::make_unique<Builder>(*module);
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));
}
}

template<typename T> void visitCallLike(T* curr) {
assert(!expressionStack.empty());
// A return_call (curr->isReturn) can be ignored here: It returns first,
// leaving the Catch, before calling.
if (curr->isReturn) {
return;
}
// Go up the stack and see if we are in a Catch.
Index i = expressionStack.size() - 1;
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--;
}
}

void visitCall(Call* curr) { visitCallLike(curr); }

void visitCallRef(CallRef* curr) { visitCallLike(curr); }

void visitCallIndirect(CallIndirect* curr) { visitCallLike(curr); }
};

struct AsyncifyAssertUnwindCorrectness : Pass {
bool isFunctionParallel() override { return true; }

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);
}

void runOnFunction(Module* module_, Function* function) override {
AsyncifyUnwindWalker walker;
walker.function = function;
walker.module = module_;
walker.walk(function->body);
}
};

// Instrument local saving/restoring.
struct AsyncifyLocals : public WalkerPass<PostWalker<AsyncifyLocals>> {
bool isFunctionParallel() override { return true; }
Expand Down Expand Up @@ -1761,6 +1873,8 @@ struct Asyncify : public Pass {
PassRunner runner(module);
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();
Expand Down
Loading
Loading