diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/binaryen-c.cpp | 172 | ||||
-rw-r--r-- | src/binaryen-c.h | 31 | ||||
-rw-r--r-- | src/gen-s-parser.inc | 71 | ||||
-rw-r--r-- | src/ir/ExpressionAnalyzer.cpp | 7 | ||||
-rw-r--r-- | src/ir/ExpressionManipulator.cpp | 18 | ||||
-rw-r--r-- | src/ir/ReFinalize.cpp | 9 | ||||
-rw-r--r-- | src/ir/branch-utils.h | 43 | ||||
-rw-r--r-- | src/ir/effects.h | 5 | ||||
-rw-r--r-- | src/ir/type-updating.h | 10 | ||||
-rw-r--r-- | src/ir/utils.h | 8 | ||||
-rw-r--r-- | src/js/binaryen.js-post.js | 48 | ||||
-rw-r--r-- | src/passes/DeadCodeElimination.cpp | 16 | ||||
-rw-r--r-- | src/passes/Precompute.cpp | 4 | ||||
-rw-r--r-- | src/passes/Print.cpp | 120 | ||||
-rw-r--r-- | src/passes/RemoveUnusedNames.cpp | 6 | ||||
-rw-r--r-- | src/wasm-binary.h | 42 | ||||
-rw-r--r-- | src/wasm-builder.h | 48 | ||||
-rw-r--r-- | src/wasm-interpreter.h | 4 | ||||
-rw-r--r-- | src/wasm-s-parser.h | 5 | ||||
-rw-r--r-- | src/wasm-stack.h | 54 | ||||
-rw-r--r-- | src/wasm-traversal.h | 102 | ||||
-rw-r--r-- | src/wasm.h | 50 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 98 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 96 | ||||
-rw-r--r-- | src/wasm/wasm-stack.cpp | 45 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 105 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 56 | ||||
-rw-r--r-- | src/wasm2js.h | 16 |
28 files changed, 1209 insertions, 80 deletions
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 81eff0c8a..5d7566e4a 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -354,6 +354,14 @@ BinaryenExpressionId BinaryenMemoryCopyId(void) { BinaryenExpressionId BinaryenMemoryFillId(void) { return Expression::Id::MemoryFillId; } +BinaryenExpressionId BinaryenTryId(void) { return Expression::Id::TryId; } +BinaryenExpressionId BinaryenThrowId(void) { return Expression::Id::ThrowId; } +BinaryenExpressionId BinaryenRethrowId(void) { + return Expression::Id::RethrowId; +} +BinaryenExpressionId BinaryenBrOnExnId(void) { + return Expression::Id::BrOnExnId; +} BinaryenExpressionId BinaryenPushId(void) { return Expression::Id::PushId; } BinaryenExpressionId BinaryenPopId(void) { return Expression::Id::PopId; } @@ -1591,6 +1599,73 @@ BinaryenExpressionRef BinaryenPop(BinaryenModuleRef module, BinaryenType type) { return static_cast<Expression*>(ret); } +BinaryenExpressionRef BinaryenTry(BinaryenModuleRef module, + BinaryenExpressionRef body, + BinaryenExpressionRef catchBody) { + auto* ret = Builder(*(Module*)module) + .makeTry((Expression*)body, (Expression*)catchBody); + if (tracing) { + traceExpression(ret, "BinaryenTry", body, catchBody); + } + return static_cast<Expression*>(ret); +} + +BinaryenExpressionRef BinaryenThrow(BinaryenModuleRef module, + const char* event, + BinaryenExpressionRef* operands, + BinaryenIndex numOperands) { + std::vector<Expression*> args; + for (BinaryenIndex i = 0; i < numOperands; i++) { + args.push_back((Expression*)operands[i]); + } + auto* ret = Builder(*(Module*)module).makeThrow(event, args); + + if (tracing) { + std::cout << " {\n"; + std::cout << " BinaryenExpressionRef operands[] = { "; + for (BinaryenIndex i = 0; i < numOperands; i++) { + if (i > 0) { + std::cout << ", "; + } + std::cout << "expressions[" << expressions[operands[i]] << "]"; + } + if (numOperands == 0) { + // ensure the array is not empty, otherwise a compiler error on VS + std::cout << "0"; + } + std::cout << " };\n "; + traceExpression( + ret, "BinaryenThrow", StringLit(event), "operands", numOperands); + std::cout << " }\n"; + } + return static_cast<Expression*>(ret); +} + +BinaryenExpressionRef BinaryenRethrow(BinaryenModuleRef module, + BinaryenExpressionRef exnref) { + auto* ret = Builder(*(Module*)module).makeRethrow((Expression*)exnref); + if (tracing) { + traceExpression(ret, "BinaryenRethrow", exnref); + } + return static_cast<Expression*>(ret); +} + +BinaryenExpressionRef BinaryenBrOnExn(BinaryenModuleRef module, + const char* name, + const char* eventName, + BinaryenExpressionRef exnref) { + Module* wasm = (Module*)module; + Event* event = wasm->getEventOrNull(eventName); + assert(event && "br_on_exn's event must exist"); + auto* ret = Builder(*wasm).makeBrOnExn(name, event, (Expression*)exnref); + + if (tracing) { + traceExpression( + ret, "BinaryenBrOnExn", StringLit(name), StringLit(eventName), exnref); + } + return static_cast<Expression*>(ret); +} + // Expression utility BinaryenExpressionId BinaryenExpressionGetId(BinaryenExpressionRef expr) { @@ -2721,6 +2796,7 @@ BinaryenExpressionRef BinaryenMemoryFillGetSize(BinaryenExpressionRef expr) { assert(expression->is<MemoryFill>()); return static_cast<MemoryFill*>(expression)->size; } +// Push BinaryenExpressionRef BinaryenPushGetValue(BinaryenExpressionRef expr) { if (tracing) { std::cout << " BinaryenPushGetValue(expressions[" << expressions[expr] @@ -2731,6 +2807,102 @@ BinaryenExpressionRef BinaryenPushGetValue(BinaryenExpressionRef expr) { assert(expression->is<Push>()); return static_cast<Push*>(expression)->value; } +// Try +BinaryenExpressionRef BinaryenTryGetBody(BinaryenExpressionRef expr) { + if (tracing) { + std::cout << " BinaryenTryGetBody(expressions[" << expressions[expr] + << "]);\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<Try>()); + return static_cast<Try*>(expression)->body; +} +BinaryenExpressionRef BinaryenTryGetCatchBody(BinaryenExpressionRef expr) { + if (tracing) { + std::cout << " BinaryenTryGetCatchBody(expressions[" << expressions[expr] + << "]);\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<Try>()); + return static_cast<Try*>(expression)->catchBody; +} +// Throw +const char* BinaryenThrowGetEvent(BinaryenExpressionRef expr) { + if (tracing) { + std::cout << " BinaryenThrowGetEvent(expressions[" << expressions[expr] + << "]);\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<Throw>()); + return static_cast<Throw*>(expression)->event.c_str(); +} +BinaryenExpressionRef BinaryenThrowGetOperand(BinaryenExpressionRef expr, + BinaryenIndex index) { + if (tracing) { + std::cout << " BinaryenThrowGetOperand(expressions[" << expressions[expr] + << "], " << index << ");\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<Throw>()); + assert(index < static_cast<Throw*>(expression)->operands.size()); + return static_cast<Throw*>(expression)->operands[index]; +} +BinaryenIndex BinaryenThrowGetNumOperands(BinaryenExpressionRef expr) { + if (tracing) { + std::cout << " BinaryenThrowGetNumOperands(expressions[" + << expressions[expr] << "]);\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<Throw>()); + return static_cast<Throw*>(expression)->operands.size(); +} +// Rethrow +BinaryenExpressionRef BinaryenRethrowGetExnref(BinaryenExpressionRef expr) { + if (tracing) { + std::cout << " BinaryenRethrowGetExnref(expressions[" << expressions[expr] + << "]);\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<Rethrow>()); + return static_cast<Rethrow*>(expression)->exnref; +} +// BrOnExn +const char* BinaryenBrOnExnGetEvent(BinaryenExpressionRef expr) { + if (tracing) { + std::cout << " BinaryenBrOnExnGetEvent(expressions[" << expressions[expr] + << "]);\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<BrOnExn>()); + return static_cast<BrOnExn*>(expression)->event.c_str(); +} +const char* BinaryenBrOnExnGetName(BinaryenExpressionRef expr) { + if (tracing) { + std::cout << " BinaryenBrOnExnGetName(expressions[" << expressions[expr] + << "]);\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<BrOnExn>()); + return static_cast<BrOnExn*>(expression)->name.c_str(); +} +BinaryenExpressionRef BinaryenBrOnExnGetExnref(BinaryenExpressionRef expr) { + if (tracing) { + std::cout << " BinaryenBrOnExnGetExnref(expressions[" << expressions[expr] + << "]);\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<BrOnExn>()); + return static_cast<BrOnExn*>(expression)->exnref; +} // Functions diff --git a/src/binaryen-c.h b/src/binaryen-c.h index dc5d50fef..37e8c3204 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -127,6 +127,10 @@ BinaryenExpressionId BinaryenMemoryInitId(void); BinaryenExpressionId BinaryenDataDropId(void); BinaryenExpressionId BinaryenMemoryCopyId(void); BinaryenExpressionId BinaryenMemoryFillId(void); +BinaryenExpressionId BinaryenTryId(void); +BinaryenExpressionId BinaryenThrowId(void); +BinaryenExpressionId BinaryenRethrowId(void); +BinaryenExpressionId BinaryenBrOnExnId(void); BinaryenExpressionId BinaryenPushId(void); BinaryenExpressionId BinaryenPopId(void); @@ -701,6 +705,19 @@ BinaryenExpressionRef BinaryenMemoryFill(BinaryenModuleRef module, BinaryenExpressionRef dest, BinaryenExpressionRef value, BinaryenExpressionRef size); +BinaryenExpressionRef BinaryenTry(BinaryenModuleRef module, + BinaryenExpressionRef body, + BinaryenExpressionRef catchBody); +BinaryenExpressionRef BinaryenThrow(BinaryenModuleRef module, + const char* event, + BinaryenExpressionRef* operands, + BinaryenIndex numOperands); +BinaryenExpressionRef BinaryenRethrow(BinaryenModuleRef module, + BinaryenExpressionRef exnref); +BinaryenExpressionRef BinaryenBrOnExn(BinaryenModuleRef module, + const char* name, + const char* eventName, + BinaryenExpressionRef exnref); BinaryenExpressionRef BinaryenPush(BinaryenModuleRef module, BinaryenExpressionRef value); BinaryenExpressionRef BinaryenPop(BinaryenModuleRef module, BinaryenType type); @@ -855,6 +872,20 @@ BinaryenExpressionRef BinaryenMemoryFillGetDest(BinaryenExpressionRef expr); BinaryenExpressionRef BinaryenMemoryFillGetValue(BinaryenExpressionRef expr); BinaryenExpressionRef BinaryenMemoryFillGetSize(BinaryenExpressionRef expr); +BinaryenExpressionRef BinaryenTryGetBody(BinaryenExpressionRef expr); +BinaryenExpressionRef BinaryenTryGetCatchBody(BinaryenExpressionRef expr); + +const char* BinaryenThrowGetEvent(BinaryenExpressionRef expr); +BinaryenExpressionRef BinaryenThrowGetOperand(BinaryenExpressionRef expr, + BinaryenIndex index); +BinaryenIndex BinaryenThrowGetNumOperands(BinaryenExpressionRef expr); + +BinaryenExpressionRef BinaryenRethrowGetExnref(BinaryenExpressionRef expr); + +const char* BinaryenBrOnExnGetEvent(BinaryenExpressionRef expr); +const char* BinaryenBrOnExnGetName(BinaryenExpressionRef expr); +BinaryenExpressionRef BinaryenBrOnExnGetExnref(BinaryenExpressionRef expr); + BinaryenExpressionRef BinaryenPushGetValue(BinaryenExpressionRef expr); // Functions diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 3fcb18000..15a73cdf2 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -25,6 +25,9 @@ switch (op[0]) { case 'i': if (strcmp(op, "br_if") == 0) { return makeBreak(s); } goto parse_error; + case 'o': + if (strcmp(op, "br_on_exn") == 0) { return makeBrOnExn(s); } + goto parse_error; case 't': if (strcmp(op, "br_table") == 0) { return makeBreakTable(s); } goto parse_error; @@ -38,12 +41,20 @@ switch (op[0]) { } } case 'c': { - switch (op[4]) { - case '\0': - if (strcmp(op, "call") == 0) { return makeCall(s, /*isReturn=*/false); } - goto parse_error; - case '_': - if (strcmp(op, "call_indirect") == 0) { return makeCallIndirect(s, /*isReturn=*/false); } + switch (op[2]) { + case 'l': { + switch (op[4]) { + case '\0': + if (strcmp(op, "call") == 0) { return makeCall(s, /*isReturn=*/false); } + goto parse_error; + case '_': + if (strcmp(op, "call_indirect") == 0) { return makeCallIndirect(s, /*isReturn=*/false); } + goto parse_error; + default: goto parse_error; + } + } + case 't': + if (strcmp(op, "catch") == 0) { return makeCatch(s); } goto parse_error; default: goto parse_error; } @@ -2237,18 +2248,26 @@ switch (op[0]) { if (strcmp(op, "push") == 0) { return makePush(s); } goto parse_error; case 'r': { - switch (op[6]) { - case '\0': - if (strcmp(op, "return") == 0) { return makeReturn(s); } + switch (op[3]) { + case 'h': + if (strcmp(op, "rethrow") == 0) { return makeRethrow(s); } goto parse_error; - case '_': { - switch (op[11]) { + case 'u': { + switch (op[6]) { case '\0': - if (strcmp(op, "return_call") == 0) { return makeCall(s, /*isReturn=*/true); } - goto parse_error; - case '_': - if (strcmp(op, "return_call_indirect") == 0) { return makeCallIndirect(s, /*isReturn=*/true); } + if (strcmp(op, "return") == 0) { return makeReturn(s); } goto parse_error; + case '_': { + switch (op[11]) { + case '\0': + if (strcmp(op, "return_call") == 0) { return makeCall(s, /*isReturn=*/true); } + goto parse_error; + case '_': + if (strcmp(op, "return_call_indirect") == 0) { return makeCallIndirect(s, /*isReturn=*/true); } + goto parse_error; + default: goto parse_error; + } + } default: goto parse_error; } } @@ -2258,9 +2277,25 @@ switch (op[0]) { case 's': if (strcmp(op, "select") == 0) { return makeSelect(s); } goto parse_error; - case 't': - if (strcmp(op, "then") == 0) { return makeThenOrElse(s); } - goto parse_error; + case 't': { + switch (op[1]) { + case 'h': { + switch (op[2]) { + case 'e': + if (strcmp(op, "then") == 0) { return makeThenOrElse(s); } + goto parse_error; + case 'r': + if (strcmp(op, "throw") == 0) { return makeThrow(s); } + goto parse_error; + default: goto parse_error; + } + } + case 'r': + if (strcmp(op, "try") == 0) { return makeTry(s); } + goto parse_error; + default: goto parse_error; + } + } case 'u': if (strcmp(op, "unreachable") == 0) { return makeUnreachable(); } goto parse_error; diff --git a/src/ir/ExpressionAnalyzer.cpp b/src/ir/ExpressionAnalyzer.cpp index 99c2798b0..f8f96d5c8 100644 --- a/src/ir/ExpressionAnalyzer.cpp +++ b/src/ir/ExpressionAnalyzer.cpp @@ -211,6 +211,13 @@ template<typename T> void visitImmediates(Expression* curr, T& visitor) { visitor.visitInt(curr->op); visitor.visitNonScopeName(curr->nameOperand); } + void visitTry(Try* curr) {} + void visitThrow(Throw* curr) { visitor.visitNonScopeName(curr->event); } + void visitRethrow(Rethrow* curr) {} + void visitBrOnExn(BrOnExn* curr) { + visitor.visitScopeName(curr->name); + visitor.visitNonScopeName(curr->event); + } void visitNop(Nop* curr) {} void visitUnreachable(Unreachable* curr) {} void visitPush(Push* curr) {} diff --git a/src/ir/ExpressionManipulator.cpp b/src/ir/ExpressionManipulator.cpp index 817dce561..e650f40a7 100644 --- a/src/ir/ExpressionManipulator.cpp +++ b/src/ir/ExpressionManipulator.cpp @@ -219,6 +219,24 @@ flexibleCopy(Expression* original, Module& wasm, CustomCopier custom) { builder.makeHost(curr->op, curr->nameOperand, std::move(operands)); return ret; } + Expression* visitTry(Try* curr) { + return builder.makeTry( + copy(curr->body), copy(curr->catchBody), curr->type); + } + Expression* visitThrow(Throw* curr) { + std::vector<Expression*> operands; + for (Index i = 0; i < curr->operands.size(); i++) { + operands.push_back(copy(curr->operands[i])); + } + return builder.makeThrow(curr->event, std::move(operands)); + } + Expression* visitRethrow(Rethrow* curr) { + return builder.makeRethrow(copy(curr->exnref)); + } + Expression* visitBrOnExn(BrOnExn* curr) { + return builder.makeBrOnExn( + curr->name, curr->event, copy(curr->exnref), curr->eventParams); + } Expression* visitNop(Nop* curr) { return builder.makeNop(); } Expression* visitUnreachable(Unreachable* curr) { return builder.makeUnreachable(); diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 9e3b59c70..05abb76e1 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -62,6 +62,8 @@ void ReFinalize::visitBlock(Block* curr) { auto type = iter->second; if (type == none) { // we need to fix this up. set the values to unreachables + // note that we don't need to handle br_on_exn here, because its value + // type is never none for (auto* br : FindAll<Break>(curr).list) { handleBranchForVisitBlock(br, curr->name, getModule()); } @@ -155,6 +157,13 @@ void ReFinalize::visitSelect(Select* curr) { curr->finalize(); } void ReFinalize::visitDrop(Drop* curr) { curr->finalize(); } void ReFinalize::visitReturn(Return* curr) { curr->finalize(); } void ReFinalize::visitHost(Host* curr) { curr->finalize(); } +void ReFinalize::visitTry(Try* curr) { curr->finalize(); } +void ReFinalize::visitThrow(Throw* curr) { curr->finalize(); } +void ReFinalize::visitRethrow(Rethrow* curr) { curr->finalize(); } +void ReFinalize::visitBrOnExn(BrOnExn* curr) { + curr->finalize(); + updateBreakValueType(curr->name, curr->getSingleSentType()); +} void ReFinalize::visitNop(Nop* curr) { curr->finalize(); } void ReFinalize::visitUnreachable(Unreachable* curr) { curr->finalize(); } void ReFinalize::visitPush(Push* curr) { curr->finalize(); } diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index ce7d7b0f6..976dd72ec 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -37,20 +37,22 @@ inline bool isBranchReachable(Switch* sw) { sw->condition->type != unreachable; } +inline bool isBranchReachable(BrOnExn* br) { + return br->exnref->type != unreachable; +} + inline bool isBranchReachable(Expression* expr) { if (auto* br = expr->dynCast<Break>()) { return isBranchReachable(br); } else if (auto* sw = expr->dynCast<Switch>()) { return isBranchReachable(sw); + } else if (auto* br = expr->dynCast<BrOnExn>()) { + return isBranchReachable(br); } WASM_UNREACHABLE(); } -inline std::set<Name> getUniqueTargets(Break* br) { - std::set<Name> ret; - ret.insert(br->name); - return ret; -} +inline std::set<Name> getUniqueTargets(Break* br) { return {br->name}; } inline std::set<Name> getUniqueTargets(Switch* sw) { std::set<Name> ret; @@ -61,6 +63,8 @@ inline std::set<Name> getUniqueTargets(Switch* sw) { return ret; } +inline std::set<Name> getUniqueTargets(BrOnExn* br) { return {br->name}; } + // If we branch to 'from', change that to 'to' instead. inline bool replacePossibleTarget(Expression* branch, Name from, Name to) { bool worked = false; @@ -80,6 +84,11 @@ inline bool replacePossibleTarget(Expression* branch, Name from, Name to) { sw->default_ = to; worked = true; } + } else if (auto* br = branch->dynCast<BrOnExn>()) { + if (br->name == from) { + br->name = to; + worked = true; + } } else { WASM_UNREACHABLE(); } @@ -99,6 +108,7 @@ inline std::set<Name> getExitingBranches(Expression* ast) { } targets.insert(curr->default_); } + void visitBrOnExn(BrOnExn* curr) { targets.insert(curr->name); } void visitBlock(Block* curr) { if (curr->name.is()) { targets.erase(curr->name); @@ -153,15 +163,15 @@ struct BranchSeeker : public PostWalker<BranchSeeker> { BranchSeeker(Name target) : target(target) {} - void noteFound(Expression* value) { + void noteFound(Expression* value) { noteFound(value ? value->type : none); } + + void noteFound(Type type) { found++; if (found == 1) { valueType = unreachable; } - if (!value) { - valueType = none; - } else if (value->type != unreachable) { - valueType = value->type; + if (type != unreachable) { + valueType = type; } } @@ -202,6 +212,19 @@ struct BranchSeeker : public PostWalker<BranchSeeker> { } } + void visitBrOnExn(BrOnExn* curr) { + if (!named) { + // ignore an unreachable br_on_exn + if (curr->exnref->type == unreachable) { + return; + } + } + // check the br_on_exn + if (curr->name == target) { + noteFound(curr->getSingleSentType()); + } + } + static bool hasReachable(Expression* tree, Name target) { if (!target.is()) { return false; diff --git a/src/ir/effects.h b/src/ir/effects.h index dac5b878a..e3997f5d2 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -374,6 +374,11 @@ struct EffectAnalyzer // Atomics are also sequentially consistent with memory.grow. isAtomic = true; } + void visitTry(Try* curr) {} + // We safely model throws as branches + void visitThrow(Throw* curr) { branches = true; } + void visitRethrow(Rethrow* curr) { branches = true; } + void visitBrOnExn(BrOnExn* curr) { breakNames.insert(curr->name); } void visitNop(Nop* curr) {} void visitUnreachable(Unreachable* curr) { branches = true; } void visitPush(Push* curr) { calls = true; } diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h index ef9fe78e9..09b1b4bdc 100644 --- a/src/ir/type-updating.h +++ b/src/ir/type-updating.h @@ -65,6 +65,8 @@ struct TypeUpdater blockInfos[target]; } blockInfos[sw->default_]; + } else if (auto* br = curr->dynCast<BrOnExn>()) { + blockInfos[br->name]; } // add a break to the info, for break and switch discoverBreaks(curr, +1); @@ -151,6 +153,8 @@ struct TypeUpdater noteBreakChange(br->name, change, br->value); } else if (auto* sw = curr->dynCast<Switch>()) { applySwitchChanges(sw, change); + } else if (auto* br = curr->dynCast<BrOnExn>()) { + noteBreakChange(br->name, change, br->getSingleSentType()); } } @@ -168,6 +172,10 @@ struct TypeUpdater // note the addition of a node void noteBreakChange(Name name, int change, Expression* value) { + noteBreakChange(name, change, value ? value->type : none); + } + + void noteBreakChange(Name name, int change, Type type) { auto iter = blockInfos.find(name); if (iter == blockInfos.end()) { return; // we can ignore breaks to loops @@ -186,7 +194,7 @@ struct TypeUpdater if (block->type != unreachable) { return; // was already reachable, had a fallthrough } - changeTypeTo(block, value ? value->type : none); + changeTypeTo(block, type); } } } diff --git a/src/ir/utils.h b/src/ir/utils.h index ae6368e3b..5c6a09290 100644 --- a/src/ir/utils.h +++ b/src/ir/utils.h @@ -144,6 +144,10 @@ struct ReFinalize void visitDrop(Drop* curr); void visitReturn(Return* curr); void visitHost(Host* curr); + void visitTry(Try* curr); + void visitThrow(Throw* curr); + void visitRethrow(Rethrow* curr); + void visitBrOnExn(BrOnExn* curr); void visitNop(Nop* curr); void visitUnreachable(Unreachable* curr); void visitPush(Push* curr); @@ -203,6 +207,10 @@ struct ReFinalizeNode : public OverriddenVisitor<ReFinalizeNode> { void visitDrop(Drop* curr) { curr->finalize(); } void visitReturn(Return* curr) { curr->finalize(); } void visitHost(Host* curr) { curr->finalize(); } + void visitTry(Try* curr) { curr->finalize(); } + void visitThrow(Throw* curr) { curr->finalize(); } + void visitRethrow(Rethrow* curr) { curr->finalize(); } + void visitBrOnExn(BrOnExn* curr) { curr->finalize(); } void visitNop(Nop* curr) { curr->finalize(); } void visitUnreachable(Unreachable* curr) { curr->finalize(); } void visitPush(Push* curr) { curr->finalize(); } diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index e08d3cf4d..a09ad76ab 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -78,6 +78,10 @@ Module['MemoryInitId'] = Module['_BinaryenMemoryInitId'](); Module['DataDropId'] = Module['_BinaryenDataDropId'](); Module['MemoryCopyId'] = Module['_BinaryenMemoryCopyId'](); Module['MemoryFillId'] = Module['_BinaryenMemoryFillId'](); +Module['TryId'] = Module['_BinaryenTryId'](); +Module['ThrowId'] = Module['_BinaryenThrowId'](); +Module['RethrowId'] = Module['_BinaryenRethrowId'](); +Module['BrOnExnId'] = Module['_BinaryenBrOnExnId'](); Module['PushId'] = Module['_BinaryenPushId'](); Module['PopId'] = Module['_BinaryenPopId'](); @@ -1771,6 +1775,22 @@ function wrapModule(module, self) { self['notify'] = function(ptr, notifyCount) { return Module['_BinaryenAtomicNotify'](module, ptr, notifyCount); }; + self['try'] = function(body, catchBody) { + return Module['_BinaryenTry'](module, body, catchBody); + }; + self['throw'] = function(event_, operands) { + return preserveStack(function() { + return Module['_BinaryenThrow'](module, strToStack(event_), i32sToStack(operands), operands.length); + }); + }; + self['rethrow'] = function(exnref) { + return Module['_BinaryenRethrow'](module, exnref); + }; + self['br_on_exn'] = function(label, event_, exnref) { + return preserveStack(function() { + return Module['_BinaryenBrOnExn'](module, strToStack(label), strToStack(event_), exnref); + }); + }; self['push'] = function(value) { return Module['_BinaryenPush'](module, value); }; @@ -2376,6 +2396,34 @@ Module['getExpressionInfo'] = function(expr) { 'value': Module['_BinaryenMemoryFillGetValue'](expr), 'size': Module['_BinaryenMemoryFillGetSize'](expr) }; + case Module['TryId']: + return { + 'id': id, + 'type': type, + 'body': Module['_BinaryenTryGetBody'](expr), + 'catchBody': Module['_BinaryenTryGetCatchBody'](expr) + }; + case Module['ThrowId']: + return { + 'id': id, + 'type': type, + 'event': UTF8ToString(Module['_BinaryenThrowGetEvent'](expr)), + 'operands': getAllNested(expr, Module['_BinaryenThrowGetNumOperands'], Module['_BinaryenThrowGetOperand']) + }; + case Module['RethrowId']: + return { + 'id': id, + 'type': type, + 'exnref': Module['_BinaryenRethrowGetExnref'](expr) + }; + case Module['BrOnExnId']: + return { + 'id': id, + 'type': type, + 'name': UTF8ToString(Module['_BinaryenBrOnExnGetName'](expr)), + 'event': UTF8ToString(Module['_BinaryenBrOnExnGetEvent'](expr)), + 'exnref': Module['_BinaryenBrOnExnGetExnref'](expr) + }; case Module['PushId']: return { 'id': id, diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp index 61d16afc0..f913c5a93 100644 --- a/src/passes/DeadCodeElimination.cpp +++ b/src/passes/DeadCodeElimination.cpp @@ -144,6 +144,14 @@ struct DeadCodeElimination reachable = false; } + void visitBrOnExn(BrOnExn* curr) { + if (isDead(curr->exnref)) { + replaceCurrent(curr->exnref); + return; + } + addBreak(curr->name); + } + void visitReturn(Return* curr) { if (isDead(curr->value)) { replaceCurrent(curr->value); @@ -312,6 +320,14 @@ struct DeadCodeElimination DELEGATE(Push); case Expression::Id::PopId: DELEGATE(Pop); + case Expression::Id::TryId: + DELEGATE(Try); + case Expression::Id::ThrowId: + DELEGATE(Throw); + case Expression::Id::RethrowId: + DELEGATE(Rethrow); + case Expression::Id::BrOnExnId: + DELEGATE(BrOnExn); case Expression::Id::InvalidId: WASM_UNREACHABLE(); case Expression::Id::NumExpressionIds: diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 55ff9e264..c4d90de04 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -130,6 +130,10 @@ public: Flow visitMemoryCopy(MemoryCopy* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } Flow visitMemoryFill(MemoryFill* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } Flow visitHost(Host* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } + Flow visitTry(Try* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } + Flow visitThrow(Throw* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } + Flow visitRethrow(Rethrow* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } + Flow visitBrOnExn(BrOnExn* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } Flow visitPush(Push* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } Flow visitPop(Pop* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 812a69692..0bf61e784 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -1162,6 +1162,23 @@ struct PrintExpressionContents break; } } + void visitTry(Try* curr) { + printMedium(o, "try"); + if (isConcreteType(curr->type)) { + o << " (result " << printType(curr->type) << ')'; + } + } + void visitThrow(Throw* curr) { + printMedium(o, "throw "); + printName(curr->event, o); + } + void visitRethrow(Rethrow* curr) { printMedium(o, "rethrow"); } + void visitBrOnExn(BrOnExn* curr) { + printMedium(o, "br_on_exn "); + printName(curr->name, o); + o << " "; + printName(curr->event, o); + } void visitNop(Nop* curr) { printMinor(o, "nop"); } void visitUnreachable(Unreachable* curr) { printMinor(o, "unreachable"); } void visitPush(Push* curr) { prepareColor(o) << "push"; } @@ -1260,6 +1277,20 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { o << maybeNewLine; } + // loop, if, and try can contain implicit blocks. But they are not needed to + // be printed in some cases. + void maybePrintImplicitBlock(Expression* curr, bool allowMultipleInsts) { + auto block = curr->dynCast<Block>(); + if (!full && block && block->name.isNull() && + (allowMultipleInsts || block->list.size() == 1)) { + for (auto expression : block->list) { + printFullLine(expression); + } + } else { + printFullLine(curr); + } + } + void visitBlock(Block* curr) { // special-case Block, because Block nesting (in their first element) can be // incredibly deep @@ -1319,22 +1350,9 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { PrintExpressionContents(currFunction, o).visit(curr); incIndent(); printFullLine(curr->condition); - // ifTrue and False have implict blocks, avoid printing them if possible - if (!full && curr->ifTrue->is<Block>() && - curr->ifTrue->dynCast<Block>()->name.isNull() && - curr->ifTrue->dynCast<Block>()->list.size() == 1) { - printFullLine(curr->ifTrue->dynCast<Block>()->list.back()); - } else { - printFullLine(curr->ifTrue); - } + maybePrintImplicitBlock(curr->ifTrue, false); if (curr->ifFalse) { - if (!full && curr->ifFalse->is<Block>() && - curr->ifFalse->dynCast<Block>()->name.isNull() && - curr->ifFalse->dynCast<Block>()->list.size() == 1) { - printFullLine(curr->ifFalse->dynCast<Block>()->list.back()); - } else { - printFullLine(curr->ifFalse); - } + maybePrintImplicitBlock(curr->ifFalse, false); } decIndent(); if (full) { @@ -1345,16 +1363,7 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { o << '('; PrintExpressionContents(currFunction, o).visit(curr); incIndent(); - auto block = curr->body->dynCast<Block>(); - if (!full && block && block->name.isNull()) { - // wasm spec has loops containing children directly, while our ast - // has a single child for simplicity. print out the optimal form. - for (auto expression : block->list) { - printFullLine(expression); - } - } else { - printFullLine(curr->body); - } + maybePrintImplicitBlock(curr->body, true); decIndent(); if (full) { o << " ;; end loop"; @@ -1631,6 +1640,54 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { } } } + // try-catch-end is written in the folded wast format as + // (try + // ... + // (catch + // ... + // ) + // ) + // The parenthesis wrapping 'catch' is just a syntax and does not affect + // nested depths of instructions within. + void visitTry(Try* curr) { + o << '('; + PrintExpressionContents(currFunction, o).visit(curr); + incIndent(); + maybePrintImplicitBlock(curr->body, true); + doIndent(o, indent); + o << "(catch"; + incIndent(); + maybePrintImplicitBlock(curr->catchBody, true); + decIndent(); + o << "\n"; + decIndent(); + if (full) { + o << " ;; end try"; + } + } + void visitThrow(Throw* curr) { + o << '('; + PrintExpressionContents(currFunction, o).visit(curr); + incIndent(); + for (auto operand : curr->operands) { + printFullLine(operand); + } + decIndent(); + } + void visitRethrow(Rethrow* curr) { + o << '('; + PrintExpressionContents(currFunction, o).visit(curr); + incIndent(); + printFullLine(curr->exnref); + decIndent(); + } + void visitBrOnExn(BrOnExn* curr) { + o << '('; + PrintExpressionContents(currFunction, o).visit(curr); + incIndent(); + printFullLine(curr->exnref); + decIndent(); + } void visitNop(Nop* curr) { o << '('; PrintExpressionContents(currFunction, o).visit(curr); @@ -2198,7 +2255,8 @@ WasmPrinter::printStackIR(StackIR* ir, std::ostream& o, Function* func) { } case StackInst::BlockBegin: case StackInst::IfBegin: - case StackInst::LoopBegin: { + case StackInst::LoopBegin: + case StackInst::TryBegin: { doIndent(); PrintExpressionContents(func, o).visit(inst->origin); indent++; @@ -2206,7 +2264,8 @@ WasmPrinter::printStackIR(StackIR* ir, std::ostream& o, Function* func) { } case StackInst::BlockEnd: case StackInst::IfEnd: - case StackInst::LoopEnd: { + case StackInst::LoopEnd: + case StackInst::TryEnd: { indent--; doIndent(); o << "end"; @@ -2219,6 +2278,13 @@ WasmPrinter::printStackIR(StackIR* ir, std::ostream& o, Function* func) { indent++; break; } + case StackInst::Catch: { + indent--; + doIndent(); + o << "catch"; + indent++; + break; + } default: WASM_UNREACHABLE(); } diff --git a/src/passes/RemoveUnusedNames.cpp b/src/passes/RemoveUnusedNames.cpp index 3db5a2173..3b04522ad 100644 --- a/src/passes/RemoveUnusedNames.cpp +++ b/src/passes/RemoveUnusedNames.cpp @@ -42,6 +42,8 @@ struct RemoveUnusedNames : public WalkerPass<PostWalker<RemoveUnusedNames>> { branchesSeen[curr->default_].insert(curr); } + void visitBrOnExn(BrOnExn* curr) { branchesSeen[curr->name].insert(curr); } + void handleBreakTarget(Name& name) { if (name.is()) { if (branchesSeen.find(name) == branchesSeen.end()) { @@ -73,6 +75,10 @@ struct RemoveUnusedNames : public WalkerPass<PostWalker<RemoveUnusedNames>> { if (sw->default_ == curr->name) { sw->default_ = child->name; } + } else if (BrOnExn* br = branch->dynCast<BrOnExn>()) { + if (br->name == curr->name) { + br->name = child->name; + } } else { WASM_UNREACHABLE(); } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index f58a10cf7..1efa24b48 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -613,12 +613,14 @@ enum ASTNodes { I64ExtendS16 = 0xc3, I64ExtendS32 = 0xc4, + // prefixes + MiscPrefix = 0xfc, SIMDPrefix = 0xfd, - AtomicPrefix = 0xfe -}; + AtomicPrefix = 0xfe, + + // atomic opcodes -enum AtomicOpcodes { AtomicNotify = 0x00, I32AtomicWait = 0x01, I64AtomicWait = 0x02, @@ -691,10 +693,10 @@ enum AtomicOpcodes { I64AtomicCmpxchg8U = 0x4c, I64AtomicCmpxchg16U = 0x4d, I64AtomicCmpxchg32U = 0x4e, - AtomicCmpxchgOps_End = 0x4e -}; + AtomicCmpxchgOps_End = 0x4e, + + // truncsat opcodes -enum TruncSatOpcodes { I32STruncSatF32 = 0x00, I32UTruncSatF32 = 0x01, I32STruncSatF64 = 0x02, @@ -703,9 +705,9 @@ enum TruncSatOpcodes { I64UTruncSatF32 = 0x05, I64STruncSatF64 = 0x06, I64UTruncSatF64 = 0x07, -}; -enum SIMDOpcodes { + // SIMD opcodes + V128Load = 0x00, V128Store = 0x01, V128Const = 0x02, @@ -845,14 +847,22 @@ enum SIMDOpcodes { F32x4ConvertSI32x4 = 0xaf, F32x4ConvertUI32x4 = 0xb0, F64x2ConvertSI64x2 = 0xb1, - F64x2ConvertUI64x2 = 0xb2 -}; + F64x2ConvertUI64x2 = 0xb2, + + // bulk memory opcodes -enum BulkMemoryOpcodes { MemoryInit = 0x08, DataDrop = 0x09, MemoryCopy = 0x0a, - MemoryFill = 0x0b + MemoryFill = 0x0b, + + // exception handling opcodes + + Try = 0x06, + Catch = 0x07, + Throw = 0x08, + Rethrow = 0x09, + BrOnExn = 0x0a }; enum MemoryAccess { @@ -1184,6 +1194,8 @@ public: void readNextDebugLocation(); void readSourceMapHeader(); + void handleBrOnExnNotTaken(Expression* curr); + // AST reading int depth = 0; // only for debugging @@ -1192,7 +1204,7 @@ public: void visitBlock(Block* curr); // Gets a block of expressions. If it's just one, return that singleton. - Expression* getBlockOrSingleton(Type type); + Expression* getBlockOrSingleton(Type type, unsigned numPops = 0); void visitIf(If* curr); void visitLoop(Loop* curr); @@ -1238,6 +1250,10 @@ public: void visitNop(Nop* curr); void visitUnreachable(Unreachable* curr); void visitDrop(Drop* curr); + void visitTry(Try* curr); + void visitThrow(Throw* curr); + void visitRethrow(Rethrow* curr); + void visitBrOnExn(BrOnExn* curr); void throwError(std::string text); }; diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 923effb76..a0a43bc07 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -453,7 +453,6 @@ public: return ret; } Const* makeConst(Literal value) { - assert(isConcreteType(value.type)); auto* ret = allocator.alloc<Const>(); ret->value = value; ret->type = value.type; @@ -497,6 +496,53 @@ public: ret->finalize(); return ret; } + Try* makeTry(Expression* body, Expression* catchBody) { + auto* ret = allocator.alloc<Try>(); + ret->body = body; + ret->catchBody = catchBody; + ret->finalize(); + return ret; + } + Try* makeTry(Expression* body, Expression* catchBody, Type type) { + auto* ret = allocator.alloc<Try>(); + ret->body = body; + ret->catchBody = catchBody; + ret->finalize(type); + return ret; + } + Throw* makeThrow(Event* event, const std::vector<Expression*>& args) { + return makeThrow(event->name, args); + } + Throw* makeThrow(Name event, const std::vector<Expression*>& args) { + auto* ret = allocator.alloc<Throw>(); + ret->event = event; + ret->operands.set(args); + ret->finalize(); + return ret; + } + Rethrow* makeRethrow(Expression* exnref) { + auto* ret = allocator.alloc<Rethrow>(); + ret->exnref = exnref; + ret->finalize(); + return ret; + } + BrOnExn* makeBrOnExn(Name name, Event* event, Expression* exnref) { + return makeBrOnExn(name, event->name, exnref, event->params); + } + BrOnExn* makeBrOnExn(Name name, + Name event, + Expression* exnref, + std::vector<Type>& eventParams) { + auto* ret = allocator.alloc<BrOnExn>(); + ret->name = name; + ret->event = event; + ret->exnref = exnref; + // Copy params info into BrOnExn, because it is necessary when BrOnExn is + // refinalized without the module. + ret->eventParams = eventParams; + ret->finalize(); + return ret; + } Unreachable* makeUnreachable() { return allocator.alloc<Unreachable>(); } Push* makePush(Expression* value) { auto* ret = allocator.alloc<Push>(); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f683f9b38..53345f0ab 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1048,6 +1048,10 @@ public: Flow visitAtomicNotify(AtomicNotify*) { WASM_UNREACHABLE(); } Flow visitPush(Push*) { WASM_UNREACHABLE(); } Flow visitPop(Pop*) { WASM_UNREACHABLE(); } + Flow visitTry(Try*) { WASM_UNREACHABLE(); } + Flow visitThrow(Throw*) { WASM_UNREACHABLE(); } + Flow visitRethrow(Rethrow*) { WASM_UNREACHABLE(); } + Flow visitBrOnExn(BrOnExn*) { WASM_UNREACHABLE(); } virtual void trap(const char* why) { WASM_UNREACHABLE(); } }; diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index c07cc49a9..d6ec89319 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -221,6 +221,11 @@ private: Expression* makeBreak(Element& s); Expression* makeBreakTable(Element& s); Expression* makeReturn(Element& s); + Expression* makeTry(Element& s); + Expression* makeCatch(Element& s); + Expression* makeThrow(Element& s); + Expression* makeRethrow(Element& s); + Expression* makeBrOnExn(Element& s); // Helper functions Type parseOptionalResultType(Element& s, Index& i); diff --git a/src/wasm-stack.h b/src/wasm-stack.h index 64718b7b2..562a00684 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -67,6 +67,9 @@ public: IfEnd, // the ending of a if LoopBegin, // the beginning of a loop LoopEnd, // the ending of a loop + TryBegin, // the beginning of a try + Catch, // the catch within a try + TryEnd // the ending of a try } op; Expression* origin; // the expression this originates from @@ -115,6 +118,10 @@ public: void visitSelect(Select* curr); void visitReturn(Return* curr); void visitHost(Host* curr); + void visitTry(Try* curr); + void visitThrow(Throw* curr); + void visitRethrow(Rethrow* curr); + void visitBrOnExn(BrOnExn* curr); void visitNop(Nop* curr); void visitUnreachable(Unreachable* curr); void visitDrop(Drop* curr); @@ -122,7 +129,8 @@ public: void visitPop(Pop* curr); void emitIfElse(); - void emitScopeEnd(); // emit an end at the end of a block/loop/if + void emitCatch(); + void emitScopeEnd(); // emit an end at the end of a block/loop/if/try void emitFunctionEnd(); // emit an end at the end of a function void emitUnreachable(); void mapLocalsAndEmitHeader(); @@ -185,6 +193,10 @@ public: void visitSelect(Select* curr); void visitReturn(Return* curr); void visitHost(Host* curr); + void visitTry(Try* curr); + void visitThrow(Throw* curr); + void visitRethrow(Rethrow* curr); + void visitBrOnExn(BrOnExn* curr); void visitNop(Nop* curr); void visitUnreachable(Unreachable* curr); void visitDrop(Drop* curr); @@ -198,6 +210,7 @@ private: void emit(Expression* curr) { static_cast<SubType*>(this)->emit(curr); } void emitHeader() { static_cast<SubType*>(this)->emitHeader(); } void emitIfElse(If* curr) { static_cast<SubType*>(this)->emitIfElse(curr); } + void emitCatch(Try* curr) { static_cast<SubType*>(this)->emitCatch(curr); } void emitScopeEnd(Expression* curr) { static_cast<SubType*>(this)->emitScopeEnd(curr); } @@ -305,7 +318,6 @@ template<typename SubType> void BinaryenIRWriter<SubType>::visitIf(If* curr) { return; } emit(curr); - // TODO: emit block contents directly, if possible visitPossibleBlockContents(curr->ifTrue); if (curr->ifFalse) { @@ -657,6 +669,40 @@ void BinaryenIRWriter<SubType>::visitHost(Host* curr) { emit(curr); } +template<typename SubType> void BinaryenIRWriter<SubType>::visitTry(Try* curr) { + emit(curr); + visitPossibleBlockContents(curr->body); + emitCatch(curr); + visitPossibleBlockContents(curr->catchBody); + emitScopeEnd(curr); + if (curr->type == unreachable) { + emitUnreachable(); + } +} + +template<typename SubType> +void BinaryenIRWriter<SubType>::visitThrow(Throw* curr) { + for (auto* operand : curr->operands) { + visit(operand); + } + emit(curr); +} + +template<typename SubType> +void BinaryenIRWriter<SubType>::visitRethrow(Rethrow* curr) { + visit(curr->exnref); + emit(curr); +} + +template<typename SubType> +void BinaryenIRWriter<SubType>::visitBrOnExn(BrOnExn* curr) { + visit(curr->exnref); + emit(curr); + if (curr->type == unreachable) { + emitUnreachable(); + } +} + template<typename SubType> void BinaryenIRWriter<SubType>::visitNop(Nop* curr) { emit(curr); } @@ -707,6 +753,7 @@ public: writer.mapLocalsAndEmitHeader(); } void emitIfElse(If* curr) { writer.emitIfElse(); } + void emitCatch(Try* curr) { writer.emitCatch(); } void emitScopeEnd(Expression* curr) { writer.emitScopeEnd(); } void emitFunctionEnd() { if (func->epilogLocation.size()) { @@ -740,6 +787,9 @@ public: void emitIfElse(If* curr) { stackIR.push_back(makeStackInst(StackInst::IfElse, curr)); } + void emitCatch(Try* curr) { + stackIR.push_back(makeStackInst(StackInst::Catch, curr)); + } void emitFunctionEnd() {} void emitUnreachable() { stackIR.push_back(makeStackInst(Builder(allocator).makeUnreachable())); diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index 04ab5f04a..5e9925b63 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -70,6 +70,10 @@ template<typename SubType, typename ReturnType = void> struct Visitor { ReturnType visitDrop(Drop* curr) { return ReturnType(); } ReturnType visitReturn(Return* curr) { return ReturnType(); } ReturnType visitHost(Host* curr) { return ReturnType(); } + ReturnType visitTry(Try* curr) { return ReturnType(); } + ReturnType visitThrow(Throw* curr) { return ReturnType(); } + ReturnType visitRethrow(Rethrow* curr) { return ReturnType(); } + ReturnType visitBrOnExn(BrOnExn* curr) { return ReturnType(); } ReturnType visitNop(Nop* curr) { return ReturnType(); } ReturnType visitUnreachable(Unreachable* curr) { return ReturnType(); } ReturnType visitPush(Push* curr) { return ReturnType(); } @@ -158,6 +162,14 @@ template<typename SubType, typename ReturnType = void> struct Visitor { DELEGATE(Return); case Expression::Id::HostId: DELEGATE(Host); + case Expression::Id::TryId: + DELEGATE(Try); + case Expression::Id::ThrowId: + DELEGATE(Throw); + case Expression::Id::RethrowId: + DELEGATE(Rethrow); + case Expression::Id::BrOnExnId: + DELEGATE(BrOnExn); case Expression::Id::NopId: DELEGATE(Nop); case Expression::Id::UnreachableId: @@ -222,6 +234,10 @@ struct OverriddenVisitor { UNIMPLEMENTED(Drop); UNIMPLEMENTED(Return); UNIMPLEMENTED(Host); + UNIMPLEMENTED(Try); + UNIMPLEMENTED(Throw); + UNIMPLEMENTED(Rethrow); + UNIMPLEMENTED(BrOnExn); UNIMPLEMENTED(Nop); UNIMPLEMENTED(Unreachable); UNIMPLEMENTED(Push); @@ -311,6 +327,14 @@ struct OverriddenVisitor { DELEGATE(Return); case Expression::Id::HostId: DELEGATE(Host); + case Expression::Id::TryId: + DELEGATE(Try); + case Expression::Id::ThrowId: + DELEGATE(Throw); + case Expression::Id::RethrowId: + DELEGATE(Rethrow); + case Expression::Id::BrOnExnId: + DELEGATE(BrOnExn); case Expression::Id::NopId: DELEGATE(Nop); case Expression::Id::UnreachableId: @@ -436,6 +460,18 @@ struct UnifiedExpressionVisitor : public Visitor<SubType, ReturnType> { ReturnType visitHost(Host* curr) { return static_cast<SubType*>(this)->visitExpression(curr); } + ReturnType visitTry(Try* curr) { + return static_cast<SubType*>(this)->visitExpression(curr); + } + ReturnType visitThrow(Throw* curr) { + return static_cast<SubType*>(this)->visitExpression(curr); + } + ReturnType visitRethrow(Rethrow* curr) { + return static_cast<SubType*>(this)->visitExpression(curr); + } + ReturnType visitBrOnExn(BrOnExn* curr) { + return static_cast<SubType*>(this)->visitExpression(curr); + } ReturnType visitNop(Nop* curr) { return static_cast<SubType*>(this)->visitExpression(curr); } @@ -723,6 +759,18 @@ struct Walker : public VisitorType { static void doVisitHost(SubType* self, Expression** currp) { self->visitHost((*currp)->cast<Host>()); } + static void doVisitTry(SubType* self, Expression** currp) { + self->visitTry((*currp)->cast<Try>()); + } + static void doVisitThrow(SubType* self, Expression** currp) { + self->visitThrow((*currp)->cast<Throw>()); + } + static void doVisitRethrow(SubType* self, Expression** currp) { + self->visitRethrow((*currp)->cast<Rethrow>()); + } + static void doVisitBrOnExn(SubType* self, Expression** currp) { + self->visitBrOnExn((*currp)->cast<BrOnExn>()); + } static void doVisitNop(SubType* self, Expression** currp) { self->visitNop((*currp)->cast<Nop>()); } @@ -755,7 +803,6 @@ template<typename SubType, typename VisitorType = Visitor<SubType>> struct PostWalker : public Walker<SubType, VisitorType> { static void scan(SubType* self, Expression** currp) { - Expression* curr = *currp; switch (curr->_id) { case Expression::Id::InvalidId: @@ -961,6 +1008,30 @@ struct PostWalker : public Walker<SubType, VisitorType> { } break; } + case Expression::Id::TryId: { + self->pushTask(SubType::doVisitTry, currp); + self->pushTask(SubType::scan, &curr->cast<Try>()->catchBody); + self->pushTask(SubType::scan, &curr->cast<Try>()->body); + break; + } + case Expression::Id::ThrowId: { + self->pushTask(SubType::doVisitThrow, currp); + auto& list = curr->cast<Throw>()->operands; + for (int i = int(list.size()) - 1; i >= 0; i--) { + self->pushTask(SubType::scan, &list[i]); + } + break; + } + case Expression::Id::RethrowId: { + self->pushTask(SubType::doVisitRethrow, currp); + self->pushTask(SubType::scan, &curr->cast<Rethrow>()->exnref); + break; + } + case Expression::Id::BrOnExnId: { + self->pushTask(SubType::doVisitBrOnExn, currp); + self->pushTask(SubType::scan, &curr->cast<BrOnExn>()->exnref); + break; + } case Expression::Id::NopId: { self->pushTask(SubType::doVisitNop, currp); break; @@ -1196,6 +1267,35 @@ struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> { self->maybePushTask(SubType::scan, &curr->cast<Return>()->value); break; } + case Expression::Id::TryId: { + self->pushTask(SubType::doVisitTry, currp); + self->pushTask(SubType::doNoteNonLinear, currp); + self->pushTask(SubType::scan, &curr->cast<Try>()->catchBody); + self->pushTask(SubType::doNoteNonLinear, currp); + self->pushTask(SubType::scan, &curr->cast<Try>()->body); + break; + } + case Expression::Id::ThrowId: { + self->pushTask(SubType::doVisitThrow, currp); + self->pushTask(SubType::doNoteNonLinear, currp); + auto& list = curr->cast<Throw>()->operands; + for (int i = int(list.size()) - 1; i >= 0; i--) { + self->pushTask(SubType::scan, &list[i]); + } + break; + } + case Expression::Id::RethrowId: { + self->pushTask(SubType::doVisitRethrow, currp); + self->pushTask(SubType::doNoteNonLinear, currp); + self->pushTask(SubType::scan, &curr->cast<Rethrow>()->exnref); + break; + } + case Expression::Id::BrOnExnId: { + self->pushTask(SubType::doVisitBrOnExn, currp); + self->pushTask(SubType::doNoteNonLinear, currp); + self->pushTask(SubType::scan, &curr->cast<BrOnExn>()->exnref); + break; + } case Expression::Id::UnreachableId: { self->pushTask(SubType::doVisitUnreachable, currp); self->pushTask(SubType::doNoteNonLinear, currp); diff --git a/src/wasm.h b/src/wasm.h index 3ac89b91d..d4441259e 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -479,6 +479,10 @@ public: MemoryFillId, PushId, PopId, + TryId, + ThrowId, + RethrowId, + BrOnExnId, NumExpressionIds }; Id _id; @@ -1003,6 +1007,52 @@ public: Pop(MixedArena& allocator) {} }; +class Try : public SpecificExpression<Expression::TryId> { +public: + Try(MixedArena& allocator) {} + + Expression* body; + Expression* catchBody; + + void finalize(); + void finalize(Type type_); +}; + +class Throw : public SpecificExpression<Expression::ThrowId> { +public: + Throw(MixedArena& allocator) : operands(allocator) {} + + Name event; + ExpressionList operands; + + void finalize(); +}; + +class Rethrow : public SpecificExpression<Expression::RethrowId> { +public: + Rethrow(MixedArena& allocator) {} + + Expression* exnref; + + void finalize(); +}; + +class BrOnExn : public SpecificExpression<Expression::BrOnExnId> { +public: + BrOnExn() { type = unreachable; } + BrOnExn(MixedArena& allocator) : BrOnExn() {} + + Name name; + Name event; + Expression* exnref; + // This is duplicate info of param types stored in Event, but this is required + // for us to know the type of the value sent to the target block. + std::vector<Type> eventParams; + + void finalize(); + Type getSingleSentType(); +}; + // Globals struct Importable { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 039cef5e5..7047b674e 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1754,7 +1754,8 @@ void WasmBinaryBuilder::processExpressions() { throwError("unexpected end of input"); } auto peek = input[pos]; - if (peek == BinaryConsts::End || peek == BinaryConsts::Else) { + if (peek == BinaryConsts::End || peek == BinaryConsts::Else || + peek == BinaryConsts::Catch) { if (debug) { std::cerr << "== processExpressions finished with unreachable" << std::endl; @@ -2260,8 +2261,21 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) { break; case BinaryConsts::End: case BinaryConsts::Else: + case BinaryConsts::Catch: curr = nullptr; break; + case BinaryConsts::Try: + visitTry((curr = allocator.alloc<Try>())->cast<Try>()); + break; + case BinaryConsts::Throw: + visitThrow((curr = allocator.alloc<Throw>())->cast<Throw>()); + break; + case BinaryConsts::Rethrow: + visitRethrow((curr = allocator.alloc<Rethrow>())->cast<Rethrow>()); + break; + case BinaryConsts::BrOnExn: + visitBrOnExn((curr = allocator.alloc<BrOnExn>())->cast<BrOnExn>()); + break; case BinaryConsts::AtomicPrefix: { code = static_cast<uint8_t>(getU32LEB()); if (maybeVisitLoad(curr, code, /*isAtomic=*/true)) { @@ -2420,6 +2434,7 @@ void WasmBinaryBuilder::visitBlock(Block* curr) { if (debug) { std::cerr << "zz node: Block" << std::endl; } + // special-case Block and de-recurse nested blocks in their first position, as // that is a common pattern that can be very highly nested. std::vector<Block*> stack; @@ -2467,10 +2482,22 @@ void WasmBinaryBuilder::visitBlock(Block* curr) { } } -Expression* WasmBinaryBuilder::getBlockOrSingleton(Type type) { +// Gets a block of expressions. If it's just one, return that singleton. +// numPops is the number of pop instructions we add before starting to parse the +// block. Can be used when we need to assume certain number of values are on top +// of the stack in the beginning. +Expression* WasmBinaryBuilder::getBlockOrSingleton(Type type, + unsigned numPops) { Name label = getNextLabel(); breakStack.push_back({label, type != none && type != unreachable}); auto start = expressionStack.size(); + + Builder builder(wasm); + for (unsigned i = 0; i < numPops; i++) { + auto* pop = builder.makePop(exnref); + expressionStack.push_back(pop); + } + processExpressions(); size_t end = expressionStack.size(); if (end < start) { @@ -4347,6 +4374,73 @@ void WasmBinaryBuilder::visitDrop(Drop* curr) { curr->finalize(); } +void WasmBinaryBuilder::visitTry(Try* curr) { + if (debug) { + std::cerr << "zz node: Try" << std::endl; + } + // For simplicity of implementation, like if scopes, we create a hidden block + // within each try-body and catch-body, and let branches target those inner + // blocks instead. + curr->type = getType(); + curr->body = getBlockOrSingleton(curr->type); + if (lastSeparator != BinaryConsts::Catch) { + throwError("No catch instruction within a try scope"); + } + curr->catchBody = getBlockOrSingleton(curr->type, 1); + curr->finalize(curr->type); + if (lastSeparator != BinaryConsts::End) { + throwError("try should end with end"); + } +} + +void WasmBinaryBuilder::visitThrow(Throw* curr) { + if (debug) { + std::cerr << "zz node: Throw" << std::endl; + } + auto index = getU32LEB(); + if (index >= wasm.events.size()) { + throwError("bad event index"); + } + auto* event = wasm.events[index].get(); + curr->event = event->name; + size_t num = event->params.size(); + curr->operands.resize(num); + for (size_t i = 0; i < num; i++) { + curr->operands[num - i - 1] = popNonVoidExpression(); + } + curr->finalize(); +} + +void WasmBinaryBuilder::visitRethrow(Rethrow* curr) { + if (debug) { + std::cerr << "zz node: Rethrow" << std::endl; + } + curr->exnref = popNonVoidExpression(); + curr->finalize(); +} + +void WasmBinaryBuilder::visitBrOnExn(BrOnExn* curr) { + if (debug) { + std::cerr << "zz node: BrOnExn" << std::endl; + } + BreakTarget target = getBreakTarget(getU32LEB()); + curr->name = target.name; + auto index = getU32LEB(); + if (index >= wasm.events.size()) { + throwError("bad event index"); + } + curr->event = wasm.events[index]->name; + curr->exnref = popNonVoidExpression(); + + Event* event = wasm.getEventOrNull(curr->event); + assert(event && "br_on_exn's event must exist"); + + // Copy params info into BrOnExn, because it is necessary when BrOnExn is + // refinalized without the module. + curr->eventParams = event->params; + curr->finalize(); +} + void WasmBinaryBuilder::throwError(std::string text) { throw ParseException(text, 0, pos); } diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 485b3251f..e1413d5fd 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -1726,6 +1726,102 @@ Expression* SExpressionWasmBuilder::makeReturn(Element& s) { return ret; } +// try-catch-end is written in the folded wast format as +// (try +// ... +// (catch +// ... +// ) +// ) +// The parenthesis wrapping 'catch' is just a syntax and does not affect nested +// depths of instructions within. +Expression* SExpressionWasmBuilder::makeTry(Element& s) { + auto ret = allocator.alloc<Try>(); + Index i = 1; + Name sName; + if (s[i]->dollared()) { + // the try is labeled + sName = s[i++]->str(); + } else { + sName = "try"; + } + auto label = nameMapper.pushLabelName(sName); + Type type = parseOptionalResultType(s, i); // signature + if (elementStartsWith(*s[i], "catch")) { // empty try body + ret->body = makeNop(); + } else { + ret->body = parseExpression(*s[i++]); + } + if (!elementStartsWith(*s[i], "catch")) { + throw ParseException("catch clause does not exist"); + } + ret->catchBody = makeCatch(*s[i++]); + ret->finalize(type); + nameMapper.popLabelName(label); + // create a break target if we must + if (BranchUtils::BranchSeeker::hasNamed(ret, label)) { + auto* block = allocator.alloc<Block>(); + block->name = label; + block->list.push_back(ret); + block->finalize(ret->type); + return block; + } + return ret; +} + +Expression* SExpressionWasmBuilder::makeCatch(Element& s) { + if (!elementStartsWith(s, "catch")) { + throw ParseException("invalid catch clause", s.line, s.col); + } + auto ret = allocator.alloc<Block>(); + for (size_t i = 1; i < s.size(); i++) { + ret->list.push_back(parseExpression(s[i])); + } + ret->finalize(); + return ret; +} + +Expression* SExpressionWasmBuilder::makeThrow(Element& s) { + auto ret = allocator.alloc<Throw>(); + Index i = 1; + + ret->event = getEventName(*s[i++]); + if (!wasm.getEventOrNull(ret->event)) { + throw ParseException("bad event name", s[1]->line, s[1]->col); + } + for (; i < s.size(); i++) { + ret->operands.push_back(parseExpression(s[i])); + } + ret->finalize(); + return ret; +} + +Expression* SExpressionWasmBuilder::makeRethrow(Element& s) { + auto ret = allocator.alloc<Rethrow>(); + ret->exnref = parseExpression(*s[1]); + ret->finalize(); + return ret; +} + +Expression* SExpressionWasmBuilder::makeBrOnExn(Element& s) { + auto ret = allocator.alloc<BrOnExn>(); + size_t i = 1; + ret->name = getLabel(*s[i++]); + ret->event = getEventName(*s[i++]); + if (!wasm.getEventOrNull(ret->event)) { + throw ParseException("bad event name", s[1]->line, s[1]->col); + } + ret->exnref = parseExpression(s[i]); + + Event* event = wasm.getEventOrNull(ret->event); + assert(event && "br_on_exn's event must exist"); + // Copy params info into BrOnExn, because it is necessary when BrOnExn is + // refinalized without the module. + ret->eventParams = event->params; + ret->finalize(); + return ret; +} + // converts an s-expression string representing binary data into an output // sequence of raw bytes this appends to data, which may already contain // content. diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index b8ec4fa35..d3aba3b8c 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -1396,6 +1396,32 @@ void BinaryInstWriter::visitHost(Host* curr) { o << U32LEB(0); // Reserved flags field } +void BinaryInstWriter::visitTry(Try* curr) { + breakStack.emplace_back(IMPOSSIBLE_CONTINUE); + o << int8_t(BinaryConsts::Try); + o << binaryType(curr->type != unreachable ? curr->type : none); +} + +void BinaryInstWriter::emitCatch() { + assert(!breakStack.empty()); + breakStack.pop_back(); + breakStack.emplace_back(IMPOSSIBLE_CONTINUE); + o << int8_t(BinaryConsts::Catch); +} + +void BinaryInstWriter::visitThrow(Throw* curr) { + o << int8_t(BinaryConsts::Throw) << U32LEB(parent.getEventIndex(curr->event)); +} + +void BinaryInstWriter::visitRethrow(Rethrow* curr) { + o << int8_t(BinaryConsts::Rethrow); +} + +void BinaryInstWriter::visitBrOnExn(BrOnExn* curr) { + o << int8_t(BinaryConsts::BrOnExn) << U32LEB(getBreakIndex(curr->name)) + << U32LEB(parent.getEventIndex(curr->event)); +} + void BinaryInstWriter::visitNop(Nop* curr) { o << int8_t(BinaryConsts::Nop); } void BinaryInstWriter::visitUnreachable(Unreachable* curr) { @@ -1466,12 +1492,18 @@ void BinaryInstWriter::mapLocalsAndEmitHeader() { mappedLocals[i] = index + currLocalsByType[v128] - 1; continue; } + index += numLocalsByType[v128]; + if (type == exnref) { + mappedLocals[i] = index + currLocalsByType[exnref] - 1; + continue; + } WASM_UNREACHABLE(); } // Emit them. o << U32LEB((numLocalsByType[i32] ? 1 : 0) + (numLocalsByType[i64] ? 1 : 0) + (numLocalsByType[f32] ? 1 : 0) + (numLocalsByType[f64] ? 1 : 0) + - (numLocalsByType[v128] ? 1 : 0)); + (numLocalsByType[v128] ? 1 : 0) + + (numLocalsByType[exnref] ? 1 : 0)); if (numLocalsByType[i32]) { o << U32LEB(numLocalsByType[i32]) << binaryType(i32); } @@ -1487,6 +1519,9 @@ void BinaryInstWriter::mapLocalsAndEmitHeader() { if (numLocalsByType[v128]) { o << U32LEB(numLocalsByType[v128]) << binaryType(v128); } + if (numLocalsByType[exnref]) { + o << U32LEB(numLocalsByType[exnref]) << binaryType(exnref); + } } void BinaryInstWriter::emitMemoryAccess(size_t alignment, @@ -1513,6 +1548,8 @@ void StackIRGenerator::emit(Expression* curr) { stackInst = makeStackInst(StackInst::IfBegin, curr); } else if (curr->is<Loop>()) { stackInst = makeStackInst(StackInst::LoopBegin, curr); + } else if (curr->is<Try>()) { + stackInst = makeStackInst(StackInst::TryBegin, curr); } else { stackInst = makeStackInst(curr); } @@ -1527,6 +1564,8 @@ void StackIRGenerator::emitScopeEnd(Expression* curr) { stackInst = makeStackInst(StackInst::IfEnd, curr); } else if (curr->is<Loop>()) { stackInst = makeStackInst(StackInst::LoopEnd, curr); + } else if (curr->is<Try>()) { + stackInst = makeStackInst(StackInst::TryEnd, curr); } else { WASM_UNREACHABLE(); } @@ -1581,6 +1620,10 @@ void StackIRToBinaryWriter::write() { writer.emitIfElse(); break; } + case StackInst::Catch: { + writer.emitCatch(); + break; + } default: WASM_UNREACHABLE(); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 73731d3e5..8eb3341de 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -254,6 +254,7 @@ public: } void noteBreak(Name name, Expression* value, Expression* curr); + void noteBreak(Name name, Type valueType, Expression* curr); void visitBreak(Break* curr); void visitSwitch(Switch* curr); void visitCall(Call* curr); @@ -284,6 +285,10 @@ public: void visitDrop(Drop* curr); void visitReturn(Return* curr); void visitHost(Host* curr); + void visitTry(Try* curr); + void visitThrow(Throw* curr); + void visitRethrow(Rethrow* curr); + void visitBrOnExn(BrOnExn* curr); void visitFunction(Function* curr); // helpers @@ -519,11 +524,15 @@ void FunctionValidator::visitIf(If* curr) { void FunctionValidator::noteBreak(Name name, Expression* value, Expression* curr) { - Type valueType = none; - Index arity = 0; if (value) { - valueType = value->type; - shouldBeUnequal(valueType, none, curr, "breaks must have a valid value"); + shouldBeUnequal(value->type, none, curr, "breaks must have a valid value"); + } + noteBreak(name, value ? value->type : none, curr); +} + +void FunctionValidator::noteBreak(Name name, Type valueType, Expression* curr) { + Index arity = 0; + if (valueType != none) { arity = 1; } auto iter = breakInfos.find(name); @@ -1581,6 +1590,94 @@ void FunctionValidator::visitHost(Host* curr) { } } +void FunctionValidator::visitTry(Try* curr) { + if (curr->type != unreachable) { + shouldBeEqualOrFirstIsUnreachable( + curr->body->type, + curr->type, + curr->body, + "try's type does not match try body's type"); + shouldBeEqualOrFirstIsUnreachable( + curr->catchBody->type, + curr->type, + curr->catchBody, + "try's type does not match catch's body type"); + } + if (isConcreteType(curr->body->type)) { + shouldBeEqualOrFirstIsUnreachable( + curr->catchBody->type, + curr->body->type, + curr->catchBody, + "try's body type must match catch's body type"); + } + if (isConcreteType(curr->catchBody->type)) { + shouldBeEqualOrFirstIsUnreachable( + curr->body->type, + curr->catchBody->type, + curr->body, + "try's body type must match catch's body type"); + } +} + +void FunctionValidator::visitThrow(Throw* curr) { + if (!info.validateGlobally) { + return; + } + shouldBeEqual( + curr->type, unreachable, curr, "throw's type must be unreachable"); + auto* event = getModule()->getEventOrNull(curr->event); + if (!shouldBeTrue(!!event, curr, "throw's event must exist")) { + return; + } + if (!shouldBeTrue(curr->operands.size() == event->params.size(), + curr, + "event's param numbers must match")) { + return; + } + for (size_t i = 0; i < curr->operands.size(); i++) { + if (!shouldBeEqualOrFirstIsUnreachable(curr->operands[i]->type, + event->params[i], + curr->operands[i], + "event param types must match") && + !info.quiet) { + getStream() << "(on argument " << i << ")\n"; + } + } +} + +void FunctionValidator::visitRethrow(Rethrow* curr) { + shouldBeEqual( + curr->type, unreachable, curr, "rethrow's type must be unreachable"); + shouldBeEqual(curr->exnref->type, + exnref, + curr->exnref, + "rethrow's argument must be exnref type"); +} + +void FunctionValidator::visitBrOnExn(BrOnExn* curr) { + Event* event = getModule()->getEventOrNull(curr->event); + shouldBeTrue(event != nullptr, curr, "br_on_exn's event must exist"); + shouldBeTrue(event->params == curr->eventParams, + curr, + "br_on_exn's event params and event's params are different"); + noteBreak(curr->name, curr->getSingleSentType(), curr); + shouldBeTrue(curr->exnref->type == unreachable || + curr->exnref->type == exnref, + curr, + "br_on_exn's argument must be unreachable or exnref type"); + if (curr->exnref->type == unreachable) { + shouldBeTrue(curr->type == unreachable, + curr, + "If exnref argument's type is unreachable, br_on_exn should " + "be unreachable too"); + } else { + shouldBeTrue(curr->type == exnref, + curr, + "br_on_exn's type should be exnref unless its exnref argument " + "is unreachable"); + } +} + void FunctionValidator::visitFunction(Function* curr) { FeatureSet typeFeatures = getFeatures(curr->result); for (auto type : curr->params) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 04abbcd9f..97b60bfb5 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -168,6 +168,14 @@ const char* getExpressionName(Expression* curr) { return "push"; case Expression::Id::PopId: return "pop"; + case Expression::TryId: + return "try"; + case Expression::ThrowId: + return "throw"; + case Expression::RethrowId: + return "rethrow"; + case Expression::BrOnExnId: + return "br_on_exn"; case Expression::Id::NumExpressionIds: WASM_UNREACHABLE(); } @@ -204,6 +212,12 @@ struct TypeSeeker : public PostWalker<TypeSeeker> { } } + void visitBrOnExn(BrOnExn* curr) { + if (curr->name == targetName) { + types.push_back(curr->getSingleSentType()); + } + } + void visitBlock(Block* curr) { if (curr == target) { if (curr->list.size() > 0) { @@ -836,6 +850,48 @@ void Host::finalize() { } } +void Try::finalize() { + if (body->type == catchBody->type) { + type = body->type; + } else if (isConcreteType(body->type) && catchBody->type == unreachable) { + type = body->type; + } else if (isConcreteType(catchBody->type) && body->type == unreachable) { + type = catchBody->type; + } else { + type = none; + } +} + +void Try::finalize(Type type_) { + type = type_; + if (type == none && body->type == unreachable && + catchBody->type == unreachable) { + type = unreachable; + } +} + +void Throw::finalize() { type = unreachable; } + +void Rethrow::finalize() { type = unreachable; } + +void BrOnExn::finalize() { + if (exnref->type == unreachable) { + type = unreachable; + } else { + type = Type::exnref; + } +} + +// br_on_exn's type is exnref, which it pushes onto the stack when it is not +// taken, but the type of the value it pushes onto the stack when it is taken +// should be the event type. So this is the type we 'send' to the block end when +// it is taken. Currently we don't support multi value return from a block, we +// pick the type of the first param from the event. +// TODO Remove this function and generalize event type after multi-value support +Type BrOnExn::getSingleSentType() { + return eventParams.empty() ? none : eventParams.front(); +} + void Push::finalize() { if (value->type == unreachable) { type = unreachable; diff --git a/src/wasm2js.h b/src/wasm2js.h index e032ac26d..25c51f490 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -1872,6 +1872,22 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m, unimplemented(curr); WASM_UNREACHABLE(); } + Ref visitTry(Try* curr) { + unimplemented(curr); + WASM_UNREACHABLE(); + } + Ref visitThrow(Throw* curr) { + unimplemented(curr); + WASM_UNREACHABLE(); + } + Ref visitRethrow(Rethrow* curr) { + unimplemented(curr); + WASM_UNREACHABLE(); + } + Ref visitBrOnExn(BrOnExn* curr) { + unimplemented(curr); + WASM_UNREACHABLE(); + } Ref visitPush(Push* curr) { unimplemented(curr); WASM_UNREACHABLE(); |