Commit aaee6958 authored by Michael Starzinger's avatar Michael Starzinger Committed by Commit Bot

[wasm] Switch to new 'catch' and 'br_on_exn' proposal.

This switches the experimental exception handling implementation to the
new proposal where 'catch' blocks behave in a catch-all fashion and a
new 'br_on_exn' operation is used to check for a certain exception type
and extract the exception values on a match.

R=clemensh@chromium.org
TEST=unittests/FunctionBodyDecoderTest,mjsunit/wasm/exceptions
BUG=v8:8091

Change-Id: Ib12ba28b3aa2a7d831312a83abcb00bf56d0adc3
Reviewed-on: https://chromium-review.googlesource.com/c/1409431
Commit-Queue: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#58832}
parent bc3d7298
......@@ -480,6 +480,10 @@ class LiftoffCompiler {
unsupported(decoder, "try");
}
void Catch(FullDecoder* decoder, Control* block, Value* exception) {
unsupported(decoder, "catch");
}
void If(FullDecoder* decoder, const Value& cond, Control* if_block) {
DCHECK_EQ(if_block, decoder->control_at(0));
DCHECK(if_block->is_if());
......@@ -1860,16 +1864,13 @@ class LiftoffCompiler {
const Vector<Value>& args) {
unsupported(decoder, "throw");
}
void Rethrow(FullDecoder* decoder, Control* block) {
void Rethrow(FullDecoder* decoder, const Value& exception) {
unsupported(decoder, "rethrow");
}
void CatchException(FullDecoder* decoder,
const ExceptionIndexImmediate<validate>& imm,
Control* block, Vector<Value> caught_values) {
unsupported(decoder, "catch");
}
void CatchAll(FullDecoder* decoder, Control* block) {
unsupported(decoder, "catch-all");
void BrOnException(FullDecoder* decoder, const Value& exception,
const ExceptionIndexImmediate<validate>& imm,
uint32_t depth, Vector<Value> values) {
unsupported(decoder, "br_on_exn");
}
void AtomicOp(FullDecoder* decoder, WasmOpcode opcode, Vector<Value> args,
const MemoryAccessImmediate<validate>& imm, Value* result) {
......
......@@ -546,8 +546,7 @@ enum ControlKind : uint8_t {
kControlBlock,
kControlLoop,
kControlTry,
kControlTryCatch,
kControlTryCatchAll
kControlTryCatch
};
enum Reachability : uint8_t {
......@@ -602,10 +601,7 @@ struct ControlBase {
bool is_loop() const { return kind == kControlLoop; }
bool is_incomplete_try() const { return kind == kControlTry; }
bool is_try_catch() const { return kind == kControlTryCatch; }
bool is_try_catchall() const { return kind == kControlTryCatchAll; }
bool is_try() const {
return is_incomplete_try() || is_try_catch() || is_try_catchall();
}
bool is_try() const { return is_incomplete_try() || is_try_catch(); }
inline Merge<Value>* br_merge() {
return is_loop() ? &this->start_merge : &this->end_merge;
......@@ -626,6 +622,7 @@ struct ControlBase {
F(Block, Control* block) \
F(Loop, Control* block) \
F(Try, Control* block) \
F(Catch, Control* block, Value* exception) \
F(If, const Value& cond, Control* if_block) \
F(FallThruTo, Control* c) \
F(PopControl, Control* block) \
......@@ -674,10 +671,10 @@ struct ControlBase {
const Value& input0, const Value& input1, Value* result) \
F(Throw, const ExceptionIndexImmediate<validate>& imm, \
const Vector<Value>& args) \
F(Rethrow, Control* block) \
F(CatchException, const ExceptionIndexImmediate<validate>& imm, \
Control* block, Vector<Value> caught_values) \
F(CatchAll, Control* block) \
F(Rethrow, const Value& exception) \
F(BrOnException, const Value& exception, \
const ExceptionIndexImmediate<validate>& imm, uint32_t depth, \
Vector<Value> values) \
F(AtomicOp, WasmOpcode opcode, Vector<Value> args, \
const MemoryAccessImmediate<validate>& imm, Value* result) \
F(MemoryInit, const MemoryInitImmediate<validate>& imm, const Value& dst, \
......@@ -1087,7 +1084,6 @@ class WasmDecoder : public Decoder {
MemoryAccessImmediate<validate> imm(decoder, pc, UINT32_MAX);
return 1 + imm.length;
}
case kExprRethrow:
case kExprBr:
case kExprBrIf: {
BranchDepthImmediate<validate> imm(decoder, pc);
......@@ -1116,12 +1112,17 @@ class WasmDecoder : public Decoder {
return 1 + imm.length;
}
case kExprThrow:
case kExprCatch: {
case kExprThrow: {
ExceptionIndexImmediate<validate> imm(decoder, pc);
return 1 + imm.length;
}
case kExprBrOnExn: {
BranchDepthImmediate<validate> imm_br(decoder, pc);
ExceptionIndexImmediate<validate> imm_idx(decoder, pc + imm_br.length);
return 1 + imm_br.length + imm_idx.length;
}
case kExprSetLocal:
case kExprTeeLocal:
case kExprGetLocal: {
......@@ -1584,15 +1585,8 @@ class WasmFullDecoder : public WasmDecoder<validate> {
}
case kExprRethrow: {
CHECK_PROTOTYPE_OPCODE(eh);
BranchDepthImmediate<validate> imm(this, this->pc_);
if (!this->Validate(this->pc_, imm, control_.size())) break;
Control* c = control_at(imm.depth);
if (!VALIDATE(c->is_try_catchall() || c->is_try_catch())) {
this->error("rethrow not targeting catch or catch-all");
break;
}
CALL_INTERFACE_IF_REACHABLE(Rethrow, c);
len = 1 + imm.length;
auto exception = Pop(0, kWasmExceptRef);
CALL_INTERFACE_IF_REACHABLE(Rethrow, exception);
EndControl();
break;
}
......@@ -1620,9 +1614,6 @@ class WasmFullDecoder : public WasmDecoder<validate> {
}
case kExprCatch: {
CHECK_PROTOTYPE_OPCODE(eh);
ExceptionIndexImmediate<validate> imm(this, this->pc_);
if (!this->Validate(this->pc_, imm)) break;
len = 1 + imm.length;
if (!VALIDATE(!control_.empty())) {
this->error("catch does not match any try");
break;
......@@ -1632,43 +1623,45 @@ class WasmFullDecoder : public WasmDecoder<validate> {
this->error("catch does not match any try");
break;
}
if (!VALIDATE(!c->is_try_catchall())) {
this->error("catch after catch-all for try");
if (!VALIDATE(c->is_incomplete_try())) {
this->error("catch already present for try");
break;
}
c->kind = kControlTryCatch;
FallThruTo(c);
stack_.erase(stack_.begin() + c->stack_depth, stack_.end());
const WasmExceptionSig* sig = imm.exception->sig;
for (size_t i = 0, e = sig->parameter_count(); i < e; ++i) {
Push(sig->GetParam(i));
}
Vector<Value> values(stack_.data() + c->stack_depth,
sig->parameter_count());
c->reachability = control_at(1)->innerReachability();
CALL_INTERFACE_IF_PARENT_REACHABLE(CatchException, imm, c, values);
auto* exception = Push(kWasmExceptRef);
CALL_INTERFACE_IF_PARENT_REACHABLE(Catch, c, exception);
break;
}
case kExprCatchAll: {
case kExprBrOnExn: {
CHECK_PROTOTYPE_OPCODE(eh);
if (!VALIDATE(!control_.empty())) {
this->error("catch-all does not match any try");
break;
}
Control* c = &control_.back();
if (!VALIDATE(c->is_try())) {
this->error("catch-all does not match any try");
break;
}
if (!VALIDATE(!c->is_try_catchall())) {
this->error("catch-all already present for try");
break;
BranchDepthImmediate<validate> imm_br(this, this->pc_);
if (!this->Validate(this->pc_, imm_br, control_.size())) break;
ExceptionIndexImmediate<validate> imm_idx(this,
this->pc_ + imm_br.length);
if (!this->Validate(this->pc_ + imm_br.length, imm_idx)) break;
Control* c = control_at(imm_br.depth);
auto exception = Pop(0, kWasmExceptRef);
const WasmExceptionSig* sig = imm_idx.exception->sig;
size_t value_count = sig->parameter_count();
// TODO(mstarzinger): This operand stack mutation is an ugly hack to
// make both type checking here as well as environment merging in the
// graph builder interface work out of the box. We should introduce
// special handling for both and do minimal/no stack mutation here.
for (size_t i = 0; i < value_count; ++i) Push(sig->GetParam(i));
Vector<Value> values(stack_.data() + c->stack_depth, value_count);
if (!TypeCheckBranch(c)) break;
if (control_.back().reachable()) {
CALL_INTERFACE(BrOnException, exception, imm_idx, imm_br.depth,
values);
c->br_merge()->reached = true;
}
c->kind = kControlTryCatchAll;
FallThruTo(c);
stack_.erase(stack_.begin() + c->stack_depth, stack_.end());
c->reachability = control_at(1)->innerReachability();
CALL_INTERFACE_IF_PARENT_REACHABLE(CatchAll, c);
len = 1 + imm_br.length + imm_idx.length;
for (size_t i = 0; i < value_count; ++i) Pop();
auto* pexception = Push(kWasmExceptRef);
*pexception = exception;
break;
}
case kExprLoop: {
......@@ -1735,14 +1728,6 @@ class WasmFullDecoder : public WasmDecoder<validate> {
break;
}
}
if (c->is_try_catch()) {
// Emulate catch-all + re-throw.
FallThruTo(c);
c->reachability = control_at(1)->innerReachability();
CALL_INTERFACE_IF_PARENT_REACHABLE(CatchAll, c);
CALL_INTERFACE_IF_REACHABLE(Rethrow, c);
EndControl();
}
if (!TypeCheckFallThru(c)) break;
......
......@@ -153,8 +153,7 @@ bool PrintRawWasmCode(AccountingAllocator* allocator, const FunctionBody& body,
WasmOpcode opcode = i.current();
if (line_numbers) line_numbers->push_back(i.position());
if (opcode == kExprElse || opcode == kExprCatch ||
opcode == kExprCatchAll) {
if (opcode == kExprElse || opcode == kExprCatch) {
control_depth--;
}
......@@ -197,7 +196,6 @@ bool PrintRawWasmCode(AccountingAllocator* allocator, const FunctionBody& body,
switch (opcode) {
case kExprElse:
case kExprCatch:
case kExprCatchAll:
os << " // @" << i.pc_offset();
control_depth++;
break;
......
......@@ -453,62 +453,45 @@ class WasmGraphBuildingInterface {
builder_->TerminateThrow(ssa_env_->effect, ssa_env_->control);
}
void Rethrow(FullDecoder* decoder, Control* block) {
DCHECK(block->is_try_catchall() || block->is_try_catch());
TFNode* exception = block->try_info->exception;
BUILD(Rethrow, exception);
void Rethrow(FullDecoder* decoder, const Value& exception) {
BUILD(Rethrow, exception.node);
builder_->TerminateThrow(ssa_env_->effect, ssa_env_->control);
}
void CatchException(FullDecoder* decoder,
const ExceptionIndexImmediate<validate>& imm,
Control* block, Vector<Value> values) {
DCHECK(block->is_try_catch());
current_catch_ = block->previous_catch; // Pop try scope.
// The catch block is unreachable if no possible throws in the try block
// exist. We only build a landing pad if some node in the try block can
// (possibly) throw. Otherwise the catch environments remain empty.
if (!block->try_info->might_throw()) {
block->reachability = kSpecOnlyReachable;
return;
}
TFNode* exception = block->try_info->exception;
SetEnv(block->try_info->catch_env);
TFNode* if_catch = nullptr;
TFNode* if_no_catch = nullptr;
void BrOnException(FullDecoder* decoder, const Value& exception,
const ExceptionIndexImmediate<validate>& imm,
uint32_t depth, Vector<Value> values) {
TFNode* if_match = nullptr;
TFNode* if_no_match = nullptr;
// Get the exception tag and see if it matches the expected one.
TFNode* caught_tag = BUILD(GetExceptionTag, exception);
TFNode* caught_tag = BUILD(GetExceptionTag, exception.node);
TFNode* exception_tag = BUILD(LoadExceptionTagFromTable, imm.index);
TFNode* compare = BUILD(ExceptionTagEqual, caught_tag, exception_tag);
BUILD(BranchNoHint, compare, &if_catch, &if_no_catch);
// If the tags don't match we continue with the next tag by setting the
// false environment as the new {TryInfo::catch_env} here.
SsaEnv* if_no_catch_env = Split(decoder, ssa_env_);
if_no_catch_env->control = if_no_catch;
SsaEnv* if_catch_env = Steal(decoder->zone(), ssa_env_);
if_catch_env->control = if_catch;
block->try_info->catch_env = if_no_catch_env;
BUILD(BranchNoHint, compare, &if_match, &if_no_match);
SsaEnv* if_no_match_env = Split(decoder, ssa_env_);
SsaEnv* if_match_env = Steal(decoder->zone(), ssa_env_);
if_no_match_env->control = if_no_match;
if_match_env->control = if_match;
// If the tags match we extract the values from the exception object and
// push them onto the operand stack using the passed {values} vector.
SetEnv(if_catch_env);
SetEnv(if_match_env);
// TODO(mstarzinger): Can't use BUILD() here, GetExceptionValues() returns
// TFNode** rather than TFNode*. Fix to add landing pads.
TFNode** caught_values =
builder_->GetExceptionValues(exception, imm.exception);
builder_->GetExceptionValues(exception.node, imm.exception);
for (size_t i = 0, e = values.size(); i < e; ++i) {
values[i].node = caught_values[i];
}
BrOrRet(decoder, depth);
// If the tags don't match we fall-through here.
SetEnv(if_no_match_env);
}
void CatchAll(FullDecoder* decoder, Control* block) {
DCHECK(block->is_try_catchall() || block->is_try_catch());
void Catch(FullDecoder* decoder, Control* block, Value* exception) {
DCHECK(block->is_try_catch());
current_catch_ = block->previous_catch; // Pop try scope.
......@@ -521,6 +504,8 @@ class WasmGraphBuildingInterface {
}
SetEnv(block->try_info->catch_env);
DCHECK_NOT_NULL(block->try_info->exception);
exception->node = block->try_info->exception;
}
void AtomicOp(FullDecoder* decoder, WasmOpcode opcode, Vector<Value> args,
......
......@@ -269,6 +269,7 @@ class V8_EXPORT_PRIVATE ValueTypes {
case kWasmF64:
return MachineRepresentation::kFloat64;
case kWasmAnyRef:
case kWasmExceptRef:
return MachineRepresentation::kTaggedPointer;
case kWasmS128:
return MachineRepresentation::kSimd128;
......@@ -335,6 +336,8 @@ class V8_EXPORT_PRIVATE ValueTypes {
return "f64";
case kWasmAnyRef:
return "ref";
case kWasmExceptRef:
return "exn";
case kWasmS128:
return "s128";
case kWasmStmt:
......
......@@ -158,12 +158,12 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_I64_OP(StoreMem32, "store32")
CASE_S128_OP(StoreMem, "store128")
// Non-standard opcodes.
// Exception handling opcodes.
CASE_OP(Try, "try")
CASE_OP(Catch, "catch")
CASE_OP(Throw, "throw")
CASE_OP(Rethrow, "rethrow")
CASE_OP(Catch, "catch")
CASE_OP(CatchAll, "catch_all")
CASE_OP(BrOnExn, "br_on_exn")
// asm.js-only opcodes.
CASE_F64_OP(Acos, "acos")
......
......@@ -19,22 +19,22 @@ std::ostream& operator<<(std::ostream& os, const FunctionSig& function);
bool IsJSCompatibleSignature(const FunctionSig* sig, bool hasBigIntFeature);
// Control expressions and blocks.
#define FOREACH_CONTROL_OPCODE(V) \
V(Unreachable, 0x00, _) \
V(Nop, 0x01, _) \
V(Block, 0x02, _) \
V(Loop, 0x03, _) \
V(If, 0x004, _) \
V(Else, 0x05, _) \
V(Try, 0x06, _ /* eh_prototype */) \
V(Catch, 0x07, _ /* eh_prototype */) \
V(Throw, 0x08, _ /* eh_prototype */) \
V(Rethrow, 0x09, _ /* eh_prototype */) \
V(CatchAll, 0x0a, _ /* eh prototype */) \
V(End, 0x0b, _) \
V(Br, 0x0c, _) \
V(BrIf, 0x0d, _) \
V(BrTable, 0x0e, _) \
#define FOREACH_CONTROL_OPCODE(V) \
V(Unreachable, 0x00, _) \
V(Nop, 0x01, _) \
V(Block, 0x02, _) \
V(Loop, 0x03, _) \
V(If, 0x04, _) \
V(Else, 0x05, _) \
V(Try, 0x06, _ /* eh_prototype */) \
V(Catch, 0x07, _ /* eh_prototype */) \
V(Throw, 0x08, _ /* eh_prototype */) \
V(Rethrow, 0x09, _ /* eh_prototype */) \
V(BrOnExn, 0x0a, _ /* eh prototype */) \
V(End, 0x0b, _) \
V(Br, 0x0c, _) \
V(BrIf, 0x0d, _) \
V(BrTable, 0x0e, _) \
V(Return, 0x0f, _)
// Constants, locals, globals, and calls.
......
......@@ -81,8 +81,7 @@ void PrintWasmText(const WasmModule* module, const ModuleWireBytes& wire_bytes,
for (; i.has_next(); i.next()) {
WasmOpcode opcode = i.current();
if (opcode == kExprElse || opcode == kExprCatch ||
opcode == kExprCatchAll || opcode == kExprEnd) {
if (opcode == kExprElse || opcode == kExprCatch || opcode == kExprEnd) {
--control_depth;
}
......@@ -120,8 +119,16 @@ void PrintWasmText(const WasmModule* module, const ModuleWireBytes& wire_bytes,
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.depth;
break;
}
case kExprBrOnExn: {
BranchDepthImmediate<Decoder::kNoValidate> imm_br(&i, i.pc());
ExceptionIndexImmediate<Decoder::kNoValidate> imm_idx(
&i, i.pc() + imm_br.length);
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm_br.depth << ' '
<< imm_idx.index;
break;
}
case kExprElse:
case kExprCatchAll:
case kExprCatch:
os << WasmOpcodes::OpcodeName(opcode);
control_depth++;
break;
......@@ -153,9 +160,6 @@ void PrintWasmText(const WasmModule* module, const ModuleWireBytes& wire_bytes,
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
case kExprCatch:
control_depth++;
V8_FALLTHROUGH;
case kExprThrow: {
ExceptionIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
......
......@@ -30,22 +30,25 @@ load("test/mjsunit/wasm/exceptions-utils.js");
let except = builder.addException(kSig_v_r);
builder.addFunction("throw_catch_null", kSig_i_i)
.addBody([
kExprTry, kWasmI32,
kExprTry, kWasmAnyRef,
kExprGetLocal, 0,
kExprI32Eqz,
kExprIf, kWasmI32,
kExprIf, kWasmAnyRef,
kExprRefNull,
kExprThrow, except,
kExprElse,
kExprI32Const, 42,
kExprReturn,
kExprEnd,
kExprCatch, except,
kExprRefIsNull,
kExprIf, kWasmI32,
kExprI32Const, 23,
kExprElse,
kExprUnreachable,
kExprEnd,
kExprCatch,
kExprBrOnExn, 0, except,
kExprRethrow,
kExprEnd,
kExprRefIsNull,
kExprIf, kWasmI32,
kExprI32Const, 23,
kExprElse,
kExprUnreachable,
kExprEnd,
]).exportFunc();
let instance = builder.instantiate();
......@@ -83,8 +86,9 @@ load("test/mjsunit/wasm/exceptions-utils.js");
kExprTry, kWasmAnyRef,
kExprGetLocal, 0,
kExprThrow, except,
kExprCatch, except,
// fall-through
kExprCatch,
kExprBrOnExn, 0, except,
kExprRethrow,
kExprEnd,
]).exportFunc();
let instance = builder.instantiate();
......
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --expose-wasm --experimental-wasm-eh
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
// Test that a catch-all block handles all exceptions thrown locally, but is
// applied only after typed catch blocks have been handled.
(function TestCatchAllLocal() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v);
builder.addFunction("catchall_local", kSig_i_i)
.addBody([
kExprTry, kWasmI32,
kExprGetLocal, 0,
kExprI32Const, 1,
kExprI32Eq,
kExprIf, kWasmStmt,
kExprThrow, except1,
kExprEnd,
kExprGetLocal, 0,
kExprI32Const, 2,
kExprI32Eq,
kExprIf, kWasmStmt,
kExprThrow, except2,
kExprEnd,
kExprI32Const, 61,
kExprCatch, except1,
kExprI32Const, 23,
kExprCatchAll,
kExprI32Const, 42,
kExprEnd
]).exportFunc();
let instance = builder.instantiate();
assertEquals(23, instance.exports.catchall_local(1));
assertEquals(42, instance.exports.catchall_local(2));
assertEquals(61, instance.exports.catchall_local(3));
})();
// Test that a catch-all block handles all exceptions thrown externally, even
// those originating from JavaScript instead of WebAssembly.
(function TestCatchAllExternal() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let sig_index = builder.addType(kSig_v_v);
let fun = builder.addImport("m", "f", sig_index);
let except = builder.addException(kSig_v_v);
builder.addFunction("throw", kSig_v_v)
.addBody([
kExprThrow, except
]).exportFunc();
builder.addFunction("catchall_external", kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprCallFunction, fun,
kExprUnreachable,
kExprCatch, except,
kExprI32Const, 23,
kExprCatchAll,
kExprI32Const, 42,
kExprEnd,
]).exportFunc();
let ex_obj = new Error("my exception");
let instance = builder.instantiate({ m: { f: function() { throw ex_obj }}});
assertThrows(() => instance.exports.throw(), WebAssembly.RuntimeError);
assertEquals(42, instance.exports.catchall_external()); // From JavaScript.
try {
instance.exports.throw();
} catch (e) {
ex_obj = e;
}
assertEquals(23, instance.exports.catchall_external()); // From WebAssembly.
})();
// Test that expressions in a catch-all block are considered to be outside of
// the corresponding try block. Exceptions raised in them will percolate up.
(function TestCatchAllThrowing() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v);
builder.addFunction("catchall", kSig_v_v)
.addBody([
kExprTry, kWasmStmt,
kExprThrow, except1,
kExprCatchAll,
kExprThrow, except2,
kExprEnd
]).exportFunc();
let instance = builder.instantiate();
assertThrows(() => instance.exports.catchall(), WebAssembly.RuntimeError);
})();
// Test that empty try blocks (with no expression that could potentially throw)
// are supported properly, even in the presence of unreachable catch blocks.
(function TestCatchAllEmptyBlock() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v);
builder.addFunction("catchall", kSig_v_v)
.addBody([
kExprTry, kWasmStmt,
kExprCatch, except1,
kExprThrow, except2,
kExprCatchAll,
kExprThrow, except2,
kExprEnd
]).exportFunc();
let instance = builder.instantiate();
assertDoesNotThrow(() => instance.exports.catchall());
})();
......@@ -8,7 +8,7 @@ load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
load("test/mjsunit/wasm/exceptions-utils.js");
// Test that rethrow expressions can target catch blocks.
// Test that rethrow expressions work inside catch blocks.
(function TestRethrowInCatch() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
......@@ -17,19 +17,22 @@ load("test/mjsunit/wasm/exceptions-utils.js");
.addBody([
kExprTry, kWasmStmt,
kExprThrow, except,
kExprCatch, except,
kExprRethrow, 0,
kExprCatch,
kExprRethrow,
kExprEnd,
]).exportFunc();
builder.addFunction("rethrow1", kSig_i_i)
.addLocals({except_count: 1})
.addBody([
kExprTry, kWasmI32,
kExprThrow, except,
kExprCatch, except,
kExprCatch,
kExprSetLocal, 1,
kExprGetLocal, 0,
kExprI32Eqz,
kExprIf, kWasmStmt,
kExprRethrow, 1,
kExprGetLocal, 1,
kExprRethrow,
kExprEnd,
kExprI32Const, 23,
kExprEnd
......@@ -41,65 +44,37 @@ load("test/mjsunit/wasm/exceptions-utils.js");
assertEquals(23, instance.exports.rethrow1(1));
})();
// Test that rethrow expressions can target catch-all blocks.
(function TestRethrowInCatchAll() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
builder.addFunction("rethrow0", kSig_v_v)
.addBody([
kExprTry, kWasmStmt,
kExprThrow, except,
kExprCatchAll,
kExprRethrow, 0,
kExprEnd,
]).exportFunc();
builder.addFunction("rethrow1", kSig_i_i)
.addBody([
kExprTry, kWasmI32,
kExprThrow, except,
kExprCatchAll,
kExprGetLocal, 0,
kExprI32Eqz,
kExprIf, kWasmStmt,
kExprRethrow, 1,
kExprEnd,
kExprI32Const, 23,
kExprEnd
]).exportFunc();
let instance = builder.instantiate();
assertWasmThrows(instance, except, [], () => instance.exports.rethrow0());
assertWasmThrows(instance, except, [], () => instance.exports.rethrow1(0));
assertEquals(23, instance.exports.rethrow1(1));
})();
// Test that rethrow expression properly target the correct surrounding try
// block even in the presence of multiple handlers being involved.
// Test that rethrow expressions work properly even in the presence of multiple
// nested handlers being involved.
(function TestRethrowNested() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v);
builder.addFunction("rethrow_nested", kSig_i_i)
.addLocals({except_count: 2})
.addBody([
kExprTry, kWasmI32,
kExprThrow, except2,
kExprCatch, except2,
kExprCatch,
kExprSetLocal, 2,
kExprTry, kWasmI32,
kExprThrow, except1,
kExprCatch, except1,
kExprCatch,
kExprSetLocal, 1,
kExprGetLocal, 0,
kExprI32Const, 0,
kExprI32Eq,
kExprIf, kWasmStmt,
kExprRethrow, 1,
kExprGetLocal, 1,
kExprRethrow,
kExprEnd,
kExprGetLocal, 0,
kExprI32Const, 1,
kExprI32Eq,
kExprIf, kWasmStmt,
kExprRethrow, 2,
kExprGetLocal, 2,
kExprRethrow,
kExprEnd,
kExprI32Const, 23,
kExprEnd,
......@@ -119,18 +94,22 @@ load("test/mjsunit/wasm/exceptions-utils.js");
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
builder.addFunction("rethrow_recatch", kSig_i_i)
.addLocals({except_count: 1})
.addBody([
kExprTry, kWasmI32,
kExprThrow, except,
kExprCatch, except,
kExprCatch,
kExprSetLocal, 1,
kExprTry, kWasmI32,
kExprGetLocal, 0,
kExprI32Eqz,
kExprIf, kWasmStmt,
kExprRethrow, 2,
kExprGetLocal, 1,
kExprRethrow,
kExprEnd,
kExprI32Const, 42,
kExprCatch, except,
kExprCatch,
kExprDrop,
kExprI32Const, 23,
kExprEnd,
kExprEnd,
......
......@@ -33,7 +33,9 @@ function NewExportedException() {
.addBody([
kExprTry, kWasmStmt,
kExprCallFunction, fun,
kExprCatch, except,
kExprCatch,
kExprBrOnExn, 0, except,
kExprRethrow,
kExprEnd,
]).exportFunc();
let ex_obj = new Error("my exception");
......@@ -65,7 +67,9 @@ function NewExportedException() {
.addBody([
kExprTry, kWasmStmt,
kExprCallFunction, fun,
kExprCatch, except,
kExprCatch,
kExprBrOnExn, 0, except,
kExprRethrow,
kExprEnd,
]).exportFunc();
let ex_obj = new Error("my exception");
......@@ -100,7 +104,9 @@ function NewExportedException() {
.addBody([
kExprTry, kWasmStmt,
kExprCallFunction, fun,
kExprCatch, except1,
kExprCatch,
kExprBrOnExn, 0, except1,
kExprRethrow,
kExprEnd,
]).exportFunc();
let ex_obj = new Error("my exception");
......@@ -139,7 +145,9 @@ function NewExportedException() {
.addBody([
kExprTry, kWasmStmt,
kExprCallFunction, fun,
kExprCatch, except,
kExprCatch,
kExprBrOnExn, 0, except,
kExprRethrow,
kExprEnd,
]).exportFunc();
let ex_obj = new Error("my exception");
......
......@@ -32,15 +32,17 @@ load("test/mjsunit/wasm/exceptions-utils.js");
builder.addFunction("throw_catch_simd", kSig_i_v)
.addLocals({s128_count: 1})
.addBody([
kExprTry, kWasmI32,
kExprTry, kWasmS128,
kExprGetLocal, 0,
kExprThrow, 0,
kExprCatch, except,
kExprCatch,
kExprBrOnExn, 0, except,
kExprRethrow,
kExprEnd,
// TODO(mstarzinger): Actually return some compressed form of the s128
// value here to make sure it is extracted properly from the exception.
kExprDrop,
kExprI32Const, 1,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
])
.exportFunc();
var instance = builder.instantiate();
......
This diff is collapsed.
......@@ -185,7 +185,7 @@ let kExprTry = 0x06;
let kExprCatch = 0x07;
let kExprThrow = 0x08;
let kExprRethrow = 0x09;
let kExprCatchAll = 0x0a;
let kExprBrOnExn = 0x0a;
let kExprEnd = 0x0b;
let kExprBr = 0x0c;
let kExprBrIf = 0x0d;
......
......@@ -2445,58 +2445,57 @@ TEST_F(FunctionBodyDecoderTest, ThrowUnreachable) {
}
#define WASM_TRY_OP kExprTry, kLocalVoid
#define WASM_CATCH(index) kExprCatch, static_cast<byte>(index)
#define WASM_RETHROW(depth) kExprRethrow, static_cast<byte>(depth)
#define WASM_BR_ON_EXN(depth, index) \
kExprBrOnExn, static_cast<byte>(depth), static_cast<byte>(index)
TEST_F(FunctionBodyDecoderTest, TryCatch) {
WASM_FEATURE_SCOPE(eh);
TestModuleBuilder builder;
module = builder.module();
byte ex1 = builder.AddException(sigs.v_v());
byte ex2 = builder.AddException(sigs.v_v());
EXPECT_VERIFIES(v_v, WASM_TRY_OP, WASM_CATCH(ex1), kExprEnd);
EXPECT_VERIFIES(v_v, WASM_TRY_OP, WASM_CATCH(ex1), WASM_CATCH(ex2), kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprEnd); // Missing catch.
EXPECT_FAILURE(v_v, WASM_TRY_OP, WASM_CATCH(ex1)); // Missing end.
EXPECT_FAILURE(v_v, WASM_CATCH(ex1), kExprEnd); // Missing try.
EXPECT_VERIFIES(v_v, WASM_TRY_OP, kExprCatch, kExprDrop, kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatch, kExprCatch, kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprEnd); // Missing catch.
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatch); // Missing end.
EXPECT_FAILURE(v_v, kExprCatch, kExprEnd); // Missing try.
}
TEST_F(FunctionBodyDecoderTest, TryCatchAll) {
TEST_F(FunctionBodyDecoderTest, Rethrow) {
WASM_FEATURE_SCOPE(eh);
TestModuleBuilder builder;
module = builder.module();
byte ex1 = builder.AddException(sigs.v_v());
byte ex2 = builder.AddException(sigs.v_v());
EXPECT_VERIFIES(v_v, WASM_TRY_OP, kExprCatchAll, kExprEnd);
EXPECT_VERIFIES(v_v, WASM_TRY_OP, WASM_CATCH(ex1), kExprCatchAll, kExprEnd);
EXPECT_VERIFIES(v_v, WASM_TRY_OP, WASM_CATCH(ex1), WASM_CATCH(ex2),
kExprCatchAll, kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatchAll, kExprCatchAll, kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatchAll, WASM_CATCH(ex1), kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatchAll); // Missing end.
EXPECT_FAILURE(v_v, kExprCatchAll, kExprEnd); // Missing try.
EXPECT_VERIFIES(v_v, WASM_TRY_OP, kExprCatch, kExprRethrow, kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprRethrow, kExprCatch, kExprEnd);
EXPECT_FAILURE(v_v, WASM_BLOCK(kExprRethrow));
EXPECT_FAILURE(v_v, kExprRethrow);
}
TEST_F(FunctionBodyDecoderTest, Rethrow) {
TEST_F(FunctionBodyDecoderTest, BrOnExn) {
WASM_FEATURE_SCOPE(eh);
TestModuleBuilder builder;
module = builder.module();
byte ex1 = builder.AddException(sigs.v_v());
EXPECT_VERIFIES(v_v, WASM_TRY_OP, WASM_CATCH(ex1), WASM_RETHROW(0), kExprEnd);
EXPECT_VERIFIES(v_v, WASM_TRY_OP, kExprCatchAll, WASM_RETHROW(0), kExprEnd);
EXPECT_VERIFIES(v_v, WASM_TRY_OP, kExprCatchAll, WASM_BLOCK(WASM_RETHROW(1)),
kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatchAll, WASM_BLOCK(WASM_RETHROW(0)),
byte ex2 = builder.AddException(sigs.v_i());
EXPECT_VERIFIES(v_v, WASM_TRY_OP, kExprCatch, WASM_BR_ON_EXN(0, ex1),
kExprDrop, kExprEnd);
EXPECT_VERIFIES(v_v, WASM_TRY_OP, kExprCatch, WASM_BR_ON_EXN(1, ex1),
kExprDrop, kExprEnd);
EXPECT_VERIFIES(v_v, WASM_TRY_OP, kExprCatch, WASM_BR_ON_EXN(0, ex1),
WASM_BR_ON_EXN(0, ex1), kExprDrop, kExprEnd);
EXPECT_VERIFIES(v_v, WASM_BLOCK(WASM_TRY_OP, kExprCatch,
WASM_BR_ON_EXN(1, ex1), kExprDrop, kExprEnd));
EXPECT_VERIFIES(i_v,
WASM_BLOCK_I(WASM_TRY_OP, kExprCatch, WASM_BR_ON_EXN(1, ex2),
kExprDrop, kExprEnd, kExprI32Const, 0));
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatch, WASM_BR_ON_EXN(2, ex1),
kExprDrop, kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatch, kExprDrop,
WASM_BR_ON_EXN(0, ex1), kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatch, WASM_BR_ON_EXN(0, ex1),
kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprCatchAll, WASM_RETHROW(23), kExprEnd);
EXPECT_FAILURE(v_v, WASM_TRY_OP, WASM_RETHROW(0), kExprCatchAll, kExprEnd);
EXPECT_FAILURE(v_v, WASM_BLOCK(WASM_RETHROW(0)));
EXPECT_FAILURE(v_v, WASM_RETHROW(0));
}
#undef WASM_BR_ON_EXN
#undef WASM_TRY_OP
#undef WASM_CATCH
#undef WASM_RETHROW
TEST_F(FunctionBodyDecoderTest, MultiValBlock1) {
WASM_FEATURE_SCOPE(mv);
......@@ -2951,11 +2950,12 @@ TEST_F(WasmOpcodeLengthTest, Statements) {
EXPECT_LENGTH(1, kExprElse);
EXPECT_LENGTH(1, kExprEnd);
EXPECT_LENGTH(1, kExprSelect);
EXPECT_LENGTH(1, kExprCatch);
EXPECT_LENGTH(1, kExprRethrow);
EXPECT_LENGTH(2, kExprBr);
EXPECT_LENGTH(2, kExprBrIf);
EXPECT_LENGTH(2, kExprThrow);
EXPECT_LENGTH(2, kExprRethrow);
EXPECT_LENGTH(2, kExprCatch);
EXPECT_LENGTH(3, kExprBrOnExn);
EXPECT_LENGTH_N(2, kExprBlock, kLocalI32);
EXPECT_LENGTH_N(2, kExprLoop, kLocalI32);
EXPECT_LENGTH_N(2, kExprIf, kLocalI32);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment