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:
parent
dbe9457fcb
commit
49106e4858
@ -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());
|
||||
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) {
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user