Commit 49106e48 authored by Karl Schimpf's avatar Karl Schimpf Committed by Commit Bot

Add capability of throwing values in WASM

This is a second attempt at landing CL 644866 which was reverted by
CL 667019.

Extends the current implementation of WASM exceptions to be able to
throw exceptions with values (not just tags).

A JS typed (uint_16) array is used to hold the thrown values. This
allows all WASM types to be stored (i32, i64, f32, and f64) as well as
be inspected in JS.

The previous CL was reverted because the WASM compiler made calls to
run time functions with tagged objects, which must not be done. To fix
this, all run time calls use the thread-level isolate to hold the
exception being processed.

Bug: v8:6577
Change-Id: I4b1ef7e2847b71a2fab8e9934a0531057db9de63
Reviewed-on: https://chromium-review.googlesource.com/677056
Commit-Queue: Karl Schimpf <kschimpf@chromium.org>
Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarEric Holk <eholk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48148}
parent dbe9457f
......@@ -55,6 +55,8 @@ namespace compiler {
namespace {
constexpr uint32_t kBytesPerExceptionValuesArrayElement = 2;
void MergeControlToEnd(JSGraph* jsgraph, Node* node) {
Graph* g = jsgraph->graph();
if (g->end()) {
......@@ -1808,28 +1810,161 @@ Node* WasmGraphBuilder::GrowMemory(Node* input) {
return result;
}
Node* WasmGraphBuilder::Throw(Node* input) {
uint32_t WasmGraphBuilder::GetExceptionEncodedSize(
const wasm::WasmException* exception) const {
const wasm::WasmExceptionSig* sig = exception->sig;
uint32_t encoded_size = 0;
for (size_t i = 0; i < sig->parameter_count(); ++i) {
size_t byte_size = size_t(1) << ElementSizeLog2Of(sig->GetParam(i));
DCHECK_EQ(byte_size % kBytesPerExceptionValuesArrayElement, 0);
DCHECK_LE(1, byte_size / kBytesPerExceptionValuesArrayElement);
encoded_size += byte_size / kBytesPerExceptionValuesArrayElement;
}
return encoded_size;
}
Node* WasmGraphBuilder::Throw(uint32_t tag,
const wasm::WasmException* exception,
const Vector<Node*> values) {
SetNeedsStackCheck();
Node* parameters[] = {BuildChangeInt32ToSmi(input)};
return BuildCallToRuntime(Runtime::kWasmThrow, parameters,
arraysize(parameters));
uint32_t encoded_size = GetExceptionEncodedSize(exception);
Node* create_parameters[] = {
BuildChangeUint32ToSmi(ConvertExceptionTagToRuntimeId(tag)),
BuildChangeUint32ToSmi(Uint32Constant(encoded_size))};
BuildCallToRuntime(Runtime::kWasmThrowCreate, create_parameters,
arraysize(create_parameters));
uint32_t index = 0;
const wasm::WasmExceptionSig* sig = exception->sig;
MachineOperatorBuilder* m = jsgraph()->machine();
for (size_t i = 0; i < sig->parameter_count(); ++i) {
Node* value = values[i];
switch (sig->GetParam(i)) {
case wasm::kWasmF32:
value = graph()->NewNode(m->BitcastFloat32ToInt32(), value);
// Intentionally fall to next case.
case wasm::kWasmI32:
BuildEncodeException32BitValue(&index, value);
break;
case wasm::kWasmF64:
value = graph()->NewNode(m->BitcastFloat64ToInt64(), value);
// Intentionally fall to next case.
case wasm::kWasmI64: {
Node* upper32 = graph()->NewNode(
m->TruncateInt64ToInt32(),
Binop(wasm::kExprI64ShrU, value, Int64Constant(32)));
BuildEncodeException32BitValue(&index, upper32);
Node* lower32 = graph()->NewNode(m->TruncateInt64ToInt32(), value);
BuildEncodeException32BitValue(&index, lower32);
break;
}
default:
CHECK(false);
break;
}
}
DCHECK_EQ(encoded_size, index);
return BuildCallToRuntime(Runtime::kWasmThrow, nullptr, 0);
}
void WasmGraphBuilder::BuildEncodeException32BitValue(uint32_t* index,
Node* value) {
MachineOperatorBuilder* machine = jsgraph()->machine();
Node* upper_parameters[] = {
BuildChangeUint32ToSmi(Int32Constant(*index)),
BuildChangeUint32ToSmi(
graph()->NewNode(machine->Word32Shr(), value, Int32Constant(16))),
};
BuildCallToRuntime(Runtime::kWasmExceptionSetElement, upper_parameters,
arraysize(upper_parameters));
++(*index);
Node* lower_parameters[] = {
BuildChangeUint32ToSmi(Int32Constant(*index)),
BuildChangeUint32ToSmi(graph()->NewNode(machine->Word32And(), value,
Int32Constant(0xFFFFu))),
};
BuildCallToRuntime(Runtime::kWasmExceptionSetElement, lower_parameters,
arraysize(lower_parameters));
++(*index);
}
Node* WasmGraphBuilder::BuildDecodeException32BitValue(Node* const* values,
uint32_t* index) {
MachineOperatorBuilder* machine = jsgraph()->machine();
Node* upper = BuildChangeSmiToInt32(values[*index]);
(*index)++;
upper = graph()->NewNode(machine->Word32Shl(), upper, Int32Constant(16));
Node* lower = BuildChangeSmiToInt32(values[*index]);
(*index)++;
Node* value = graph()->NewNode(machine->Word32Or(), upper, lower);
return value;
}
Node* WasmGraphBuilder::Rethrow() {
SetNeedsStackCheck();
Node* result = BuildCallToRuntime(Runtime::kWasmRethrow, nullptr, 0);
Node* result = BuildCallToRuntime(Runtime::kWasmThrow, nullptr, 0);
return result;
}
Node* WasmGraphBuilder::Catch(Node* input, wasm::WasmCodePosition position) {
Node* WasmGraphBuilder::ConvertExceptionTagToRuntimeId(uint32_t tag) {
// TODO(kschimpf): Handle exceptions from different modules, when they are
// linked at runtime.
return Uint32Constant(tag);
}
Node* WasmGraphBuilder::GetExceptionRuntimeId() {
SetNeedsStackCheck();
Node* parameters[] = {input}; // caught value
Node* value = BuildCallToRuntime(Runtime::kWasmSetCaughtExceptionValue,
return BuildChangeSmiToInt32(
BuildCallToRuntime(Runtime::kWasmGetExceptionRuntimeId, nullptr, 0));
}
Node** WasmGraphBuilder::GetExceptionValues(
const wasm::WasmException* except_decl) {
// TODO(kschimpf): We need to move this code to the function-body-decoder.cc
// in order to build landing-pad (exception) edges in case the runtime
// call causes an exception.
// Start by getting the encoded values from the exception.
uint32_t encoded_size = GetExceptionEncodedSize(except_decl);
Node** values = Buffer(encoded_size);
for (uint32_t i = 0; i < encoded_size; ++i) {
Node* parameters[] = {BuildChangeUint32ToSmi(Uint32Constant(i))};
values[i] = BuildCallToRuntime(Runtime::kWasmExceptionGetElement,
parameters, arraysize(parameters));
parameters[0] = value;
value = BuildCallToRuntime(Runtime::kWasmGetExceptionTag, parameters,
arraysize(parameters));
return BuildChangeSmiToInt32(value);
}
// Now convert the leading entries to the corresponding parameter values.
uint32_t index = 0;
const wasm::WasmExceptionSig* sig = except_decl->sig;
for (size_t i = 0; i < sig->parameter_count(); ++i) {
Node* value = BuildDecodeException32BitValue(values, &index);
switch (wasm::ValueType type = sig->GetParam(i)) {
case wasm::kWasmF32: {
value = Unop(wasm::kExprF32ReinterpretI32, value);
break;
}
case wasm::kWasmI32:
break;
case wasm::kWasmF64:
case wasm::kWasmI64: {
Node* upper =
Binop(wasm::kExprI64Shl, Unop(wasm::kExprI64UConvertI32, value),
Int64Constant(32));
Node* lower = Unop(wasm::kExprI64UConvertI32,
BuildDecodeException32BitValue(values, &index));
value = Binop(wasm::kExprI64Ior, upper, lower);
if (type == wasm::kWasmF64) {
value = Unop(wasm::kExprF64ReinterpretI64, value);
}
break;
}
default:
CHECK(false);
break;
}
values[i] = value;
}
DCHECK_EQ(index, encoded_size);
return values;
}
Node* WasmGraphBuilder::BuildI32DivS(Node* left, Node* right,
......
......@@ -212,9 +212,12 @@ class WasmGraphBuilder {
Node* Unop(wasm::WasmOpcode opcode, Node* input,
wasm::WasmCodePosition position = wasm::kNoCodePosition);
Node* GrowMemory(Node* input);
Node* Throw(Node* input);
Node* Throw(uint32_t tag, const wasm::WasmException* exception,
const Vector<Node*> values);
Node* Rethrow();
Node* Catch(Node* input, wasm::WasmCodePosition position);
Node* ConvertExceptionTagToRuntimeId(uint32_t tag);
Node* GetExceptionRuntimeId();
Node** GetExceptionValues(const wasm::WasmException* except_decl);
unsigned InputCount(Node* node);
bool IsPhiWithMerge(Node* phi, Node* merge);
bool ThrowsException(Node* node, Node** if_success, Node** if_exception);
......@@ -478,6 +481,10 @@ class WasmGraphBuilder {
Node* BuildAsmjsLoadMem(MachineType type, Node* index);
Node* BuildAsmjsStoreMem(MachineType type, Node* index, Node* val);
uint32_t GetExceptionEncodedSize(const wasm::WasmException* exception) const;
void BuildEncodeException32BitValue(uint32_t* index, Node* value);
Node* BuildDecodeException32BitValue(Node* const* values, uint32_t* index);
Node** Realloc(Node** buffer, size_t old_count, size_t new_count) {
Node** buf = Buffer(new_count);
if (buf != buffer) memcpy(buf, buffer, old_count * sizeof(Node*));
......
......@@ -203,8 +203,7 @@
V(will_handle_string, "willHandle") \
V(writable_string, "writable") \
V(year_string, "year") \
V(zero_string, "0") \
V(WasmExceptionTag_string, "WasmExceptionTag")
V(zero_string, "0")
#define PRIVATE_SYMBOL_LIST(V) \
V(array_iteration_kind_symbol) \
......
......@@ -47,12 +47,12 @@ bool Isolate::has_pending_exception() {
return !thread_local_top_.pending_exception_->IsTheHole(this);
}
Object* Isolate::get_wasm_caught_exception() const {
Object* Isolate::get_wasm_caught_exception() {
return thread_local_top_.wasm_caught_exception_;
}
void Isolate::set_wasm_caught_exception(Object* exception_obj) {
thread_local_top_.wasm_caught_exception_ = exception_obj;
void Isolate::set_wasm_caught_exception(Object* exception) {
thread_local_top_.wasm_caught_exception_ = exception;
}
void Isolate::clear_wasm_caught_exception() {
......@@ -82,21 +82,10 @@ void Isolate::clear_scheduled_exception() {
thread_local_top_.scheduled_exception_ = heap_.the_hole_value();
}
bool Isolate::is_catchable_by_javascript(Object* exception) {
return exception != heap()->termination_exception();
}
bool Isolate::is_catchable_by_wasm(Object* exception) {
if (!is_catchable_by_javascript(exception) || !exception->IsJSError())
return false;
HandleScope scope(this);
Handle<Object> exception_handle(exception, this);
return JSReceiver::HasProperty(Handle<JSReceiver>::cast(exception_handle),
factory()->WasmExceptionTag_string())
.IsJust();
}
void Isolate::FireBeforeCallEnteredCallback() {
for (auto& callback : before_call_entered_callbacks_) {
callback(reinterpret_cast<v8::Isolate*>(this));
......
......@@ -121,6 +121,7 @@ void ThreadLocalTop::Initialize() {
void ThreadLocalTop::Free() {
wasm_caught_exception_ = nullptr;
// Match unmatched PopPromise calls.
while (promise_on_stack_) isolate_->PopPromise();
}
......@@ -1064,6 +1065,16 @@ void ReportBootstrappingException(Handle<Object> exception,
#endif
}
bool Isolate::is_catchable_by_wasm(Object* exception) {
if (!is_catchable_by_javascript(exception) || !exception->IsJSError())
return false;
HandleScope scope(this);
Handle<Object> exception_handle(exception, this);
return JSReceiver::HasProperty(Handle<JSReceiver>::cast(exception_handle),
factory()->InternalizeUtf8String(
wasm::WasmException::kRuntimeIdStr))
.IsJust();
}
Object* Isolate::Throw(Object* exception, MessageLocation* location) {
DCHECK(!has_pending_exception());
......@@ -1248,6 +1259,7 @@ Object* Isolate::UnwindAndFindHandler() {
// again.
trap_handler::SetThreadInWasm();
set_wasm_caught_exception(exception);
return FoundHandler(nullptr, frame->LookupCode(), offset, return_sp,
frame->fp());
}
......
......@@ -608,8 +608,8 @@ class Isolate {
inline void clear_pending_exception();
// Interface to wasm caught exception.
inline Object* get_wasm_caught_exception() const;
inline void set_wasm_caught_exception(Object* exception_obj);
inline Object* get_wasm_caught_exception();
inline void set_wasm_caught_exception(Object* exception);
inline void clear_wasm_caught_exception();
THREAD_LOCAL_TOP_ADDRESS(Object*, pending_exception)
......@@ -646,7 +646,7 @@ class Isolate {
bool IsExternalHandlerOnTop(Object* exception);
inline bool is_catchable_by_javascript(Object* exception);
inline bool is_catchable_by_wasm(Object* exception);
bool is_catchable_by_wasm(Object* exception);
// JS execution stack (see frames.h).
static Address c_entry_fp(ThreadLocalTop* thread) {
......
......@@ -24,8 +24,6 @@ namespace internal {
namespace {
constexpr int kInvalidExceptionTag = -1;
WasmInstanceObject* GetWasmInstanceOnStackTop(Isolate* isolate) {
DisallowHeapAllocation no_allocation;
const Address entry = Isolate::c_entry_fp(isolate->thread_local_top());
......@@ -144,55 +142,121 @@ RUNTIME_FUNCTION(Runtime_WasmThrowTypeError) {
isolate, NewTypeError(MessageTemplate::kWasmTrapTypeError));
}
RUNTIME_FUNCTION(Runtime_WasmThrow) {
RUNTIME_FUNCTION(Runtime_WasmThrowCreate) {
// TODO(kschimpf): Can this be replaced with equivalent TurboFan code/calls.
HandleScope scope(isolate);
DCHECK_NULL(isolate->context());
isolate->set_context(GetWasmContextOnStackTop(isolate));
DCHECK_EQ(1, args.length());
Handle<Object> tag = args.at(0);
Handle<Object> except = isolate->factory()->NewWasmRuntimeError(
DCHECK_EQ(2, args.length());
Handle<Object> exception = isolate->factory()->NewWasmRuntimeError(
static_cast<MessageTemplate::Template>(
MessageTemplate::kWasmExceptionError));
DCHECK(tag->IsSmi());
CHECK(!JSReceiver::SetProperty(
except, isolate->factory()->WasmExceptionTag_string(), tag, STRICT)
isolate->set_wasm_caught_exception(*exception);
CONVERT_ARG_HANDLE_CHECKED(Smi, id, 0);
CHECK(!JSReceiver::SetProperty(exception,
isolate->factory()->InternalizeUtf8String(
wasm::WasmException::kRuntimeIdStr),
id, STRICT)
.is_null());
CONVERT_SMI_ARG_CHECKED(size, 1);
Handle<JSTypedArray> values =
isolate->factory()->NewJSTypedArray(ElementsKind::UINT16_ELEMENTS, size);
CHECK(!JSReceiver::SetProperty(exception,
isolate->factory()->InternalizeUtf8String(
wasm::WasmException::kRuntimeValuesStr),
values, STRICT)
.is_null());
return isolate->Throw(*except);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmRethrow) {
RUNTIME_FUNCTION(Runtime_WasmThrow) {
// TODO(kschimpf): Can this be replaced with equivalent TurboFan code/calls.
HandleScope scope(isolate);
DCHECK_NULL(isolate->context());
isolate->set_context(GetWasmContextOnStackTop(isolate));
DCHECK_EQ(0, args.length());
Object* exception = isolate->get_wasm_caught_exception();
Handle<Object> exception(isolate->get_wasm_caught_exception(), isolate);
CHECK(!exception.is_null());
isolate->clear_wasm_caught_exception();
return isolate->Throw(exception);
return isolate->Throw(*exception);
}
RUNTIME_FUNCTION(Runtime_WasmGetExceptionTag) {
RUNTIME_FUNCTION(Runtime_WasmGetExceptionRuntimeId) {
// TODO(kschimpf): Can this be replaced with equivalent TurboFan code/calls.
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Object* exception = args[0];
DCHECK(isolate->is_catchable_by_wasm(exception));
Handle<Object> exception_handle(exception, isolate);
Handle<Object> tag_handle;
if (JSReceiver::GetProperty(Handle<JSReceiver>::cast(exception_handle),
isolate->factory()->WasmExceptionTag_string())
.ToHandle(&tag_handle)) {
if (tag_handle->IsSmi()) return *tag_handle;
DCHECK_NULL(isolate->context());
isolate->set_context(GetWasmContextOnStackTop(isolate));
Handle<Object> except_obj(isolate->get_wasm_caught_exception(), isolate);
if (!except_obj.is_null() && except_obj->IsJSReceiver()) {
Handle<JSReceiver> exception(JSReceiver::cast(*except_obj));
Handle<Object> tag;
if (JSReceiver::GetProperty(exception,
isolate->factory()->InternalizeUtf8String(
wasm::WasmException::kRuntimeIdStr))
.ToHandle(&tag)) {
if (tag->IsSmi()) {
return *tag;
}
}
}
return Smi::FromInt(kInvalidExceptionTag);
return Smi::FromInt(wasm::WasmModule::kInvalidExceptionTag);
}
RUNTIME_FUNCTION(Runtime_WasmSetCaughtExceptionValue) {
// TODO(kschimpf): Implement stack of caught exceptions, rather than
// just innermost.
RUNTIME_FUNCTION(Runtime_WasmExceptionGetElement) {
// TODO(kschimpf): Can this be replaced with equivalent TurboFan code/calls.
HandleScope scope(isolate);
DCHECK_NULL(isolate->context());
isolate->set_context(GetWasmContextOnStackTop(isolate));
DCHECK_EQ(1, args.length());
Object* exception = args[0];
DCHECK(isolate->is_catchable_by_wasm(exception));
isolate->set_wasm_caught_exception(exception);
return exception;
Handle<Object> except_obj(isolate->get_wasm_caught_exception(), isolate);
if (!except_obj.is_null() && except_obj->IsJSReceiver()) {
Handle<JSReceiver> exception(JSReceiver::cast(*except_obj));
Handle<Object> values_obj;
if (JSReceiver::GetProperty(exception,
isolate->factory()->InternalizeUtf8String(
wasm::WasmException::kRuntimeValuesStr))
.ToHandle(&values_obj)) {
if (values_obj->IsJSTypedArray()) {
Handle<JSTypedArray> values = Handle<JSTypedArray>::cast(values_obj);
CHECK_EQ(values->type(), kExternalUint16Array);
CONVERT_SMI_ARG_CHECKED(index, 0);
CHECK_LT(index, Smi::ToInt(values->length()));
auto* vals =
reinterpret_cast<uint16_t*>(values->GetBuffer()->allocation_base());
return Smi::FromInt(vals[index]);
}
}
}
return Smi::FromInt(0);
}
RUNTIME_FUNCTION(Runtime_WasmExceptionSetElement) {
// TODO(kschimpf): Can this be replaced with equivalent TurboFan code/calls.
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
DCHECK_NULL(isolate->context());
isolate->set_context(GetWasmContextOnStackTop(isolate));
Handle<Object> except_obj(isolate->get_wasm_caught_exception(), isolate);
if (!except_obj.is_null() && except_obj->IsJSReceiver()) {
Handle<JSReceiver> exception(JSReceiver::cast(*except_obj));
Handle<Object> values_obj;
if (JSReceiver::GetProperty(exception,
isolate->factory()->InternalizeUtf8String(
wasm::WasmException::kRuntimeValuesStr))
.ToHandle(&values_obj)) {
if (values_obj->IsJSTypedArray()) {
Handle<JSTypedArray> values = Handle<JSTypedArray>::cast(values_obj);
CHECK_EQ(values->type(), kExternalUint16Array);
CONVERT_SMI_ARG_CHECKED(index, 0);
CHECK_LT(index, Smi::ToInt(values->length()));
CONVERT_SMI_ARG_CHECKED(value, 1);
auto* vals =
reinterpret_cast<uint16_t*>(values->GetBuffer()->allocation_base());
vals[index] = static_cast<uint16_t>(value);
}
}
}
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_SetThreadInWasm) {
......
......@@ -638,21 +638,22 @@ namespace internal {
F(IsSharedInteger32TypedArray, 1, 1) \
F(TypedArraySpeciesCreateByLength, 2, 1)
#define FOR_EACH_INTRINSIC_WASM(F) \
F(WasmGrowMemory, 1, 1) \
F(WasmMemorySize, 0, 1) \
F(ThrowWasmError, 2, 1) \
F(ThrowWasmErrorFromTrapIf, 1, 1) \
F(ThrowWasmStackOverflow, 0, 1) \
F(WasmThrowTypeError, 0, 1) \
F(WasmThrow, 1, 1) \
F(WasmRethrow, 0, 1) \
F(WasmGetExceptionTag, 1, 1) \
F(WasmSetCaughtExceptionValue, 1, 1) \
F(WasmRunInterpreter, 3, 1) \
F(WasmStackGuard, 0, 1) \
F(SetThreadInWasm, 0, 1) \
F(ClearThreadInWasm, 0, 1) \
#define FOR_EACH_INTRINSIC_WASM(F) \
F(WasmGrowMemory, 1, 1) \
F(WasmMemorySize, 0, 1) \
F(ThrowWasmError, 2, 1) \
F(ThrowWasmErrorFromTrapIf, 1, 1) \
F(ThrowWasmStackOverflow, 0, 1) \
F(WasmThrowTypeError, 0, 1) \
F(WasmThrowCreate, 2, 1) \
F(WasmThrow, 0, 1) \
F(WasmGetExceptionRuntimeId, 0, 1) \
F(WasmExceptionSetElement, 2, 1) \
F(WasmExceptionGetElement, 1, 1) \
F(WasmRunInterpreter, 3, 1) \
F(WasmStackGuard, 0, 1) \
F(SetThreadInWasm, 0, 1) \
F(ClearThreadInWasm, 0, 1) \
F(WasmCompileLazy, 0, 1)
#define FOR_EACH_INTRINSIC_RETURN_PAIR(F) \
......
......@@ -562,8 +562,11 @@ struct ControlWithNamedConstructors : public ControlBase<Value> {
const Value& input, Value* result) \
F(Simd8x16ShuffleOp, const Simd8x16ShuffleOperand<validate>& operand, \
const Value& input0, const Value& input1, Value* result) \
F(Throw, const ExceptionIndexOperand<validate>&) \
F(Catch, const ExceptionIndexOperand<validate>& operand, Control* block) \
F(Throw, const ExceptionIndexOperand<validate>&, Control* block, \
const Vector<Value>& args) \
F(CatchException, const ExceptionIndexOperand<validate>& operand, \
Control* block, void** caught_values) \
F(SetCaughtValue, void* caught_values, Value* value, size_t index) \
F(AtomicOp, WasmOpcode opcode, Vector<Value> args, \
const MemoryAccessOperand<validate>& operand, Value* result)
......@@ -1245,16 +1248,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
ExceptionIndexOperand<true> operand(this, this->pc_);
len = 1 + operand.length;
if (!this->Validate(this->pc_, operand)) break;
if (operand.exception->sig->parameter_count() > 0) {
// TODO(kschimpf): Fix to pull values off stack and build throw.
OPCODE_ERROR(opcode, "can't handle exceptions with values yet");
break;
}
interface_.Throw(this, operand);
// TODO(titzer): Throw should end control, but currently we build a
// (reachable) runtime call instead of connecting it directly to
// end.
// EndControl();
std::vector<Value> args;
PopArgs(operand.exception->ToFunctionSig(), &args);
interface_.Throw(this, operand, &control_.back(), vec2vec(args));
break;
}
case kExprTry: {
......@@ -1292,8 +1288,13 @@ class WasmFullDecoder : public WasmDecoder<validate> {
c->kind = kControlTryCatch;
FallThruTo(c);
stack_.resize(c->stack_depth);
interface_.Catch(this, operand, c);
void* caught_values = nullptr;
interface_.CatchException(this, operand, c, &caught_values);
const WasmExceptionSig* sig = operand.exception->sig;
for (size_t i = 0, e = sig->parameter_count(); i < e; ++i) {
auto* value = Push(sig->GetParam(i));
interface_.SetCaughtValue(this, caught_values, value, i);
}
break;
}
case kExprCatchAll: {
......
......@@ -27,6 +27,11 @@ namespace wasm {
namespace {
template <typename T>
Vector<T> vec2vec(ZoneVector<T>& vec) {
return Vector<T>(vec.data(), vec.size());
}
// An SsaEnv environment carries the current local variable renaming
// as well as the current effect and control dependency in the TF graph.
// It maintains a control state that tracks whether the environment
......@@ -140,8 +145,11 @@ class WasmGraphBuildingInterface {
void Try(Decoder* decoder, Control* block) {
SsaEnv* outer_env = ssa_env_;
SsaEnv* catch_env = Split(decoder, outer_env);
// Mark catch environment as unreachable, since only accessable
// through catch unwinding (i.e. landing pads).
catch_env->state = SsaEnv::kUnreachable;
SsaEnv* try_env = Steal(decoder->zone(), outer_env);
SsaEnv* catch_env = UnreachableEnv(decoder->zone());
SetEnv(try_env);
TryInfo* try_info = new (decoder->zone()) TryInfo(catch_env);
block->end_env = outer_env;
......@@ -370,40 +378,81 @@ class WasmGraphBuildingInterface {
return BUILD(Int32Constant, operand.index);
}
void Throw(Decoder* decoder, const ExceptionIndexOperand<true>& operand) {
BUILD(Throw, GetExceptionTag(decoder, operand));
void Throw(Decoder* decoder, const ExceptionIndexOperand<true>& operand,
Control* block, const Vector<Value>& value_args) {
int count = value_args.length();
ZoneVector<TFNode*> args(count, decoder->zone());
for (int i = 0; i < count; ++i) {
args[i] = value_args[i].node;
}
BUILD(Throw, operand.index, operand.exception, vec2vec(args));
Unreachable(decoder);
EndControl(decoder, block);
}
void Catch(Decoder* decoder, const ExceptionIndexOperand<true>& operand,
Control* block) {
void CatchException(Decoder* decoder,
const ExceptionIndexOperand<true>& operand,
Control* block, void** caught_values_ptr) {
DCHECK(block->is_try_catch());
current_catch_ = block->previous_catch;
SsaEnv* catch_env = block->try_info->catch_env;
SetEnv(catch_env);
// Get the exception and see if wanted exception.
TFNode* exception_as_i32 =
BUILD(Catch, block->try_info->exception, decoder->position());
TFNode* exception_tag = GetExceptionTag(decoder, operand);
TFNode* compare_i32 = BUILD(Binop, kExprI32Eq, exception_as_i32,
exception_tag, decoder->position());
TFNode* if_true = nullptr;
TFNode* if_false = nullptr;
BUILD(BranchNoHint, compare_i32, &if_true, &if_false);
SsaEnv* false_env = Split(decoder, catch_env);
false_env->control = if_false;
SsaEnv* true_env = Steal(decoder->zone(), catch_env);
true_env->control = if_true;
block->try_info->catch_env = false_env;
TFNode*** caught_values = reinterpret_cast<TFNode***>(caught_values_ptr);
TFNode* compare_i32 = nullptr;
if (block->try_info->exception == nullptr) {
// Catch not applicable, no possible throws in the try
// block. Create dummy code so that body of catch still
// compiles. Note: This only happens because the current
// implementation only builds a landing pad if some node in the
// try block can (possibly) throw.
//
// TODO(kschimpf): Always generate a landing pad for a try block.
compare_i32 = BUILD(Int32Constant, 0);
} else {
// Get the exception and see if wanted exception.
TFNode* caught_tag = BUILD(GetExceptionRuntimeId);
TFNode* exception_tag =
BUILD(ConvertExceptionTagToRuntimeId, operand.index);
compare_i32 = BUILD(Binop, kExprI32Eq, caught_tag, exception_tag);
}
// Generate code to re-throw the exception.
DCHECK_NOT_NULL(block->try_info->catch_env);
SetEnv(false_env);
TFNode* if_catch = nullptr;
TFNode* if_no_catch = nullptr;
BUILD(BranchNoHint, compare_i32, &if_catch, &if_no_catch);
SsaEnv* if_no_catch_env = Split(decoder, ssa_env_);
if_no_catch_env->control = if_no_catch;
SsaEnv* if_catch_env = Steal(decoder->zone(), ssa_env_);
if_catch_env->control = if_catch;
// TODO(kschimpf): Generalize to allow more catches. Will force
// moving no_catch code to END opcode.
SetEnv(if_no_catch_env);
BUILD(Rethrow);
FallThruTo(decoder, block);
Unreachable(decoder);
EndControl(decoder, block);
SetEnv(true_env);
// TODO(kschimpf): Add code to pop caught exception from isolate.
SetEnv(if_catch_env);
if (block->try_info->exception == nullptr) {
*caught_values = nullptr;
} else {
// TODO(kschimpf): Can't use BUILD() here, GetExceptionValues() returns
// TFNode** rather than TFNode*. Fix to add landing pads.
*caught_values = builder_->GetExceptionValues(operand.exception);
}
}
void SetCaughtValue(Decoder* decoder, void* caught_values_ptr, Value* value,
size_t index) {
if (caught_values_ptr) {
TFNode** caught_values = reinterpret_cast<TFNode**>(caught_values_ptr);
value->node = caught_values[index];
return;
}
// No caught value, make up filler node so that catch block still compiles.
value->node = DefaultValue(value->type);
}
void AtomicOp(Decoder* decoder, WasmOpcode opcode, Vector<Value> args,
......
......@@ -246,6 +246,12 @@ compiler::ModuleEnv CreateModuleEnvFromCompiledModule(
// static
const WasmExceptionSig WasmException::empty_sig_(0, 0, nullptr);
// static
constexpr const char* WasmException::kRuntimeIdStr;
// static
constexpr const char* WasmException::kRuntimeValuesStr;
Handle<JSArrayBuffer> SetupArrayBuffer(Isolate* isolate, void* allocation_base,
size_t allocation_length,
void* backing_store, size_t size,
......
......@@ -89,9 +89,14 @@ typedef FunctionSig WasmExceptionSig;
struct WasmException {
explicit WasmException(const WasmExceptionSig* sig = &empty_sig_)
: sig(sig) {}
FunctionSig* ToFunctionSig() const { return const_cast<FunctionSig*>(sig); }
const WasmExceptionSig* sig; // type signature of the exception.
// Used to hold data on runtime exceptions.
static constexpr const char* kRuntimeIdStr = "WasmExceptionRuntimeId";
static constexpr const char* kRuntimeValuesStr = "WasmExceptionValues";
private:
static const WasmExceptionSig empty_sig_;
};
......@@ -154,6 +159,8 @@ struct V8_EXPORT_PRIVATE WasmModule {
static const uint32_t kPageSize = 0x10000; // Page size, 64kb.
static const uint32_t kMinMemPages = 1; // Minimum memory size = 64kb
static constexpr int kInvalidExceptionTag = -1;
std::unique_ptr<Zone> signature_zone;
uint32_t initial_pages = 0; // initial size of the memory in 64k pages
uint32_t maximum_pages = 0; // maximum size of the memory in 64k pages
......
......@@ -21,8 +21,10 @@ class FlagScope {
T previous_value_;
};
#define EXPERIMENTAL_FLAG_SCOPE(flag) \
FlagScope<bool> __scope_##__LINE__(&FLAG_experimental_wasm_##flag, true)
#define FLAG_SCOPE(flag) \
FlagScope<bool> __scope_##flag##__LINE__(&FLAG_##flag, true)
#define EXPERIMENTAL_FLAG_SCOPE(flag) FLAG_SCOPE(experimental_wasm_##flag)
} // namespace internal
} // namespace v8
......
This diff is collapsed.
......@@ -124,6 +124,10 @@ let kSig_v_d = makeSig([kWasmF64], []);
let kSig_v_dd = makeSig([kWasmF64, kWasmF64], []);
let kSig_v_ddi = makeSig([kWasmF64, kWasmF64, kWasmI32], []);
let kSig_v_f = makeSig([kWasmF32], []);
let kSig_f_f = makeSig([kWasmF32], [kWasmF32]);
let kSig_d_d = makeSig([kWasmF64], [kWasmF64]);
function makeSig(params, results) {
return {params: params, results: results};
}
......@@ -388,7 +392,7 @@ function assertTraps(trap, code) {
throw new MjsUnitAssertionError('Did not trap, expected: ' + kTrapMsgs[trap]);
}
function assertWasmThrows(values, code) {
function assertWasmThrows(runtime_id, values, code) {
try {
if (typeof code === 'function') {
code();
......@@ -397,13 +401,16 @@ function assertWasmThrows(values, code) {
}
} catch (e) {
assertTrue(e instanceof WebAssembly.RuntimeError);
assertNotEquals(e['WasmExceptionTag'], undefined);
assertTrue(Number.isInteger(e['WasmExceptionTag']));
// TODO(kschimpf): Extract values from the exception.
let e_values = [];
assertEquals(values, e_values);
var e_runtime_id = e['WasmExceptionRuntimeId'];
assertEquals(e_runtime_id, runtime_id);
assertTrue(Number.isInteger(e_runtime_id));
var e_values = e['WasmExceptionValues'];
assertEquals(values.length, e_values.length);
for (i = 0; i < values.length; ++i) {
assertEquals(values[i], e_values[i]);
}
// Success.
return;
}
throw new MjsUnitAssertionError('Did not throw, expected: ' + values);
throw new MjsUnitAssertionError('Did not throw expected: ' + runtime_id + values);
}
......@@ -523,7 +523,7 @@ class WasmModuleBuilder {
for (let type of wasm.exceptions) {
section.emit_u32v(type.params.length);
for (let param of type.params) {
section.enit_u8(param);
section.emit_u8(param);
}
}
});
......
......@@ -2336,8 +2336,7 @@ TEST_F(FunctionBodyDecoderTest, Throw) {
// exception index out of range.
EXPECT_FAILURE(v_v, kExprThrow, 2);
// TODO(kschimpf): Fix when we can create exceptions with values.
EXPECT_FAILURE(v_v, WASM_I32V(0), kExprThrow, 1);
EXPECT_VERIFIES(v_v, WASM_I32V(0), kExprThrow, 1);
// TODO(kschimpf): Add more tests.
}
......
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