Commit 46a99b07 authored by Michael Starzinger's avatar Michael Starzinger Committed by Commit Bot

[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: 's avatarClemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60598}
parent 72269e3f
...@@ -893,6 +893,19 @@ class SideTable : public ZoneObject { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 @@ ...@@ -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 { ...@@ -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) { ...@@ -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) { ...@@ -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)
......
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