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

[wasm] Initial catch-all control flow wiring.

This adds support to wire control flow of catch-all expressions into an
existing try-catch cascade. Note that multiple typed catch blocks are
not yet supported, only one typed catch block followed by one catch-all
block is supported.

In case the explicit catch-all block is missing, we emulate the correct
semantics by internally always emitting a catch-all containing a simple
rethrow instruction.

R=clemensh@chromium.org
TEST=mjsunit/wasm/exceptions-catchall
BUG=v8:8091

Change-Id: I6b29a98c4f1a558fabe6012f4ba6c7b7d43529bb
Reviewed-on: https://chromium-review.googlesource.com/c/1270585Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Commit-Queue: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56476}
parent 2f9fe115
...@@ -1818,11 +1818,17 @@ class LiftoffCompiler { ...@@ -1818,11 +1818,17 @@ class LiftoffCompiler {
Control* block, const Vector<Value>& args) { Control* block, const Vector<Value>& args) {
unsupported(decoder, "throw"); unsupported(decoder, "throw");
} }
void Rethrow(FullDecoder* decoder, Control* block) {
unsupported(decoder, "rethrow");
}
void CatchException(FullDecoder* decoder, void CatchException(FullDecoder* decoder,
const ExceptionIndexImmediate<validate>& imm, const ExceptionIndexImmediate<validate>& imm,
Control* block, Vector<Value> caught_values) { Control* block, Vector<Value> caught_values) {
unsupported(decoder, "catch"); unsupported(decoder, "catch");
} }
void CatchAll(FullDecoder* decoder, Control* block) {
unsupported(decoder, "catch-all");
}
void AtomicOp(FullDecoder* decoder, WasmOpcode opcode, Vector<Value> args, void AtomicOp(FullDecoder* decoder, WasmOpcode opcode, Vector<Value> args,
const MemoryAccessImmediate<validate>& imm, Value* result) { const MemoryAccessImmediate<validate>& imm, Value* result) {
unsupported(decoder, "atomicop"); unsupported(decoder, "atomicop");
......
...@@ -660,10 +660,12 @@ struct ControlWithNamedConstructors : public ControlBase<Value> { ...@@ -660,10 +660,12 @@ struct ControlWithNamedConstructors : public ControlBase<Value> {
const Value& input, Value* result) \ const Value& input, Value* result) \
F(Simd8x16ShuffleOp, const Simd8x16ShuffleImmediate<validate>& imm, \ F(Simd8x16ShuffleOp, const Simd8x16ShuffleImmediate<validate>& imm, \
const Value& input0, const Value& input1, Value* result) \ const Value& input0, const Value& input1, Value* result) \
F(Throw, const ExceptionIndexImmediate<validate>&, Control* block, \ F(Throw, const ExceptionIndexImmediate<validate>& imm, Control* block, \
const Vector<Value>& args) \ const Vector<Value>& args) \
F(Rethrow, Control* block) \
F(CatchException, const ExceptionIndexImmediate<validate>& imm, \ F(CatchException, const ExceptionIndexImmediate<validate>& imm, \
Control* block, Vector<Value> caught_values) \ Control* block, Vector<Value> caught_values) \
F(CatchAll, Control* block) \
F(AtomicOp, WasmOpcode opcode, Vector<Value> args, \ F(AtomicOp, WasmOpcode opcode, Vector<Value> args, \
const MemoryAccessImmediate<validate>& imm, Value* result) const MemoryAccessImmediate<validate>& imm, Value* result)
...@@ -1549,6 +1551,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -1549,6 +1551,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
break; break;
} }
c->kind = kControlTryCatchAll; c->kind = kControlTryCatchAll;
FallThruTo(c);
stack_.resize(c->stack_depth);
CALL_INTERFACE_IF_PARENT_REACHABLE(CatchAll, c);
// TODO(mstarzinger): Implement control flow for catch-all. // TODO(mstarzinger): Implement control flow for catch-all.
break; break;
} }
...@@ -1615,6 +1620,12 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -1615,6 +1620,12 @@ class WasmFullDecoder : public WasmDecoder<validate> {
PushMergeValues(c, &c->start_merge); PushMergeValues(c, &c->start_merge);
c->reachability = control_at(1)->innerReachability(); c->reachability = control_at(1)->innerReachability();
} }
if (c->is_try_catch()) {
// Emulate catch-all + re-throw.
FallThruTo(c);
CALL_INTERFACE_IF_PARENT_REACHABLE(CatchAll, c);
CALL_INTERFACE_IF_PARENT_REACHABLE(Rethrow, c);
}
FallThruTo(c); FallThruTo(c);
// A loop just leaves the values on the stack. // A loop just leaves the values on the stack.
......
...@@ -407,13 +407,6 @@ class WasmGraphBuildingInterface { ...@@ -407,13 +407,6 @@ class WasmGraphBuildingInterface {
result->node = BUILD(Simd8x16ShuffleOp, imm.shuffle, input_nodes); result->node = BUILD(Simd8x16ShuffleOp, imm.shuffle, input_nodes);
} }
TFNode* GetExceptionTag(FullDecoder* decoder,
const ExceptionIndexImmediate<validate>& imm) {
// TODO(kschimpf): Need to get runtime exception tag values. This
// code only handles non-imported/exported exceptions.
return BUILD(Int32Constant, imm.index);
}
void Throw(FullDecoder* decoder, const ExceptionIndexImmediate<validate>& imm, void Throw(FullDecoder* decoder, const ExceptionIndexImmediate<validate>& imm,
Control* block, const Vector<Value>& value_args) { Control* block, const Vector<Value>& value_args) {
int count = value_args.length(); int count = value_args.length();
...@@ -426,6 +419,18 @@ class WasmGraphBuildingInterface { ...@@ -426,6 +419,18 @@ class WasmGraphBuildingInterface {
EndControl(decoder, block); EndControl(decoder, block);
} }
void Rethrow(FullDecoder* decoder, Control* block) {
TFNode* exception = block->try_info->exception;
// TODO(mstarzinger): The below check is a workaround because we still
// determine reachability via the SSA environment. This should be done via
// the control stack reachability instead.
if (exception != nullptr) {
BUILD(Rethrow, exception);
Unreachable(decoder);
EndControl(decoder, block);
}
}
void CatchException(FullDecoder* decoder, void CatchException(FullDecoder* decoder,
const ExceptionIndexImmediate<validate>& imm, const ExceptionIndexImmediate<validate>& imm,
Control* block, Vector<Value> values) { Control* block, Vector<Value> values) {
...@@ -454,15 +459,7 @@ class WasmGraphBuildingInterface { ...@@ -454,15 +459,7 @@ class WasmGraphBuildingInterface {
if_no_catch_env->control = if_no_catch; if_no_catch_env->control = if_no_catch;
SsaEnv* if_catch_env = Steal(decoder->zone(), ssa_env_); SsaEnv* if_catch_env = Steal(decoder->zone(), ssa_env_);
if_catch_env->control = if_catch; if_catch_env->control = if_catch;
block->try_info->catch_env = if_no_catch_env;
SetEnv(if_no_catch_env);
if (exception != nullptr) {
// TODO(kschimpf): Generalize to allow more catches. Will force
// moving no_catch code to END opcode.
BUILD(Rethrow, exception);
Unreachable(decoder);
EndControl(decoder, block);
}
SetEnv(if_catch_env); SetEnv(if_catch_env);
if (exception != nullptr) { if (exception != nullptr) {
...@@ -476,6 +473,12 @@ class WasmGraphBuildingInterface { ...@@ -476,6 +473,12 @@ class WasmGraphBuildingInterface {
} }
} }
void CatchAll(FullDecoder* decoder, Control* block) {
DCHECK(block->is_try_catchall() || block->is_try_catch());
SsaEnv* catch_env = block->try_info->catch_env;
SetEnv(catch_env);
}
void AtomicOp(FullDecoder* decoder, WasmOpcode opcode, Vector<Value> args, void AtomicOp(FullDecoder* decoder, WasmOpcode opcode, Vector<Value> args,
const MemoryAccessImmediate<validate>& imm, Value* result) { const MemoryAccessImmediate<validate>& imm, Value* result) {
TFNode** inputs = GetNodes(args); TFNode** inputs = GetNodes(args);
......
// 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.
})();
...@@ -29,6 +29,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -29,6 +29,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// First we just test that "except_ref" local variables are allowed. // First we just test that "except_ref" local variables are allowed.
(function TestLocalExceptRef() { (function TestLocalExceptRef() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
builder.addFunction("push_and_drop_except_ref", kSig_v_v) builder.addFunction("push_and_drop_except_ref", kSig_v_v)
.addBody([ .addBody([
...@@ -42,6 +43,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -42,6 +43,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// The following method doesn't attempt to catch an raised exception. // The following method doesn't attempt to catch an raised exception.
(function TestThrowSimple() { (function TestThrowSimple() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v); let except = builder.addException(kSig_v_v);
builder.addFunction("throw_if_param_not_zero", kSig_i_i) builder.addFunction("throw_if_param_not_zero", kSig_i_i)
...@@ -63,6 +65,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -63,6 +65,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test that empty try/catch blocks work. // Test that empty try/catch blocks work.
(function TestCatchEmptyBlocks() { (function TestCatchEmptyBlocks() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v); let except = builder.addException(kSig_v_v);
builder.addFunction("catch_empty_try", kSig_v_v) builder.addFunction("catch_empty_try", kSig_v_v)
...@@ -78,6 +81,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -78,6 +81,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Now that we know throwing works, we test catching the exceptions we raise. // Now that we know throwing works, we test catching the exceptions we raise.
(function TestCatchSimple() { (function TestCatchSimple() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v); let except = builder.addException(kSig_v_v);
builder.addFunction("simple_throw_catch_to_0_1", kSig_i_i) builder.addFunction("simple_throw_catch_to_0_1", kSig_i_i)
...@@ -101,6 +105,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -101,6 +105,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test that we can distinguish which exception was thrown. // Test that we can distinguish which exception was thrown.
(function TestCatchComplex() { (function TestCatchComplex() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v); let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v); let except2 = builder.addException(kSig_v_v);
...@@ -140,6 +145,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -140,6 +145,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing an exception with multiple values. // Test throwing an exception with multiple values.
(function TestThrowMultipleValues() { (function TestThrowMultipleValues() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_ii); let except = builder.addException(kSig_v_ii);
builder.addFunction("throw_1_2", kSig_v_v) builder.addFunction("throw_1_2", kSig_v_v)
...@@ -155,6 +161,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -155,6 +161,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing/catching the i32 parameter value. // Test throwing/catching the i32 parameter value.
(function TestThrowCatchParamI() { (function TestThrowCatchParamI() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_i); let except = builder.addException(kSig_v_i);
builder.addFunction("throw_catch_param", kSig_i_i) builder.addFunction("throw_catch_param", kSig_i_i)
...@@ -176,6 +183,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -176,6 +183,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a thrown exception with an integer exception. // Test the encoding of a thrown exception with an integer exception.
(function TestThrowParamI() { (function TestThrowParamI() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_i); let except = builder.addException(kSig_v_i);
builder.addFunction("throw_param", kSig_v_i) builder.addFunction("throw_param", kSig_v_i)
...@@ -191,6 +199,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -191,6 +199,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing/catching the f32 parameter value. // Test throwing/catching the f32 parameter value.
(function TestThrowCatchParamF() { (function TestThrowCatchParamF() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_f); let except = builder.addException(kSig_v_f);
builder.addFunction("throw_catch_param", kSig_f_f) builder.addFunction("throw_catch_param", kSig_f_f)
...@@ -211,6 +220,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -211,6 +220,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a thrown exception with a float value. // Test the encoding of a thrown exception with a float value.
(function TestThrowParamF() { (function TestThrowParamF() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_f); let except = builder.addException(kSig_v_f);
builder.addFunction("throw_param", kSig_v_f) builder.addFunction("throw_param", kSig_v_f)
...@@ -226,6 +236,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -226,6 +236,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing/catching an I64 value // Test throwing/catching an I64 value
(function TestThrowCatchParamL() { (function TestThrowCatchParamL() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_l); let except = builder.addException(kSig_v_l);
builder.addFunction("throw_catch_param", kSig_i_i) builder.addFunction("throw_catch_param", kSig_i_i)
...@@ -258,6 +269,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -258,6 +269,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a thrown exception with an I64 value. // Test the encoding of a thrown exception with an I64 value.
(function TestThrowParamL() { (function TestThrowParamL() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_l); let except = builder.addException(kSig_v_l);
builder.addFunction("throw_param", kSig_v_ii) builder.addFunction("throw_param", kSig_v_ii)
...@@ -279,6 +291,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -279,6 +291,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing/catching the F64 parameter value // Test throwing/catching the F64 parameter value
(function TestThrowCatchParamD() { (function TestThrowCatchParamD() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_d); let except = builder.addException(kSig_v_d);
builder.addFunction("throw_catch_param", kSig_d_d) builder.addFunction("throw_catch_param", kSig_d_d)
...@@ -299,6 +312,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -299,6 +312,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a thrown exception with an f64 value. // Test the encoding of a thrown exception with an f64 value.
(function TestThrowParamD() { (function TestThrowParamD() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_d); let except = builder.addException(kSig_v_d);
builder.addFunction("throw_param", kSig_v_f) builder.addFunction("throw_param", kSig_v_f)
...@@ -315,6 +329,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -315,6 +329,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a computed parameter value. // Test the encoding of a computed parameter value.
(function TestThrowParamComputed() { (function TestThrowParamComputed() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_i); let except = builder.addException(kSig_v_i);
builder.addFunction("throw_expr_with_params", kSig_v_ddi) builder.addFunction("throw_expr_with_params", kSig_v_ddi)
...@@ -341,6 +356,7 @@ function assertWasmThrows(instance, runtime_id, values, code) { ...@@ -341,6 +356,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Now that we know catching works locally, we test catching exceptions that // Now that we know catching works locally, we test catching exceptions that
// cross function boundaries and/or raised by JavaScript. // cross function boundaries and/or raised by JavaScript.
(function TestCatchCrossFunctions() { (function TestCatchCrossFunctions() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder(); let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_i); let except = builder.addException(kSig_v_i);
......
...@@ -172,6 +172,7 @@ let kExprElse = 0x05; ...@@ -172,6 +172,7 @@ let kExprElse = 0x05;
let kExprTry = 0x06; let kExprTry = 0x06;
let kExprCatch = 0x07; let kExprCatch = 0x07;
let kExprThrow = 0x08; let kExprThrow = 0x08;
let kExprCatchAll = 0x0a;
let kExprEnd = 0x0b; let kExprEnd = 0x0b;
let kExprBr = 0x0c; let kExprBr = 0x0c;
let kExprBrIf = 0x0d; let kExprBrIf = 0x0d;
......
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