Commit ad49b7b4 authored by Karl Schimpf's avatar Karl Schimpf Committed by Commit Bot

Reland "Start migration of try/throw/catch to match proposal."

This is a reland of 470a1001
Original change's description:
> Start migration of try/throw/catch to match proposal.
> 
> This CL does the first baby steps on moving the current (experimental)
> exception handling to match that of the WebAssembly proposal.
> 
> It does the following:
> 
> 1) Use exception tags instead of integers.
> 
> 2) Only handle empty exception signatures (i.e. no values associated
>    with the exception tag.
> 
> 3) Only handle one catch clause.
> 
> 4) Be sure to rethrow the exception if the exception tag does not match.
> 
> Note: There are many things that need to be fixed, and are too
> numerous to list here. However, the code should have TODO's on each
> missing parts of the implementation.
> 
> Also note that the code currently doesn't handle nested catch blocks,
> nor does it change the throw value being an integer. Rather, the
> integer value is still being thrown, and currently is the exception
> tag. Therefore, we don't build an exception object. This is the reason
> why this CL doesn't handle exceptions that pass values.
> 
> Also, the current implementation still can't handle multiple modules
> because tag resolution (between) modules has not be implemented yet.
> 
> Bug: v8:6577
> Change-Id: Id6d08b641b3c42d1eec7d4db582f2dab35406114
> Reviewed-on: https://chromium-review.googlesource.com/591910
> Reviewed-by: Brad Nelson <bradnelson@chromium.org>
> Commit-Queue: Karl Schimpf <kschimpf@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#47087}

Bug: v8:6577
Change-Id: I41c3309827c292cb787681a95aaef7cf9b931835
Reviewed-on: https://chromium-review.googlesource.com/598968Reviewed-by: 's avatarMichael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarBrad Nelson <bradnelson@chromium.org>
Commit-Queue: Brad Nelson <bradnelson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47100}
parent 19fee8b2
......@@ -1830,12 +1830,18 @@ Node* WasmGraphBuilder::Throw(Node* input) {
arraysize(parameters));
}
Node* WasmGraphBuilder::Rethrow() {
SetNeedsStackCheck();
Node* result = BuildCallToRuntime(Runtime::kWasmRethrow, nullptr, 0);
return result;
}
Node* WasmGraphBuilder::Catch(Node* input, wasm::WasmCodePosition position) {
SetNeedsStackCheck();
CommonOperatorBuilder* common = jsgraph()->common();
Node* parameters[] = {input}; // caught value
Node* value = BuildCallToRuntime(Runtime::kWasmGetCaughtExceptionValue,
Node* value = BuildCallToRuntime(Runtime::kWasmSetCaughtExceptionValue,
parameters, arraysize(parameters));
Node* is_smi;
......
......@@ -168,6 +168,7 @@ class WasmGraphBuilder {
wasm::WasmCodePosition position = wasm::kNoCodePosition);
Node* GrowMemory(Node* input);
Node* Throw(Node* input);
Node* Rethrow();
Node* Catch(Node* input, wasm::WasmCodePosition position);
unsigned InputCount(Node* node);
bool IsPhiWithMerge(Node* phi, Node* merge);
......
......@@ -47,6 +47,17 @@ bool Isolate::has_pending_exception() {
return !thread_local_top_.pending_exception_->IsTheHole(this);
}
Object* Isolate::get_wasm_caught_exception() const {
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::clear_wasm_caught_exception() {
thread_local_top_.wasm_caught_exception_ = nullptr;
}
void Isolate::clear_pending_message() {
thread_local_top_.pending_message_obj_ = heap_.the_hole_value();
......
......@@ -103,6 +103,7 @@ void ThreadLocalTop::InitializeInternal() {
// These members are re-initialized later after deserialization
// is complete.
pending_exception_ = NULL;
wasm_caught_exception_ = NULL;
rethrowing_message_ = false;
pending_message_obj_ = NULL;
scheduled_exception_ = NULL;
......@@ -215,6 +216,7 @@ void Isolate::IterateThread(ThreadVisitor* v, char* t) {
void Isolate::Iterate(RootVisitor* v, ThreadLocalTop* thread) {
// Visit the roots from the top for a given thread.
v->VisitRootPointer(Root::kTop, &thread->pending_exception_);
v->VisitRootPointer(Root::kTop, &thread->wasm_caught_exception_);
v->VisitRootPointer(Root::kTop, &thread->pending_message_obj_);
v->VisitRootPointer(Root::kTop, bit_cast<Object**>(&(thread->context_)));
v->VisitRootPointer(Root::kTop, &thread->scheduled_exception_);
......
......@@ -326,6 +326,9 @@ class ThreadLocalTop BASE_EMBEDDED {
Context* context_;
ThreadId thread_id_;
Object* pending_exception_;
// TODO(kschimpf): Change this to a stack of caught exceptions (rather than
// just innermost catching try block).
Object* wasm_caught_exception_;
// Communication channel between Isolate::FindHandler and the CEntryStub.
Context* pending_handler_context_;
......@@ -602,6 +605,11 @@ class Isolate {
inline void set_pending_exception(Object* exception_obj);
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 void clear_wasm_caught_exception();
THREAD_LOCAL_TOP_ADDRESS(Object*, pending_exception)
inline bool has_pending_exception();
......
......@@ -142,6 +142,8 @@ RUNTIME_FUNCTION(Runtime_WasmThrowTypeError) {
}
RUNTIME_FUNCTION(Runtime_WasmThrow) {
// TODO(kschimpf): Change this to build a runtime exception with
// wasm properties, instead of just an integer.
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_SMI_ARG_CHECKED(lower, 0);
......@@ -156,7 +158,15 @@ RUNTIME_FUNCTION(Runtime_WasmThrow) {
return isolate->Throw(*isolate->factory()->NewNumberFromInt(thrown_value));
}
RUNTIME_FUNCTION(Runtime_WasmGetCaughtExceptionValue) {
RUNTIME_FUNCTION(Runtime_WasmRethrow) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
Object* exception = isolate->get_wasm_caught_exception();
isolate->clear_wasm_caught_exception();
return isolate->Throw(exception);
}
RUNTIME_FUNCTION(Runtime_WasmSetCaughtExceptionValue) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Object* exception = args[0];
......@@ -164,6 +174,7 @@ RUNTIME_FUNCTION(Runtime_WasmGetCaughtExceptionValue) {
// Number or a Smi (which we have just converted to a Number.) This logic
// lives in Isolate::is_catchable_by_wasm(Object*).
CHECK(exception->IsNumber());
isolate->set_wasm_caught_exception(exception);
return exception;
}
......
......@@ -634,7 +634,8 @@ namespace internal {
F(ThrowWasmStackOverflow, 0, 1) \
F(WasmThrowTypeError, 0, 1) \
F(WasmThrow, 2, 1) \
F(WasmGetCaughtExceptionValue, 1, 1) \
F(WasmRethrow, 0, 1) \
F(WasmSetCaughtExceptionValue, 1, 1) \
F(WasmRunInterpreter, 3, 1) \
F(WasmStackGuard, 0, 1) \
F(SetThreadInWasm, 0, 1) \
......
......@@ -13,6 +13,7 @@ namespace internal {
namespace wasm {
struct WasmGlobal;
struct WasmException;
// Use this macro to check a condition if checked == true, and DCHECK the
// condition otherwise.
......@@ -34,6 +35,17 @@ struct LocalIndexOperand {
}
};
template <bool checked>
struct ExceptionIndexOperand {
uint32_t index;
const WasmException* exception = nullptr;
unsigned length;
inline ExceptionIndexOperand(Decoder* decoder, const byte* pc) {
index = decoder->read_u32v<checked>(pc + 1, &length, "exception index");
}
};
template <bool checked>
struct ImmI32Operand {
int32_t value;
......
......@@ -44,9 +44,8 @@ namespace wasm {
break; \
}
#define PROTOTYPE_NOT_FUNCTIONAL(opcode) \
errorf(pc_, "Prototype still not functional: %s", \
WasmOpcodes::OpcodeName(opcode));
#define OPCODE_ERROR(opcode, message) \
(errorf(pc_, "%s: %s", WasmOpcodes::OpcodeName(opcode), (message)))
// An SsaEnv environment carries the current local variable renaming
// as well as the current effect and control dependency in the TF graph.
......@@ -82,8 +81,10 @@ struct Value {
struct TryInfo : public ZoneObject {
SsaEnv* catch_env;
TFNode* exception;
size_t catch_count; // Number of catch blocks associated with the try.
explicit TryInfo(SsaEnv* c) : catch_env(c), exception(nullptr) {}
explicit TryInfo(SsaEnv* c)
: catch_env(c), exception(nullptr), catch_count(0) {}
};
struct MergeValues {
......@@ -283,6 +284,15 @@ class WasmDecoder : public Decoder {
return false;
}
inline bool Validate(const byte* pc, ExceptionIndexOperand<true>& operand) {
if (module_ != nullptr && operand.index < module_->exceptions.size()) {
operand.exception = &module_->exceptions[operand.index];
return true;
}
errorf(pc + 1, "Invalid exception index: %u", operand.index);
return false;
}
inline bool Validate(const byte* pc, GlobalIndexOperand<true>& operand) {
if (module_ != nullptr && operand.index < module_->globals.size()) {
operand.global = &module_->globals[operand.index];
......@@ -462,10 +472,15 @@ class WasmDecoder : public Decoder {
return 1 + operand.length;
}
case kExprThrow:
case kExprCatch: {
ExceptionIndexOperand<true> operand(decoder, pc);
return 1 + operand.length;
}
case kExprSetLocal:
case kExprTeeLocal:
case kExprGetLocal:
case kExprCatch: {
case kExprGetLocal: {
LocalIndexOperand<true> operand(decoder, pc);
return 1 + operand.length;
}
......@@ -752,6 +767,13 @@ class WasmFullDecoder : public WasmDecoder {
return module_->has_memory;
}
template <bool check>
inline TFNode* GetExceptionTag(ExceptionIndexOperand<check>& operand) {
// TODO(kschimpf): Need to get runtime exception tag values. This
// code only handles non-imported/exported exceptions.
return BUILD(Int32Constant, operand.index);
}
// Decodes the body of a function.
void DecodeFunctionBody() {
TRACE("wasm-decode %p...%p (module+%u, %d bytes) %s\n",
......@@ -808,15 +830,20 @@ class WasmFullDecoder : public WasmDecoder {
case kExprRethrow: {
// TODO(kschimpf): Implement.
CHECK_PROTOTYPE_OPCODE(eh);
PROTOTYPE_NOT_FUNCTIONAL(opcode);
OPCODE_ERROR(opcode, "not implemented yet");
break;
}
case kExprThrow: {
// TODO(kschimpf): Fix to use type signature of exception.
CHECK_PROTOTYPE_OPCODE(eh);
PROTOTYPE_NOT_FUNCTIONAL(opcode);
Value value = Pop(0, kWasmI32);
BUILD(Throw, value.node);
ExceptionIndexOperand<true> operand(this, pc_);
len = 1 + operand.length;
if (!Validate(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;
}
BUILD(Throw, GetExceptionTag(operand));
// TODO(titzer): Throw should end control, but currently we build a
// (reachable) runtime call instead of connecting it directly to
// end.
......@@ -838,49 +865,62 @@ class WasmFullDecoder : public WasmDecoder {
case kExprCatch: {
// TODO(kschimpf): Fix to use type signature of exception.
CHECK_PROTOTYPE_OPCODE(eh);
PROTOTYPE_NOT_FUNCTIONAL(opcode);
LocalIndexOperand<true> operand(this, pc_);
ExceptionIndexOperand<true> operand(this, pc_);
len = 1 + operand.length;
if (!Validate(pc_, operand)) break;
if (control_.empty()) {
error("catch does not match any try");
break;
}
Control* c = &control_.back();
DCHECK_NOT_NULL(c->try_info);
if (!c->is_try()) {
error("catch does not match any try");
break;
}
if (c->try_info->catch_env == nullptr) {
error(pc_, "catch already present for try with catch");
if (c->try_info->catch_count > 0) {
OPCODE_ERROR(opcode, "multiple catch blocks not implemented");
break;
}
++c->try_info->catch_count;
FallThruTo(c);
stack_.resize(c->stack_depth);
DCHECK_NOT_NULL(c->try_info);
SsaEnv* catch_env = c->try_info->catch_env;
c->try_info->catch_env = nullptr;
SetEnv("catch:begin", catch_env);
current_catch_ = c->previous_catch;
if (Validate(pc_, operand)) {
if (ssa_env_->locals) {
TFNode* exception_as_i32 =
BUILD(Catch, c->try_info->exception, position());
ssa_env_->locals[operand.index] = exception_as_i32;
}
}
current_catch_ = c->previous_catch;
// Get the exception and see if wanted exception.
TFNode* exception_as_i32 =
BUILD(Catch, c->try_info->exception, position());
TFNode* exception_tag = GetExceptionTag(operand);
TFNode* compare_i32 = BUILD(Binop, kExprI32Eq, exception_as_i32,
exception_tag, position());
TFNode* if_true = nullptr;
TFNode* if_false = nullptr;
BUILD(BranchNoHint, compare_i32, &if_true, &if_false);
SsaEnv* end_env = ssa_env_;
SsaEnv* false_env = Split(end_env);
false_env->control = if_false;
SsaEnv* true_env = Steal(ssa_env_);
true_env->control = if_true;
c->try_info->catch_env = false_env;
SetEnv("Try:catch", true_env);
len = 1 + operand.length;
// TODO(kschimpf): Add code to pop caught exception from isolate.
break;
}
case kExprCatchAll: {
// TODO(kschimpf): Implement.
CHECK_PROTOTYPE_OPCODE(eh);
PROTOTYPE_NOT_FUNCTIONAL(opcode);
OPCODE_ERROR(opcode, "not implemented yet");
break;
}
case kExprLoop: {
......@@ -969,10 +1009,17 @@ class WasmFullDecoder : public WasmDecoder {
name = "try:end";
// validate that catch was seen.
if (c->try_info->catch_env != nullptr) {
if (c->try_info->catch_count == 0) {
error(pc_, "missing catch in try");
break;
}
SsaEnv* fallthru_ssa_env = ssa_env_;
DCHECK_NOT_NULL(c->try_info->catch_env);
SetEnv("Catch fail", c->try_info->catch_env);
BUILD0(Rethrow);
// TODO(karlschimpf): Why not use EndControl ()? (currently fails)
FallThruTo(c);
SetEnv("Catch fallthru", fallthru_ssa_env);
}
FallThruTo(c);
SetEnv(name, c->end_env);
......
......@@ -31,9 +31,9 @@ namespace wasm {
#endif
namespace {
const char kNameString[] = "name";
const char kExceptionString[] = "exception";
constexpr char kNameString[] = "name";
constexpr char kExceptionString[] = "exception";
constexpr char kUnknownString[] = "<unknown>";
template <size_t N>
constexpr size_t num_chars(const char (&)[N]) {
......@@ -71,9 +71,10 @@ const char* SectionName(SectionCode code) {
case kNameSectionCode:
return kNameString;
case kExceptionSectionCode:
return kExceptionString;
if (FLAG_experimental_wasm_eh) return kExceptionString;
return kUnknownString;
default:
return "<unknown>";
return kUnknownString;
}
}
......@@ -218,11 +219,6 @@ class WasmSectionIterator {
strncmp(reinterpret_cast<const char*>(section_name_start),
kNameString, num_chars(kNameString)) == 0) {
section_code = kNameSectionCode;
} else if (FLAG_experimental_wasm_eh &&
string.length() == num_chars(kExceptionString) &&
strncmp(reinterpret_cast<const char*>(section_name_start),
kExceptionString, num_chars(kExceptionString)) == 0) {
section_code = kExceptionSectionCode;
}
} else if (!IsValidSectionCode(section_code)) {
decoder_.errorf(decoder_.pc(), "unknown section code #0x%02x",
......@@ -332,9 +328,25 @@ class ModuleDecoder : public Decoder {
errorf(pc(), "unexpected section: %s", SectionName(section_code));
return;
}
if (section_code != kUnknownSectionCode) {
next_section_ = section_code;
++next_section_;
switch (section_code) {
case kUnknownSectionCode:
break;
case kExceptionSectionCode:
// Note: kExceptionSectionCode > kCodeSectionCode, but must appear
// before the code section. Hence, treat it as a special case.
if (++number_of_exception_sections > 1) {
errorf(pc(), "Multiple exception sections not allowed");
return;
} else if (next_section_ >= kCodeSectionCode) {
errorf(pc(), "Exception section must appear before the code section");
return;
}
break;
default:
next_section_ = section_code;
++next_section_;
break;
}
switch (section_code) {
......@@ -377,7 +389,11 @@ class ModuleDecoder : public Decoder {
DecodeNameSection();
break;
case kExceptionSectionCode:
DecodeExceptionSection();
if (FLAG_experimental_wasm_eh) {
DecodeExceptionSection();
} else {
errorf(pc(), "unexpected section: %s", SectionName(section_code));
}
break;
default:
errorf(pc(), "unexpected section: %s", SectionName(section_code));
......@@ -877,6 +893,7 @@ class ModuleDecoder : public Decoder {
Counters* counters_ = nullptr;
// The type section is the first section in a module.
uint8_t next_section_ = kFirstSectionInModule;
uint32_t number_of_exception_sections = 0;
// We store next_section_ as uint8_t instead of SectionCode so that we can
// increment it. This static_assert should make sure that SectionCode does not
// get bigger than uint8_t accidentially.
......
......@@ -34,16 +34,17 @@ enum SectionCode : int8_t {
kCodeSectionCode = 10, // Function code
kDataSectionCode = 11, // Data segments
kNameSectionCode = 12, // Name section (encoded as a string)
kExceptionSectionCode = 13, // Exception section (encoded as a string)
kExceptionSectionCode = 13, // Exception section
// Helper values
kFirstSectionInModule = kTypeSectionCode,
kLastKnownModuleSection = kExceptionSectionCode,
};
enum NameSectionType : uint8_t { kModule = 0, kFunction = 1, kLocal = 2 };
inline bool IsValidSectionCode(uint8_t byte) {
return kTypeSectionCode <= byte && byte <= kDataSectionCode;
return kTypeSectionCode <= byte && byte <= kLastKnownModuleSection;
}
const char* SectionName(SectionCode code);
......
......@@ -143,12 +143,17 @@ void wasm::PrintWasmText(const WasmModule *module,
}
case kExprGetLocal:
case kExprSetLocal:
case kExprTeeLocal:
case kExprCatch: {
case kExprTeeLocal: {
LocalIndexOperand<false> operand(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << operand.index;
break;
}
case kExprThrow:
case kExprCatch: {
ExceptionIndexOperand<false> operand(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << operand.index;
break;
}
case kExprGetGlobal:
case kExprSetGlobal: {
GlobalIndexOperand<false> operand(&i, i.pc());
......@@ -183,7 +188,6 @@ void wasm::PrintWasmText(const WasmModule *module,
case kExprGrowMemory:
case kExprDrop:
case kExprSelect:
case kExprThrow:
os << WasmOpcodes::OpcodeName(opcode);
break;
......
......@@ -177,9 +177,6 @@
# BUG(v8:6306).
'wasm/huge-memory': [SKIP],
# BUG(v8:6577).
'wasm/exceptions': [SKIP],
}], # ALWAYS
['novfp3 == True', {
......
......@@ -7,6 +7,73 @@
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
// The following method doesn't attempt to catch an raised exception.
var test_throw = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_v);
builder.addFunction("throw_if_param_not_zero", kSig_i_i)
.addBody([
kExprGetLocal, 0,
kExprI32Const, 0,
kExprI32Ne,
kExprIf, kWasmStmt,
kExprThrow, 0,
kExprEnd,
kExprI32Const, 1
]).exportFunc();
return builder.instantiate();
})();
// Check the test_throw exists.
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_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) });
// Now that we know throwing works, we test catching the exceptions we raise.
var test_catch = (function () {
var builder = new WasmModuleBuilder();
builder.addException(kSig_v_v);
builder.addFunction("simple_throw_catch_to_0_1", kSig_i_i)
.addBody([
kExprTry, kWasmI32,
kExprGetLocal, 0,
kExprI32Eqz,
kExprIf, kWasmStmt,
kExprThrow, 0,
kExprEnd,
kExprI32Const, 1,
kExprCatch, 0,
kExprI32Const, 0,
kExprEnd
]).exportFunc();
return builder.instantiate();
})();
// Check the test_catch exists.
assertFalse(test_catch === undefined);
assertFalse(test_catch === null);
assertFalse(test_catch === 0);
assertEquals("object", typeof test_catch.exports);
assertEquals("function", typeof test_catch.exports.simple_throw_catch_to_0_1);
// Test expected behavior of simple catch.
assertEquals(0, test_catch.exports.simple_throw_catch_to_0_1(0));
assertEquals(1, test_catch.exports.simple_throw_catch_to_0_1(1));
/* 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();
......@@ -229,16 +296,16 @@ var test_catch = (function () {
kExprI32Eq,
kExprIf, kWasmStmt,
kExprGetLocal, 2,
kExprI32Const, /*64=*/ 192, 0,
kExprI32Const, / *64=* / 192, 0,
kExprI32Ior,
kExprThrow,
kExprUnreachable,
kExprEnd,
kExprI32Const, /*128=*/ 128, 1,
kExprI32Const, / *128=* / 128, 1,
kExprI32Ior,
kExprCatch, 1,
kExprGetLocal, 1,
kExprI32Const, /*256=*/ 128, 2,
kExprI32Const, / *256=* / 128, 2,
kExprI32Ior,
kExprEnd,
])
......@@ -252,7 +319,7 @@ var test_catch = (function () {
kExprTry, kWasmI32,
kExprGetLocal, 0,
kExprCallFunction, kWasmThrowFunction,
kExprI32Const, /*-1=*/ 127,
kExprI32Const, / *-1=* / 127,
kExprCatch, 1,
kExprGetLocal, 1,
kExprEnd
......@@ -287,7 +354,7 @@ var test_catch = (function () {
kExprGetLocal, 0,
kExprI32Const, 0,
kExprCallFunction, kFromIndirectCalleeHelper,
kExprI32Const, /*-1=*/ 127,
kExprI32Const, / *-1=* / 127,
kExprCatch, 1,
kExprGetLocal, 1,
kExprEnd
......@@ -301,7 +368,7 @@ var test_catch = (function () {
kExprTry, kWasmI32,
kExprGetLocal, 0,
kExprCallFunction, kJSThrowI,
kExprI32Const, /*-1=*/ 127,
kExprI32Const, / *-1=* / 127,
kExprCatch, 1,
kExprGetLocal, 1,
kExprEnd,
......@@ -381,3 +448,4 @@ assertEquals(-10, test_catch.exports.from_js(-10));
assertThrowsEquals(test_catch.exports.string_from_js, "use wasm;");
assertThrowsEquals(test_catch.exports.large_from_js, 1e+28);
assertThrowsEquals(test_catch.exports.undefined_from_js, undefined);
*/
......@@ -52,18 +52,19 @@ let kDeclNoLocals = 0;
// Section declaration constants
let kUnknownSectionCode = 0;
let kTypeSectionCode = 1; // Function signature declarations
let kImportSectionCode = 2; // Import declarations
let kFunctionSectionCode = 3; // Function declarations
let kTableSectionCode = 4; // Indirect function table and other tables
let kMemorySectionCode = 5; // Memory attributes
let kGlobalSectionCode = 6; // Global declarations
let kExportSectionCode = 7; // Exports
let kStartSectionCode = 8; // Start function declaration
let kElementSectionCode = 9; // Elements section
let kCodeSectionCode = 10; // Function code
let kDataSectionCode = 11; // Data segments
let kNameSectionCode = 12; // Name section (encoded as string)
let kTypeSectionCode = 1; // Function signature declarations
let kImportSectionCode = 2; // Import declarations
let kFunctionSectionCode = 3; // Function declarations
let kTableSectionCode = 4; // Indirect function table and other tables
let kMemorySectionCode = 5; // Memory attributes
let kGlobalSectionCode = 6; // Global declarations
let kExportSectionCode = 7; // Exports
let kStartSectionCode = 8; // Start function declaration
let kElementSectionCode = 9; // Elements section
let kCodeSectionCode = 10; // Function code
let kDataSectionCode = 11; // Data segments
let kNameSectionCode = 12; // Name section (encoded as string)
let kExceptionSectionCode = 13; // Exception section (must appear before code section)
// Name section types
let kModuleNameCode = 0;
......@@ -357,8 +358,7 @@ function assertTraps(trap, code) {
throw new MjsUnitAssertionError('Did not trap, expected: ' + kTrapMsgs[trap]);
}
function assertWasmThrows(value, code) {
assertEquals('number', typeof value);
function assertWasmThrows(values, code) {
try {
if (typeof code === 'function') {
code();
......@@ -366,10 +366,11 @@ function assertWasmThrows(value, code) {
eval(code);
}
} catch (e) {
assertEquals('number', typeof e);
assertEquals(value, e);
// TODO(kschimpf): Extract values from the exception.
let e_values = [];
assertEquals(values, e_values);
// Success.
return;
}
throw new MjsUnitAssertionError('Did not throw, expected: ' + value);
throw new MjsUnitAssertionError('Did not throw, expected: ' + values);
}
......@@ -153,6 +153,7 @@ class WasmModuleBuilder {
this.imports = [];
this.exports = [];
this.globals = [];
this.exceptions = [];
this.functions = [];
this.function_table = [];
this.function_table_length = 0;
......@@ -208,6 +209,13 @@ class WasmModuleBuilder {
return glob;
}
addException(type) {
if (type.results.length != 0)
throw new Error('Invalid exception signature: ' + type);
this.exceptions.push(type);
return this.exceptions.length - 1;
}
addFunction(name, type) {
let type_index = (typeof type) == "number" ? type : this.addType(type);
let func = new WasmFunctionBuilder(this, name, type_index);
......@@ -487,6 +495,20 @@ class WasmModuleBuilder {
});
}
// Add exceptions.
if (wasm.exceptions.length > 0) {
if (debug) print("emitting exceptions @ " + binary.length);
binary.emit_section(kExceptionSectionCode, section => {
section.emit_u32v(wasm.exceptions.length);
for (let type of wasm.exceptions) {
section.emit_u32v(type.params.length);
for (let param of type.params) {
section.enit_u8(param);
}
}
});
}
// Add function bodies.
if (wasm.functions.length > 0) {
// emit function bodies
......
......@@ -186,6 +186,9 @@ class FunctionBodyDecoderTest : public TestWithZone {
};
namespace {
constexpr size_t kMaxByteSizedLeb128 = 127;
// A helper for tests that require a module environment for functions,
// globals, or memories.
class TestModuleEnv : public ModuleEnv {
......@@ -196,12 +199,12 @@ class TestModuleEnv : public ModuleEnv {
}
byte AddGlobal(ValueType type, bool mutability = true) {
mod.globals.push_back({type, mutability, WasmInitExpr(), 0, false, false});
CHECK(mod.globals.size() <= 127);
CHECK(mod.globals.size() <= kMaxByteSizedLeb128);
return static_cast<byte>(mod.globals.size() - 1);
}
byte AddSignature(FunctionSig* sig) {
mod.signatures.push_back(sig);
CHECK(mod.signatures.size() <= 127);
CHECK(mod.signatures.size() <= kMaxByteSizedLeb128);
return static_cast<byte>(mod.signatures.size() - 1);
}
byte AddFunction(FunctionSig* sig) {
......@@ -212,7 +215,7 @@ class TestModuleEnv : public ModuleEnv {
{0, 0}, // code
false, // import
false}); // export
CHECK(mod.functions.size() <= 127);
CHECK(mod.functions.size() <= kMaxByteSizedLeb128);
return static_cast<byte>(mod.functions.size() - 1);
}
byte AddImport(FunctionSig* sig) {
......@@ -220,6 +223,11 @@ class TestModuleEnv : public ModuleEnv {
mod.functions[result].imported = true;
return result;
}
byte AddException(WasmExceptionSig* sig) {
mod.exceptions.emplace_back(sig);
CHECK(mod.signatures.size() <= kMaxByteSizedLeb128);
return static_cast<byte>(mod.exceptions.size() - 1);
}
void InitializeMemory() {
mod.has_memory = true;
......@@ -2233,28 +2241,53 @@ TEST_F(FunctionBodyDecoderTest, Select_TypeCheck) {
TEST_F(FunctionBodyDecoderTest, Throw) {
EXPERIMENTAL_FLAG_SCOPE(eh);
// TODO(kschimpf): Need to fix throw to use declared exception.
EXPECT_FAILURE(v_i, WASM_GET_LOCAL(0), kExprThrow);
TestModuleEnv module_env;
module = &module_env;
module_env.AddException(sigs.v_v());
module_env.AddException(sigs.v_i());
AddLocals(kWasmI32, 1);
EXPECT_VERIFIES(v_v, kExprThrow, 0);
// 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_FAILURE(i_d, WASM_GET_LOCAL(0), kExprThrow, WASM_I32V(0));
EXPECT_FAILURE(i_f, WASM_GET_LOCAL(0), kExprThrow, WASM_I32V(0));
EXPECT_FAILURE(l_l, WASM_GET_LOCAL(0), kExprThrow, WASM_I64V(0));
// TODO(kschimpf): Add more tests.
}
TEST_F(FunctionBodyDecoderTest, ThrowUnreachable) {
// TODO(titzer): unreachable code after throw should validate.
// EXPERIMENTAL_FLAG_SCOPE(eh);
// EXPECT_VERIFIES(v_i, WASM_GET_LOCAL(0), kExprThrow, kExprSetLocal, 0);
EXPERIMENTAL_FLAG_SCOPE(eh);
TestModuleEnv module_env;
module = &module_env;
module_env.AddException(sigs.v_v());
module_env.AddException(sigs.v_i());
AddLocals(kWasmI32, 1);
EXPECT_VERIFIES(i_i, kExprThrow, 0, WASM_GET_LOCAL(0));
// TODO(kschimpf): Add more (block-level) tests of unreachable to see
// if they validate.
}
#define WASM_TRY_OP kExprTry, kLocalVoid
#define WASM_CATCH(local) kExprCatch, static_cast<byte>(local)
#define WASM_CATCH(index) kExprCatch, static_cast<byte>(index)
TEST_F(FunctionBodyDecoderTest, TryCatch) {
EXPERIMENTAL_FLAG_SCOPE(eh);
TestModuleEnv module_env;
module = &module_env;
module_env.AddException(sigs.v_v());
module_env.AddException(sigs.v_v());
// TODO(kschimpf): Need to fix catch to use declared exception.
EXPECT_FAILURE(v_i, WASM_TRY_OP, WASM_CATCH(0), kExprEnd);
EXPECT_VERIFIES(v_v, WASM_TRY_OP, WASM_CATCH(0), kExprEnd);
// Missing catch.
EXPECT_FAILURE(v_v, WASM_TRY_OP, kExprEnd);
......@@ -2263,7 +2296,8 @@ TEST_F(FunctionBodyDecoderTest, TryCatch) {
EXPECT_FAILURE(v_i, WASM_TRY_OP, WASM_CATCH(0));
// Double catch.
EXPECT_FAILURE(v_i, WASM_TRY_OP, WASM_CATCH(0), WASM_CATCH(0), kExprEnd);
// TODO(kschimpf): Fix this to verify.
EXPECT_FAILURE(v_i, WASM_TRY_OP, WASM_CATCH(0), WASM_CATCH(1), kExprEnd);
}
TEST_F(FunctionBodyDecoderTest, MultiValBlock1) {
......@@ -2416,7 +2450,7 @@ TEST_F(WasmOpcodeLengthTest, Statements) {
EXPECT_LENGTH(1, kExprSelect);
EXPECT_LENGTH(2, kExprBr);
EXPECT_LENGTH(2, kExprBrIf);
EXPECT_LENGTH(1, kExprThrow);
EXPECT_LENGTH(2, kExprThrow);
EXPECT_LENGTH(2, kExprTry);
EXPECT_LENGTH(2, kExprCatch);
}
......
......@@ -50,10 +50,16 @@ namespace wasm {
#define EMPTY_FUNCTION_SIGNATURES_SECTION SECTION(Function, 1), 0
#define EMPTY_FUNCTION_BODIES_SECTION SECTION(Code, 1), 0
#define SECTION_NAMES(size) SECTION(Unknown, size + 5), 4, 'n', 'a', 'm', 'e'
#define SECTION_EXCEPTIONS(size) \
SECTION(Unknown, size + 10), 9, 'e', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n'
#define SECTION_EXCEPTIONS(size) SECTION(Exception, size)
#define EMPTY_NAMES_SECTION SECTION_NAMES(1), 0
#define FAIL_IF_NO_EXPERIMENTAL_EH(data) \
do { \
ModuleResult result = DecodeModule((data), (data) + sizeof((data))); \
EXPECT_FALSE(result.ok()); \
EXPECT_EQ(0u, result.val->exceptions.size()); \
} while (0)
#define X1(...) __VA_ARGS__
#define X2(...) __VA_ARGS__, __VA_ARGS__
#define X3(...) __VA_ARGS__, __VA_ARGS__, __VA_ARGS__
......@@ -369,92 +375,62 @@ TEST_F(WasmModuleVerifyTest, ZeroExceptions) {
static const byte data[] = {
SECTION_EXCEPTIONS(1), 0,
};
FAIL_IF_NO_EXPERIMENTAL_EH(data);
{
// Should decode exception section with no exceptions
EXPERIMENTAL_FLAG_SCOPE(eh);
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(0u, result.val->exceptions.size());
}
{
// Should read exception section as unknown section.
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(0u, result.val->exceptions.size());
}
EXPERIMENTAL_FLAG_SCOPE(eh);
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(0u, result.val->exceptions.size());
}
TEST_F(WasmModuleVerifyTest, OneI32Exception) {
static const byte data[] = {
SECTION_EXCEPTIONS(3), 1,
1, // except[0] (i32)
kLocalI32,
// except[0] (i32)
1, kLocalI32,
};
FAIL_IF_NO_EXPERIMENTAL_EH(data);
{
// Should decode to exactly one exception
EXPERIMENTAL_FLAG_SCOPE(eh);
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(1u, result.val->exceptions.size());
EXPERIMENTAL_FLAG_SCOPE(eh);
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(1u, result.val->exceptions.size());
const WasmException& e0 = result.val->exceptions.front();
EXPECT_EQ(1u, e0.sig->parameter_count());
EXPECT_EQ(MachineRepresentation::kWord32, e0.sig->GetParam(0));
}
{
// Should read exception section as unknown section.
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(0u, result.val->exceptions.size());
}
const WasmException& e0 = result.val->exceptions.front();
EXPECT_EQ(1u, e0.sig->parameter_count());
EXPECT_EQ(MachineRepresentation::kWord32, e0.sig->GetParam(0));
}
TEST_F(WasmModuleVerifyTest, TwoExceptions) {
static const byte data[] = {SECTION_EXCEPTIONS(6),
2,
2, // except[0] (f32, i64)
kLocalF32,
kLocalI64,
1, // except[1] (i32)
kLocalI32};
{
// Should decode to exactly two exceptions
EXPERIMENTAL_FLAG_SCOPE(eh);
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(2u, result.val->exceptions.size());
const WasmException& e0 = result.val->exceptions.front();
EXPECT_EQ(2u, e0.sig->parameter_count());
EXPECT_EQ(MachineRepresentation::kFloat32, e0.sig->GetParam(0));
EXPECT_EQ(MachineRepresentation::kWord64, e0.sig->GetParam(1));
}
{
// Should read exception section as unknown section.
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(0u, result.val->exceptions.size());
}
static const byte data[] = {SECTION_EXCEPTIONS(6), 2,
// except[0] (f32, i64)
2, kLocalF32, kLocalI64,
// except[1] (i32)
1, kLocalI32};
FAIL_IF_NO_EXPERIMENTAL_EH(data);
EXPERIMENTAL_FLAG_SCOPE(eh);
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(2u, result.val->exceptions.size());
const WasmException& e0 = result.val->exceptions.front();
EXPECT_EQ(2u, e0.sig->parameter_count());
EXPECT_EQ(MachineRepresentation::kFloat32, e0.sig->GetParam(0));
EXPECT_EQ(MachineRepresentation::kWord64, e0.sig->GetParam(1));
const WasmException& e1 = result.val->exceptions.back();
EXPECT_EQ(MachineRepresentation::kWord32, e1.sig->GetParam(0));
}
TEST_F(WasmModuleVerifyTest, Exception_invalid_type) {
static const byte data[] = {SECTION_EXCEPTIONS(3), 1,
1, // except[0] (?)
64};
// except[0] (?)
1, 64};
FAIL_IF_NO_EXPERIMENTAL_EH(data);
{
// Should fail decoding exception section.
EXPERIMENTAL_FLAG_SCOPE(eh);
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_FALSE(result.ok());
}
{
// Should read exception section as unknown section.
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(0u, result.val->exceptions.size());
}
// Should fail decoding exception section.
EXPERIMENTAL_FLAG_SCOPE(eh);
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_FALSE(result.ok());
}
TEST_F(WasmModuleVerifyTest, OneSignature) {
......
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