[wasm][eh] Add delegate instruction to the EH prototype

Drive-by: remove reference to BrOnExnNull in wasm-module-builder.js.

R=clemensb@chromium.org
CC=aheejin@chromium.org

Bug: v8:8091
Change-Id: I42821b21c32fe8bf3410e75cf81bbff9678d3fa9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2575059
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71766}
This commit is contained in:
Thibaud Michaud 2020-12-10 11:39:00 +01:00 committed by Commit Bot
parent dfcdf7837e
commit 8eb97f5a4b
9 changed files with 330 additions and 3 deletions

View File

@ -932,6 +932,10 @@ class LiftoffCompiler {
unsupported(decoder, kExceptionHandling, "catch");
}
void Delegate(FullDecoder* decoder, uint32_t depth, Control* block) {
unsupported(decoder, kExceptionHandling, "delegate");
}
void Rethrow(FullDecoder* decoder, Control* block) {
unsupported(decoder, kExceptionHandling, "rethrow");
}

View File

@ -1055,6 +1055,7 @@ struct ControlBase : public PcForErrors<validate> {
F(Rethrow, Control* block) \
F(CatchException, const ExceptionIndexImmediate<validate>& imm, \
Control* block, Vector<Value> caught_values) \
F(Delegate, uint32_t depth, Control* block) \
F(CatchAll, Control* block) \
F(AtomicOp, WasmOpcode opcode, Vector<Value> args, \
const MemoryAccessImmediate<validate>& imm, Value* result) \
@ -1634,7 +1635,8 @@ class WasmDecoder : public Decoder {
case kExprRethrow:
case kExprBr:
case kExprBrIf:
case kExprBrOnNull: {
case kExprBrOnNull:
case kExprDelegate: {
BranchDepthImmediate<validate> imm(decoder, pc + 1);
return 1 + imm.length;
}
@ -2468,6 +2470,33 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return 1 + imm.length;
}
DECODE(Delegate) {
BranchDepthImmediate<validate> imm(this, this->pc_ + 1);
// -1 because the current try block is not included in the count.
if (!this->Validate(this->pc_ + 1, imm, control_depth() - 1)) return 0;
Control* c = &control_.back();
if (!VALIDATE(c->is_incomplete_try())) {
this->DecodeError("delegate does not match a try");
return 0;
}
// +1 because the current try block is not included in the count.
uint32_t next_try = imm.depth + 1;
while (next_try < control_depth() && !control_at(next_try)->is_try()) {
next_try++;
}
if (next_try == control_depth()) {
this->DecodeError("delegate does not target a try-catch");
return 0;
}
FallThruTo(c);
CALL_INTERFACE_IF_PARENT_REACHABLE(Delegate, next_try, c);
current_code_reachable_ = this->ok() && control_.back().reachable();
EndControl();
PopControl(c);
c->reachability = control_at(1)->innerReachability();
return 1 + imm.length;
}
DECODE(CatchAll) {
if (!VALIDATE(!control_.empty())) {
this->error("catch-all does not match any try");
@ -3212,6 +3241,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
DECODE_IMPL(Throw);
DECODE_IMPL(Try);
DECODE_IMPL(Catch);
DECODE_IMPL(Delegate);
DECODE_IMPL(CatchAll);
DECODE_IMPL(BrOnNull);
DECODE_IMPL(Let);

View File

@ -622,6 +622,29 @@ class WasmGraphBuildingInterface {
}
}
void Delegate(FullDecoder* decoder, uint32_t depth, Control* block) {
DCHECK_EQ(decoder->control_at(0), block);
DCHECK(block->is_incomplete_try());
if (block->try_info->might_throw()) {
// Merge the current env into the target handler's env.
SetEnv(block->try_info->catch_env);
TryInfo* target_try = decoder->control_at(depth)->try_info;
Goto(decoder, target_try->catch_env);
// Create or merge the exception.
if (target_try->catch_env->state == SsaEnv::kReached) {
target_try->exception = block->try_info->exception;
} else {
DCHECK_EQ(target_try->catch_env->state, SsaEnv::kMerged);
TFNode* inputs[] = {target_try->exception, block->try_info->exception,
target_try->catch_env->control};
target_try->exception = builder_->Phi(kWasmExnRef, 2, inputs);
}
}
current_catch_ = block->previous_catch;
}
void CatchAll(FullDecoder* decoder, Control* block) {
DCHECK(block->is_try_catchall() || block->is_try_catch());
DCHECK_EQ(decoder->control_at(0), block);

View File

@ -182,6 +182,7 @@ constexpr const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
// Exception handling opcodes.
CASE_OP(Try, "try")
CASE_OP(Catch, "catch")
CASE_OP(Delegate, "delegate")
CASE_OP(Throw, "throw")
CASE_OP(Rethrow, "rethrow")
CASE_OP(CatchAll, "catch-all")

View File

@ -44,6 +44,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(BrIf, 0x0d, _) \
V(BrTable, 0x0e, _) \
V(Return, 0x0f, _) \
V(Delegate, 0x16, _ /* eh_prototype */) \
V(Let, 0x17, _ /* typed_funcref prototype */) \
V(BrOnNull, 0xd4, _ /* gc prototype */)

View File

@ -188,6 +188,8 @@
#define WASM_TRY_CATCH_ALL_T(t, trystmt, catchstmt) \
kExprTry, static_cast<byte>((t).value_type_code()), trystmt, kExprCatchAll, \
catchstmt, kExprEnd
#define WASM_TRY_DELEGATE(trystmt, depth) \
kExprTry, kVoidCode, trystmt, kExprDelegate, depth
#define WASM_SELECT(tval, fval, cond) tval, fval, cond, kExprSelect
#define WASM_SELECT_I(tval, fval, cond) \

View File

@ -803,3 +803,239 @@ load("test/mjsunit/wasm/exceptions-utils.js");
assertThrowsEquals(instance.exports.large_from_js, 1e+28);
assertThrowsEquals(instance.exports.undefined_from_js, undefined);
})();
// Delegate with a try block that never throws.
(function TestDelegateNoThrow() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
builder.addFunction('test', kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprTry, kWasmI32,
kExprI32Const, 1,
kExprDelegate, 0,
kExprCatch, except1,
kExprI32Const, 2,
kExprEnd,
]).exportFunc();
instance = builder.instantiate();
assertEquals(1, instance.exports.test());
})();
// Delegate exception handling to outer try/catch block.
(function TestDelegateThrow() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
let throw_if = builder.addFunction('throw', kSig_v_i)
.addBody([
kExprLocalGet, 0,
kExprIf, kWasmStmt,
kExprThrow, except,
kExprEnd]).exportFunc();
builder.addFunction('test', kSig_i_i)
.addBody([
kExprTry, kWasmI32,
kExprTry, kWasmI32,
kExprLocalGet, 0,
kExprCallFunction, throw_if.index,
kExprI32Const, 1,
kExprDelegate, 0,
kExprCatch, except,
kExprI32Const, 2,
kExprEnd,
]).exportFunc();
instance = builder.instantiate();
assertEquals(1, instance.exports.test(0));
assertEquals(2, instance.exports.test(1));
})();
// No catch block matching the exception in the delegate target.
(function TestDelegateThrowNoCatch() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v);
let throw_fn = builder.addFunction('throw', kSig_v_v)
.addBody([kExprThrow, except1])
.exportFunc();
let throw_fn_2 = builder.addFunction('throw_2', kSig_v_v)
.addBody([kExprThrow, except2])
.exportFunc();
builder.addFunction('test', kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprTry, kWasmI32,
kExprCallFunction, throw_fn.index,
kExprI32Const, 1,
kExprDelegate, 0,
kExprCatch, except2,
kExprI32Const, 2,
kExprEnd,
]).exportFunc();
instance = builder.instantiate();
assertTraps(WebAssembly.RuntimeError, instance.exports.test);
})();
// Check that the exception is merged properly when both scopes can throw.
(function TestDelegateMerge() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v);
// throw_fn: 0 -> returns
// 1 -> throw except1
// 2 -> throw except2
let throw_fn = builder.addFunction('throw', kSig_v_i)
.addBody([
kExprBlock, kWasmStmt,
kExprBlock, kWasmStmt,
kExprBlock, kWasmStmt,
kExprLocalGet, 0,
kExprBrTable, 2, 0, 1, 2,
kExprEnd,
kExprReturn,
kExprEnd,
kExprThrow, except1,
kExprEnd,
kExprThrow, except2])
.exportFunc();
builder.addFunction('test', kSig_i_ii)
.addBody([
kExprTry, kWasmI32,
kExprLocalGet, 0,
kExprCallFunction, throw_fn.index,
kExprTry, kWasmI32,
kExprLocalGet, 1,
kExprCallFunction, throw_fn.index,
kExprI32Const, 1,
kExprDelegate, 0,
kExprCatch, except1,
kExprI32Const, 2,
kExprEnd,
]).exportFunc();
instance = builder.instantiate();
assertEquals(2, instance.exports.test(1, 0));
assertTraps(WebAssembly.RuntimeError, () => instance.exports.test(2, 0));
assertEquals(2, instance.exports.test(0, 1));
assertTraps(WebAssembly.RuntimeError, () => instance.exports.test(0, 2));
assertEquals(1, instance.exports.test(0, 0));
})();
// Delegating to a non-try block should delegate to the next try block down the
// control stack.
(function TestDelegateNonTryBlock() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
let throw_fn = builder.addFunction('throw', kSig_v_v)
.addBody([kExprThrow, except])
.exportFunc();
builder.addFunction('test', kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprBlock, kWasmI32,
kExprTry, kWasmI32,
kExprCallFunction, throw_fn.index,
kExprI32Const, 1,
kExprDelegate, 0,
kExprEnd,
kExprCatch, except,
kExprI32Const, 2,
kExprEnd,
]).exportFunc();
instance = builder.instantiate();
assertEquals(2, instance.exports.test());
})();
// Delegate to second enclosing try scope.
(function TestDelegate1() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
let throw_fn = builder.addFunction('throw', kSig_v_v)
.addBody([kExprThrow, except])
.exportFunc();
builder.addFunction('test', kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprTry, kWasmI32,
kExprTry, kWasmI32,
kExprCallFunction, throw_fn.index,
kExprI32Const, 1,
kExprDelegate, 1,
kExprCatch, except,
kExprI32Const, 2,
kExprEnd,
kExprCatch, except,
kExprI32Const, 3,
kExprEnd,
]).exportFunc();
instance = builder.instantiate();
assertEquals(3, instance.exports.test());
})();
(function TestDelegateInCatch() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v);
// throw_fn: 0 -> returns
// 1 -> throw except1
// 2 -> throw except2
let throw_fn = builder.addFunction('throw', kSig_v_i)
.addBody([
kExprBlock, kWasmStmt,
kExprBlock, kWasmStmt,
kExprBlock, kWasmStmt,
kExprLocalGet, 0,
kExprBrTable, 2, 0, 1, 2,
kExprEnd,
kExprReturn,
kExprEnd,
kExprThrow, except1,
kExprEnd,
kExprThrow, except2])
.exportFunc();
builder.addFunction('test', kSig_i_i)
.addBody([
kExprTry, kWasmI32,
kExprThrow, except1,
kExprCatch, except1,
kExprTry, kWasmStmt,
kExprLocalGet, 0,
kExprCallFunction, throw_fn.index,
kExprDelegate, 0,
kExprI32Const, 1,
kExprCatch, except2,
kExprI32Const, 2,
kExprEnd,
]).exportFunc();
instance = builder.instantiate();
assertEquals(1, instance.exports.test(0));
assertTraps(WebAssembly.RuntimeError, () => instance.exports.test(1));
assertEquals(2, instance.exports.test(2));
})();
(function TestDelegateUnreachable() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except1 = builder.addException(kSig_v_v);
let except2 = builder.addException(kSig_v_v);
builder.addFunction('test', kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprTry, kWasmStmt,
kExprThrow, except1,
kExprDelegate, 0,
kExprI32Const, 1,
kExprCatch, except1,
kExprI32Const, 2,
kExprCatch, except2,
kExprI32Const, 3,
kExprEnd,
]).exportFunc();
instance = builder.instantiate();
assertEquals(2, instance.exports.test());
})();

View File

@ -230,6 +230,7 @@ const kWasmOpcodes = {
'ReturnCallIndirect': 0x13,
'CallRef': 0x14,
'ReturnCallRef': 0x15,
'Delegate': 0x16,
'Let': 0x17,
'Drop': 0x1a,
'Select': 0x1b,
@ -734,7 +735,7 @@ let kTrapFuncSigMismatch = 7;
let kTrapUnalignedAccess = 8;
let kTrapDataSegmentDropped = 9;
let kTrapElemSegmentDropped = 10;
let kTrapRethrowNull = 12;
let kTrapRethrowNull = 11;
let kTrapMsgs = [
"unreachable",
@ -748,7 +749,6 @@ let kTrapMsgs = [
"operation does not support unaligned accesses",
"data segment has been dropped",
"element segment has been dropped",
"br_on_exn on null value",
"rethrowing null value"
];

View File

@ -2889,6 +2889,35 @@ TEST_F(FunctionBodyDecoderTest, Rethrow) {
ExpectFailure(sigs.v_v(), {kExprRethrow});
}
TEST_F(FunctionBodyDecoderTest, TryDelegate) {
WASM_FEATURE_SCOPE(eh);
byte ex = builder.AddException(sigs.v_v());
ExpectValidates(sigs.v_v(), {WASM_TRY_OP,
WASM_TRY_DELEGATE(WASM_STMTS(kExprThrow, ex), 0),
kExprCatch, ex, kExprEnd});
ExpectValidates(sigs.v_v(),
{WASM_TRY_OP,
WASM_BLOCK(WASM_TRY_DELEGATE(WASM_STMTS(kExprThrow, ex), 0)),
kExprCatch, ex, kExprEnd});
// delegate after catch.
// delegate after catch_all.
ExpectFailure(
sigs.v_v(),
{WASM_BLOCK(WASM_TRY_OP, WASM_TRY_DELEGATE(WASM_STMTS(kExprThrow, ex), 1),
kExprCatch, ex, kExprEnd)},
kAppendEnd, "delegate does not target a try-catch");
ExpectFailure(
sigs.v_v(),
{WASM_TRY_OP, WASM_TRY_OP, kExprCatch, ex, kExprDelegate, 0, kExprEnd},
kAppendEnd, "delegate does not match a try");
ExpectFailure(
sigs.v_v(),
{WASM_TRY_OP, WASM_TRY_OP, kExprCatchAll, kExprDelegate, 1, kExprEnd},
kAppendEnd, "delegate does not match a try");
}
#undef WASM_TRY_OP
TEST_F(FunctionBodyDecoderTest, MultiValBlock1) {
@ -4504,6 +4533,7 @@ TEST_F(WasmOpcodeLengthTest, Statements) {
ExpectLength(1, kExprEnd);
ExpectLength(1, kExprSelect);
ExpectLength(2, kExprCatch);
ExpectLength(2, kExprDelegate);
ExpectLength(2, kExprRethrow);
ExpectLength(2, kExprBr);
ExpectLength(2, kExprBrIf);