[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:
Michael Starzinger 2019-04-03 13:06:41 +02:00 committed by Commit Bot
parent 72269e3fa4
commit 46a99b07fc
4 changed files with 161 additions and 10 deletions

View File

@ -893,6 +893,19 @@ class SideTable : public ZoneObject {
stack_height = c->end_label->target_stack_height + kCatchInArity;
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: {
Control* c = &control_stack.back();
TRACE("control @%u: End\n", i.pc_offset());
@ -1253,10 +1266,7 @@ class ThreadImpl {
InterpreterCode* code = frame.code;
if (code->side_table->HasEntryAt(frame.pc)) {
TRACE("----- HANDLE -----\n");
// TODO(mstarzinger): Push a reference to the pending exception instead
// of a bogus {int32_t(0)} value here once the interpreter supports it.
USE(isolate->pending_exception());
Push(WasmValue(int32_t{0}));
Push(WasmValue(handle(isolate->pending_exception(), isolate)));
isolate->clear_pending_exception();
frame.pc += JumpToHandlerDelta(code, frame.pc);
TRACE(" => handler #%zu (#%u @%zu)\n", frames_.size() - 1,
@ -1291,6 +1301,8 @@ class ThreadImpl {
CodeMap* codemap_;
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_;
WasmValue* stack_limit_ = nullptr; // End of allocated stack space.
WasmValue* sp_ = nullptr; // Current stack pointer.
@ -1349,6 +1361,13 @@ class ThreadImpl {
break;
WASM_CTYPES(CASE_TYPE)
#undef CASE_TYPE
case kWasmAnyRef:
case kWasmAnyFunc:
case kWasmExceptRef: {
Isolate* isolate = instance_object_->GetIsolate();
val = WasmValue(isolate->factory()->null_value());
break;
}
default:
UNREACHABLE();
break;
@ -2410,14 +2429,90 @@ class ThreadImpl {
// Throw a given existing exception. Returns true if the exception is being
// handled locally by the interpreter, false otherwise (interpreter exits).
bool DoRethrowException(WasmValue* exception) {
bool DoRethrowException(WasmValue exception) {
Isolate* isolate = instance_object_->GetIsolate();
// TODO(mstarzinger): Use the passed {exception} here once reference types
// as values on the operand stack are supported by the interpreter.
isolate->ReThrow(*isolate->factory()->undefined_value());
isolate->ReThrow(*exception.to_anyref());
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) {
DCHECK_NOT_NULL(code->side_table);
DCHECK(!frames_.empty());
@ -2531,10 +2626,27 @@ class ThreadImpl {
case kExprRethrow: {
WasmValue ex = Pop();
CommitPc(pc); // Needed for local unwinding.
if (!DoRethrowException(&ex)) return;
if (!DoRethrowException(ex)) return;
ReloadFromFrameOnException(&decoder, &code, &pc, &limit);
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: {
WasmValue cond = Pop();
WasmValue fval = Pop();
@ -2614,6 +2726,11 @@ class ThreadImpl {
len = 1 + imm.length;
break;
}
case kExprRefNull: {
Isolate* isolate = instance_object_->GetIsolate();
Push(WasmValue(isolate->factory()->null_value()));
break;
}
case kExprGetLocal: {
LocalIndexImmediate<Decoder::kNoValidate> imm(&decoder, code->at(pc));
Push(GetStackValue(frames_.back().sp + imm.index));
@ -2968,6 +3085,11 @@ class ThreadImpl {
SIGN_EXTENSION_CASE(I64SExtendI16, int64_t, int16_t);
SIGN_EXTENSION_CASE(I64SExtendI32, int64_t, int32_t);
#undef SIGN_EXTENSION_CASE
case kExprRefIsNull: {
uint32_t result = Pop().to_anyref()->IsNull() ? 1 : 0;
Push(WasmValue(result));
break;
}
case kNumericPrefix: {
++len;
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]);
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:
PrintF("void");
break;

View File

@ -6,6 +6,7 @@
#define V8_WASM_WASM_VALUE_H_
#include "src/boxed-float.h"
#include "src/handles.h"
#include "src/v8memory.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/zone/zone-containers.h"
@ -62,7 +63,10 @@ class Simd128 {
V(f32_boxed, kWasmF32, Float32) \
V(f64, kWasmF64, double) \
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.
class WasmValue {

View File

@ -430,6 +430,21 @@ TEST(MemoryGrowInvalidSize) {
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) {
{
WasmRunner<int32_t, float> r(ExecutionTier::kInterpreter);

View File

@ -346,6 +346,7 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 56)
#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_SET_LOCAL(index, val) val, kExprSetLocal, static_cast<byte>(index)