Skip to content

Commit be0a16d

Browse files
committed
Add partial support for -fwasm-exceptions in Asyncify (#5343)
1 parent 1d97f47 commit be0a16d

File tree

5 files changed

+1942
-10
lines changed

5 files changed

+1942
-10
lines changed

src/passes/Asyncify.cpp

Lines changed: 275 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@
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
//
@@ -239,6 +246,12 @@
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
@@ -317,13 +330,16 @@
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

339355
static const Name ASYNCIFY_STATE = "__asyncify_state";
340356
static 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";
341359
static const Name ASYNCIFY_DATA = "__asyncify_data";
342360
static const Name ASYNCIFY_START_UNWIND = "asyncify_start_unwind";
343361
static 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.
12221448
struct 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

17922025
private:
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

Comments
 (0)