[wasm] Add support for "br_on_exn" in the interpreter.
This adds preliminary support for handling the "br_on_exn" opcode in the interpreter. It also makes "catch" and "rethrow" use a proper exception reference instead of a dummy value. To that end this also adds {Handle<>} as a new kind of {WasmValue} which is intended to pass reference values (e.g. "anyref" or "except_ref") to the runtime system. Therefore lifetime of such a {WasmValue} is directly coupled to any surrounding {HandleScope}. For now we just store {Handle<>} directly on the simulated operand stack of the interpreter. This is of course bogus, since the surrounding scope does not outlive the interpreter activation. Decoupling the lifetime of the operand stack from a {HandleScope} will be done in a follow-up CL. As a drive-by this change also implements support for the "ref_null" and the "ref_is_null" opcodes as a proof-of-concept that the new {WasmValue} is also applicable to the "anyref" reference type. R=clemensh@chromium.org TEST=cctest/test-run-wasm-interpreter/ReferenceTypeLocals BUG=v8:8091,v8:7581 Change-Id: I2307e0689a19c4aab1d67f1ba6742cb3cc31aa3c Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1550299 Commit-Queue: Michael Starzinger <mstarzinger@chromium.org> Reviewed-by: Clemens Hammacher <clemensh@chromium.org> Cr-Commit-Position: refs/heads/master@{#60598}
This commit is contained in:
parent
72269e3fa4
commit
46a99b07fc
@ -893,6 +893,19 @@ class SideTable : public ZoneObject {
|
|||||||
stack_height = c->end_label->target_stack_height + kCatchInArity;
|
stack_height = c->end_label->target_stack_height + kCatchInArity;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case kExprBrOnExn: {
|
||||||
|
BranchOnExceptionImmediate<Decoder::kNoValidate> imm(&i, i.pc());
|
||||||
|
uint32_t depth = imm.depth.depth; // Extracted for convenience.
|
||||||
|
imm.index.exception = &module->exceptions[imm.index.index];
|
||||||
|
DCHECK_EQ(0, imm.index.exception->sig->return_count());
|
||||||
|
size_t params = imm.index.exception->sig->parameter_count();
|
||||||
|
// Taken branches pop the exception and push the encoded values.
|
||||||
|
uint32_t height = stack_height - 1 + static_cast<uint32_t>(params);
|
||||||
|
TRACE("control @%u: BrOnExn[depth=%u]\n", i.pc_offset(), depth);
|
||||||
|
Control* c = &control_stack[control_stack.size() - depth - 1];
|
||||||
|
if (!unreachable) c->end_label->Ref(i.pc(), height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case kExprEnd: {
|
case kExprEnd: {
|
||||||
Control* c = &control_stack.back();
|
Control* c = &control_stack.back();
|
||||||
TRACE("control @%u: End\n", i.pc_offset());
|
TRACE("control @%u: End\n", i.pc_offset());
|
||||||
@ -1253,10 +1266,7 @@ class ThreadImpl {
|
|||||||
InterpreterCode* code = frame.code;
|
InterpreterCode* code = frame.code;
|
||||||
if (code->side_table->HasEntryAt(frame.pc)) {
|
if (code->side_table->HasEntryAt(frame.pc)) {
|
||||||
TRACE("----- HANDLE -----\n");
|
TRACE("----- HANDLE -----\n");
|
||||||
// TODO(mstarzinger): Push a reference to the pending exception instead
|
Push(WasmValue(handle(isolate->pending_exception(), isolate)));
|
||||||
// of a bogus {int32_t(0)} value here once the interpreter supports it.
|
|
||||||
USE(isolate->pending_exception());
|
|
||||||
Push(WasmValue(int32_t{0}));
|
|
||||||
isolate->clear_pending_exception();
|
isolate->clear_pending_exception();
|
||||||
frame.pc += JumpToHandlerDelta(code, frame.pc);
|
frame.pc += JumpToHandlerDelta(code, frame.pc);
|
||||||
TRACE(" => handler #%zu (#%u @%zu)\n", frames_.size() - 1,
|
TRACE(" => handler #%zu (#%u @%zu)\n", frames_.size() - 1,
|
||||||
@ -1291,6 +1301,8 @@ class ThreadImpl {
|
|||||||
|
|
||||||
CodeMap* codemap_;
|
CodeMap* codemap_;
|
||||||
Handle<WasmInstanceObject> instance_object_;
|
Handle<WasmInstanceObject> instance_object_;
|
||||||
|
// TODO(mstarzinger): The operand stack will need to be changed so that the
|
||||||
|
// value lifetime of {WasmValue} is not coupled to a {HandleScope}.
|
||||||
std::unique_ptr<WasmValue[]> stack_;
|
std::unique_ptr<WasmValue[]> stack_;
|
||||||
WasmValue* stack_limit_ = nullptr; // End of allocated stack space.
|
WasmValue* stack_limit_ = nullptr; // End of allocated stack space.
|
||||||
WasmValue* sp_ = nullptr; // Current stack pointer.
|
WasmValue* sp_ = nullptr; // Current stack pointer.
|
||||||
@ -1349,6 +1361,13 @@ class ThreadImpl {
|
|||||||
break;
|
break;
|
||||||
WASM_CTYPES(CASE_TYPE)
|
WASM_CTYPES(CASE_TYPE)
|
||||||
#undef CASE_TYPE
|
#undef CASE_TYPE
|
||||||
|
case kWasmAnyRef:
|
||||||
|
case kWasmAnyFunc:
|
||||||
|
case kWasmExceptRef: {
|
||||||
|
Isolate* isolate = instance_object_->GetIsolate();
|
||||||
|
val = WasmValue(isolate->factory()->null_value());
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
break;
|
break;
|
||||||
@ -2410,14 +2429,90 @@ class ThreadImpl {
|
|||||||
|
|
||||||
// Throw a given existing exception. Returns true if the exception is being
|
// Throw a given existing exception. Returns true if the exception is being
|
||||||
// handled locally by the interpreter, false otherwise (interpreter exits).
|
// handled locally by the interpreter, false otherwise (interpreter exits).
|
||||||
bool DoRethrowException(WasmValue* exception) {
|
bool DoRethrowException(WasmValue exception) {
|
||||||
Isolate* isolate = instance_object_->GetIsolate();
|
Isolate* isolate = instance_object_->GetIsolate();
|
||||||
// TODO(mstarzinger): Use the passed {exception} here once reference types
|
isolate->ReThrow(*exception.to_anyref());
|
||||||
// as values on the operand stack are supported by the interpreter.
|
|
||||||
isolate->ReThrow(*isolate->factory()->undefined_value());
|
|
||||||
return HandleException(isolate) == WasmInterpreter::Thread::HANDLED;
|
return HandleException(isolate) == WasmInterpreter::Thread::HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determines whether the given exception has a tag matching the expected tag
|
||||||
|
// for the given index within the exception table of the current instance.
|
||||||
|
bool MatchingExceptionTag(Handle<Object> exception_object, uint32_t index) {
|
||||||
|
Isolate* isolate = instance_object_->GetIsolate();
|
||||||
|
Handle<Object> caught_tag =
|
||||||
|
WasmExceptionPackage::GetExceptionTag(isolate, exception_object);
|
||||||
|
Handle<Object> expected_tag =
|
||||||
|
handle(instance_object_->exceptions_table()->get(index), isolate);
|
||||||
|
DCHECK(expected_tag->IsWasmExceptionTag());
|
||||||
|
return expected_tag.is_identical_to(caught_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DecodeI32ExceptionValue(Handle<FixedArray> encoded_values,
|
||||||
|
uint32_t* encoded_index, uint32_t* value) {
|
||||||
|
uint32_t msb = Smi::cast(encoded_values->get((*encoded_index)++)).value();
|
||||||
|
uint32_t lsb = Smi::cast(encoded_values->get((*encoded_index)++)).value();
|
||||||
|
*value = (msb << 16) | (lsb & 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DecodeI64ExceptionValue(Handle<FixedArray> encoded_values,
|
||||||
|
uint32_t* encoded_index, uint64_t* value) {
|
||||||
|
uint32_t lsb = 0, msb = 0;
|
||||||
|
DecodeI32ExceptionValue(encoded_values, encoded_index, &msb);
|
||||||
|
DecodeI32ExceptionValue(encoded_values, encoded_index, &lsb);
|
||||||
|
*value = (static_cast<uint64_t>(msb) << 32) | static_cast<uint64_t>(lsb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack the values encoded in the given exception. The exception values are
|
||||||
|
// pushed onto the operand stack. Callers must perform a tag check to ensure
|
||||||
|
// the encoded values match the expected signature of the exception.
|
||||||
|
void DoUnpackException(const WasmException* exception,
|
||||||
|
Handle<Object> exception_object) {
|
||||||
|
Isolate* isolate = instance_object_->GetIsolate();
|
||||||
|
Handle<FixedArray> encoded_values = Handle<FixedArray>::cast(
|
||||||
|
WasmExceptionPackage::GetExceptionValues(isolate, exception_object));
|
||||||
|
// Decode the exception values from the given exception package and push
|
||||||
|
// them onto the operand stack. This encoding has to be in sync with other
|
||||||
|
// backends so that exceptions can be passed between them.
|
||||||
|
const WasmExceptionSig* sig = exception->sig;
|
||||||
|
uint32_t encoded_index = 0;
|
||||||
|
for (size_t i = 0; i < sig->parameter_count(); ++i) {
|
||||||
|
WasmValue value;
|
||||||
|
switch (sig->GetParam(i)) {
|
||||||
|
case kWasmI32: {
|
||||||
|
uint32_t u32 = 0;
|
||||||
|
DecodeI32ExceptionValue(encoded_values, &encoded_index, &u32);
|
||||||
|
value = WasmValue(u32);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kWasmF32: {
|
||||||
|
uint32_t f32_bits = 0;
|
||||||
|
DecodeI32ExceptionValue(encoded_values, &encoded_index, &f32_bits);
|
||||||
|
value = WasmValue(Float32::FromBits(f32_bits));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kWasmI64: {
|
||||||
|
uint64_t u64 = 0;
|
||||||
|
DecodeI64ExceptionValue(encoded_values, &encoded_index, &u64);
|
||||||
|
value = WasmValue(u64);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kWasmF64: {
|
||||||
|
uint64_t f64_bits = 0;
|
||||||
|
DecodeI64ExceptionValue(encoded_values, &encoded_index, &f64_bits);
|
||||||
|
value = WasmValue(Float64::FromBits(f64_bits));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kWasmAnyRef:
|
||||||
|
UNIMPLEMENTED();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
Push(value);
|
||||||
|
}
|
||||||
|
DCHECK_EQ(WasmExceptionPackage::GetEncodedSize(exception), encoded_index);
|
||||||
|
}
|
||||||
|
|
||||||
void Execute(InterpreterCode* code, pc_t pc, int max) {
|
void Execute(InterpreterCode* code, pc_t pc, int max) {
|
||||||
DCHECK_NOT_NULL(code->side_table);
|
DCHECK_NOT_NULL(code->side_table);
|
||||||
DCHECK(!frames_.empty());
|
DCHECK(!frames_.empty());
|
||||||
@ -2531,10 +2626,27 @@ class ThreadImpl {
|
|||||||
case kExprRethrow: {
|
case kExprRethrow: {
|
||||||
WasmValue ex = Pop();
|
WasmValue ex = Pop();
|
||||||
CommitPc(pc); // Needed for local unwinding.
|
CommitPc(pc); // Needed for local unwinding.
|
||||||
if (!DoRethrowException(&ex)) return;
|
if (!DoRethrowException(ex)) return;
|
||||||
ReloadFromFrameOnException(&decoder, &code, &pc, &limit);
|
ReloadFromFrameOnException(&decoder, &code, &pc, &limit);
|
||||||
continue; // Do not bump pc.
|
continue; // Do not bump pc.
|
||||||
}
|
}
|
||||||
|
case kExprBrOnExn: {
|
||||||
|
BranchOnExceptionImmediate<Decoder::kNoValidate> imm(&decoder,
|
||||||
|
code->at(pc));
|
||||||
|
WasmValue ex = Pop();
|
||||||
|
Handle<Object> exception = ex.to_anyref();
|
||||||
|
if (MatchingExceptionTag(exception, imm.index.index)) {
|
||||||
|
imm.index.exception = &module()->exceptions[imm.index.index];
|
||||||
|
DoUnpackException(imm.index.exception, exception);
|
||||||
|
len = DoBreak(code, pc, imm.depth.depth);
|
||||||
|
TRACE(" match => @%zu\n", pc + len);
|
||||||
|
} else {
|
||||||
|
Push(ex); // Exception remains on stack.
|
||||||
|
TRACE(" false => fallthrough\n");
|
||||||
|
len = 1 + imm.length;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case kExprSelect: {
|
case kExprSelect: {
|
||||||
WasmValue cond = Pop();
|
WasmValue cond = Pop();
|
||||||
WasmValue fval = Pop();
|
WasmValue fval = Pop();
|
||||||
@ -2614,6 +2726,11 @@ class ThreadImpl {
|
|||||||
len = 1 + imm.length;
|
len = 1 + imm.length;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case kExprRefNull: {
|
||||||
|
Isolate* isolate = instance_object_->GetIsolate();
|
||||||
|
Push(WasmValue(isolate->factory()->null_value()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case kExprGetLocal: {
|
case kExprGetLocal: {
|
||||||
LocalIndexImmediate<Decoder::kNoValidate> imm(&decoder, code->at(pc));
|
LocalIndexImmediate<Decoder::kNoValidate> imm(&decoder, code->at(pc));
|
||||||
Push(GetStackValue(frames_.back().sp + imm.index));
|
Push(GetStackValue(frames_.back().sp + imm.index));
|
||||||
@ -2968,6 +3085,11 @@ class ThreadImpl {
|
|||||||
SIGN_EXTENSION_CASE(I64SExtendI16, int64_t, int16_t);
|
SIGN_EXTENSION_CASE(I64SExtendI16, int64_t, int16_t);
|
||||||
SIGN_EXTENSION_CASE(I64SExtendI32, int64_t, int32_t);
|
SIGN_EXTENSION_CASE(I64SExtendI32, int64_t, int32_t);
|
||||||
#undef SIGN_EXTENSION_CASE
|
#undef SIGN_EXTENSION_CASE
|
||||||
|
case kExprRefIsNull: {
|
||||||
|
uint32_t result = Pop().to_anyref()->IsNull() ? 1 : 0;
|
||||||
|
Push(WasmValue(result));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case kNumericPrefix: {
|
case kNumericPrefix: {
|
||||||
++len;
|
++len;
|
||||||
if (!ExecuteNumericOp(opcode, &decoder, code, pc, len)) return;
|
if (!ExecuteNumericOp(opcode, &decoder, code, pc, len)) return;
|
||||||
@ -3149,6 +3271,15 @@ class ThreadImpl {
|
|||||||
PrintF("i32x4:%d %d %d %d", s.val[0], s.val[1], s.val[2], s.val[3]);
|
PrintF("i32x4:%d %d %d %d", s.val[0], s.val[1], s.val[2], s.val[3]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case kWasmAnyRef: {
|
||||||
|
Handle<Object> ref = val.to_anyref();
|
||||||
|
if (ref->IsNull()) {
|
||||||
|
PrintF("ref:null");
|
||||||
|
} else {
|
||||||
|
PrintF("ref:0x%" V8PRIxPTR, ref->ptr());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case kWasmStmt:
|
case kWasmStmt:
|
||||||
PrintF("void");
|
PrintF("void");
|
||||||
break;
|
break;
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#define V8_WASM_WASM_VALUE_H_
|
#define V8_WASM_WASM_VALUE_H_
|
||||||
|
|
||||||
#include "src/boxed-float.h"
|
#include "src/boxed-float.h"
|
||||||
|
#include "src/handles.h"
|
||||||
#include "src/v8memory.h"
|
#include "src/v8memory.h"
|
||||||
#include "src/wasm/wasm-opcodes.h"
|
#include "src/wasm/wasm-opcodes.h"
|
||||||
#include "src/zone/zone-containers.h"
|
#include "src/zone/zone-containers.h"
|
||||||
@ -62,7 +63,10 @@ class Simd128 {
|
|||||||
V(f32_boxed, kWasmF32, Float32) \
|
V(f32_boxed, kWasmF32, Float32) \
|
||||||
V(f64, kWasmF64, double) \
|
V(f64, kWasmF64, double) \
|
||||||
V(f64_boxed, kWasmF64, Float64) \
|
V(f64_boxed, kWasmF64, Float64) \
|
||||||
V(s128, kWasmS128, Simd128)
|
V(s128, kWasmS128, Simd128) \
|
||||||
|
V(anyref, kWasmAnyRef, Handle<Object>)
|
||||||
|
|
||||||
|
ASSERT_TRIVIALLY_COPYABLE(Handle<Object>);
|
||||||
|
|
||||||
// A wasm value with type information.
|
// A wasm value with type information.
|
||||||
class WasmValue {
|
class WasmValue {
|
||||||
|
@ -430,6 +430,21 @@ TEST(MemoryGrowInvalidSize) {
|
|||||||
CHECK_EQ(-1, r.Call(1048575));
|
CHECK_EQ(-1, r.Call(1048575));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ReferenceTypeLocals) {
|
||||||
|
{
|
||||||
|
WasmRunner<int32_t> r(ExecutionTier::kInterpreter);
|
||||||
|
BUILD(r, WASM_REF_IS_NULL(WASM_REF_NULL));
|
||||||
|
CHECK_EQ(1, r.Call());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
WasmRunner<int32_t> r(ExecutionTier::kInterpreter);
|
||||||
|
r.AllocateLocal(kWasmAnyRef);
|
||||||
|
BUILD(r, WASM_REF_IS_NULL(WASM_GET_LOCAL(0)));
|
||||||
|
CHECK_EQ(1, r.Call());
|
||||||
|
}
|
||||||
|
// TODO(mstarzinger): Test and support global anyref variables.
|
||||||
|
}
|
||||||
|
|
||||||
TEST(TestPossibleNondeterminism) {
|
TEST(TestPossibleNondeterminism) {
|
||||||
{
|
{
|
||||||
WasmRunner<int32_t, float> r(ExecutionTier::kInterpreter);
|
WasmRunner<int32_t, float> r(ExecutionTier::kInterpreter);
|
||||||
|
@ -346,6 +346,7 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
|
|||||||
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 56)
|
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 56)
|
||||||
|
|
||||||
#define WASM_REF_NULL kExprRefNull
|
#define WASM_REF_NULL kExprRefNull
|
||||||
|
#define WASM_REF_IS_NULL(val) val, kExprRefIsNull
|
||||||
|
|
||||||
#define WASM_GET_LOCAL(index) kExprGetLocal, static_cast<byte>(index)
|
#define WASM_GET_LOCAL(index) kExprGetLocal, static_cast<byte>(index)
|
||||||
#define WASM_SET_LOCAL(index, val) val, kExprSetLocal, static_cast<byte>(index)
|
#define WASM_SET_LOCAL(index, val) val, kExprSetLocal, static_cast<byte>(index)
|
||||||
|
Loading…
Reference in New Issue
Block a user