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}
This commit is contained in:
Karl Schimpf 2017-08-01 13:56:39 -07:00 committed by Commit Bot
parent 35c923cc10
commit 470a10015d
18 changed files with 379 additions and 160 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -177,9 +177,6 @@
# BUG(v8:6306).
'wasm/huge-memory': [SKIP],
# BUG(v8:6577).
'wasm/exceptions': [SKIP],
}], # ALWAYS
['novfp3 == True', {

View File

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

View File

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

View File

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

View File

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

View File

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