Commit 8eb97f5a authored by Thibaud Michaud's avatar Thibaud Michaud Committed by Commit Bot

[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: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71766}
parent dfcdf783
...@@ -932,6 +932,10 @@ class LiftoffCompiler { ...@@ -932,6 +932,10 @@ class LiftoffCompiler {
unsupported(decoder, kExceptionHandling, "catch"); unsupported(decoder, kExceptionHandling, "catch");
} }
void Delegate(FullDecoder* decoder, uint32_t depth, Control* block) {
unsupported(decoder, kExceptionHandling, "delegate");
}
void Rethrow(FullDecoder* decoder, Control* block) { void Rethrow(FullDecoder* decoder, Control* block) {
unsupported(decoder, kExceptionHandling, "rethrow"); unsupported(decoder, kExceptionHandling, "rethrow");
} }
......
...@@ -1055,6 +1055,7 @@ struct ControlBase : public PcForErrors<validate> { ...@@ -1055,6 +1055,7 @@ struct ControlBase : public PcForErrors<validate> {
F(Rethrow, Control* block) \ 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(Delegate, uint32_t depth, Control* block) \
F(CatchAll, Control* block) \ 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) \
...@@ -1634,7 +1635,8 @@ class WasmDecoder : public Decoder { ...@@ -1634,7 +1635,8 @@ class WasmDecoder : public Decoder {
case kExprRethrow: case kExprRethrow:
case kExprBr: case kExprBr:
case kExprBrIf: case kExprBrIf:
case kExprBrOnNull: { case kExprBrOnNull:
case kExprDelegate: {
BranchDepthImmediate<validate> imm(decoder, pc + 1); BranchDepthImmediate<validate> imm(decoder, pc + 1);
return 1 + imm.length; return 1 + imm.length;
} }
...@@ -2468,6 +2470,33 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2468,6 +2470,33 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return 1 + imm.length; 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) { DECODE(CatchAll) {
if (!VALIDATE(!control_.empty())) { if (!VALIDATE(!control_.empty())) {
this->error("catch-all does not match any try"); this->error("catch-all does not match any try");
...@@ -3212,6 +3241,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -3212,6 +3241,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
DECODE_IMPL(Throw); DECODE_IMPL(Throw);
DECODE_IMPL(Try); DECODE_IMPL(Try);
DECODE_IMPL(Catch); DECODE_IMPL(Catch);
DECODE_IMPL(Delegate);
DECODE_IMPL(CatchAll); DECODE_IMPL(CatchAll);
DECODE_IMPL(BrOnNull); DECODE_IMPL(BrOnNull);
DECODE_IMPL(Let); DECODE_IMPL(Let);
......
...@@ -622,6 +622,29 @@ class WasmGraphBuildingInterface { ...@@ -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) { void CatchAll(FullDecoder* decoder, Control* block) {
DCHECK(block->is_try_catchall() || block->is_try_catch()); DCHECK(block->is_try_catchall() || block->is_try_catch());
DCHECK_EQ(decoder->control_at(0), block); DCHECK_EQ(decoder->control_at(0), block);
......
...@@ -182,6 +182,7 @@ constexpr const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) { ...@@ -182,6 +182,7 @@ constexpr const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
// Exception handling opcodes. // Exception handling opcodes.
CASE_OP(Try, "try") CASE_OP(Try, "try")
CASE_OP(Catch, "catch") CASE_OP(Catch, "catch")
CASE_OP(Delegate, "delegate")
CASE_OP(Throw, "throw") CASE_OP(Throw, "throw")
CASE_OP(Rethrow, "rethrow") CASE_OP(Rethrow, "rethrow")
CASE_OP(CatchAll, "catch-all") CASE_OP(CatchAll, "catch-all")
......
...@@ -44,6 +44,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig, ...@@ -44,6 +44,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(BrIf, 0x0d, _) \ V(BrIf, 0x0d, _) \
V(BrTable, 0x0e, _) \ V(BrTable, 0x0e, _) \
V(Return, 0x0f, _) \ V(Return, 0x0f, _) \
V(Delegate, 0x16, _ /* eh_prototype */) \
V(Let, 0x17, _ /* typed_funcref prototype */) \ V(Let, 0x17, _ /* typed_funcref prototype */) \
V(BrOnNull, 0xd4, _ /* gc prototype */) V(BrOnNull, 0xd4, _ /* gc prototype */)
......
...@@ -188,6 +188,8 @@ ...@@ -188,6 +188,8 @@
#define WASM_TRY_CATCH_ALL_T(t, trystmt, catchstmt) \ #define WASM_TRY_CATCH_ALL_T(t, trystmt, catchstmt) \
kExprTry, static_cast<byte>((t).value_type_code()), trystmt, kExprCatchAll, \ kExprTry, static_cast<byte>((t).value_type_code()), trystmt, kExprCatchAll, \
catchstmt, kExprEnd 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(tval, fval, cond) tval, fval, cond, kExprSelect
#define WASM_SELECT_I(tval, fval, cond) \ #define WASM_SELECT_I(tval, fval, cond) \
......
...@@ -803,3 +803,239 @@ load("test/mjsunit/wasm/exceptions-utils.js"); ...@@ -803,3 +803,239 @@ load("test/mjsunit/wasm/exceptions-utils.js");
assertThrowsEquals(instance.exports.large_from_js, 1e+28); assertThrowsEquals(instance.exports.large_from_js, 1e+28);
assertThrowsEquals(instance.exports.undefined_from_js, undefined); 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 = { ...@@ -230,6 +230,7 @@ const kWasmOpcodes = {
'ReturnCallIndirect': 0x13, 'ReturnCallIndirect': 0x13,
'CallRef': 0x14, 'CallRef': 0x14,
'ReturnCallRef': 0x15, 'ReturnCallRef': 0x15,
'Delegate': 0x16,
'Let': 0x17, 'Let': 0x17,
'Drop': 0x1a, 'Drop': 0x1a,
'Select': 0x1b, 'Select': 0x1b,
...@@ -734,7 +735,7 @@ let kTrapFuncSigMismatch = 7; ...@@ -734,7 +735,7 @@ let kTrapFuncSigMismatch = 7;
let kTrapUnalignedAccess = 8; let kTrapUnalignedAccess = 8;
let kTrapDataSegmentDropped = 9; let kTrapDataSegmentDropped = 9;
let kTrapElemSegmentDropped = 10; let kTrapElemSegmentDropped = 10;
let kTrapRethrowNull = 12; let kTrapRethrowNull = 11;
let kTrapMsgs = [ let kTrapMsgs = [
"unreachable", "unreachable",
...@@ -748,7 +749,6 @@ let kTrapMsgs = [ ...@@ -748,7 +749,6 @@ let kTrapMsgs = [
"operation does not support unaligned accesses", "operation does not support unaligned accesses",
"data segment has been dropped", "data segment has been dropped",
"element segment has been dropped", "element segment has been dropped",
"br_on_exn on null value",
"rethrowing null value" "rethrowing null value"
]; ];
......
...@@ -2889,6 +2889,35 @@ TEST_F(FunctionBodyDecoderTest, Rethrow) { ...@@ -2889,6 +2889,35 @@ TEST_F(FunctionBodyDecoderTest, Rethrow) {
ExpectFailure(sigs.v_v(), {kExprRethrow}); 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 #undef WASM_TRY_OP
TEST_F(FunctionBodyDecoderTest, MultiValBlock1) { TEST_F(FunctionBodyDecoderTest, MultiValBlock1) {
...@@ -4504,6 +4533,7 @@ TEST_F(WasmOpcodeLengthTest, Statements) { ...@@ -4504,6 +4533,7 @@ TEST_F(WasmOpcodeLengthTest, Statements) {
ExpectLength(1, kExprEnd); ExpectLength(1, kExprEnd);
ExpectLength(1, kExprSelect); ExpectLength(1, kExprSelect);
ExpectLength(2, kExprCatch); ExpectLength(2, kExprCatch);
ExpectLength(2, kExprDelegate);
ExpectLength(2, kExprRethrow); ExpectLength(2, kExprRethrow);
ExpectLength(2, kExprBr); ExpectLength(2, kExprBr);
ExpectLength(2, kExprBrIf); ExpectLength(2, kExprBrIf);
......
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