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: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: Eric Holk <eholk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48148}
This commit is contained in:
Karl Schimpf 2017-09-25 08:57:50 -07:00 committed by Commit Bot
parent dbe9457fcb
commit 49106e4858
17 changed files with 715 additions and 159 deletions

View File

@ -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,

View File

@ -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*));

View File

@ -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) \

View File

@ -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));

View File

@ -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());
}

View File

@ -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) {

View File

@ -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());
return isolate->Throw(*except);
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->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) {

View File

@ -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) \

View File

@ -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: {

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -19,7 +19,7 @@ var test_throw = (function () {
kExprI32Const, 0,
kExprI32Ne,
kExprIf, kWasmStmt,
kExprThrow, 0,
kExprThrow, 0,
kExprEnd,
kExprI32Const, 1
]).exportFunc();
@ -36,8 +36,8 @@ assertEquals("function", typeof test_throw.exports.throw_if_param_not_zero);
// Test expected behavior of throws
assertEquals(1, test_throw.exports.throw_if_param_not_zero(0));
assertWasmThrows([], function() { test_throw.exports.throw_if_param_not_zero(10) });
assertWasmThrows([], function() { test_throw.exports.throw_if_param_not_zero(-1) });
assertWasmThrows(0, [], function() { test_throw.exports.throw_if_param_not_zero(10) });
assertWasmThrows(0, [], function() { test_throw.exports.throw_if_param_not_zero(-1) });
// Now that we know throwing works, we test catching the exceptions we raise.
var test_catch = (function () {
@ -72,32 +72,315 @@ assertEquals("function", typeof test_catch.exports.simple_throw_catch_to_0_1);
assertEquals(0, test_catch.exports.simple_throw_catch_to_0_1(0));
assertEquals(1, test_catch.exports.simple_throw_catch_to_0_1(1));
// Test that we can distinguish which exception was thrown.
var test_catch_2 = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_v);
builder.addException(kSig_v_v);
builder.addException(kSig_v_v);
builder.addFunction("catch_different_exceptions", kSig_i_i)
.addBody([
kExprTry, kWasmI32,
kExprTry, kWasmI32,
kExprGetLocal, 0,
kExprI32Eqz,
kExprIf, kWasmStmt,
kExprThrow, 0,
kExprElse,
kExprGetLocal, 0,
kExprI32Const, 1,
kExprI32Eq,
kExprIf, kWasmStmt,
kExprThrow, 1,
kExprElse,
kExprThrow, 2,
kExprEnd,
kExprEnd,
kExprI32Const, 2,
kExprCatch, 0,
kExprI32Const, 3,
kExprEnd,
kExprCatch, 1,
kExprI32Const, 4,
kExprEnd
]).exportFunc();
return builder.instantiate();
})();
assertFalse(test_catch_2 === undefined);
assertFalse(test_catch_2 === null);
assertFalse(test_catch_2 === 0);
assertEquals("object", typeof test_catch_2.exports);
assertEquals("function", typeof test_catch_2.exports.catch_different_exceptions);
assertEquals(3, test_catch_2.exports.catch_different_exceptions(0));
assertEquals(4, test_catch_2.exports.catch_different_exceptions(1));
assertWasmThrows(2, [], function() { test_catch_2.exports.catch_different_exceptions(2) });
// Test throwing an exception with multiple values.
var test_throw_1_2 = (function() {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_ii);
builder.addFunction("throw_1_2", kSig_v_v)
.addBody([
kExprI32Const, 1,
kExprI32Const, 2,
kExprThrow, 0,
]).exportFunc();
return builder.instantiate();
})();
assertFalse(test_throw_1_2 === undefined);
assertFalse(test_throw_1_2 === null);
assertFalse(test_throw_1_2 === 0);
assertEquals("object", typeof test_throw_1_2.exports);
assertEquals("function", typeof test_throw_1_2.exports.throw_1_2);
assertWasmThrows(0, [0, 1, 0, 2], function() { test_throw_1_2.exports.throw_1_2(); });
// Test throwing/catching the i32 parameter value.
var test_throw_catch_param_i = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_i);
builder.addFunction("throw_catch_param", kSig_i_i)
.addBody([
kExprTry, kWasmI32,
kExprGetLocal, 0,
kExprThrow, 0,
kExprI32Const, 2,
kExprCatch, 0,
kExprReturn,
kExprEnd,
]).exportFunc();
return builder.instantiate();
})();
assertFalse(test_throw_catch_param_i === undefined);
assertFalse(test_throw_catch_param_i === null);
assertFalse(test_throw_catch_param_i === 0);
assertEquals("object", typeof test_throw_catch_param_i.exports);
assertEquals("function",
typeof test_throw_catch_param_i.exports.throw_catch_param);
assertEquals(0, test_throw_catch_param_i.exports.throw_catch_param(0));
assertEquals(1, test_throw_catch_param_i.exports.throw_catch_param(1));
assertEquals(10, test_throw_catch_param_i.exports.throw_catch_param(10));
// Test the encoding of a thrown exception with an integer exception.
var test_throw_param_i = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_i);
builder.addFunction("throw_param", kSig_v_i)
.addBody([
kExprGetLocal, 0,
kExprThrow, 0,
]).exportFunc();
return builder.instantiate();
})();
assertFalse(test_throw_param_i === undefined);
assertFalse(test_throw_param_i === null);
assertFalse(test_throw_param_i === 0);
assertEquals("object", typeof test_throw_param_i.exports);
assertEquals("function",
typeof test_throw_param_i.exports.throw_param);
assertWasmThrows(0, [0, 5], function() { test_throw_param_i.exports.throw_param(5); });
assertWasmThrows(0, [6, 31026],
function() { test_throw_param_i.exports.throw_param(424242); });
// Test throwing/catching the f32 parameter value.
var test_throw_catch_param_f = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_f);
builder.addFunction("throw_catch_param", kSig_f_f)
.addBody([
kExprTry, kWasmF32,
kExprGetLocal, 0,
kExprThrow, 0,
kExprF32Const, 0, 0, 0, 0,
kExprCatch, 0,
kExprReturn,
kExprEnd,
]).exportFunc();
return builder.instantiate();
})();
assertFalse(test_throw_catch_param_f === undefined);
assertFalse(test_throw_catch_param_f === null);
assertFalse(test_throw_catch_param_f === 0);
assertEquals("object", typeof test_throw_catch_param_f.exports);
assertEquals("function",
typeof test_throw_catch_param_f.exports.throw_catch_param);
assertEquals(5.0, test_throw_catch_param_f.exports.throw_catch_param(5.0));
assertEquals(10.5, test_throw_catch_param_f.exports.throw_catch_param(10.5));
// Test the encoding of a thrown exception with a float value.
var test_throw_param_f = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_f);
builder.addFunction("throw_param", kSig_v_f)
.addBody([
kExprGetLocal, 0,
kExprThrow, 0,
]).exportFunc();
return builder.instantiate();
})();
assertFalse(test_throw_param_f === undefined);
assertFalse(test_throw_param_f === null);
assertFalse(test_throw_param_f === 0);
assertEquals("object", typeof test_throw_param_f.exports);
assertEquals("function",
typeof test_throw_param_f.exports.throw_param);
assertWasmThrows(0, [16544, 0],
function() { test_throw_param_f.exports.throw_param(5.0); });
assertWasmThrows(0, [16680, 0],
function() { test_throw_param_f.exports.throw_param(10.5); });
// Test throwing/catching an I64 value
var test_throw_catch_param_l = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_l);
builder.addFunction("throw_catch_param", kSig_i_i)
.addBody([
kExprGetLocal, 0,
kExprI64UConvertI32,
kExprSetLocal, 1,
kExprTry, kWasmI32,
kExprGetLocal, 1,
kExprThrow, 0,
kExprI32Const, 2,
kExprCatch, 0,
kExprGetLocal, 1,
kExprI64Eq,
kExprIf, kWasmI32,
kExprI32Const, 1,
kExprElse,
kExprI32Const, 0,
kExprEnd,
// TODO(kschimpf): Why is this return necessary?
kExprReturn,
kExprEnd,
]).addLocals({i64_count: 1}).exportFunc();
return builder.instantiate();
})();
assertFalse(test_throw_catch_param_l === undefined);
assertFalse(test_throw_catch_param_l === null);
assertFalse(test_throw_catch_param_l === 0);
assertEquals("object", typeof test_throw_catch_param_l.exports);
assertEquals("function",
typeof test_throw_catch_param_l.exports.throw_catch_param);
assertEquals(1, test_throw_catch_param_l.exports.throw_catch_param(5));
assertEquals(1, test_throw_catch_param_l.exports.throw_catch_param(0));
assertEquals(1, test_throw_catch_param_l.exports.throw_catch_param(-1));
// Test the encoding of a thrown exception with an I64 value.
var test_throw_param_l = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_l);
builder.addFunction("throw_param", kSig_v_ii)
.addBody([
kExprGetLocal, 0,
kExprI64UConvertI32,
kExprI64Const, 32,
kExprI64Shl,
kExprGetLocal, 1,
kExprI64UConvertI32,
kExprI64Ior,
kExprThrow, 0
]).exportFunc();
return builder.instantiate();
})();
assertFalse(test_throw_param_l === undefined);
assertFalse(test_throw_param_l === null);
assertFalse(test_throw_param_l === 0);
assertEquals("object", typeof test_throw_param_l.exports);
assertEquals("function",
typeof test_throw_param_l.exports.throw_param);
assertWasmThrows(0, [0, 10, 0, 5],
function() { test_throw_param_l.exports.throw_param(10, 5); });
assertWasmThrows(0, [65535, 65535, 0, 13],
function() { test_throw_param_l.exports.throw_param(-1, 13); });
// Test throwing/catching the F64 parameter value
var test_throw_catch_param_d = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_d);
builder.addFunction("throw_catch_param", kSig_d_d)
.addBody([
kExprTry, kWasmF64,
kExprGetLocal, 0,
kExprThrow, 0,
kExprF64Const, 0, 0, 0, 0, 0, 0, 0, 0,
kExprCatch, 0,
kExprReturn,
kExprEnd,
]).exportFunc();
return builder.instantiate();
})();
assertFalse(test_throw_catch_param_d === undefined);
assertFalse(test_throw_catch_param_d === null);
assertFalse(test_throw_catch_param_d === 0);
assertEquals("object", typeof test_throw_catch_param_d.exports);
assertEquals("function",
typeof test_throw_catch_param_d.exports.throw_catch_param);
assertEquals(5.0, test_throw_catch_param_d.exports.throw_catch_param(5.0));
assertEquals(10.5, test_throw_catch_param_d.exports.throw_catch_param(10.5));
// Test the encoding of a thrown exception with an f64 value.
var test_throw_param_d = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_d);
builder.addFunction("throw_param", kSig_v_f)
.addBody([
kExprGetLocal, 0,
kExprF64ConvertF32,
kExprThrow, 0
]).exportFunc();
return builder.instantiate();
})();
assertFalse(test_throw_param_d === undefined);
assertFalse(test_throw_param_d === null);
assertFalse(test_throw_param_d === 0);
assertEquals("object", typeof test_throw_param_d.exports);
assertEquals("function",
typeof test_throw_param_d.exports.throw_param);
assertWasmThrows(0, [16404, 0, 0, 0],
function() { test_throw_param_d.exports.throw_param(5.0); });
assertWasmThrows(0, [16739, 4816, 0, 0],
function() { test_throw_param_d.exports.throw_param(10000000.5); });
/* TODO(kschimpf) Convert these tests to work for the proposed exceptions.
// The following methods do not attempt to catch the exception they raise.
var test_throw = (function () {
var builder = new WasmModuleBuilder();
builder.addFunction("throw_param_if_not_zero", kSig_i_i)
.addBody([
kExprGetLocal, 0,
kExprI32Const, 0,
kExprI32Ne,
kExprIf, kWasmStmt,
kExprGetLocal, 0,
kExprThrow,
kExprEnd,
kExprI32Const, 1
])
.exportFunc()
builder.addFunction("throw_20", kSig_v_v)
.addBody([
kExprI32Const, 20,
kExprThrow,
])
.exportFunc()
builder.addFunction("throw_expr_with_params", kSig_v_ddi)
.addBody([
// p2 * (p0 + min(p0, p1))|0 - 20
@ -123,14 +406,9 @@ assertFalse(test_throw === undefined);
assertFalse(test_throw === null);
assertFalse(test_throw === 0);
assertEquals("object", typeof test_throw.exports);
assertEquals("function", typeof test_throw.exports.throw_param_if_not_zero);
assertEquals("function", typeof test_throw.exports.throw_20);
assertEquals("function", typeof test_throw.exports.throw_expr_with_params);
assertEquals(1, test_throw.exports.throw_param_if_not_zero(0));
assertWasmThrows(10, function() { test_throw.exports.throw_param_if_not_zero(10) });
assertWasmThrows(-1, function() { test_throw.exports.throw_param_if_not_zero(-1) });
assertWasmThrows(20, test_throw.exports.throw_20);
assertWasmThrows(
-8, function() { test_throw.exports.throw_expr_with_params(1.5, 2.5, 4); });
assertWasmThrows(

View File

@ -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);
}

View File

@ -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);
}
}
});

View File

@ -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.
}