[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:
parent
dfcdf7837e
commit
8eb97f5a4b
@ -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");
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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")
|
||||
|
@ -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 */)
|
||||
|
||||
|
@ -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) \
|
||||
|
@ -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());
|
||||
})();
|
||||
|
@ -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"
|
||||
];
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user