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 {
Control* block, const Vector<Value>& args) {
unsupported(decoder, "throw");
}
void Rethrow(FullDecoder* decoder, Control* block) {
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 AtomicOp(FullDecoder* decoder, WasmOpcode opcode, Vector<Value> args,
const MemoryAccessImmediate<validate>& imm, Value* result) {
unsupported(decoder, "atomicop");
......
......@@ -660,10 +660,12 @@ struct ControlWithNamedConstructors : public ControlBase<Value> {
const Value& input, Value* result) \
F(Simd8x16ShuffleOp, const Simd8x16ShuffleImmediate<validate>& imm, \
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) \
F(Rethrow, Control* block) \
F(CatchException, const ExceptionIndexImmediate<validate>& imm, \
Control* block, Vector<Value> caught_values) \
F(CatchAll, Control* block) \
F(AtomicOp, WasmOpcode opcode, Vector<Value> args, \
const MemoryAccessImmediate<validate>& imm, Value* result)
......@@ -1549,6 +1551,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
break;
}
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.
break;
}
......@@ -1615,6 +1620,12 @@ class WasmFullDecoder : public WasmDecoder<validate> {
PushMergeValues(c, &c->start_merge);
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);
// A loop just leaves the values on the stack.
......
......@@ -407,13 +407,6 @@ class WasmGraphBuildingInterface {
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,
Control* block, const Vector<Value>& value_args) {
int count = value_args.length();
......@@ -426,6 +419,18 @@ class WasmGraphBuildingInterface {
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,
const ExceptionIndexImmediate<validate>& imm,
Control* block, Vector<Value> values) {
......@@ -454,15 +459,7 @@ class WasmGraphBuildingInterface {
if_no_catch_env->control = if_no_catch;
SsaEnv* if_catch_env = Steal(decoder->zone(), ssa_env_);
if_catch_env->control = if_catch;
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);
}
block->try_info->catch_env = if_no_catch_env;
SetEnv(if_catch_env);
if (exception != nullptr) {
......@@ -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,
const MemoryAccessImmediate<validate>& imm, Value* result) {
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) {
// First we just test that "except_ref" local variables are allowed.
(function TestLocalExceptRef() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction("push_and_drop_except_ref", kSig_v_v)
.addBody([
......@@ -42,6 +43,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// The following method doesn't attempt to catch an raised exception.
(function TestThrowSimple() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
builder.addFunction("throw_if_param_not_zero", kSig_i_i)
......@@ -63,6 +65,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test that empty try/catch blocks work.
(function TestCatchEmptyBlocks() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
builder.addFunction("catch_empty_try", kSig_v_v)
......@@ -78,6 +81,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Now that we know throwing works, we test catching the exceptions we raise.
(function TestCatchSimple() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
builder.addFunction("simple_throw_catch_to_0_1", kSig_i_i)
......@@ -101,6 +105,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test that we can distinguish which exception was thrown.
(function TestCatchComplex() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v);
......@@ -140,6 +145,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing an exception with multiple values.
(function TestThrowMultipleValues() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_ii);
builder.addFunction("throw_1_2", kSig_v_v)
......@@ -155,6 +161,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing/catching the i32 parameter value.
(function TestThrowCatchParamI() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_i);
builder.addFunction("throw_catch_param", kSig_i_i)
......@@ -176,6 +183,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a thrown exception with an integer exception.
(function TestThrowParamI() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_i);
builder.addFunction("throw_param", kSig_v_i)
......@@ -191,6 +199,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing/catching the f32 parameter value.
(function TestThrowCatchParamF() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_f);
builder.addFunction("throw_catch_param", kSig_f_f)
......@@ -211,6 +220,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a thrown exception with a float value.
(function TestThrowParamF() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_f);
builder.addFunction("throw_param", kSig_v_f)
......@@ -226,6 +236,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing/catching an I64 value
(function TestThrowCatchParamL() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_l);
builder.addFunction("throw_catch_param", kSig_i_i)
......@@ -258,6 +269,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a thrown exception with an I64 value.
(function TestThrowParamL() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_l);
builder.addFunction("throw_param", kSig_v_ii)
......@@ -279,6 +291,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test throwing/catching the F64 parameter value
(function TestThrowCatchParamD() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_d);
builder.addFunction("throw_catch_param", kSig_d_d)
......@@ -299,6 +312,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a thrown exception with an f64 value.
(function TestThrowParamD() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_d);
builder.addFunction("throw_param", kSig_v_f)
......@@ -315,6 +329,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Test the encoding of a computed parameter value.
(function TestThrowParamComputed() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_i);
builder.addFunction("throw_expr_with_params", kSig_v_ddi)
......@@ -341,6 +356,7 @@ function assertWasmThrows(instance, runtime_id, values, code) {
// Now that we know catching works locally, we test catching exceptions that
// cross function boundaries and/or raised by JavaScript.
(function TestCatchCrossFunctions() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_i);
......
......@@ -172,6 +172,7 @@ let kExprElse = 0x05;
let kExprTry = 0x06;
let kExprCatch = 0x07;
let kExprThrow = 0x08;
let kExprCatchAll = 0x0a;
let kExprEnd = 0x0b;
let kExprBr = 0x0c;
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