From 1254b564b2d36cbf96ef8b8fe0c17fa2fa668ae3 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 18 Feb 2021 04:36:56 +0900 Subject: [PATCH] [EH] Make rethrow's target a try label (#3568) I was previously mistaken about `rethrow`'s argument rule and thought it only counted `catch`'s depth. But it turns out it follows the same rule `delegate`'s label: the immediate argument follows the same rule as when computing branch labels, but it only can target `try` labels (semantically it targets that `try`'s corresponding `catch`); otherwise it will be a validation failure. Unlike `delegate`, `rethrow`'s label denotes not where to rethrow, but which exception to rethrow. For example, ```wasm try $l0 catch ($l0) try $l1 catch ($l1) rethrow $l0 ;; rethrow the exception caught by 'catch ($l0)' end end ``` Refer to this comment for the more detailed informal semantics: https://github.com/WebAssembly/exception-handling/issues/146#issuecomment-777714491 --- This also reverts some of `delegateTarget` -> `exceptionTarget` changes done in #3562 in the validator. Label validation rules apply differently for `delegate` and `rethrow` for try-catch. For example, this is valid: ```wasm try $l0 try delegate $l0 catch ($l0) end ``` But this is NOT valid: ```wasm try $l0 catch ($l0) try delegate $l0 end ``` So `try`'s label should be used within try-catch range (not catch-end range) for `delegate`s. But for the `rethrow` the rule is different. For example, this is valid: ```wasm try $l0 catch ($l0) rethrow $l0 end ``` But this is NOT valid: ```wasm try $l0 rethrow $l0 catch ($l0) end ``` So the `try`'s label should be used within catch-end range instead. --- src/binaryen-c.cpp | 13 +- src/binaryen-c.h | 12 +- src/ir/branch-utils.h | 6 +- src/js/binaryen.js-post.js | 15 +- src/passes/Print.cpp | 2 +- src/wasm-builder.h | 4 +- src/wasm-delegations-fields.h | 2 +- src/wasm-interpreter.h | 12 +- src/wasm.h | 2 +- src/wasm/wasm-binary.cpp | 23 +-- src/wasm/wasm-s-parser.cpp | 29 +--- src/wasm/wasm-stack.cpp | 2 +- src/wasm/wasm-validator.cpp | 36 +++-- test/binaryen.js/exception-handling.js | 10 +- test/binaryen.js/exception-handling.js.txt | 8 +- test/binaryen.js/expressions.js | 10 +- test/binaryen.js/expressions.js.txt | 2 +- test/exception-handling.wast | 119 ++++++++++++-- test/exception-handling.wast.from-wast | 145 +++++++++++++++--- test/exception-handling.wast.fromBinary | 119 ++++++++++++-- ...ption-handling.wast.fromBinary.noDebugInfo | 119 ++++++++++++-- test/passes/code-pushing_all-features.txt | 4 +- test/passes/code-pushing_all-features.wast | 4 +- test/passes/dce_all-features.txt | 12 +- test/passes/dce_all-features.wast | 18 +-- test/passes/dwarf_with_exceptions.bin.txt | 4 +- ...e-stack-ir_print-stack-ir_all-features.txt | 20 +-- ...-stack-ir_print-stack-ir_all-features.wast | 4 +- test/passes/rse_all-features.txt | 12 +- test/passes/rse_all-features.wast | 12 +- test/spec/exception-handling.wast | 64 ++++++-- 31 files changed, 626 insertions(+), 218 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index f4d93afa177..26a9c5801ac 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1248,8 +1248,9 @@ BinaryenExpressionRef BinaryenThrow(BinaryenModuleRef module, } BinaryenExpressionRef BinaryenRethrow(BinaryenModuleRef module, - BinaryenIndex depth) { - return static_cast(Builder(*(Module*)module).makeRethrow(depth)); + const char* target) { + return static_cast( + Builder(*(Module*)module).makeRethrow(target)); } BinaryenExpressionRef BinaryenI31New(BinaryenModuleRef module, @@ -2965,15 +2966,15 @@ BinaryenExpressionRef BinaryenThrowRemoveOperandAt(BinaryenExpressionRef expr, return static_cast(expression)->operands.removeAt(index); } // Rethrow -BinaryenIndex BinaryenRethrowGetDepth(BinaryenExpressionRef expr) { +const char* BinaryenRethrowGetTarget(BinaryenExpressionRef expr) { auto* expression = (Expression*)expr; assert(expression->is()); - return static_cast(expression)->depth; + return static_cast(expression)->target.c_str(); } -void BinaryenRethrowSetDepth(BinaryenExpressionRef expr, BinaryenIndex depth) { +void BinaryenRethrowSetTarget(BinaryenExpressionRef expr, const char* target) { auto* expression = (Expression*)expr; assert(expression->is()); - static_cast(expression)->depth = depth; + static_cast(expression)->target = target; } // TupleMake BinaryenIndex BinaryenTupleMakeGetNumOperands(BinaryenExpressionRef expr) { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 967bb547f78..23bd5fdcc3e 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -818,7 +818,7 @@ BinaryenThrow(BinaryenModuleRef module, BinaryenExpressionRef* operands, BinaryenIndex numOperands); BINARYEN_API BinaryenExpressionRef BinaryenRethrow(BinaryenModuleRef module, - BinaryenIndex depth); + const char* target); BINARYEN_API BinaryenExpressionRef BinaryenTupleMake(BinaryenModuleRef module, BinaryenExpressionRef* operands, @@ -1829,11 +1829,11 @@ BinaryenThrowRemoveOperandAt(BinaryenExpressionRef expr, BinaryenIndex index); // Rethrow -// Gets the depth of a `rethrow` expression. -BINARYEN_API BinaryenIndex BinaryenRethrowGetDepth(BinaryenExpressionRef expr); -// Sets the exception reference expression of a `rethrow` expression. -BINARYEN_API void BinaryenRethrowSetDepth(BinaryenExpressionRef expr, - BinaryenIndex depth); +// Gets the target catch's corresponding try label of a `rethrow` expression. +BINARYEN_API const char* BinaryenRethrowGetTarget(BinaryenExpressionRef expr); +// Sets the target catch's corresponding try label of a `rethrow` expression. +BINARYEN_API void BinaryenRethrowSetTarget(BinaryenExpressionRef expr, + const char* target); // TupleMake diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index 826483b0949..f6c55c5633e 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -83,7 +83,7 @@ void operateOnScopeNameUsesAndSentTypes(Expression* expr, T func) { } else if (auto* br = expr->dynCast()) { func(name, br->getCastType()); } else { - assert(expr->is()); // delegate + assert(expr->is() || expr->is()); // delegate or rethrow } }); } @@ -135,14 +135,14 @@ inline bool replacePossibleTarget(Expression* branch, Name from, Name to) { return worked; } -// Replace all delegate targets within the given AST. +// Replace all delegate/rethrow targets within the given AST. inline void replaceExceptionTargets(Expression* ast, Name from, Name to) { struct Replacer : public PostWalker> { Name from, to; Replacer(Name from, Name to) : from(from), to(to) {} void visitExpression(Expression* curr) { - if (curr->is()) { + if (curr->is() || curr->is()) { operateOnScopeNameUses(curr, [&](Name& name) { if (name == from) { name = to; diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 63be4d446c8..7c53298ad80 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -2154,8 +2154,8 @@ function wrapModule(module, self = {}) { self['throw'] = function(event_, operands) { return preserveStack(() => Module['_BinaryenThrow'](module, strToStack(event_), i32sToStack(operands), operands.length)); }; - self['rethrow'] = function(depth) { - return Module['_BinaryenRethrow'](module, depth); + self['rethrow'] = function(target) { + return Module['_BinaryenRethrow'](module, strToStack(target)); }; self['tuple'] = { @@ -2916,7 +2916,7 @@ Module['getExpressionInfo'] = function(expr) { return { 'id': id, 'type': type, - 'depth': Module['_BinaryenRethrowGetDepth'](expr) + 'target': UTF8ToString(Module['_BinaryenRethrowGetTarget'](expr)) }; case Module['TupleMakeId']: return { @@ -4287,11 +4287,12 @@ Module['Throw'] = makeExpressionWrapper({ }); Module['Rethrow'] = makeExpressionWrapper({ - 'getDepth'(expr) { - return Module['_BinaryenRethrowGetDepth'](expr); + 'getTarget'(expr) { + const target = Module['_BinaryenRethrowGetTarget'](expr); + return target ? UTF8ToString(target) : null; }, - 'setDepth'(expr, depthExpr) { - Module['_BinaryenRethrowSetDepth'](expr, depthExpr); + 'setTarget'(expr, target) { + preserveStack(() => { Module['_BinaryenRethrowSetTarget'](expr, strToStack(target)) }); } }); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index cc4318ba9f0..b067e43d53a 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -1778,7 +1778,7 @@ struct PrintExpressionContents } void visitRethrow(Rethrow* curr) { printMedium(o, "rethrow "); - o << curr->depth; + printName(curr->target, o); } void visitNop(Nop* curr) { printMinor(o, "nop"); } void visitUnreachable(Unreachable* curr) { printMinor(o, "unreachable"); } diff --git a/src/wasm-builder.h b/src/wasm-builder.h index fb67597b9be..2277fed93ad 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -708,9 +708,9 @@ class Builder { ret->finalize(); return ret; } - Rethrow* makeRethrow(Index depth) { + Rethrow* makeRethrow(Name target) { auto* ret = wasm.allocator.alloc(); - ret->depth = depth; + ret->target = target; ret->finalize(); return ret; } diff --git a/src/wasm-delegations-fields.h b/src/wasm-delegations-fields.h index 9677aa4de02..63ff235d712 100644 --- a/src/wasm-delegations-fields.h +++ b/src/wasm-delegations-fields.h @@ -532,7 +532,7 @@ switch (DELEGATE_ID) { } case Expression::Id::RethrowId: { DELEGATE_START(Rethrow); - DELEGATE_FIELD_INT(Rethrow, depth); + DELEGATE_FIELD_SCOPE_NAME_USE(Rethrow, target); DELEGATE_END(Rethrow); break; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 05135bfb312..f880e5c5ae0 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2402,7 +2402,8 @@ template class ModuleInstanceBase { : public ExpressionRunner { ModuleInstanceBase& instance; FunctionScope& scope; - SmallVector exceptionStack; + // Stack of + SmallVector, 4> exceptionStack; public: RuntimeExpressionRunner(ModuleInstanceBase& instance, @@ -3048,7 +3049,7 @@ template class ModuleInstanceBase { auto processCatchBody = [&](Expression* catchBody) { // Push the current exception onto the exceptionStack in case // 'rethrow's use it - exceptionStack.push_back(e); + exceptionStack.push_back(std::make_pair(e, curr->name)); // We need to pop exceptionStack in either case: when the catch body // exits normally or when a new exception is thrown Flow ret; @@ -3076,8 +3077,11 @@ template class ModuleInstanceBase { } } Flow visitRethrow(Rethrow* curr) { - assert(exceptionStack.size() > curr->depth); - throwException(exceptionStack[exceptionStack.size() - 1 - curr->depth]); + for (int i = exceptionStack.size() - 1; i >= 0; i--) { + if (exceptionStack[i].second == curr->target) { + throwException(exceptionStack[i].first); + } + } WASM_UNREACHABLE("rethrow"); } Flow visitPop(Pop* curr) { diff --git a/src/wasm.h b/src/wasm.h index c2df1616ecd..19925792527 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1335,7 +1335,7 @@ class Rethrow : public SpecificExpression { public: Rethrow(MixedArena& allocator) {} - Index depth; + Name target; void finalize(); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index c6ed1dcfbcf..b843aad0363 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3475,12 +3475,12 @@ Name WasmBinaryBuilder::getExceptionTargetName(int32_t offset) { } size_t index = breakStack.size() - 1 - offset; if (index > breakStack.size()) { - throwError("bad delegate index (high)"); + throwError("bad try index (high)"); } - BYN_TRACE("delegate target " << breakStack[index].name << std::endl); + BYN_TRACE("exception target " << breakStack[index].name << std::endl); auto& ret = breakStack[index]; - // if the delegate is in literally unreachable code, then we will not emit it - // anyhow, so do not note that the target has delegate to it + // if the delegate/rethrow is in literally unreachable code, then we will not + // emit it anyhow, so do not note that the target has a reference to it if (!willBeIgnored) { exceptionTargetNames.insert(ret.name); } @@ -5835,11 +5835,12 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) { curr->delegateTarget = getExceptionTargetName(getU32LEB()); } - // For simplicity, we make try's labels only can be targeted by delegates, and - // delegates can only target try's labels. (If they target blocks or loops, it - // is a validation failure.) Because we create an inner block within each try - // and catch body, if any delegate targets those inner blocks, we should make - // them target the try's label instead. + // For simplicity, we ensure that try's labels can only be targeted by + // delegates and rethrows, and delegates/rethrows can only target try's + // labels. (If they target blocks or loops, it is a validation failure.) + // Because we create an inner block within each try and catch body, if any + // delegate/rethrow targets those inner blocks, we should make them target the + // try's label instead. curr->name = getNextLabel(); if (auto* block = curr->body->dynCast()) { if (block->name.is()) { @@ -5936,7 +5937,9 @@ void WasmBinaryBuilder::visitThrow(Throw* curr) { void WasmBinaryBuilder::visitRethrow(Rethrow* curr) { BYN_TRACE("zz node: Rethrow\n"); - curr->depth = getU32LEB(); + curr->target = getExceptionTargetName(getU32LEB()); + // This special target is valid only for delegates + assert(curr->target != DELEGATE_CALLER_TARGET); curr->finalize(); } diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 0ff4bfb9f17..13b76801bf2 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -2067,8 +2067,8 @@ Expression* SExpressionWasmBuilder::makeTry(Element& s) { // We create a different name for the wrapping block, because try's name can // be used by internal delegates block->name = nameMapper.pushLabelName(sName); - // For simplicity, try's name canonly be targeted by delegates. Make the - // branches target the new wrapping block instead. + // For simplicity, try's name can only be targeted by delegates and + // rethrows. Make the branches target the new wrapping block instead. BranchUtils::replaceBranchTargets(ret, ret->name, block->name); block->list.push_back(ret); nameMapper.popLabelName(block->name); @@ -2078,29 +2078,6 @@ Expression* SExpressionWasmBuilder::makeTry(Element& s) { return ret; } -Expression* -SExpressionWasmBuilder::makeTryOrCatchBody(Element& s, Type type, bool isTry) { - if (isTry && !elementStartsWith(s, "do")) { - throw ParseException("invalid try do clause", s.line, s.col); - } - if (!isTry && !elementStartsWith(s, "catch") && - !elementStartsWith(s, "catch_all")) { - throw ParseException("invalid catch clause", s.line, s.col); - } - if (s.size() == 1) { // (do) / (catch) / (catch_all) without instructions - return makeNop(); - } - auto ret = allocator.alloc(); - for (size_t i = 1; i < s.size(); i++) { - ret->list.push_back(parseExpression(s[i])); - } - if (ret->list.size() == 1) { - return ret->list[0]; - } - ret->finalize(type); - return ret; -} - Expression* SExpressionWasmBuilder::makeThrow(Element& s) { auto ret = allocator.alloc(); Index i = 1; @@ -2118,7 +2095,7 @@ Expression* SExpressionWasmBuilder::makeThrow(Element& s) { Expression* SExpressionWasmBuilder::makeRethrow(Element& s) { auto ret = allocator.alloc(); - ret->depth = atoi(s[1]->str().c_str()); + ret->target = getLabel(*s[1], LabelType::Exception); ret->finalize(); return ret; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index d36c2386cde..b6c0392ca04 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -1936,7 +1936,7 @@ void BinaryInstWriter::visitThrow(Throw* curr) { } void BinaryInstWriter::visitRethrow(Rethrow* curr) { - o << int8_t(BinaryConsts::Rethrow) << U32LEB(curr->depth); + o << int8_t(BinaryConsts::Rethrow) << U32LEB(getBreakIndex(curr->target)); } void BinaryInstWriter::visitNop(Nop* curr) { o << int8_t(BinaryConsts::Nop); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 736d669fbf5..6bcdb0e7c89 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -236,7 +236,8 @@ struct FunctionValidator : public WalkerPass> { }; std::unordered_map breakInfos; - std::unordered_set exceptionTargetNames; + std::unordered_set delegateTargetNames; + std::unordered_set rethrowTargetNames; std::set returnTypes; // types used in returns @@ -280,7 +281,7 @@ struct FunctionValidator : public WalkerPass> { static void visitPreTry(FunctionValidator* self, Expression** currp) { auto* curr = (*currp)->cast(); if (curr->name.is()) { - self->exceptionTargetNames.insert(curr->name); + self->delegateTargetNames.insert(curr->name); } } @@ -300,7 +301,8 @@ struct FunctionValidator : public WalkerPass> { static void visitPreCatch(FunctionValidator* self, Expression** currp) { auto* curr = (*currp)->cast(); if (curr->name.is()) { - self->exceptionTargetNames.erase(curr->name); + self->delegateTargetNames.erase(curr->name); + self->rethrowTargetNames.insert(curr->name); } } @@ -376,7 +378,8 @@ struct FunctionValidator : public WalkerPass> { void visitRefIs(RefIs* curr); void visitRefFunc(RefFunc* curr); void visitRefEq(RefEq* curr); - void noteException(Name name, Expression* curr); + void noteDelegate(Name name, Expression* curr); + void noteRethrow(Name name, Expression* curr); void visitTry(Try* curr); void visitThrow(Throw* curr); void visitRethrow(Rethrow* curr); @@ -2073,14 +2076,20 @@ void FunctionValidator::visitRefEq(RefEq* curr) { "ref.eq's right argument should be a subtype of eqref"); } -void FunctionValidator::noteException(Name name, Expression* curr) { +void FunctionValidator::noteDelegate(Name name, Expression* curr) { if (name != DELEGATE_CALLER_TARGET) { - shouldBeTrue(exceptionTargetNames.find(name) != exceptionTargetNames.end(), + shouldBeTrue(delegateTargetNames.count(name) != 0, curr, "all delegate targets must be valid"); } } +void FunctionValidator::noteRethrow(Name name, Expression* curr) { + shouldBeTrue(rethrowTargetNames.count(name) != 0, + curr, + "all rethrow targets must be valid"); +} + void FunctionValidator::visitTry(Try* curr) { shouldBeTrue(getModule()->features.hasExceptionHandling(), curr, @@ -2122,8 +2131,10 @@ void FunctionValidator::visitTry(Try* curr) { "try should have either catches or a delegate"); if (curr->isDelegate()) { - noteException(curr->delegateTarget, curr); + noteDelegate(curr->delegateTarget, curr); } + + rethrowTargetNames.erase(curr->name); } void FunctionValidator::visitThrow(Throw* curr) { @@ -2167,8 +2178,7 @@ void FunctionValidator::visitRethrow(Rethrow* curr) { Type(Type::unreachable), curr, "rethrow's type must be unreachable"); - // TODO Validate the depth field. The current LLVM toolchain only generates - // depth 0 for C++ support. + noteRethrow(curr->target, curr); } void FunctionValidator::visitTupleMake(TupleMake* curr) { @@ -2531,11 +2541,9 @@ void FunctionValidator::visitFunction(Function* curr) { "function result must match, if function has returns"); } - shouldBeTrue( - breakInfos.empty(), curr->body, "all named break targets must exist"); - shouldBeTrue(exceptionTargetNames.empty(), - curr->body, - "all named delegate targets must exist"); + assert(breakInfos.empty()); + assert(delegateTargetNames.empty()); + assert(rethrowTargetNames.empty()); returnTypes.clear(); labelNames.clear(); // validate optional local names diff --git a/test/binaryen.js/exception-handling.js b/test/binaryen.js/exception-handling.js index 40dc9d489c2..a8d3dd3a40c 100644 --- a/test/binaryen.js/exception-handling.js +++ b/test/binaryen.js/exception-handling.js @@ -3,7 +3,7 @@ function cleanInfo(info) { for (var x in info) { // Filter out address pointers and only print meaningful info if (x == 'id' || x == 'type' || x == 'name' || x == 'event' || - x == 'depth' || x == 'hasCatchAll' || x == 'delegateTarget' || + x == 'target' || x == 'hasCatchAll' || x == 'delegateTarget' || x == 'isDelegate') { ret[x] = info[x]; } @@ -21,19 +21,19 @@ module.setFeatures(binaryen.Features.ReferenceTypes | var event_ = module.addEvent("e", 0, binaryen.i32, binaryen.none); -// (try +// (try $l0 // (do // (throw $e (i32.const 0)) // ) // (catch // (drop (pop i32)) -// (rethrow 0) +// (rethrow $l0) // ) // ) var throw_ = module.throw("e", [module.i32.const(0)]); -var rethrow = module.rethrow(0); +var rethrow = module.rethrow("l0"); var try_catch = module.try( - '', + "l0", throw_, ["e"], [ diff --git a/test/binaryen.js/exception-handling.js.txt b/test/binaryen.js/exception-handling.js.txt index 350d543e5db..434546126ac 100644 --- a/test/binaryen.js/exception-handling.js.txt +++ b/test/binaryen.js/exception-handling.js.txt @@ -3,7 +3,7 @@ (type $i32_=>_none (func (param i32))) (event $e (attr 0) (param i32)) (func $test - (try + (try $l0 (do (throw $e (i32.const 0) @@ -13,7 +13,7 @@ (drop (pop i32) ) - (rethrow 0) + (rethrow $l0) ) ) (try $try_outer @@ -35,6 +35,6 @@ ) getExpressionInfo(throw) = {"id":48,"type":1,"event":"e"} -getExpressionInfo(rethrow) = {"id":49,"type":1,"depth":0} -getExpressionInfo(try_catch) = {"id":47,"type":1,"name":"","hasCatchAll":0,"delegateTarget":"","isDelegate":0} +getExpressionInfo(rethrow) = {"id":49,"type":1,"target":"l0"} +getExpressionInfo(try_catch) = {"id":47,"type":1,"name":"l0","hasCatchAll":0,"delegateTarget":"","isDelegate":0} getExpressionInfo(try_delegate) = {"id":47,"type":0,"name":"try_outer","hasCatchAll":1,"delegateTarget":"","isDelegate":0} diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js index 688321675b1..1ffd453628e 100644 --- a/test/binaryen.js/expressions.js +++ b/test/binaryen.js/expressions.js @@ -1585,14 +1585,14 @@ console.log("# Rethrow"); (function testRethrow() { const module = new binaryen.Module(); - const theRethrow = binaryen.Rethrow(module.rethrow(0)); + const theRethrow = binaryen.Rethrow(module.rethrow("l0")); assert(theRethrow instanceof binaryen.Rethrow); assert(theRethrow instanceof binaryen.Expression); - assert(theRethrow.depth === 0); + assert(theRethrow.target === "l0"); assert(theRethrow.type === binaryen.unreachable); - theRethrow.depth = 1 - assert(theRethrow.depth === 1); + theRethrow.target = "l1"; + assert(theRethrow.target === "l1"); theRethrow.type = binaryen.f64; theRethrow.finalize(); assert(theRethrow.type === binaryen.unreachable); @@ -1601,7 +1601,7 @@ console.log("# Rethrow"); assert( theRethrow.toText() == - "(rethrow 1)\n" + "(rethrow $l1)\n" ); module.dispose(); diff --git a/test/binaryen.js/expressions.js.txt b/test/binaryen.js/expressions.js.txt index 8467c55c053..1efbd9e1397 100644 --- a/test/binaryen.js/expressions.js.txt +++ b/test/binaryen.js/expressions.js.txt @@ -297,7 +297,7 @@ ) # Rethrow -(rethrow 1) +(rethrow $l1) # TupleMake (tuple.make diff --git a/test/exception-handling.wast b/test/exception-handling.wast index 399fef14c3d..45cb21910c6 100644 --- a/test/exception-handling.wast +++ b/test/exception-handling.wast @@ -130,17 +130,6 @@ ) ) ) - - ;; rethrow - (try - (do - (throw $e-i32 (i32.const 0)) - ) - (catch $e-i32 - (drop (pop i32)) - (rethrow 0) - ) - ) ) (func $delegate-test @@ -200,4 +189,112 @@ (delegate 0) ) ) + + (func $rethrow-test + ;; Simple try-catch-rethrow + (try $l0 + (do + (call $foo) + ) + (catch $e-i32 + (drop (pop i32)) + (rethrow $l0) ;; by label + ) + (catch_all + (rethrow 0) ;; by depth + ) + ) + + ;; When there are both a branch and a rethrow that target the same try + ;; label. Because binaryen only allows blocks and loops to be targetted by + ;; branches, we wrap the try with a block and make branches that block + ;; instead, resulting in the br and rethrow target different labels in the + ;; output. + (try $l0 + (do + (call $foo) + ) + (catch $e-i32 + (drop (pop i32)) + (rethrow $l0) + ) + (catch_all + (br $l0) + ) + ) + + ;; One more level deep + (try $l0 + (do + (call $foo) + ) + (catch_all + (try + (do + (call $foo) + ) + (catch $e-i32 + (drop (pop i32)) + (rethrow $l0) ;; by label + ) + (catch_all + (rethrow 1) ;; by depth + ) + ) + ) + ) + + ;; Interleaving block + (try $l0 + (do + (call $foo) + ) + (catch_all + (try + (do + (call $foo) + ) + (catch $e-i32 + (drop (pop i32)) + (block $b0 + (rethrow $l0) ;; by label + ) + ) + (catch_all + (block $b1 + (rethrow 2) ;; by depth + ) + ) + ) + ) + ) + + ;; Within nested try, but rather in 'try' part and not 'catch' + (try $l0 + (do + (call $foo) + ) + (catch_all + (try + (do + (rethrow $l0) ;; by label + ) + (catch_all) + ) + ) + ) + (try $l0 + (do + (call $foo) + ) + (catch_all + (try + (do + (rethrow 1) ;; by depth + ) + (catch_all) + ) + ) + ) + ) ) diff --git a/test/exception-handling.wast.from-wast b/test/exception-handling.wast.from-wast index 5a0a1249d2d..c397edcd86e 100644 --- a/test/exception-handling.wast.from-wast +++ b/test/exception-handling.wast.from-wast @@ -169,19 +169,6 @@ ) ) ) - (try $try10 - (do - (throw $e-i32 - (i32.const 0) - ) - ) - (catch $e-i32 - (drop - (pop i32) - ) - (rethrow 0) - ) - ) ) (func $delegate-test (try $l0 @@ -192,7 +179,7 @@ ) (delegate $l0) ) - (try $try11 + (try $try10 (do (call $foo) ) @@ -203,24 +190,24 @@ (nop) ) ) - (block $l015 - (try $l012 + (block $l014 + (try $l011 (do - (try $try13 + (try $try12 (do - (br_if $l015 + (br_if $l014 (i32.const 1) ) ) - (delegate $l012) + (delegate $l011) ) - (try $try14 + (try $try13 (do - (br_if $l015 + (br_if $l014 (i32.const 1) ) ) - (delegate $l012) + (delegate $l011) ) ) (catch_all @@ -228,16 +215,124 @@ ) ) ) - (try $l016 + (try $l015 (do - (try $try17 + (try $try16 (do (call $foo) ) - (delegate $l016) + (delegate $l015) ) ) (delegate 0) ) ) + (func $rethrow-test + (try $l0 + (do + (call $foo) + ) + (catch $e-i32 + (drop + (pop i32) + ) + (rethrow $l0) + ) + (catch_all + (rethrow $l0) + ) + ) + (block $l018 + (try $l017 + (do + (call $foo) + ) + (catch $e-i32 + (drop + (pop i32) + ) + (rethrow $l017) + ) + (catch_all + (br $l018) + ) + ) + ) + (try $l019 + (do + (call $foo) + ) + (catch_all + (try $try + (do + (call $foo) + ) + (catch $e-i32 + (drop + (pop i32) + ) + (rethrow $l019) + ) + (catch_all + (rethrow $l019) + ) + ) + ) + ) + (try $l020 + (do + (call $foo) + ) + (catch_all + (try $try21 + (do + (call $foo) + ) + (catch $e-i32 + (drop + (pop i32) + ) + (block $b0 + (rethrow $l020) + ) + ) + (catch_all + (block $b1 + (rethrow $l020) + ) + ) + ) + ) + ) + (try $l022 + (do + (call $foo) + ) + (catch_all + (try $try23 + (do + (rethrow $l022) + ) + (catch_all + (nop) + ) + ) + ) + ) + (try $l024 + (do + (call $foo) + ) + (catch_all + (try $try25 + (do + (rethrow $l024) + ) + (catch_all + (nop) + ) + ) + ) + ) + ) ) diff --git a/test/exception-handling.wast.fromBinary b/test/exception-handling.wast.fromBinary index d6dd331b13a..89d0bb7fb08 100644 --- a/test/exception-handling.wast.fromBinary +++ b/test/exception-handling.wast.fromBinary @@ -194,19 +194,6 @@ ) ) ) - (try $label$37 - (do - (throw $event$0 - (i32.const 0) - ) - ) - (catch $event$0 - (drop - (pop i32) - ) - (rethrow 0) - ) - ) ) (func $delegate-test (try $label$9 @@ -269,5 +256,111 @@ (delegate 0) ) ) + (func $rethrow-test + (try $label$3 + (do + (call $foo) + ) + (catch $event$0 + (drop + (pop i32) + ) + (rethrow $label$3) + ) + (catch_all + (rethrow $label$3) + ) + ) + (block $label$4 + (try $label$7 + (do + (call $foo) + ) + (catch $event$0 + (drop + (pop i32) + ) + (rethrow $label$7) + ) + (catch_all + (br $label$4) + ) + ) + ) + (try $label$13 + (do + (call $foo) + ) + (catch_all + (try $label$12 + (do + (call $foo) + ) + (catch $event$0 + (drop + (pop i32) + ) + (rethrow $label$13) + ) + (catch_all + (rethrow $label$13) + ) + ) + ) + ) + (try $label$20 + (do + (call $foo) + ) + (catch_all + (try $label$19 + (do + (call $foo) + ) + (catch $event$0 + (drop + (pop i32) + ) + (block $label$18 + (rethrow $label$20) + ) + ) + (catch_all + (rethrow $label$20) + ) + ) + ) + ) + (try $label$26 + (do + (call $foo) + ) + (catch_all + (try $label$25 + (do + (rethrow $label$26) + ) + (catch_all + (nop) + ) + ) + ) + ) + (try $label$32 + (do + (call $foo) + ) + (catch_all + (try $label$31 + (do + (rethrow $label$32) + ) + (catch_all + (nop) + ) + ) + ) + ) + ) ) diff --git a/test/exception-handling.wast.fromBinary.noDebugInfo b/test/exception-handling.wast.fromBinary.noDebugInfo index cf7372b6e49..47577065cac 100644 --- a/test/exception-handling.wast.fromBinary.noDebugInfo +++ b/test/exception-handling.wast.fromBinary.noDebugInfo @@ -194,19 +194,6 @@ ) ) ) - (try $label$37 - (do - (throw $event$0 - (i32.const 0) - ) - ) - (catch $event$0 - (drop - (pop i32) - ) - (rethrow 0) - ) - ) ) (func $3 (try $label$9 @@ -269,5 +256,111 @@ (delegate 0) ) ) + (func $4 + (try $label$3 + (do + (call $0) + ) + (catch $event$0 + (drop + (pop i32) + ) + (rethrow $label$3) + ) + (catch_all + (rethrow $label$3) + ) + ) + (block $label$4 + (try $label$7 + (do + (call $0) + ) + (catch $event$0 + (drop + (pop i32) + ) + (rethrow $label$7) + ) + (catch_all + (br $label$4) + ) + ) + ) + (try $label$13 + (do + (call $0) + ) + (catch_all + (try $label$12 + (do + (call $0) + ) + (catch $event$0 + (drop + (pop i32) + ) + (rethrow $label$13) + ) + (catch_all + (rethrow $label$13) + ) + ) + ) + ) + (try $label$20 + (do + (call $0) + ) + (catch_all + (try $label$19 + (do + (call $0) + ) + (catch $event$0 + (drop + (pop i32) + ) + (block $label$18 + (rethrow $label$20) + ) + ) + (catch_all + (rethrow $label$20) + ) + ) + ) + ) + (try $label$26 + (do + (call $0) + ) + (catch_all + (try $label$25 + (do + (rethrow $label$26) + ) + (catch_all + (nop) + ) + ) + ) + ) + (try $label$32 + (do + (call $0) + ) + (catch_all + (try $label$31 + (do + (rethrow $label$32) + ) + (catch_all + (nop) + ) + ) + ) + ) + ) ) diff --git a/test/passes/code-pushing_all-features.txt b/test/passes/code-pushing_all-features.txt index 5226a7b3c9c..19931399d7d 100644 --- a/test/passes/code-pushing_all-features.txt +++ b/test/passes/code-pushing_all-features.txt @@ -105,14 +105,14 @@ (local.set $x (i32.const 1) ) - (try $try + (try $l0 (do (throw $e (i32.const 0) ) ) (catch_all - (rethrow 0) + (rethrow $l0) ) ) (drop diff --git a/test/passes/code-pushing_all-features.wast b/test/passes/code-pushing_all-features.wast index 0c17c0bab01..b7f12bb0a2b 100644 --- a/test/passes/code-pushing_all-features.wast +++ b/test/passes/code-pushing_all-features.wast @@ -72,12 +72,12 @@ ;; This local.set cannot be pushed down, because there is 'rethrow' within ;; the inner catch_all (local.set $x (i32.const 1)) - (try + (try $l0 (do (throw $e (i32.const 0)) ) (catch_all - (rethrow 0) + (rethrow $l0) ) ) (drop (i32.const 1)) diff --git a/test/passes/dce_all-features.txt b/test/passes/dce_all-features.txt index 575d0406599..a64f033f68c 100644 --- a/test/passes/dce_all-features.txt +++ b/test/passes/dce_all-features.txt @@ -587,9 +587,15 @@ ) ) (func $rethrow - (block $label$0 - (block $label$1 - (rethrow 0) + (try $l0 + (do + (nop) + ) + (catch $e + (drop + (i32.const 0) + ) + (rethrow $l0) ) ) ) diff --git a/test/passes/dce_all-features.wast b/test/passes/dce_all-features.wast index fac6d762342..104093d08f3 100644 --- a/test/passes/dce_all-features.wast +++ b/test/passes/dce_all-features.wast @@ -774,6 +774,7 @@ ) (func $throw + ;; All these wrapping expressions before 'throw' will be dce'd (drop (block $label$0 (result externref) (if @@ -790,17 +791,16 @@ ) (func $rethrow - (drop - (block $label$0 (result externref) - (if - (i32.clz - (block $label$1 (result i32) - (rethrow 0) - ) + (try $l0 + (do) + (catch $e + (drop + ;; This i32.add will be dce'd + (i32.add + (i32.const 0) + (rethrow $l0) ) - (nop) ) - (ref.null extern) ) ) ) diff --git a/test/passes/dwarf_with_exceptions.bin.txt b/test/passes/dwarf_with_exceptions.bin.txt index 5dd594461b2..9b853e17911 100644 --- a/test/passes/dwarf_with_exceptions.bin.txt +++ b/test/passes/dwarf_with_exceptions.bin.txt @@ -93,7 +93,7 @@ ) ) ;; code offset: 0x6c - (rethrow 0) + (rethrow $label$8) ) ) ;; code offset: 0x6f @@ -515,7 +515,7 @@ file_names[ 1]: ) ) ;; code offset: 0x3e - (rethrow 0) + (rethrow $label$8) ) ) ;; code offset: 0x41 diff --git a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.txt b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.txt index f8e7778c502..756b692c7f9 100644 --- a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.txt +++ b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.txt @@ -3,20 +3,20 @@ (type $i32_=>_none (func (param i32))) (event $e0 (attr 0) (param i32)) (func $eh - try $try + try $l0 i32.const 0 throw $e0 catch $e0 drop catch_all - rethrow 0 + rethrow $l0 end - try $l0 - try $try0 + try $l00 + try $try i32.const 0 throw $e0 - delegate $l0 + delegate $l00 unreachable catch_all nop @@ -31,7 +31,7 @@ (type $i32_=>_none (func (param i32))) (event $e0 (attr 0) (param i32)) (func $eh (; has Stack IR ;) - (try $try + (try $l0 (do (throw $e0 (i32.const 0) @@ -43,18 +43,18 @@ ) ) (catch_all - (rethrow 0) + (rethrow $l0) ) ) - (try $l0 + (try $l00 (do - (try $try0 + (try $try (do (throw $e0 (i32.const 0) ) ) - (delegate $l0) + (delegate $l00) ) ) (catch_all diff --git a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.wast b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.wast index f82f7e19ac6..298cb3a3ce0 100644 --- a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.wast +++ b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.wast @@ -2,7 +2,7 @@ (event $e0 (attr 0) (param i32)) (func $eh - (try + (try $l0 (do (throw $e0 (i32.const 0)) ) @@ -10,7 +10,7 @@ (drop (pop i32)) ) (catch_all - (rethrow 0) + (rethrow $l0) ) ) diff --git a/test/passes/rse_all-features.txt b/test/passes/rse_all-features.txt index eaef1445526..6c9d5258568 100644 --- a/test/passes/rse_all-features.txt +++ b/test/passes/rse_all-features.txt @@ -569,14 +569,14 @@ (local $x i32) (try $try (do - (try $try6 + (try $l0 (do (throw $e (i32.const 0) ) ) (catch_all - (rethrow 0) + (rethrow $l0) ) ) ) @@ -594,7 +594,7 @@ (local $x i32) (try $try (do - (try $try7 + (try $l0 (do (throw $e (i32.const 0) @@ -604,7 +604,7 @@ (local.set $x (i32.const 1) ) - (rethrow 0) + (rethrow $l0) ) ) ) @@ -620,7 +620,7 @@ (local $x i32) (try $try (do - (try $try8 + (try $l0 (do (throw $e (i32.const 0) @@ -633,7 +633,7 @@ (local.set $x (i32.const 1) ) - (rethrow 0) + (rethrow $l0) ) ) ) diff --git a/test/passes/rse_all-features.wast b/test/passes/rse_all-features.wast index 2a8b818742a..b3c684fbd85 100644 --- a/test/passes/rse_all-features.wast +++ b/test/passes/rse_all-features.wast @@ -357,12 +357,12 @@ (local $x i32) (try (do - (try + (try $l0 (do (throw $e (i32.const 0)) ) (catch_all - (rethrow 0) + (rethrow $l0) ) ) ) @@ -379,13 +379,13 @@ (local $x i32) (try (do - (try + (try $l0 (do (throw $e (i32.const 0)) ) (catch_all (local.set $x (i32.const 1)) - (rethrow 0) + (rethrow $l0) ) ) ) @@ -399,14 +399,14 @@ (local $x i32) (try (do - (try + (try $l0 (do (throw $e (i32.const 0)) ) (catch $e (drop (pop i32)) (local.set $x (i32.const 1)) - (rethrow 0) + (rethrow $l0) ) ) ) diff --git a/test/spec/exception-handling.wast b/test/spec/exception-handling.wast index 807ee0a5254..21dc98bd4e1 100644 --- a/test/spec/exception-handling.wast +++ b/test/spec/exception-handling.wast @@ -92,29 +92,29 @@ ) (func (export "try_throw_rethrow") - (try + (try $l0 (do (throw $e-i32 (i32.const 5)) ) (catch $e-i32 (drop (pop i32)) - (rethrow 0) + (rethrow $l0) ) ) ) (func (export "try_call_rethrow") - (try + (try $l0 (do (call $throw_single_value) ) (catch_all - (rethrow 0) + (rethrow $l0) ) ) ) - (func (export "rethrow_depth_test1") (result i32) + (func (export "rethrow_target_test1") (result i32) (try (result i32) (do (try @@ -122,13 +122,13 @@ (throw $e-i32 (i32.const 1)) ) (catch_all - (try + (try $l0 (do (throw $e-i32 (i32.const 2)) ) (catch $e-i32 (drop (pop i32)) - (rethrow 0) ;; rethrow (i32.const 2) + (rethrow $l0) ;; rethrow (i32.const 2) ) ) ) @@ -141,10 +141,10 @@ ) ;; Can we handle rethrows with the depth > 0? - (func (export "rethrow_depth_test2") (result i32) + (func (export "rethrow_target_test2") (result i32) (try (result i32) (do - (try + (try $l0 (do (throw $e-i32 (i32.const 1)) ) @@ -168,12 +168,12 @@ ) ;; Tests whether the exception stack is managed correctly after rethrows - (func (export "rethrow_depth_test3") (result i32) + (func (export "rethrow_target_test3") (result i32) (try (result i32) (do - (try + (try $l0 (do - (try + (try $l1 (do (throw $e-i32 (i32.const 1)) ) @@ -184,14 +184,14 @@ ) (catch $e-i32 (drop (pop i32)) - (rethrow 1) ;; rethrow (i32.const 1) + (rethrow $l1) ;; rethrow (i32.const 1) ) ) ) ) ) (catch $e-i32 - (rethrow 0) ;; rethrow (i32.const 1) again + (rethrow $l0) ;; rethrow (i32.const 1) again ) ) ) @@ -212,9 +212,9 @@ (assert_return (invoke "try_throw_multivalue_catch") (i32.const 5)) (assert_trap (invoke "try_throw_rethrow")) (assert_trap (invoke "try_call_rethrow")) -(assert_return (invoke "rethrow_depth_test1") (i32.const 2)) -(assert_return (invoke "rethrow_depth_test2") (i32.const 1)) -(assert_return (invoke "rethrow_depth_test3") (i32.const 1)) +(assert_return (invoke "rethrow_target_test1") (i32.const 2)) +(assert_return (invoke "rethrow_target_test2") (i32.const 1)) +(assert_return (invoke "rethrow_target_test3") (i32.const 1)) (assert_invalid (module @@ -299,3 +299,33 @@ ) "all delegate targets must be valid" ) + +(assert_invalid + (module + (func $f0 + (block $l0 + (try + (do) + (catch_all + (rethrow $l0) ;; target is a block + ) + ) + ) + ) + ) + "all rethrow targets must be valid" +) + +(assert_invalid + (module + (func $f0 + (try $l0 + (do + (rethrow $l0) ;; Not within the target try's catch + ) + (catch_all) + ) + ) + ) + "all rethrow targets must be valid" +)