diff options
author | Michaël Zasso <targos@protonmail.com> | 2016-12-23 16:30:57 +0100 |
---|---|---|
committer | Michaël Zasso <targos@protonmail.com> | 2017-01-26 22:46:17 +0100 |
commit | 2739185b790e040c3b044c577327f5d44bffad4a (patch) | |
tree | 29a466999212f4c85958379d9d400eec8a185ba5 /deps/v8/src/wasm | |
parent | a67a04d7654faaa04c8da00e42981ebc9fd0911c (diff) | |
download | android-node-v8-2739185b790e040c3b044c577327f5d44bffad4a.tar.gz android-node-v8-2739185b790e040c3b044c577327f5d44bffad4a.tar.bz2 android-node-v8-2739185b790e040c3b044c577327f5d44bffad4a.zip |
deps: update V8 to 5.5.372.40
PR-URL: https://github.com/nodejs/node/pull/9618
Reviewed-By: Ali Ijaz Sheikh <ofrobots@google.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Diffstat (limited to 'deps/v8/src/wasm')
21 files changed, 4018 insertions, 2607 deletions
diff --git a/deps/v8/src/wasm/ast-decoder.cc b/deps/v8/src/wasm/ast-decoder.cc index 0f192508ba..02d1db5bda 100644 --- a/deps/v8/src/wasm/ast-decoder.cc +++ b/deps/v8/src/wasm/ast-decoder.cc @@ -7,7 +7,7 @@ #include "src/bit-vector.h" #include "src/flags.h" #include "src/handles.h" -#include "src/zone-containers.h" +#include "src/zone/zone-containers.h" #include "src/wasm/ast-decoder.h" #include "src/wasm/decoder.h" @@ -36,6 +36,8 @@ namespace wasm { error("Invalid opcode (enable with --" #flag ")"); \ break; \ } +// TODO(titzer): this is only for intermediate migration. +#define IMPLICIT_FUNCTION_END 1 // An SsaEnv environment carries the current local variable renaming // as well as the current effect and control dependency in the TF graph. @@ -68,62 +70,82 @@ struct Value { LocalType type; }; -// An entry on the control stack (i.e. if, block, loop). -struct Control { - const byte* pc; - int stack_depth; // stack height at the beginning of the construct. - SsaEnv* end_env; // end environment for the construct. - SsaEnv* false_env; // false environment (only for if). - SsaEnv* catch_env; // catch environment (only for try with catch). - SsaEnv* finish_try_env; // the environment where a try with finally lives. - TFNode* node; // result node for the construct. - LocalType type; // result type for the construct. - bool is_loop; // true if this is the inner label of a loop. +struct TryInfo : public ZoneObject { + SsaEnv* catch_env; + TFNode* exception; - bool is_if() const { return *pc == kExprIf; } + explicit TryInfo(SsaEnv* c) : catch_env(c), exception(nullptr) {} +}; - bool is_try() const { - return *pc == kExprTryCatch || *pc == kExprTryCatchFinally || - *pc == kExprTryFinally; - } +struct MergeValues { + uint32_t arity; + union { + Value* array; + Value first; + } vals; // Either multiple values or a single value. - bool has_catch() const { - return *pc == kExprTryCatch || *pc == kExprTryCatchFinally; + Value& first() { + DCHECK_GT(arity, 0u); + return arity == 1 ? vals.first : vals.array[0]; } +}; - bool has_finally() const { - return *pc == kExprTryCatchFinally || *pc == kExprTryFinally; - } +static Value* NO_VALUE = nullptr; + +enum ControlKind { kControlIf, kControlBlock, kControlLoop, kControlTry }; + +// An entry on the control stack (i.e. if, block, loop). +struct Control { + const byte* pc; + ControlKind kind; + int stack_depth; // stack height at the beginning of the construct. + SsaEnv* end_env; // end environment for the construct. + SsaEnv* false_env; // false environment (only for if). + TryInfo* try_info; // Information used for compiling try statements. + int32_t previous_catch; // The previous Control (on the stack) with a catch. + + // Values merged into the end of this control construct. + MergeValues merge; + + inline bool is_if() const { return kind == kControlIf; } + inline bool is_block() const { return kind == kControlBlock; } + inline bool is_loop() const { return kind == kControlLoop; } + inline bool is_try() const { return kind == kControlTry; } // Named constructors. - static Control Block(const byte* pc, int stack_depth, SsaEnv* end_env) { - return {pc, stack_depth, end_env, nullptr, nullptr, - nullptr, nullptr, kAstEnd, false}; + static Control Block(const byte* pc, int stack_depth, SsaEnv* end_env, + int32_t previous_catch) { + return {pc, kControlBlock, stack_depth, end_env, + nullptr, nullptr, previous_catch, {0, {NO_VALUE}}}; } static Control If(const byte* pc, int stack_depth, SsaEnv* end_env, - SsaEnv* false_env) { - return {pc, stack_depth, end_env, false_env, nullptr, - nullptr, nullptr, kAstStmt, false}; + SsaEnv* false_env, int32_t previous_catch) { + return {pc, kControlIf, stack_depth, end_env, + false_env, nullptr, previous_catch, {0, {NO_VALUE}}}; } - static Control Loop(const byte* pc, int stack_depth, SsaEnv* end_env) { - return {pc, stack_depth, end_env, nullptr, nullptr, - nullptr, nullptr, kAstEnd, true}; + static Control Loop(const byte* pc, int stack_depth, SsaEnv* end_env, + int32_t previous_catch) { + return {pc, kControlLoop, stack_depth, end_env, + nullptr, nullptr, previous_catch, {0, {NO_VALUE}}}; } static Control Try(const byte* pc, int stack_depth, SsaEnv* end_env, - SsaEnv* catch_env, SsaEnv* finish_try_env) { - return {pc, stack_depth, end_env, nullptr, catch_env, finish_try_env, - nullptr, kAstEnd, false}; + Zone* zone, SsaEnv* catch_env, int32_t previous_catch) { + DCHECK_NOT_NULL(catch_env); + TryInfo* try_info = new (zone) TryInfo(catch_env); + return {pc, kControlTry, stack_depth, end_env, + nullptr, try_info, previous_catch, {0, {NO_VALUE}}}; } }; // Macros that build nodes only if there is a graph and the current SSA // environment is reachable from start. This avoids problems with malformed // TF graphs when decoding inputs that have unreachable code. -#define BUILD(func, ...) (build() ? builder_->func(__VA_ARGS__) : nullptr) -#define BUILD0(func) (build() ? builder_->func() : nullptr) +#define BUILD(func, ...) \ + (build() ? CheckForException(builder_->func(__VA_ARGS__)) : nullptr) +#define BUILD0(func) (build() ? CheckForException(builder_->func()) : nullptr) // Generic Wasm bytecode decoder with utilities for decoding operands, // lengths, etc. @@ -150,17 +172,18 @@ class WasmDecoder : public Decoder { } return true; } - error(pc, pc + 1, "invalid local index"); + error(pc, pc + 1, "invalid local index: %u", operand.index); return false; } inline bool Validate(const byte* pc, GlobalIndexOperand& operand) { ModuleEnv* m = module_; if (m && m->module && operand.index < m->module->globals.size()) { - operand.type = m->module->globals[operand.index].type; + operand.global = &m->module->globals[operand.index]; + operand.type = operand.global->type; return true; } - error(pc, pc + 1, "invalid global index"); + error(pc, pc + 1, "invalid global index: %u", operand.index); return false; } @@ -175,16 +198,9 @@ class WasmDecoder : public Decoder { inline bool Validate(const byte* pc, CallFunctionOperand& operand) { if (Complete(pc, operand)) { - uint32_t expected = static_cast<uint32_t>(operand.sig->parameter_count()); - if (operand.arity != expected) { - error(pc, pc + 1, - "arity mismatch in direct function call (expected %u, got %u)", - expected, operand.arity); - return false; - } return true; } - error(pc, pc + 1, "invalid function index"); + error(pc, pc + 1, "invalid function index: %u", operand.index); return false; } @@ -199,161 +215,28 @@ class WasmDecoder : public Decoder { inline bool Validate(const byte* pc, CallIndirectOperand& operand) { if (Complete(pc, operand)) { - uint32_t expected = static_cast<uint32_t>(operand.sig->parameter_count()); - if (operand.arity != expected) { - error(pc, pc + 1, - "arity mismatch in indirect function call (expected %u, got %u)", - expected, operand.arity); - return false; - } - return true; - } - error(pc, pc + 1, "invalid signature index"); - return false; - } - - inline bool Complete(const byte* pc, CallImportOperand& operand) { - ModuleEnv* m = module_; - if (m && m->module && operand.index < m->module->import_table.size()) { - operand.sig = m->module->import_table[operand.index].sig; - return true; - } - return false; - } - - inline bool Validate(const byte* pc, CallImportOperand& operand) { - if (Complete(pc, operand)) { - uint32_t expected = static_cast<uint32_t>(operand.sig->parameter_count()); - if (operand.arity != expected) { - error(pc, pc + 1, "arity mismatch in import call (expected %u, got %u)", - expected, operand.arity); - return false; - } return true; } - error(pc, pc + 1, "invalid signature index"); + error(pc, pc + 1, "invalid signature index: #%u", operand.index); return false; } inline bool Validate(const byte* pc, BreakDepthOperand& operand, ZoneVector<Control>& control) { - if (operand.arity > 1) { - error(pc, pc + 1, "invalid arity for br or br_if"); - return false; - } if (operand.depth < control.size()) { operand.target = &control[control.size() - operand.depth - 1]; return true; } - error(pc, pc + 1, "invalid break depth"); + error(pc, pc + 1, "invalid break depth: %u", operand.depth); return false; } bool Validate(const byte* pc, BranchTableOperand& operand, size_t block_depth) { - if (operand.arity > 1) { - error(pc, pc + 1, "invalid arity for break"); - return false; - } - // Verify table. - for (uint32_t i = 0; i < operand.table_count + 1; ++i) { - uint32_t target = operand.read_entry(this, i); - if (target >= block_depth) { - error(operand.table + i * 2, "improper branch in br_table"); - return false; - } - } + // TODO(titzer): add extra redundant validation for br_table here? return true; } - unsigned OpcodeArity(const byte* pc) { -#define DECLARE_ARITY(name, ...) \ - static const LocalType kTypes_##name[] = {__VA_ARGS__}; \ - static const int kArity_##name = \ - static_cast<int>(arraysize(kTypes_##name) - 1); - - FOREACH_SIGNATURE(DECLARE_ARITY); -#undef DECLARE_ARITY - - switch (static_cast<WasmOpcode>(*pc)) { - case kExprI8Const: - case kExprI32Const: - case kExprI64Const: - case kExprF64Const: - case kExprF32Const: - case kExprGetLocal: - case kExprGetGlobal: - case kExprNop: - case kExprUnreachable: - case kExprEnd: - case kExprBlock: - case kExprThrow: - case kExprTryCatch: - case kExprTryCatchFinally: - case kExprTryFinally: - case kExprFinally: - case kExprLoop: - return 0; - - case kExprSetGlobal: - case kExprSetLocal: - case kExprElse: - case kExprCatch: - return 1; - - case kExprBr: { - BreakDepthOperand operand(this, pc); - return operand.arity; - } - case kExprBrIf: { - BreakDepthOperand operand(this, pc); - return 1 + operand.arity; - } - case kExprBrTable: { - BranchTableOperand operand(this, pc); - return 1 + operand.arity; - } - - case kExprIf: - return 1; - case kExprSelect: - return 3; - - case kExprCallFunction: { - CallFunctionOperand operand(this, pc); - return operand.arity; - } - case kExprCallIndirect: { - CallIndirectOperand operand(this, pc); - return 1 + operand.arity; - } - case kExprCallImport: { - CallImportOperand operand(this, pc); - return operand.arity; - } - case kExprReturn: { - ReturnArityOperand operand(this, pc); - return operand.arity; - } - -#define DECLARE_OPCODE_CASE(name, opcode, sig) \ - case kExpr##name: \ - return kArity_##sig; - - FOREACH_LOAD_MEM_OPCODE(DECLARE_OPCODE_CASE) - FOREACH_STORE_MEM_OPCODE(DECLARE_OPCODE_CASE) - FOREACH_MISC_MEM_OPCODE(DECLARE_OPCODE_CASE) - FOREACH_SIMPLE_OPCODE(DECLARE_OPCODE_CASE) - FOREACH_SIMPLE_MEM_OPCODE(DECLARE_OPCODE_CASE) - FOREACH_ASMJS_COMPAT_OPCODE(DECLARE_OPCODE_CASE) - FOREACH_SIMD_OPCODE(DECLARE_OPCODE_CASE) -#undef DECLARE_OPCODE_CASE - default: - UNREACHABLE(); - return 0; - } - } - unsigned OpcodeLength(const byte* pc) { switch (static_cast<WasmOpcode>(*pc)) { #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: @@ -361,7 +244,7 @@ class WasmDecoder : public Decoder { FOREACH_STORE_MEM_OPCODE(DECLARE_OPCODE_CASE) #undef DECLARE_OPCODE_CASE { - MemoryAccessOperand operand(this, pc); + MemoryAccessOperand operand(this, pc, UINT32_MAX); return 1 + operand.length; } case kExprBr: @@ -383,12 +266,17 @@ class WasmDecoder : public Decoder { CallIndirectOperand operand(this, pc); return 1 + operand.length; } - case kExprCallImport: { - CallImportOperand operand(this, pc); + + case kExprTry: + case kExprIf: // fall thru + case kExprLoop: + case kExprBlock: { + BlockTypeOperand operand(this, pc); return 1 + operand.length; } case kExprSetLocal: + case kExprTeeLocal: case kExprGetLocal: case kExprCatch: { LocalIndexOperand operand(this, pc); @@ -396,7 +284,8 @@ class WasmDecoder : public Decoder { } case kExprBrTable: { BranchTableOperand operand(this, pc); - return 1 + operand.length; + BranchTableIterator iterator(this, operand); + return 1 + iterator.length(); } case kExprI32Const: { ImmI32Operand operand(this, pc); @@ -412,17 +301,14 @@ class WasmDecoder : public Decoder { return 5; case kExprF64Const: return 9; - case kExprReturn: { - ReturnArityOperand operand(this, pc); - return 1 + operand.length; - } - default: return 1; } } }; +static const int32_t kNullCatch = -1; + // The full WASM decoder for bytecode. Both verifies bytecode and generates // a TurboFan IR graph. class WasmFullDecoder : public WasmDecoder { @@ -434,7 +320,9 @@ class WasmFullDecoder : public WasmDecoder { base_(body.base), local_type_vec_(zone), stack_(zone), - control_(zone) { + control_(zone), + last_end_found_(false), + current_catch_(kNullCatch) { local_types_ = &local_type_vec_; } @@ -447,7 +335,7 @@ class WasmFullDecoder : public WasmDecoder { control_.clear(); if (end_ < pc_) { - error(pc_, "function body end < start"); + error("function body end < start"); return false; } @@ -457,23 +345,55 @@ class WasmFullDecoder : public WasmDecoder { if (failed()) return TraceFailed(); +#if IMPLICIT_FUNCTION_END + // With implicit end support (old style), the function block + // remains on the stack. Other control blocks are an error. + if (control_.size() > 1) { + error(pc_, control_.back().pc, "unterminated control structure"); + return TraceFailed(); + } + + // Assume an implicit end to the function body block. + if (control_.size() == 1) { + Control* c = &control_.back(); + if (ssa_env_->go()) { + FallThruTo(c); + } + + if (c->end_env->go()) { + // Push the end values onto the stack. + stack_.resize(c->stack_depth); + if (c->merge.arity == 1) { + stack_.push_back(c->merge.vals.first); + } else { + for (unsigned i = 0; i < c->merge.arity; i++) { + stack_.push_back(c->merge.vals.array[i]); + } + } + + TRACE(" @%-8d #xx:%-20s|", startrel(pc_), "ImplicitReturn"); + SetEnv("function:end", c->end_env); + DoReturn(); + TRACE("\n"); + } + } +#else if (!control_.empty()) { error(pc_, control_.back().pc, "unterminated control structure"); return TraceFailed(); } - if (ssa_env_->go()) { - TRACE(" @%-6d #xx:%-20s|", startrel(pc_), "ImplicitReturn"); - DoReturn(); - if (failed()) return TraceFailed(); - TRACE("\n"); + if (!last_end_found_) { + error("function body must end with \"end\" opcode."); + return false; } +#endif if (FLAG_trace_wasm_decode_time) { double ms = decode_timer.Elapsed().InMillisecondsF(); - PrintF("wasm-decode ok (%0.3f ms)\n\n", ms); + PrintF("wasm-decode %s (%0.3f ms)\n\n", ok() ? "ok" : "failed", ms); } else { - TRACE("wasm-decode ok\n\n"); + TRACE("wasm-decode %s\n\n", ok() ? "ok" : "failed"); } return true; @@ -526,6 +446,11 @@ class WasmFullDecoder : public WasmDecoder { ZoneVector<LocalType> local_type_vec_; // types of local variables. ZoneVector<Value> stack_; // stack of values. ZoneVector<Control> control_; // stack of blocks, loops, and ifs. + bool last_end_found_; + + int32_t current_catch_; + + TryInfo* current_try_info() { return control_[current_catch_].try_info; } inline bool build() { return builder_ && ssa_env_->go(); } @@ -574,6 +499,8 @@ class WasmFullDecoder : public WasmDecoder { return builder_->Float32Constant(0); case kAstF64: return builder_->Float64Constant(0); + case kAstS128: + return builder_->DefaultS128Value(); default: UNREACHABLE(); return nullptr; @@ -603,8 +530,13 @@ class WasmFullDecoder : public WasmDecoder { } // Decode local declarations, if any. uint32_t entries = consume_u32v("local decls count"); + TRACE("local decls count: %u\n", entries); while (entries-- > 0 && pc_ < limit_) { uint32_t count = consume_u32v("local count"); + if (count > kMaxNumWasmLocals) { + error(pc_ - 1, "local count too large"); + return; + } byte code = consume_u8("local type"); LocalType type; switch (code) { @@ -620,6 +552,9 @@ class WasmFullDecoder : public WasmDecoder { case kLocalF64: type = kAstF64; break; + case kLocalS128: + type = kAstS128; + break; default: error(pc_ - 1, "invalid local type"); return; @@ -636,82 +571,68 @@ class WasmFullDecoder : public WasmDecoder { reinterpret_cast<const void*>(limit_), baserel(pc_), static_cast<int>(limit_ - start_), builder_ ? "graph building" : ""); + { + // Set up initial function block. + SsaEnv* break_env = ssa_env_; + SetEnv("initial env", Steal(break_env)); + PushBlock(break_env); + Control* c = &control_.back(); + c->merge.arity = static_cast<uint32_t>(sig_->return_count()); + + if (c->merge.arity == 1) { + c->merge.vals.first = {pc_, nullptr, sig_->GetReturn(0)}; + } else if (c->merge.arity > 1) { + c->merge.vals.array = zone_->NewArray<Value>(c->merge.arity); + for (unsigned i = 0; i < c->merge.arity; i++) { + c->merge.vals.array[i] = {pc_, nullptr, sig_->GetReturn(i)}; + } + } + } + if (pc_ >= limit_) return; // Nothing to do. while (true) { // decoding loop. unsigned len = 1; WasmOpcode opcode = static_cast<WasmOpcode>(*pc_); - TRACE(" @%-6d #%02x:%-20s|", startrel(pc_), opcode, - WasmOpcodes::ShortOpcodeName(opcode)); + if (!WasmOpcodes::IsPrefixOpcode(opcode)) { + TRACE(" @%-8d #%02x:%-20s|", startrel(pc_), opcode, + WasmOpcodes::ShortOpcodeName(opcode)); + } FunctionSig* sig = WasmOpcodes::Signature(opcode); if (sig) { - // Fast case of a simple operator. - TFNode* node; - switch (sig->parameter_count()) { - case 1: { - Value val = Pop(0, sig->GetParam(0)); - node = BUILD(Unop, opcode, val.node, position()); - break; - } - case 2: { - Value rval = Pop(1, sig->GetParam(1)); - Value lval = Pop(0, sig->GetParam(0)); - node = BUILD(Binop, opcode, lval.node, rval.node, position()); - break; - } - default: - UNREACHABLE(); - node = nullptr; - break; - } - Push(GetReturnType(sig), node); + BuildSimpleOperator(opcode, sig); } else { // Complex bytecode. switch (opcode) { case kExprNop: - Push(kAstStmt, nullptr); break; case kExprBlock: { // The break environment is the outer environment. + BlockTypeOperand operand(this, pc_); SsaEnv* break_env = ssa_env_; PushBlock(break_env); SetEnv("block:start", Steal(break_env)); + SetBlockType(&control_.back(), operand); + len = 1 + operand.length; break; } case kExprThrow: { CHECK_PROTOTYPE_OPCODE(wasm_eh_prototype); - Pop(0, kAstI32); - - // TODO(jpp): start exception propagation. + Value value = Pop(0, kAstI32); + BUILD(Throw, value.node); break; } - case kExprTryCatch: { + case kExprTry: { CHECK_PROTOTYPE_OPCODE(wasm_eh_prototype); + BlockTypeOperand operand(this, pc_); SsaEnv* outer_env = ssa_env_; SsaEnv* try_env = Steal(outer_env); - SsaEnv* catch_env = Split(try_env); - PushTry(outer_env, catch_env, nullptr); + SsaEnv* catch_env = UnreachableEnv(); + PushTry(outer_env, catch_env); SetEnv("try_catch:start", try_env); - break; - } - case kExprTryCatchFinally: { - CHECK_PROTOTYPE_OPCODE(wasm_eh_prototype); - SsaEnv* outer_env = ssa_env_; - SsaEnv* try_env = Steal(outer_env); - SsaEnv* catch_env = Split(try_env); - SsaEnv* finally_env = Split(try_env); - PushTry(finally_env, catch_env, outer_env); - SetEnv("try_catch_finally:start", try_env); - break; - } - case kExprTryFinally: { - CHECK_PROTOTYPE_OPCODE(wasm_eh_prototype); - SsaEnv* outer_env = ssa_env_; - SsaEnv* try_env = Steal(outer_env); - SsaEnv* finally_env = Split(outer_env); - PushTry(finally_env, nullptr, outer_env); - SetEnv("try_finally:start", try_env); + SetBlockType(&control_.back(), operand); + len = 1 + operand.length; break; } case kExprCatch: { @@ -720,97 +641,57 @@ class WasmFullDecoder : public WasmDecoder { len = 1 + operand.length; if (control_.empty()) { - error(pc_, "catch does not match a any try"); + error("catch does not match any try"); break; } Control* c = &control_.back(); - if (!c->has_catch()) { - error(pc_, "catch does not match a try with catch"); + if (!c->is_try()) { + error("catch does not match any try"); break; } - if (c->catch_env == nullptr) { + if (c->try_info->catch_env == nullptr) { error(pc_, "catch already present for try with catch"); break; } - Goto(ssa_env_, c->end_env); + if (ssa_env_->go()) { + MergeValuesInto(c); + } + stack_.resize(c->stack_depth); - SsaEnv* catch_env = c->catch_env; - c->catch_env = nullptr; + 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)) { - // TODO(jpp): figure out how thrown value is propagated. It is - // unlikely to be a value on the stack. if (ssa_env_->locals) { - ssa_env_->locals[operand.index] = nullptr; + TFNode* exception_as_i32 = + BUILD(Catch, c->try_info->exception, position()); + ssa_env_->locals[operand.index] = exception_as_i32; } } - PopUpTo(c->stack_depth); - - break; - } - case kExprFinally: { - CHECK_PROTOTYPE_OPCODE(wasm_eh_prototype); - if (control_.empty()) { - error(pc_, "finally does not match a any try"); - break; - } - - Control* c = &control_.back(); - if (c->has_catch() && c->catch_env != nullptr) { - error(pc_, "missing catch for try with catch and finally"); - break; - } - - if (!c->has_finally()) { - error(pc_, "finally does not match a try with finally"); - break; - } - - if (c->finish_try_env == nullptr) { - error(pc_, "finally already present for try with finally"); - break; - } - - // ssa_env_ is either the env for either the try or the catch, but - // it does not matter: either way we need to direct the control flow - // to the end_env, which is the env for the finally. - // c->finish_try_env is the the environment enclosing the try block. - Goto(ssa_env_, c->end_env); - - PopUpTo(c->stack_depth); - - // The current environment becomes end_env, and finish_try_env - // becomes the new end_env. This ensures that any control flow - // leaving a try block up to now will do so by branching to the - // finally block. Setting the end_env to be finish_try_env ensures - // that kExprEnd below can handle the try block as it would any - // other block construct. - SsaEnv* finally_env = c->end_env; - c->end_env = c->finish_try_env; - SetEnv("finally:begin", finally_env); - c->finish_try_env = nullptr; - break; } case kExprLoop: { - // The break environment is the outer environment. - SsaEnv* break_env = ssa_env_; - PushBlock(break_env); - SsaEnv* finish_try_env = Steal(break_env); + BlockTypeOperand operand(this, pc_); + SsaEnv* finish_try_env = Steal(ssa_env_); // The continue environment is the inner environment. PrepareForLoop(pc_, finish_try_env); SetEnv("loop:start", Split(finish_try_env)); ssa_env_->SetNotMerged(); PushLoop(finish_try_env); + SetBlockType(&control_.back(), operand); + len = 1 + operand.length; break; } case kExprIf: { // Condition on top of stack. Split environments for branches. + BlockTypeOperand operand(this, pc_); Value cond = Pop(0, kAstI32); TFNode* if_true = nullptr; TFNode* if_false = nullptr; @@ -822,11 +703,13 @@ class WasmFullDecoder : public WasmDecoder { true_env->control = if_true; PushIf(end_env, false_env); SetEnv("if:true", true_env); + SetBlockType(&control_.back(), operand); + len = 1 + operand.length; break; } case kExprElse: { if (control_.empty()) { - error(pc_, "else does not match any if"); + error("else does not match any if"); break; } Control* c = &control_.back(); @@ -838,31 +721,38 @@ class WasmFullDecoder : public WasmDecoder { error(pc_, c->pc, "else already present for if"); break; } - Value val = PopUpTo(c->stack_depth); - MergeInto(c->end_env, &c->node, &c->type, val); + FallThruTo(c); // Switch to environment for false branch. + stack_.resize(c->stack_depth); SetEnv("if_else:false", c->false_env); c->false_env = nullptr; // record that an else is already seen break; } case kExprEnd: { if (control_.empty()) { - error(pc_, "end does not match any if or block"); - break; + error("end does not match any if, try, or block"); + return; } const char* name = "block:end"; Control* c = &control_.back(); - Value val = PopUpTo(c->stack_depth); - if (c->is_loop) { - // Loops always push control in pairs. - control_.pop_back(); - c = &control_.back(); - name = "loop:end"; - } else if (c->is_if()) { + if (c->is_loop()) { + // A loop just leaves the values on the stack. + TypeCheckLoopFallThru(c); + PopControl(); + SetEnv("loop:end", ssa_env_); + break; + } + if (c->is_if()) { if (c->false_env != nullptr) { // End the true branch of a one-armed if. Goto(c->false_env, c->end_env); - val = {val.pc, nullptr, kAstStmt}; + if (ssa_env_->go() && stack_.size() != c->stack_depth) { + error("end of if expected empty stack"); + stack_.resize(c->stack_depth); + } + if (c->merge.arity > 0) { + error("non-void one-armed if"); + } name = "if:merge"; } else { // End the false branch of a two-armed if. @@ -871,28 +761,41 @@ class WasmFullDecoder : public WasmDecoder { } else if (c->is_try()) { name = "try:end"; - // try blocks do not yield a value. - val = {val.pc, nullptr, kAstStmt}; - - // validate that catch/finally were seen. - if (c->catch_env != nullptr) { - error(pc_, "missing catch in try with catch"); + // validate that catch was seen. + if (c->try_info->catch_env != nullptr) { + error(pc_, "missing catch in try"); break; } + } + FallThruTo(c); + SetEnv(name, c->end_env); - if (c->finish_try_env != nullptr) { - error(pc_, "missing finally in try with finally"); - break; + // Push the end values onto the stack. + stack_.resize(c->stack_depth); + if (c->merge.arity == 1) { + stack_.push_back(c->merge.vals.first); + } else { + for (unsigned i = 0; i < c->merge.arity; i++) { + stack_.push_back(c->merge.vals.array[i]); } } - if (ssa_env_->go()) { - MergeInto(c->end_env, &c->node, &c->type, val); + PopControl(); + + if (control_.empty()) { + // If the last (implicit) control was popped, check we are at end. + if (pc_ + 1 != end_) { + error(pc_, pc_ + 1, "trailing code after function end"); + } + last_end_found_ = true; + if (ssa_env_->go()) { + // The result of the block is the return value. + TRACE(" @%-8d #xx:%-20s|", startrel(pc_), "ImplicitReturn"); + DoReturn(); + TRACE("\n"); + } + return; } - SetEnv(name, c->end_env); - stack_.resize(c->stack_depth); - Push(c->type, c->node); - control_.pop_back(); break; } case kExprSelect: { @@ -901,7 +804,7 @@ class WasmFullDecoder : public WasmDecoder { Value tval = Pop(); if (tval.type == kAstStmt || tval.type != fval.type) { if (tval.type != kAstEnd && fval.type != kAstEnd) { - error(pc_, "type mismatch in select"); + error("type mismatch in select"); break; } } @@ -923,39 +826,33 @@ class WasmFullDecoder : public WasmDecoder { } case kExprBr: { BreakDepthOperand operand(this, pc_); - Value val = {pc_, nullptr, kAstStmt}; - if (operand.arity) val = Pop(); if (Validate(pc_, operand, control_)) { - BreakTo(operand.target, val); + BreakTo(operand.depth); } len = 1 + operand.length; - Push(kAstEnd, nullptr); + EndControl(); break; } case kExprBrIf: { BreakDepthOperand operand(this, pc_); - Value cond = Pop(operand.arity, kAstI32); - Value val = {pc_, nullptr, kAstStmt}; - if (operand.arity == 1) val = Pop(); - if (Validate(pc_, operand, control_)) { + Value cond = Pop(0, kAstI32); + if (ok() && Validate(pc_, operand, control_)) { SsaEnv* fenv = ssa_env_; SsaEnv* tenv = Split(fenv); fenv->SetNotMerged(); BUILD(Branch, cond.node, &tenv->control, &fenv->control); ssa_env_ = tenv; - BreakTo(operand.target, val); + BreakTo(operand.depth); ssa_env_ = fenv; } len = 1 + operand.length; - Push(kAstStmt, nullptr); break; } case kExprBrTable: { BranchTableOperand operand(this, pc_); + BranchTableIterator iterator(this, operand); if (Validate(pc_, operand, control_.size())) { - Value key = Pop(operand.arity, kAstI32); - Value val = {pc_, nullptr, kAstStmt}; - if (operand.arity == 1) val = Pop(); + Value key = Pop(0, kAstI32); if (failed()) break; SsaEnv* break_env = ssa_env_; @@ -965,42 +862,43 @@ class WasmFullDecoder : public WasmDecoder { SsaEnv* copy = Steal(break_env); ssa_env_ = copy; - for (uint32_t i = 0; i < operand.table_count + 1; ++i) { - uint16_t target = operand.read_entry(this, i); + while (iterator.has_next()) { + uint32_t i = iterator.cur_index(); + const byte* pos = iterator.pc(); + uint32_t target = iterator.next(); + if (target >= control_.size()) { + error(pos, "improper branch in br_table"); + break; + } ssa_env_ = Split(copy); ssa_env_->control = (i == operand.table_count) ? BUILD(IfDefault, sw) : BUILD(IfValue, i, sw); - int depth = target; - Control* c = &control_[control_.size() - depth - 1]; - MergeInto(c->end_env, &c->node, &c->type, val); + BreakTo(target); } } else { // Only a default target. Do the equivalent of br. - uint16_t target = operand.read_entry(this, 0); - int depth = target; - Control* c = &control_[control_.size() - depth - 1]; - MergeInto(c->end_env, &c->node, &c->type, val); + const byte* pos = iterator.pc(); + uint32_t target = iterator.next(); + if (target >= control_.size()) { + error(pos, "improper branch in br_table"); + break; + } + BreakTo(target); } // br_table ends the control flow like br. ssa_env_ = break_env; - Push(kAstStmt, nullptr); } - len = 1 + operand.length; + len = 1 + iterator.length(); break; } case kExprReturn: { - ReturnArityOperand operand(this, pc_); - if (operand.arity != sig_->return_count()) { - error(pc_, pc_ + 1, "arity mismatch in return"); - } DoReturn(); - len = 1 + operand.length; break; } case kExprUnreachable: { - Push(kAstEnd, BUILD(Unreachable, position())); - ssa_env_->Kill(SsaEnv::kControlEnd); + BUILD(Unreachable, position()); + EndControl(); break; } case kExprI8Const: { @@ -1050,11 +948,24 @@ class WasmFullDecoder : public WasmDecoder { if (Validate(pc_, operand)) { Value val = Pop(0, local_type_vec_[operand.index]); if (ssa_env_->locals) ssa_env_->locals[operand.index] = val.node; + } + len = 1 + operand.length; + break; + } + case kExprTeeLocal: { + LocalIndexOperand operand(this, pc_); + if (Validate(pc_, operand)) { + Value val = Pop(0, local_type_vec_[operand.index]); + if (ssa_env_->locals) ssa_env_->locals[operand.index] = val.node; Push(val.type, val.node); } len = 1 + operand.length; break; } + case kExprDrop: { + Pop(); + break; + } case kExprGetGlobal: { GlobalIndexOperand operand(this, pc_); if (Validate(pc_, operand)) { @@ -1066,9 +977,13 @@ class WasmFullDecoder : public WasmDecoder { case kExprSetGlobal: { GlobalIndexOperand operand(this, pc_); if (Validate(pc_, operand)) { - Value val = Pop(0, operand.type); - BUILD(SetGlobal, operand.index, val.node); - Push(val.type, val.node); + if (operand.global->mutability) { + Value val = Pop(0, operand.type); + BUILD(SetGlobal, operand.index, val.node); + } else { + error(pc_, pc_ + 1, "immutable global #%u cannot be assigned", + operand.index); + } } len = 1 + operand.length; break; @@ -1088,7 +1003,6 @@ class WasmFullDecoder : public WasmDecoder { case kExprI32LoadMem: len = DecodeLoadMem(kAstI32, MachineType::Int32()); break; - case kExprI64LoadMem8S: len = DecodeLoadMem(kAstI64, MachineType::Int8()); break; @@ -1143,17 +1057,24 @@ class WasmFullDecoder : public WasmDecoder { case kExprF64StoreMem: len = DecodeStoreMem(kAstF64, MachineType::Float64()); break; - + case kExprGrowMemory: + if (module_->origin != kAsmJsOrigin) { + Value val = Pop(0, kAstI32); + Push(kAstI32, BUILD(GrowMemory, val.node)); + } else { + error("grow_memory is not supported for asmjs modules"); + } + break; case kExprMemorySize: - Push(kAstI32, BUILD(MemSize, 0)); + Push(kAstI32, BUILD(CurrentMemoryPages)); break; case kExprCallFunction: { CallFunctionOperand operand(this, pc_); if (Validate(pc_, operand)) { TFNode** buffer = PopArgs(operand.sig); - TFNode* call = - BUILD(CallDirect, operand.index, buffer, position()); - Push(GetReturnType(operand.sig), call); + TFNode** rets = nullptr; + BUILD(CallDirect, operand.index, buffer, &rets, position()); + PushReturns(operand.sig, rets); } len = 1 + operand.length; break; @@ -1161,23 +1082,12 @@ class WasmFullDecoder : public WasmDecoder { case kExprCallIndirect: { CallIndirectOperand operand(this, pc_); if (Validate(pc_, operand)) { - TFNode** buffer = PopArgs(operand.sig); Value index = Pop(0, kAstI32); - if (buffer) buffer[0] = index.node; - TFNode* call = - BUILD(CallIndirect, operand.index, buffer, position()); - Push(GetReturnType(operand.sig), call); - } - len = 1 + operand.length; - break; - } - case kExprCallImport: { - CallImportOperand operand(this, pc_); - if (Validate(pc_, operand)) { TFNode** buffer = PopArgs(operand.sig); - TFNode* call = - BUILD(CallImport, operand.index, buffer, position()); - Push(GetReturnType(operand.sig), call); + if (buffer) buffer[0] = index.node; + TFNode** rets = nullptr; + BUILD(CallIndirect, operand.index, buffer, &rets, position()); + PushReturns(operand.sig, rets); } len = 1 + operand.length; break; @@ -1187,20 +1097,34 @@ class WasmFullDecoder : public WasmDecoder { len++; byte simd_index = *(pc_ + 1); opcode = static_cast<WasmOpcode>(opcode << 8 | simd_index); - DecodeSimdOpcode(opcode); + TRACE(" @%-4d #%02x #%02x:%-20s|", startrel(pc_), kSimdPrefix, + simd_index, WasmOpcodes::ShortOpcodeName(opcode)); + len += DecodeSimdOpcode(opcode); break; } - default: - error("Invalid opcode"); - return; + default: { + // Deal with special asmjs opcodes. + if (module_ && module_->origin == kAsmJsOrigin) { + sig = WasmOpcodes::AsmjsSignature(opcode); + if (sig) { + BuildSimpleOperator(opcode, sig); + } + } else { + error("Invalid opcode"); + return; + } + } } - } // end complex bytecode + } #if DEBUG if (FLAG_trace_wasm_decoder) { for (size_t i = 0; i < stack_.size(); ++i) { Value& val = stack_[i]; WasmOpcode opcode = static_cast<WasmOpcode>(*val.pc); + if (WasmOpcodes::IsPrefixOpcode(opcode)) { + opcode = static_cast<WasmOpcode>(opcode << 8 | *(val.pc + 1)); + } PrintF(" %c@%d:%s", WasmOpcodes::ShortNameOf(val.type), static_cast<int>(val.pc - start_), WasmOpcodes::ShortOpcodeName(opcode)); @@ -1215,7 +1139,8 @@ class WasmFullDecoder : public WasmDecoder { PrintF("[%u]", operand.index); break; } - case kExprSetLocal: { + case kExprSetLocal: // fallthru + case kExprTeeLocal: { LocalIndexOperand operand(this, val.pc); PrintF("[%u]", operand.index); break; @@ -1234,7 +1159,21 @@ class WasmFullDecoder : public WasmDecoder { return; } } // end decode loop - } // end DecodeFunctionBody() + } + + void EndControl() { ssa_env_->Kill(SsaEnv::kControlEnd); } + + void SetBlockType(Control* c, BlockTypeOperand& operand) { + c->merge.arity = operand.arity; + if (c->merge.arity == 1) { + c->merge.vals.first = {pc_, nullptr, operand.read_entry(0)}; + } else if (c->merge.arity > 1) { + c->merge.vals.array = zone_->NewArray<Value>(c->merge.arity); + for (unsigned i = 0; i < c->merge.arity; i++) { + c->merge.vals.array[i] = {pc_, nullptr, operand.read_entry(i)}; + } + } + } TFNode** PopArgs(FunctionSig* sig) { if (build()) { @@ -1260,27 +1199,35 @@ class WasmFullDecoder : public WasmDecoder { void PushBlock(SsaEnv* end_env) { const int stack_depth = static_cast<int>(stack_.size()); - control_.emplace_back(Control::Block(pc_, stack_depth, end_env)); + control_.emplace_back( + Control::Block(pc_, stack_depth, end_env, current_catch_)); } void PushLoop(SsaEnv* end_env) { const int stack_depth = static_cast<int>(stack_.size()); - control_.emplace_back(Control::Loop(pc_, stack_depth, end_env)); + control_.emplace_back( + Control::Loop(pc_, stack_depth, end_env, current_catch_)); } void PushIf(SsaEnv* end_env, SsaEnv* false_env) { const int stack_depth = static_cast<int>(stack_.size()); - control_.emplace_back(Control::If(pc_, stack_depth, end_env, false_env)); + control_.emplace_back( + Control::If(pc_, stack_depth, end_env, false_env, current_catch_)); } - void PushTry(SsaEnv* end_env, SsaEnv* catch_env, SsaEnv* finish_try_env) { + void PushTry(SsaEnv* end_env, SsaEnv* catch_env) { const int stack_depth = static_cast<int>(stack_.size()); - control_.emplace_back( - Control::Try(pc_, stack_depth, end_env, catch_env, finish_try_env)); + control_.emplace_back(Control::Try(pc_, stack_depth, end_env, zone_, + catch_env, current_catch_)); + current_catch_ = static_cast<int32_t>(control_.size() - 1); } + void PopControl() { control_.pop_back(); } + int DecodeLoadMem(LocalType type, MachineType mem_type) { - MemoryAccessOperand operand(this, pc_); + MemoryAccessOperand operand(this, pc_, + ElementSizeLog2Of(mem_type.representation())); + Value index = Pop(0, kAstI32); TFNode* node = BUILD(LoadMem, type, mem_type, index.node, operand.offset, operand.alignment, position()); @@ -1289,24 +1236,45 @@ class WasmFullDecoder : public WasmDecoder { } int DecodeStoreMem(LocalType type, MachineType mem_type) { - MemoryAccessOperand operand(this, pc_); + MemoryAccessOperand operand(this, pc_, + ElementSizeLog2Of(mem_type.representation())); Value val = Pop(1, type); Value index = Pop(0, kAstI32); BUILD(StoreMem, mem_type, index.node, operand.offset, operand.alignment, val.node, position()); - Push(type, val.node); return 1 + operand.length; } - void DecodeSimdOpcode(WasmOpcode opcode) { - FunctionSig* sig = WasmOpcodes::Signature(opcode); - compiler::NodeVector inputs(sig->parameter_count(), zone_); - for (size_t i = sig->parameter_count(); i > 0; i--) { - Value val = Pop(static_cast<int>(i - 1), sig->GetParam(i - 1)); - inputs[i - 1] = val.node; + unsigned DecodeSimdOpcode(WasmOpcode opcode) { + unsigned len = 0; + switch (opcode) { + case kExprI32x4ExtractLane: { + uint8_t lane = this->checked_read_u8(pc_, 2, "lane number"); + if (lane < 0 || lane > 3) { + error(pc_, pc_ + 2, "invalid extract lane value"); + } + TFNode* input = Pop(0, LocalType::kSimd128).node; + TFNode* node = BUILD(SimdExtractLane, opcode, lane, input); + Push(LocalType::kWord32, node); + len++; + break; + } + default: { + FunctionSig* sig = WasmOpcodes::Signature(opcode); + if (sig != nullptr) { + compiler::NodeVector inputs(sig->parameter_count(), zone_); + for (size_t i = sig->parameter_count(); i > 0; i--) { + Value val = Pop(static_cast<int>(i - 1), sig->GetParam(i - 1)); + inputs[i - 1] = val.node; + } + TFNode* node = BUILD(SimdOp, opcode, inputs); + Push(GetReturnType(sig), node); + } else { + error("invalid simd opcode"); + } + } } - TFNode* node = BUILD(SimdOp, opcode, inputs); - Push(GetReturnType(sig), node); + return len; } void DoReturn() { @@ -1320,12 +1288,21 @@ class WasmFullDecoder : public WasmDecoder { if (buffer) buffer[i] = val.node; } - Push(kAstEnd, BUILD(Return, count, buffer)); - ssa_env_->Kill(SsaEnv::kControlEnd); + BUILD(Return, count, buffer); + EndControl(); } void Push(LocalType type, TFNode* node) { - stack_.push_back({pc_, node, type}); + if (type != kAstStmt && type != kAstEnd) { + stack_.push_back({pc_, node, type}); + } + } + + void PushReturns(FunctionSig* sig, TFNode** rets) { + for (size_t i = 0; i < sig->return_count(); i++) { + // When verifying only, then {rets} will be null, so push null. + Push(sig->GetReturn(i), rets ? rets[i] : nullptr); + } } const char* SafeOpcodeNameAt(const byte* pc) { @@ -1334,6 +1311,10 @@ class WasmFullDecoder : public WasmDecoder { } Value Pop(int index, LocalType expected) { + if (!ssa_env_->go()) { + // Unreachable code is essentially not typechecked. + return {pc_, nullptr, expected}; + } Value val = Pop(); if (val.type != expected) { if (val.type != kAstEnd) { @@ -1346,6 +1327,10 @@ class WasmFullDecoder : public WasmDecoder { } Value Pop() { + if (!ssa_env_->go()) { + // Unreachable code is essentially not typechecked. + return {pc_, nullptr, kAstEnd}; + } size_t limit = control_.empty() ? 0 : control_.back().stack_depth; if (stack_.size() <= limit) { Value val = {pc_, nullptr, kAstStmt}; @@ -1358,6 +1343,10 @@ class WasmFullDecoder : public WasmDecoder { } Value PopUpTo(int stack_depth) { + if (!ssa_env_->go()) { + // Unreachable code is essentially not typechecked. + return {pc_, nullptr, kAstEnd}; + } if (stack_depth == stack_.size()) { Value val = {pc_, nullptr, kAstStmt}; return val; @@ -1375,34 +1364,82 @@ class WasmFullDecoder : public WasmDecoder { int startrel(const byte* ptr) { return static_cast<int>(ptr - start_); } - void BreakTo(Control* block, Value& val) { - if (block->is_loop) { + void BreakTo(unsigned depth) { + if (!ssa_env_->go()) return; + Control* c = &control_[control_.size() - depth - 1]; + if (c->is_loop()) { // This is the inner loop block, which does not have a value. - Goto(ssa_env_, block->end_env); + Goto(ssa_env_, c->end_env); } else { - // Merge the value into the production for the block. - MergeInto(block->end_env, &block->node, &block->type, val); + // Merge the value(s) into the end of the block. + if (static_cast<size_t>(c->stack_depth + c->merge.arity) > + stack_.size()) { + error( + pc_, pc_, + "expected at least %d values on the stack for br to @%d, found %d", + c->merge.arity, startrel(c->pc), + static_cast<int>(stack_.size() - c->stack_depth)); + return; + } + MergeValuesInto(c); + } + } + + void FallThruTo(Control* c) { + if (!ssa_env_->go()) return; + // Merge the value(s) into the end of the block. + int arity = static_cast<int>(c->merge.arity); + if (c->stack_depth + arity != stack_.size()) { + error(pc_, pc_, "expected %d elements on the stack for fallthru to @%d", + arity, startrel(c->pc)); + return; } + MergeValuesInto(c); } - void MergeInto(SsaEnv* target, TFNode** node, LocalType* type, Value& val) { + inline Value& GetMergeValueFromStack(Control* c, int i) { + return stack_[stack_.size() - c->merge.arity + i]; + } + + void TypeCheckLoopFallThru(Control* c) { if (!ssa_env_->go()) return; - DCHECK_NE(kAstEnd, val.type); + // Fallthru must match arity exactly. + int arity = static_cast<int>(c->merge.arity); + if (c->stack_depth + arity != stack_.size()) { + error(pc_, pc_, "expected %d elements on the stack for fallthru to @%d", + arity, startrel(c->pc)); + return; + } + // Typecheck the values left on the stack. + for (unsigned i = 0; i < c->merge.arity; i++) { + Value& val = GetMergeValueFromStack(c, i); + Value& old = + c->merge.arity == 1 ? c->merge.vals.first : c->merge.vals.array[i]; + if (val.type != old.type) { + error(pc_, pc_, "type error in merge[%d] (expected %s, got %s)", i, + WasmOpcodes::TypeName(old.type), WasmOpcodes::TypeName(val.type)); + return; + } + } + } + void MergeValuesInto(Control* c) { + SsaEnv* target = c->end_env; bool first = target->state == SsaEnv::kUnreachable; Goto(ssa_env_, target); - if (first) { - // first merge to this environment; set the type and the node. - *type = val.type; - *node = val.node; - } else if (val.type == *type && val.type != kAstStmt) { - // merge with the existing value for this block. - *node = CreateOrMergeIntoPhi(*type, target->control, *node, val.node); - } else { - // types don't match, or block is already a stmt. - *type = kAstStmt; - *node = nullptr; + for (unsigned i = 0; i < c->merge.arity; i++) { + Value& val = GetMergeValueFromStack(c, i); + Value& old = + c->merge.arity == 1 ? c->merge.vals.first : c->merge.vals.array[i]; + if (val.type != old.type) { + error(pc_, pc_, "type error in merge[%d] (expected %s, got %s)", i, + WasmOpcodes::TypeName(old.type), WasmOpcodes::TypeName(val.type)); + return; + } + old.node = + first ? val.node : CreateOrMergeIntoPhi(old.type, target->control, + old.node, val.node); } } @@ -1442,6 +1479,45 @@ class WasmFullDecoder : public WasmDecoder { } } + TFNode* CheckForException(TFNode* node) { + if (node == nullptr) { + return nullptr; + } + + const bool inside_try_scope = current_catch_ != kNullCatch; + + if (!inside_try_scope) { + return node; + } + + TFNode* if_success = nullptr; + TFNode* if_exception = nullptr; + if (!builder_->ThrowsException(node, &if_success, &if_exception)) { + return node; + } + + SsaEnv* success_env = Steal(ssa_env_); + success_env->control = if_success; + + SsaEnv* exception_env = Split(success_env); + exception_env->control = if_exception; + TryInfo* try_info = current_try_info(); + Goto(exception_env, try_info->catch_env); + TFNode* exception = try_info->exception; + if (exception == nullptr) { + DCHECK_EQ(SsaEnv::kReached, try_info->catch_env->state); + try_info->exception = if_exception; + } else { + DCHECK_EQ(SsaEnv::kMerged, try_info->catch_env->state); + try_info->exception = + CreateOrMergeIntoPhi(kAstI32, try_info->catch_env->control, + try_info->exception, if_exception); + } + + SetEnv("if_success", success_env); + return node; + } + void Goto(SsaEnv* from, SsaEnv* to) { DCHECK_NOT_NULL(to); if (!from->go()) return; @@ -1630,16 +1706,15 @@ class WasmFullDecoder : public WasmDecoder { case kExprLoop: case kExprIf: case kExprBlock: - case kExprTryCatch: - case kExprTryCatchFinally: - case kExprTryFinally: + case kExprTry: + length = OpcodeLength(pc); depth++; - DCHECK_EQ(1, OpcodeLength(pc)); break; - case kExprSetLocal: { + case kExprSetLocal: // fallthru + case kExprTeeLocal: { LocalIndexOperand operand(this, pc); if (assigned->length() > 0 && - static_cast<int>(operand.index) < assigned->length()) { + operand.index < static_cast<uint32_t>(assigned->length())) { // Unverified code might have an out-of-bounds index. assigned->Add(operand.index); } @@ -1664,11 +1739,33 @@ class WasmFullDecoder : public WasmDecoder { DCHECK_EQ(pc_ - start_, offset); // overflows cannot happen return offset; } + + inline void BuildSimpleOperator(WasmOpcode opcode, FunctionSig* sig) { + TFNode* node; + switch (sig->parameter_count()) { + case 1: { + Value val = Pop(0, sig->GetParam(0)); + node = BUILD(Unop, opcode, val.node, position()); + break; + } + case 2: { + Value rval = Pop(1, sig->GetParam(1)); + Value lval = Pop(0, sig->GetParam(0)); + node = BUILD(Binop, opcode, lval.node, rval.node, position()); + break; + } + default: + UNREACHABLE(); + node = nullptr; + break; + } + Push(GetReturnType(sig), node); + } }; bool DecodeLocalDecls(AstLocalDecls& decls, const byte* start, const byte* end) { - base::AccountingAllocator allocator; + AccountingAllocator allocator; Zone tmp(&allocator); FunctionBody body = {nullptr, nullptr, nullptr, start, end}; WasmFullDecoder decoder(&tmp, nullptr, body); @@ -1686,7 +1783,7 @@ BytecodeIterator::BytecodeIterator(const byte* start, const byte* end, } } -DecodeResult VerifyWasmCode(base::AccountingAllocator* allocator, +DecodeResult VerifyWasmCode(AccountingAllocator* allocator, FunctionBody& body) { Zone zone(allocator); WasmFullDecoder decoder(&zone, nullptr, body); @@ -1694,8 +1791,8 @@ DecodeResult VerifyWasmCode(base::AccountingAllocator* allocator, return decoder.toResult<DecodeStruct*>(nullptr); } -DecodeResult BuildTFGraph(base::AccountingAllocator* allocator, - TFBuilder* builder, FunctionBody& body) { +DecodeResult BuildTFGraph(AccountingAllocator* allocator, TFBuilder* builder, + FunctionBody& body) { Zone zone(allocator); WasmFullDecoder decoder(&zone, builder, body); decoder.Decode(); @@ -1707,18 +1804,13 @@ unsigned OpcodeLength(const byte* pc, const byte* end) { return decoder.OpcodeLength(pc); } -unsigned OpcodeArity(const byte* pc, const byte* end) { - WasmDecoder decoder(nullptr, nullptr, pc, end); - return decoder.OpcodeArity(pc); -} - void PrintAstForDebugging(const byte* start, const byte* end) { - base::AccountingAllocator allocator; + AccountingAllocator allocator; OFStream os(stdout); PrintAst(&allocator, FunctionBodyForTesting(start, end), os, nullptr); } -bool PrintAst(base::AccountingAllocator* allocator, const FunctionBody& body, +bool PrintAst(AccountingAllocator* allocator, const FunctionBody& body, std::ostream& os, std::vector<std::tuple<uint32_t, int, int>>* offset_table) { Zone zone(allocator); @@ -1777,68 +1869,57 @@ bool PrintAst(base::AccountingAllocator* allocator, const FunctionBody& body, } switch (opcode) { - case kExprIf: case kExprElse: + os << " // @" << i.pc_offset(); + control_depth++; + break; case kExprLoop: + case kExprIf: case kExprBlock: - case kExprTryCatch: - case kExprTryCatchFinally: - case kExprTryFinally: + case kExprTry: { + BlockTypeOperand operand(&i, i.pc()); os << " // @" << i.pc_offset(); + for (unsigned i = 0; i < operand.arity; i++) { + os << " " << WasmOpcodes::TypeName(operand.read_entry(i)); + } control_depth++; break; + } case kExprEnd: os << " // @" << i.pc_offset(); control_depth--; break; case kExprBr: { BreakDepthOperand operand(&i, i.pc()); - os << " // arity=" << operand.arity << " depth=" << operand.depth; + os << " // depth=" << operand.depth; break; } case kExprBrIf: { BreakDepthOperand operand(&i, i.pc()); - os << " // arity=" << operand.arity << " depth" << operand.depth; + os << " // depth=" << operand.depth; break; } case kExprBrTable: { BranchTableOperand operand(&i, i.pc()); - os << " // arity=" << operand.arity - << " entries=" << operand.table_count; + os << " // entries=" << operand.table_count; break; } case kExprCallIndirect: { CallIndirectOperand operand(&i, i.pc()); + os << " // sig #" << operand.index; if (decoder.Complete(i.pc(), operand)) { - os << " // sig #" << operand.index << ": " << *operand.sig; - } else { - os << " // arity=" << operand.arity << " sig #" << operand.index; - } - break; - } - case kExprCallImport: { - CallImportOperand operand(&i, i.pc()); - if (decoder.Complete(i.pc(), operand)) { - os << " // import #" << operand.index << ": " << *operand.sig; - } else { - os << " // arity=" << operand.arity << " import #" << operand.index; + os << ": " << *operand.sig; } break; } case kExprCallFunction: { CallFunctionOperand operand(&i, i.pc()); + os << " // function #" << operand.index; if (decoder.Complete(i.pc(), operand)) { - os << " // function #" << operand.index << ": " << *operand.sig; - } else { - os << " // arity=" << operand.arity << " function #" << operand.index; + os << ": " << *operand.sig; } break; } - case kExprReturn: { - ReturnArityOperand operand(&i, i.pc()); - os << " // arity=" << operand.arity; - break; - } default: break; } diff --git a/deps/v8/src/wasm/ast-decoder.h b/deps/v8/src/wasm/ast-decoder.h index c4f6c1679a..8c2c2c4734 100644 --- a/deps/v8/src/wasm/ast-decoder.h +++ b/deps/v8/src/wasm/ast-decoder.h @@ -21,6 +21,9 @@ class WasmGraphBuilder; namespace wasm { +const uint32_t kMaxNumWasmLocals = 8000000; +struct WasmGlobal; + // Helpers for decoding different kinds of operands which follow bytecodes. struct LocalIndexOperand { uint32_t index; @@ -79,39 +82,111 @@ struct ImmF64Operand { struct GlobalIndexOperand { uint32_t index; LocalType type; + const WasmGlobal* global; unsigned length; inline GlobalIndexOperand(Decoder* decoder, const byte* pc) { index = decoder->checked_read_u32v(pc, 1, &length, "global index"); + global = nullptr; type = kAstStmt; } }; +struct BlockTypeOperand { + uint32_t arity; + const byte* types; // pointer to encoded types for the block. + unsigned length; + + inline BlockTypeOperand(Decoder* decoder, const byte* pc) { + uint8_t val = decoder->checked_read_u8(pc, 1, "block type"); + LocalType type = kAstStmt; + length = 1; + arity = 0; + types = nullptr; + if (decode_local_type(val, &type)) { + arity = type == kAstStmt ? 0 : 1; + types = pc + 1; + } else { + // Handle multi-value blocks. + if (!FLAG_wasm_mv_prototype) { + decoder->error(pc, pc + 1, "invalid block arity > 1"); + return; + } + if (val != kMultivalBlock) { + decoder->error(pc, pc + 1, "invalid block type"); + return; + } + // Decode and check the types vector of the block. + unsigned len = 0; + uint32_t count = decoder->checked_read_u32v(pc, 2, &len, "block arity"); + // {count} is encoded as {arity-2}, so that a {0} count here corresponds + // to a block with 2 values. This makes invalid/redundant encodings + // impossible. + arity = count + 2; + length = 1 + len + arity; + types = pc + 1 + 1 + len; + + for (uint32_t i = 0; i < arity; i++) { + uint32_t offset = 1 + 1 + len + i; + val = decoder->checked_read_u8(pc, offset, "block type"); + decode_local_type(val, &type); + if (type == kAstStmt) { + decoder->error(pc, pc + offset, "invalid block type"); + return; + } + } + } + } + // Decode a byte representing a local type. Return {false} if the encoded + // byte was invalid or {kMultivalBlock}. + bool decode_local_type(uint8_t val, LocalType* result) { + switch (static_cast<LocalTypeCode>(val)) { + case kLocalVoid: + *result = kAstStmt; + return true; + case kLocalI32: + *result = kAstI32; + return true; + case kLocalI64: + *result = kAstI64; + return true; + case kLocalF32: + *result = kAstF32; + return true; + case kLocalF64: + *result = kAstF64; + return true; + default: + *result = kAstStmt; + return false; + } + } + LocalType read_entry(unsigned index) { + DCHECK_LT(index, arity); + LocalType result; + CHECK(decode_local_type(types[index], &result)); + return result; + } +}; + struct Control; struct BreakDepthOperand { - uint32_t arity; uint32_t depth; Control* target; unsigned length; inline BreakDepthOperand(Decoder* decoder, const byte* pc) { - unsigned len1 = 0; - unsigned len2 = 0; - arity = decoder->checked_read_u32v(pc, 1, &len1, "argument count"); - depth = decoder->checked_read_u32v(pc, 1 + len1, &len2, "break depth"); - length = len1 + len2; + depth = decoder->checked_read_u32v(pc, 1, &length, "break depth"); target = nullptr; } }; struct CallIndirectOperand { - uint32_t arity; uint32_t index; FunctionSig* sig; unsigned length; inline CallIndirectOperand(Decoder* decoder, const byte* pc) { unsigned len1 = 0; unsigned len2 = 0; - arity = decoder->checked_read_u32v(pc, 1, &len1, "argument count"); index = decoder->checked_read_u32v(pc, 1 + len1, &len2, "signature index"); length = len1 + len2; sig = nullptr; @@ -119,59 +194,32 @@ struct CallIndirectOperand { }; struct CallFunctionOperand { - uint32_t arity; uint32_t index; FunctionSig* sig; unsigned length; inline CallFunctionOperand(Decoder* decoder, const byte* pc) { unsigned len1 = 0; unsigned len2 = 0; - arity = decoder->checked_read_u32v(pc, 1, &len1, "argument count"); index = decoder->checked_read_u32v(pc, 1 + len1, &len2, "function index"); length = len1 + len2; sig = nullptr; } }; -struct CallImportOperand { - uint32_t arity; - uint32_t index; - FunctionSig* sig; - unsigned length; - inline CallImportOperand(Decoder* decoder, const byte* pc) { - unsigned len1 = 0; - unsigned len2 = 0; - arity = decoder->checked_read_u32v(pc, 1, &len1, "argument count"); - index = decoder->checked_read_u32v(pc, 1 + len1, &len2, "import index"); - length = len1 + len2; - sig = nullptr; - } -}; - struct BranchTableOperand { - uint32_t arity; uint32_t table_count; + const byte* start; const byte* table; - unsigned length; inline BranchTableOperand(Decoder* decoder, const byte* pc) { + DCHECK_EQ(kExprBrTable, decoder->checked_read_u8(pc, 0, "opcode")); + start = pc + 1; unsigned len1 = 0; - unsigned len2 = 0; - arity = decoder->checked_read_u32v(pc, 1, &len1, "argument count"); - table_count = - decoder->checked_read_u32v(pc, 1 + len1, &len2, "table count"); + table_count = decoder->checked_read_u32v(pc, 1, &len1, "table count"); if (table_count > (UINT_MAX / sizeof(uint32_t)) - 1 || - len1 + len2 > UINT_MAX - (table_count + 1) * sizeof(uint32_t)) { + len1 > UINT_MAX - (table_count + 1) * sizeof(uint32_t)) { decoder->error(pc, "branch table size overflow"); } - length = len1 + len2 + (table_count + 1) * sizeof(uint32_t); - - uint32_t table_start = 1 + len1 + len2; - if (decoder->check(pc, table_start, (table_count + 1) * sizeof(uint32_t), - "expected <table entries>")) { - table = pc + table_start; - } else { - table = nullptr; - } + table = pc + 1 + len1; } inline uint32_t read_entry(Decoder* decoder, unsigned i) { DCHECK(i <= table_count); @@ -179,14 +227,58 @@ struct BranchTableOperand { } }; +// A helper to iterate over a branch table. +class BranchTableIterator { + public: + unsigned cur_index() { return index_; } + bool has_next() { return index_ <= table_count_; } + uint32_t next() { + DCHECK(has_next()); + index_++; + unsigned length = 0; + uint32_t result = + decoder_->checked_read_u32v(pc_, 0, &length, "branch table entry"); + pc_ += length; + return result; + } + // length, including the length of the {BranchTableOperand}, but not the + // opcode. + unsigned length() { + while (has_next()) next(); + return static_cast<unsigned>(pc_ - start_); + } + const byte* pc() { return pc_; } + + BranchTableIterator(Decoder* decoder, BranchTableOperand& operand) + : decoder_(decoder), + start_(operand.start), + pc_(operand.table), + index_(0), + table_count_(operand.table_count) {} + + private: + Decoder* decoder_; + const byte* start_; + const byte* pc_; + uint32_t index_; // the current index. + uint32_t table_count_; // the count of entries, not including default. +}; + struct MemoryAccessOperand { uint32_t alignment; uint32_t offset; unsigned length; - inline MemoryAccessOperand(Decoder* decoder, const byte* pc) { + inline MemoryAccessOperand(Decoder* decoder, const byte* pc, + uint32_t max_alignment) { unsigned alignment_length; alignment = decoder->checked_read_u32v(pc, 1, &alignment_length, "alignment"); + if (max_alignment < alignment) { + decoder->error(pc, pc + 1, + "invalid alignment; expected maximum alignment is %u, " + "actual alignment is %u", + max_alignment, alignment); + } unsigned offset_length; offset = decoder->checked_read_u32v(pc, 1 + alignment_length, &offset_length, "offset"); @@ -194,15 +286,6 @@ struct MemoryAccessOperand { } }; -struct ReturnArityOperand { - uint32_t arity; - unsigned length; - - inline ReturnArityOperand(Decoder* decoder, const byte* pc) { - arity = decoder->checked_read_u32v(pc, 1, &length, "return count"); - } -}; - typedef compiler::WasmGraphBuilder TFBuilder; struct ModuleEnv; // forward declaration of module interface. @@ -228,25 +311,25 @@ inline std::ostream& operator<<(std::ostream& os, const DecodeStruct& tree) { return os; } -DecodeResult VerifyWasmCode(base::AccountingAllocator* allocator, - FunctionBody& body); -DecodeResult BuildTFGraph(base::AccountingAllocator* allocator, - TFBuilder* builder, FunctionBody& body); -bool PrintAst(base::AccountingAllocator* allocator, const FunctionBody& body, +V8_EXPORT_PRIVATE DecodeResult VerifyWasmCode(AccountingAllocator* allocator, + FunctionBody& body); +DecodeResult BuildTFGraph(AccountingAllocator* allocator, TFBuilder* builder, + FunctionBody& body); +bool PrintAst(AccountingAllocator* allocator, const FunctionBody& body, std::ostream& os, std::vector<std::tuple<uint32_t, int, int>>* offset_table); // A simplified form of AST printing, e.g. from a debugger. void PrintAstForDebugging(const byte* start, const byte* end); -inline DecodeResult VerifyWasmCode(base::AccountingAllocator* allocator, +inline DecodeResult VerifyWasmCode(AccountingAllocator* allocator, ModuleEnv* module, FunctionSig* sig, const byte* start, const byte* end) { FunctionBody body = {module, sig, nullptr, start, end}; return VerifyWasmCode(allocator, body); } -inline DecodeResult BuildTFGraph(base::AccountingAllocator* allocator, +inline DecodeResult BuildTFGraph(AccountingAllocator* allocator, TFBuilder* builder, ModuleEnv* module, FunctionSig* sig, const byte* start, const byte* end) { @@ -276,9 +359,6 @@ BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, size_t num_locals, // Computes the length of the opcode at the given address. unsigned OpcodeLength(const byte* pc, const byte* end); -// Computes the arity (number of sub-nodes) of the opcode at the given address. -unsigned OpcodeArity(const byte* pc, const byte* end); - // A simple forward iterator for bytecodes. class BytecodeIterator : public Decoder { public: diff --git a/deps/v8/src/wasm/decoder.h b/deps/v8/src/wasm/decoder.h index a6ede54bec..d5c9f43c57 100644 --- a/deps/v8/src/wasm/decoder.h +++ b/deps/v8/src/wasm/decoder.h @@ -12,7 +12,7 @@ #include "src/signature.h" #include "src/utils.h" #include "src/wasm/wasm-result.h" -#include "src/zone-containers.h" +#include "src/zone/zone-containers.h" namespace v8 { namespace internal { @@ -208,6 +208,19 @@ class Decoder { // Consume {size} bytes and send them to the bit bucket, advancing {pc_}. void consume_bytes(int size) { + TRACE(" +%d %-20s: %d bytes\n", static_cast<int>(pc_ - start_), "skip", + size); + if (checkAvailable(size)) { + pc_ += size; + } else { + pc_ = limit_; + } + } + + // Consume {size} bytes and send them to the bit bucket, advancing {pc_}. + void consume_bytes(uint32_t size, const char* name = "skip") { + TRACE(" +%d %-20s: %d bytes\n", static_cast<int>(pc_ - start_), name, + size); if (checkAvailable(size)) { pc_ += size; } else { diff --git a/deps/v8/src/wasm/module-decoder.cc b/deps/v8/src/wasm/module-decoder.cc index 542c47ca15..90065616d9 100644 --- a/deps/v8/src/wasm/module-decoder.cc +++ b/deps/v8/src/wasm/module-decoder.cc @@ -27,6 +27,141 @@ namespace wasm { namespace { +const char* kNameString = "name"; +const size_t kNameStringLength = 4; + +LocalType TypeOf(const WasmModule* module, const WasmInitExpr& expr) { + switch (expr.kind) { + case WasmInitExpr::kNone: + return kAstStmt; + case WasmInitExpr::kGlobalIndex: + return expr.val.global_index < module->globals.size() + ? module->globals[expr.val.global_index].type + : kAstStmt; + case WasmInitExpr::kI32Const: + return kAstI32; + case WasmInitExpr::kI64Const: + return kAstI64; + case WasmInitExpr::kF32Const: + return kAstF32; + case WasmInitExpr::kF64Const: + return kAstF64; + default: + UNREACHABLE(); + return kAstStmt; + } +} + +// An iterator over the sections in a WASM binary module. +// Automatically skips all unknown sections. +class WasmSectionIterator { + public: + explicit WasmSectionIterator(Decoder& decoder) + : decoder_(decoder), + section_code_(kUnknownSectionCode), + section_start_(decoder.pc()), + section_end_(decoder.pc()) { + next(); + } + + inline bool more() const { + return section_code_ != kUnknownSectionCode && decoder_.more(); + } + + inline WasmSectionCode section_code() const { return section_code_; } + + inline const byte* section_start() const { return section_start_; } + + inline uint32_t section_length() const { + return static_cast<uint32_t>(section_end_ - section_start_); + } + + inline const byte* section_end() const { return section_end_; } + + // Advances to the next section, checking that decoding the current section + // stopped at {section_end_}. + void advance() { + if (decoder_.pc() != section_end_) { + const char* msg = decoder_.pc() < section_end_ ? "shorter" : "longer"; + decoder_.error(decoder_.pc(), decoder_.pc(), + "section was %s than expected size " + "(%u bytes expected, %zu decoded)", + msg, section_length(), + static_cast<size_t>(decoder_.pc() - section_start_)); + } + next(); + } + + private: + Decoder& decoder_; + WasmSectionCode section_code_; + const byte* section_start_; + const byte* section_end_; + + // Reads the section code/name at the current position and sets up + // the internal fields. + void next() { + while (true) { + if (!decoder_.more()) { + section_code_ = kUnknownSectionCode; + return; + } + uint8_t section_code = decoder_.consume_u8("section code"); + // Read and check the section size. + uint32_t section_length = decoder_.consume_u32v("section length"); + section_start_ = decoder_.pc(); + if (decoder_.checkAvailable(section_length)) { + // Get the limit of the section within the module. + section_end_ = section_start_ + section_length; + } else { + // The section would extend beyond the end of the module. + section_end_ = section_start_; + } + + if (section_code == kUnknownSectionCode) { + // Check for the known "names" section. + uint32_t string_length = decoder_.consume_u32v("section name length"); + const byte* section_name_start = decoder_.pc(); + decoder_.consume_bytes(string_length, "section name"); + if (decoder_.failed() || decoder_.pc() > section_end_) { + TRACE("Section name of length %u couldn't be read\n", string_length); + section_code_ = kUnknownSectionCode; + return; + } + + TRACE(" +%d section name : \"%.*s\"\n", + static_cast<int>(section_name_start - decoder_.start()), + string_length < 20 ? string_length : 20, section_name_start); + + if (string_length == kNameStringLength && + strncmp(reinterpret_cast<const char*>(section_name_start), + kNameString, kNameStringLength) == 0) { + section_code = kNameSectionCode; + } else { + section_code = kUnknownSectionCode; + } + } else if (!IsValidSectionCode(section_code)) { + decoder_.error(decoder_.pc(), decoder_.pc(), + "unknown section code #0x%02x", section_code); + section_code = kUnknownSectionCode; + } + section_code_ = static_cast<WasmSectionCode>(section_code); + + TRACE("Section: %s\n", SectionName(section_code_)); + if (section_code_ == kUnknownSectionCode && + section_end_ > decoder_.pc()) { + // skip to the end of the unknown section. + uint32_t remaining = + static_cast<uint32_t>(section_end_ - decoder_.pc()); + decoder_.consume_bytes(remaining, "section payload"); + // fall through and continue to the next section. + } else { + return; + } + } + } +}; + // The main logic for decoding the bytes of a module. class ModuleDecoder : public Decoder { public: @@ -77,11 +212,9 @@ class ModuleDecoder : public Decoder { module->min_mem_pages = 0; module->max_mem_pages = 0; module->mem_export = false; - module->mem_external = false; module->origin = origin_; const byte* pos = pc_; - int current_order = 0; uint32_t magic_word = consume_u32("wasm magic"); #define BYTES(x) (x & 0xff), (x >> 8) & 0xff, (x >> 16) & 0xff, (x >> 24) & 0xff if (magic_word != kWasmMagic) { @@ -89,7 +222,6 @@ class ModuleDecoder : public Decoder { "expected magic word %02x %02x %02x %02x, " "found %02x %02x %02x %02x", BYTES(kWasmMagic), BYTES(magic_word)); - goto done; } pos = pc_; @@ -100,302 +232,367 @@ class ModuleDecoder : public Decoder { "expected version %02x %02x %02x %02x, " "found %02x %02x %02x %02x", BYTES(kWasmVersion), BYTES(magic_version)); - goto done; } } - // Decode the module sections. - while (pc_ < limit_) { - TRACE("DecodeSection\n"); - pos = pc_; - - // Read the section name. - uint32_t string_length = consume_u32v("section name length"); - const byte* section_name_start = pc_; - consume_bytes(string_length); - if (failed()) { - TRACE("Section name of length %u couldn't be read\n", string_length); - break; + WasmSectionIterator section_iter(*this); + + // ===== Type section ==================================================== + if (section_iter.section_code() == kTypeSectionCode) { + uint32_t signatures_count = consume_u32v("signatures count"); + module->signatures.reserve(SafeReserve(signatures_count)); + for (uint32_t i = 0; ok() && i < signatures_count; ++i) { + TRACE("DecodeSignature[%d] module+%d\n", i, + static_cast<int>(pc_ - start_)); + FunctionSig* s = consume_sig(); + module->signatures.push_back(s); } + section_iter.advance(); + } - TRACE(" +%d section name : \"%.*s\"\n", - static_cast<int>(section_name_start - start_), - string_length < 20 ? string_length : 20, section_name_start); - - WasmSection::Code section = - WasmSection::lookup(section_name_start, string_length); - - // Read and check the section size. - uint32_t section_length = consume_u32v("section length"); - if (!checkAvailable(section_length)) { - // The section would extend beyond the end of the module. - break; - } - const byte* section_start = pc_; - const byte* expected_section_end = pc_ + section_length; - - current_order = CheckSectionOrder(current_order, section); - - switch (section) { - case WasmSection::Code::End: - // Terminate section decoding. - limit_ = pc_; - break; - case WasmSection::Code::Memory: { - module->min_mem_pages = consume_u32v("min memory"); - module->max_mem_pages = consume_u32v("max memory"); - module->mem_export = consume_u8("export memory") != 0; - break; - } - case WasmSection::Code::Signatures: { - uint32_t signatures_count = consume_u32v("signatures count"); - module->signatures.reserve(SafeReserve(signatures_count)); - // Decode signatures. - for (uint32_t i = 0; i < signatures_count; ++i) { - if (failed()) break; - TRACE("DecodeSignature[%d] module+%d\n", i, - static_cast<int>(pc_ - start_)); - FunctionSig* s = consume_sig(); - module->signatures.push_back(s); - } - break; + // ===== Import section ================================================== + if (section_iter.section_code() == kImportSectionCode) { + uint32_t import_table_count = consume_u32v("import table count"); + module->import_table.reserve(SafeReserve(import_table_count)); + for (uint32_t i = 0; ok() && i < import_table_count; ++i) { + TRACE("DecodeImportTable[%d] module+%d\n", i, + static_cast<int>(pc_ - start_)); + + module->import_table.push_back({ + 0, // module_name_length + 0, // module_name_offset + 0, // field_name_offset + 0, // field_name_length + kExternalFunction, // kind + 0 // index + }); + WasmImport* import = &module->import_table.back(); + const byte* pos = pc_; + import->module_name_offset = + consume_string(&import->module_name_length, true); + if (import->module_name_length == 0) { + error(pos, "import module name cannot be NULL"); } - case WasmSection::Code::FunctionSignatures: { - uint32_t functions_count = consume_u32v("functions count"); - module->functions.reserve(SafeReserve(functions_count)); - for (uint32_t i = 0; i < functions_count; ++i) { - module->functions.push_back({nullptr, // sig - i, // func_index - 0, // sig_index - 0, // name_offset - 0, // name_length - 0, // code_start_offset - 0}); // code_end_offset + import->field_name_offset = + consume_string(&import->field_name_length, true); + + import->kind = static_cast<WasmExternalKind>(consume_u8("import kind")); + switch (import->kind) { + case kExternalFunction: { + // ===== Imported function ======================================= + import->index = static_cast<uint32_t>(module->functions.size()); + module->num_imported_functions++; + module->functions.push_back({nullptr, // sig + import->index, // func_index + 0, // sig_index + 0, // name_offset + 0, // name_length + 0, // code_start_offset + 0, // code_end_offset + true, // imported + false}); // exported WasmFunction* function = &module->functions.back(); function->sig_index = consume_sig_index(module, &function->sig); - } - break; - } - case WasmSection::Code::FunctionBodies: { - const byte* pos = pc_; - uint32_t functions_count = consume_u32v("functions count"); - if (functions_count != module->functions.size()) { - error(pos, pos, "function body count %u mismatch (%u expected)", - functions_count, - static_cast<uint32_t>(module->functions.size())); break; } - for (uint32_t i = 0; i < functions_count; ++i) { - WasmFunction* function = &module->functions[i]; - uint32_t size = consume_u32v("body size"); - function->code_start_offset = pc_offset(); - function->code_end_offset = pc_offset() + size; - - TRACE(" +%d %-20s: (%d bytes)\n", pc_offset(), "function body", - size); - pc_ += size; - if (pc_ > limit_) { - error(pc_, "function body extends beyond end of file"); - } - } - break; - } - case WasmSection::Code::Names: { - const byte* pos = pc_; - uint32_t functions_count = consume_u32v("functions count"); - if (functions_count != module->functions.size()) { - error(pos, pos, "function name count %u mismatch (%u expected)", - functions_count, - static_cast<uint32_t>(module->functions.size())); + case kExternalTable: { + // ===== Imported table ========================================== + import->index = + static_cast<uint32_t>(module->function_tables.size()); + module->function_tables.push_back( + {0, 0, std::vector<int32_t>(), true, false}); + expect_u8("element type", 0x20); + WasmIndirectFunctionTable* table = &module->function_tables.back(); + consume_resizable_limits("element count", "elements", kMaxUInt32, + &table->size, &table->max_size); break; } - - for (uint32_t i = 0; i < functions_count; ++i) { - WasmFunction* function = &module->functions[i]; - function->name_offset = - consume_string(&function->name_length, false); - - uint32_t local_names_count = consume_u32v("local names count"); - for (uint32_t j = 0; j < local_names_count; j++) { - uint32_t unused = 0; - uint32_t offset = consume_string(&unused, false); - USE(unused); - USE(offset); - } + case kExternalMemory: { + // ===== Imported memory ========================================= + // import->index = + // static_cast<uint32_t>(module->memories.size()); + // TODO(titzer): imported memories + break; } - break; - } - case WasmSection::Code::Globals: { - uint32_t globals_count = consume_u32v("globals count"); - module->globals.reserve(SafeReserve(globals_count)); - // Decode globals. - for (uint32_t i = 0; i < globals_count; ++i) { - if (failed()) break; - TRACE("DecodeGlobal[%d] module+%d\n", i, - static_cast<int>(pc_ - start_)); - // Add an uninitialized global and pass a pointer to it. - module->globals.push_back({0, 0, kAstStmt, 0, false}); + case kExternalGlobal: { + // ===== Imported global ========================================= + import->index = static_cast<uint32_t>(module->globals.size()); + module->globals.push_back( + {kAstStmt, false, NO_INIT, 0, true, false}); WasmGlobal* global = &module->globals.back(); - DecodeGlobalInModule(global); - } - break; - } - case WasmSection::Code::DataSegments: { - uint32_t data_segments_count = consume_u32v("data segments count"); - module->data_segments.reserve(SafeReserve(data_segments_count)); - // Decode data segments. - for (uint32_t i = 0; i < data_segments_count; ++i) { - if (failed()) break; - TRACE("DecodeDataSegment[%d] module+%d\n", i, - static_cast<int>(pc_ - start_)); - module->data_segments.push_back({0, // dest_addr - 0, // source_offset - 0, // source_size - false}); // init - WasmDataSegment* segment = &module->data_segments.back(); - DecodeDataSegmentInModule(module, segment); + global->type = consume_value_type(); + global->mutability = consume_u8("mutability") != 0; + break; } - break; + default: + error(pos, pos, "unknown import kind 0x%02x", import->kind); + break; } - case WasmSection::Code::FunctionTable: { - // An indirect function table requires functions first. - CheckForFunctions(module, section); - // Assume only one table for now. - static const uint32_t kSupportedTableCount = 1; - module->function_tables.reserve(SafeReserve(kSupportedTableCount)); - // Decode function table. - for (uint32_t i = 0; i < kSupportedTableCount; ++i) { - if (failed()) break; - TRACE("DecodeFunctionTable[%d] module+%d\n", i, - static_cast<int>(pc_ - start_)); - module->function_tables.push_back({0, 0, std::vector<uint16_t>()}); - DecodeFunctionTableInModule(module, &module->function_tables[i]); + } + section_iter.advance(); + } + + // ===== Function section ================================================ + if (section_iter.section_code() == kFunctionSectionCode) { + uint32_t functions_count = consume_u32v("functions count"); + module->functions.reserve(SafeReserve(functions_count)); + module->num_declared_functions = functions_count; + for (uint32_t i = 0; ok() && i < functions_count; ++i) { + uint32_t func_index = static_cast<uint32_t>(module->functions.size()); + module->functions.push_back({nullptr, // sig + func_index, // func_index + 0, // sig_index + 0, // name_offset + 0, // name_length + 0, // code_start_offset + 0, // code_end_offset + false, // imported + false}); // exported + WasmFunction* function = &module->functions.back(); + function->sig_index = consume_sig_index(module, &function->sig); + } + section_iter.advance(); + } + + // ===== Table section =================================================== + if (section_iter.section_code() == kTableSectionCode) { + const byte* pos = pc_; + uint32_t table_count = consume_u32v("table count"); + // Require at most one table for now. + if (table_count > 1) { + error(pos, pos, "invalid table count %d, maximum 1", table_count); + } + + for (uint32_t i = 0; ok() && i < table_count; i++) { + module->function_tables.push_back( + {0, 0, std::vector<int32_t>(), false, false}); + WasmIndirectFunctionTable* table = &module->function_tables.back(); + expect_u8("table type", kWasmAnyFunctionTypeForm); + consume_resizable_limits("table elements", "elements", kMaxUInt32, + &table->size, &table->max_size); + } + section_iter.advance(); + } + + // ===== Memory section ================================================== + if (section_iter.section_code() == kMemorySectionCode) { + const byte* pos = pc_; + uint32_t memory_count = consume_u32v("memory count"); + // Require at most one memory for now. + if (memory_count > 1) { + error(pos, pos, "invalid memory count %d, maximum 1", memory_count); + } + + for (uint32_t i = 0; ok() && i < memory_count; i++) { + consume_resizable_limits("memory", "pages", WasmModule::kMaxLegalPages, + &module->min_mem_pages, + &module->max_mem_pages); + } + section_iter.advance(); + } + + // ===== Global section ================================================== + if (section_iter.section_code() == kGlobalSectionCode) { + uint32_t globals_count = consume_u32v("globals count"); + module->globals.reserve(SafeReserve(globals_count)); + for (uint32_t i = 0; ok() && i < globals_count; ++i) { + TRACE("DecodeGlobal[%d] module+%d\n", i, + static_cast<int>(pc_ - start_)); + // Add an uninitialized global and pass a pointer to it. + module->globals.push_back({kAstStmt, false, NO_INIT, 0, false, false}); + WasmGlobal* global = &module->globals.back(); + DecodeGlobalInModule(module, i, global); + } + section_iter.advance(); + } + + // ===== Export section ================================================== + if (section_iter.section_code() == kExportSectionCode) { + uint32_t export_table_count = consume_u32v("export table count"); + module->export_table.reserve(SafeReserve(export_table_count)); + for (uint32_t i = 0; ok() && i < export_table_count; ++i) { + TRACE("DecodeExportTable[%d] module+%d\n", i, + static_cast<int>(pc_ - start_)); + + module->export_table.push_back({ + 0, // name_length + 0, // name_offset + kExternalFunction, // kind + 0 // index + }); + WasmExport* exp = &module->export_table.back(); + + exp->name_offset = consume_string(&exp->name_length, true); + const byte* pos = pc(); + exp->kind = static_cast<WasmExternalKind>(consume_u8("export kind")); + switch (exp->kind) { + case kExternalFunction: { + WasmFunction* func = nullptr; + exp->index = consume_func_index(module, &func); + module->num_exported_functions++; + if (func) func->exported = true; + break; } - break; - } - case WasmSection::Code::StartFunction: { - // Declares a start function for a module. - CheckForFunctions(module, section); - if (module->start_function_index >= 0) { - error("start function already declared"); + case kExternalTable: { + WasmIndirectFunctionTable* table = nullptr; + exp->index = consume_table_index(module, &table); + if (table) table->exported = true; break; } - WasmFunction* func; - const byte* pos = pc_; - module->start_function_index = consume_func_index(module, &func); - if (func && func->sig->parameter_count() > 0) { - error(pos, "invalid start function: non-zero parameter count"); + case kExternalMemory: { + uint32_t index = consume_u32v("memory index"); + if (index != 0) error("invalid memory index != 0"); + module->mem_export = true; break; } - break; - } - case WasmSection::Code::ImportTable: { - uint32_t import_table_count = consume_u32v("import table count"); - module->import_table.reserve(SafeReserve(import_table_count)); - // Decode import table. - for (uint32_t i = 0; i < import_table_count; ++i) { - if (failed()) break; - TRACE("DecodeImportTable[%d] module+%d\n", i, - static_cast<int>(pc_ - start_)); - - module->import_table.push_back({nullptr, // sig - 0, // sig_index - 0, // module_name_offset - 0, // module_name_length - 0, // function_name_offset - 0}); // function_name_length - WasmImport* import = &module->import_table.back(); - - import->sig_index = consume_sig_index(module, &import->sig); - const byte* pos = pc_; - import->module_name_offset = - consume_string(&import->module_name_length, true); - if (import->module_name_length == 0) { - error(pos, "import module name cannot be NULL"); - } - import->function_name_offset = - consume_string(&import->function_name_length, true); + case kExternalGlobal: { + WasmGlobal* global = nullptr; + exp->index = consume_global_index(module, &global); + if (global) global->exported = true; + break; } - break; + default: + error(pos, pos, "invalid export kind 0x%02x", exp->kind); + break; } - case WasmSection::Code::ExportTable: { - // Declares an export table. - CheckForFunctions(module, section); - uint32_t export_table_count = consume_u32v("export table count"); - module->export_table.reserve(SafeReserve(export_table_count)); - // Decode export table. - for (uint32_t i = 0; i < export_table_count; ++i) { - if (failed()) break; - TRACE("DecodeExportTable[%d] module+%d\n", i, - static_cast<int>(pc_ - start_)); - - module->export_table.push_back({0, // func_index - 0, // name_offset - 0}); // name_length - WasmExport* exp = &module->export_table.back(); - - WasmFunction* func; - exp->func_index = consume_func_index(module, &func); - exp->name_offset = consume_string(&exp->name_length, true); + } + // Check for duplicate exports. + if (ok() && module->export_table.size() > 1) { + std::vector<WasmExport> sorted_exports(module->export_table); + const byte* base = start_; + auto cmp_less = [base](const WasmExport& a, const WasmExport& b) { + // Return true if a < b. + if (a.name_length != b.name_length) { + return a.name_length < b.name_length; } - // Check for duplicate exports. - if (ok() && module->export_table.size() > 1) { - std::vector<WasmExport> sorted_exports(module->export_table); - const byte* base = start_; - auto cmp_less = [base](const WasmExport& a, const WasmExport& b) { - // Return true if a < b. - uint32_t len = a.name_length; - if (len != b.name_length) return len < b.name_length; - return memcmp(base + a.name_offset, base + b.name_offset, len) < - 0; - }; - std::stable_sort(sorted_exports.begin(), sorted_exports.end(), - cmp_less); - auto it = sorted_exports.begin(); - WasmExport* last = &*it++; - for (auto end = sorted_exports.end(); it != end; last = &*it++) { - DCHECK(!cmp_less(*it, *last)); // Vector must be sorted. - if (!cmp_less(*last, *it)) { - const byte* pc = start_ + it->name_offset; - error(pc, pc, - "Duplicate export name '%.*s' for functions %d and %d", - it->name_length, pc, last->func_index, it->func_index); - break; - } - } + return memcmp(base + a.name_offset, base + b.name_offset, + a.name_length) < 0; + }; + std::stable_sort(sorted_exports.begin(), sorted_exports.end(), + cmp_less); + auto it = sorted_exports.begin(); + WasmExport* last = &*it++; + for (auto end = sorted_exports.end(); it != end; last = &*it++) { + DCHECK(!cmp_less(*it, *last)); // Vector must be sorted. + if (!cmp_less(*last, *it)) { + const byte* pc = start_ + it->name_offset; + error(pc, pc, + "Duplicate export name '%.*s' for functions %d and %d", + it->name_length, pc, last->index, it->index); + break; } - break; } - case WasmSection::Code::Max: - // Skip unknown sections. - TRACE("Unknown section: '"); - for (uint32_t i = 0; i != string_length; ++i) { - TRACE("%c", *(section_name_start + i)); - } - TRACE("'\n"); - consume_bytes(section_length); - break; - } - - if (pc_ != expected_section_end) { - const char* diff = pc_ < expected_section_end ? "shorter" : "longer"; - size_t expected_length = static_cast<size_t>(section_length); - size_t actual_length = static_cast<size_t>(pc_ - section_start); - error(pc_, pc_, - "section \"%s\" %s (%zu bytes) than specified (%zu bytes)", - WasmSection::getName(section), diff, actual_length, - expected_length); - break; } + section_iter.advance(); + } + + // ===== Start section =================================================== + if (section_iter.section_code() == kStartSectionCode) { + WasmFunction* func; + const byte* pos = pc_; + module->start_function_index = consume_func_index(module, &func); + if (func && func->sig->parameter_count() > 0) { + error(pos, "invalid start function: non-zero parameter count"); + } + section_iter.advance(); + } + + // ===== Elements section ================================================ + if (section_iter.section_code() == kElementSectionCode) { + uint32_t element_count = consume_u32v("element count"); + for (uint32_t i = 0; ok() && i < element_count; ++i) { + uint32_t table_index = consume_u32v("table index"); + if (table_index != 0) error("illegal table index != 0"); + WasmInitExpr offset = consume_init_expr(module, kAstI32); + uint32_t num_elem = consume_u32v("number of elements"); + std::vector<uint32_t> vector; + module->table_inits.push_back({table_index, offset, vector}); + WasmTableInit* init = &module->table_inits.back(); + init->entries.reserve(SafeReserve(num_elem)); + for (uint32_t j = 0; ok() && j < num_elem; j++) { + WasmFunction* func = nullptr; + init->entries.push_back(consume_func_index(module, &func)); + } + } + + section_iter.advance(); + } + + // ===== Code section ==================================================== + if (section_iter.section_code() == kCodeSectionCode) { + const byte* pos = pc_; + uint32_t functions_count = consume_u32v("functions count"); + if (functions_count != module->num_declared_functions) { + error(pos, pos, "function body count %u mismatch (%u expected)", + functions_count, module->num_declared_functions); + } + for (uint32_t i = 0; ok() && i < functions_count; ++i) { + WasmFunction* function = + &module->functions[i + module->num_imported_functions]; + uint32_t size = consume_u32v("body size"); + function->code_start_offset = pc_offset(); + function->code_end_offset = pc_offset() + size; + consume_bytes(size, "function body"); + } + section_iter.advance(); + } + + // ===== Data section ==================================================== + if (section_iter.section_code() == kDataSectionCode) { + uint32_t data_segments_count = consume_u32v("data segments count"); + module->data_segments.reserve(SafeReserve(data_segments_count)); + for (uint32_t i = 0; ok() && i < data_segments_count; ++i) { + TRACE("DecodeDataSegment[%d] module+%d\n", i, + static_cast<int>(pc_ - start_)); + module->data_segments.push_back({ + NO_INIT, // dest_addr + 0, // source_offset + 0 // source_size + }); + WasmDataSegment* segment = &module->data_segments.back(); + DecodeDataSegmentInModule(module, segment); + } + section_iter.advance(); } - done: - if (ok()) CalculateGlobalsOffsets(module); + // ===== Name section ==================================================== + if (section_iter.section_code() == kNameSectionCode) { + const byte* pos = pc_; + uint32_t functions_count = consume_u32v("functions count"); + if (functions_count != module->num_declared_functions) { + error(pos, pos, "function name count %u mismatch (%u expected)", + functions_count, module->num_declared_functions); + } + + for (uint32_t i = 0; ok() && i < functions_count; ++i) { + WasmFunction* function = + &module->functions[i + module->num_imported_functions]; + function->name_offset = consume_string(&function->name_length, false); + + uint32_t local_names_count = consume_u32v("local names count"); + for (uint32_t j = 0; ok() && j < local_names_count; j++) { + uint32_t unused = 0; + uint32_t offset = consume_string(&unused, false); + USE(unused); + USE(offset); + } + } + section_iter.advance(); + } + + // ===== Remaining sections ============================================== + if (section_iter.more() && ok()) { + error(pc(), pc(), "unexpected section: %s", + SectionName(section_iter.section_code())); + } + + if (ok()) { + CalculateGlobalOffsets(module); + PreinitializeIndirectFunctionTables(module); + } const WasmModule* finished_module = module; ModuleResult result = toResult(finished_module); - if (FLAG_dump_wasm_module) { - DumpModule(module, result); - } + if (FLAG_dump_wasm_module) DumpModule(module, result); return result; } @@ -405,27 +602,6 @@ class ModuleDecoder : public Decoder { return count < kMaxReserve ? count : kMaxReserve; } - void CheckForFunctions(WasmModule* module, WasmSection::Code section) { - if (module->functions.size() == 0) { - error(pc_ - 1, nullptr, "functions must appear before section %s", - WasmSection::getName(section)); - } - } - - int CheckSectionOrder(int current_order, WasmSection::Code section) { - int next_order = WasmSection::getOrder(section); - if (next_order == 0) return current_order; - if (next_order == current_order) { - error(pc_, pc_, "section \"%s\" already defined", - WasmSection::getName(section)); - } - if (next_order < current_order) { - error(pc_, pc_, "section \"%s\" out of order", - WasmSection::getName(section)); - } - return next_order; - } - // Decodes a single anonymous function starting at {start_}. FunctionResult DecodeSingleFunction(ModuleEnv* module_env, WasmFunction* function) { @@ -451,6 +627,11 @@ class ModuleDecoder : public Decoder { return ok() ? result : nullptr; } + WasmInitExpr DecodeInitExpr(const byte* start) { + pc_ = start; + return consume_init_expr(nullptr, kAstStmt); + } + private: Zone* module_zone; ModuleResult result_; @@ -459,15 +640,28 @@ class ModuleDecoder : public Decoder { uint32_t off(const byte* ptr) { return static_cast<uint32_t>(ptr - start_); } // Decodes a single global entry inside a module starting at {pc_}. - void DecodeGlobalInModule(WasmGlobal* global) { - global->name_offset = consume_string(&global->name_length, false); - if (!unibrow::Utf8::Validate(start_ + global->name_offset, - global->name_length)) { - error("global name is not valid utf8"); + void DecodeGlobalInModule(WasmModule* module, uint32_t index, + WasmGlobal* global) { + global->type = consume_value_type(); + global->mutability = consume_u8("mutability") != 0; + const byte* pos = pc(); + global->init = consume_init_expr(module, kAstStmt); + switch (global->init.kind) { + case WasmInitExpr::kGlobalIndex: + if (global->init.val.global_index >= index) { + error("invalid global index in init expression"); + } else if (module->globals[index].type != global->type) { + error("type mismatch in global initialization"); + } + break; + default: + if (global->type != TypeOf(module, global->init)) { + error(pos, pos, + "type error in global initialization, expected %s, got %s", + WasmOpcodes::TypeName(global->type), + WasmOpcodes::TypeName(TypeOf(module, global->init))); + } } - global->type = consume_local_type(); - global->offset = 0; - global->exported = consume_u8("exported") != 0; } bool IsWithinLimit(uint32_t limit, uint32_t offset, uint32_t size) { @@ -479,10 +673,10 @@ class ModuleDecoder : public Decoder { // Decodes a single data segment entry inside a module starting at {pc_}. void DecodeDataSegmentInModule(WasmModule* module, WasmDataSegment* segment) { const byte* start = pc_; - segment->dest_addr = consume_u32v("destination"); + expect_u8("linear memory index", 0); + segment->dest_addr = consume_init_expr(module, kAstI32); segment->source_size = consume_u32v("source size"); segment->source_offset = static_cast<uint32_t>(pc_ - start_); - segment->init = true; // Validate the data is in the module. uint32_t module_limit = static_cast<uint32_t>(limit_ - start_); @@ -491,40 +685,11 @@ class ModuleDecoder : public Decoder { error(start, "segment out of bounds of module"); } - // Validate that the segment will fit into the (minimum) memory. - uint32_t memory_limit = - WasmModule::kPageSize * (module ? module->min_mem_pages - : WasmModule::kMaxMemPages); - if (!IsWithinLimit(memory_limit, segment->dest_addr, - segment->source_size)) { - error(start, "segment out of bounds of memory"); - } - - consume_bytes(segment->source_size); - } - - // Decodes a single function table inside a module starting at {pc_}. - void DecodeFunctionTableInModule(WasmModule* module, - WasmIndirectFunctionTable* table) { - table->size = consume_u32v("function table entry count"); - table->max_size = table->size; - - if (table->max_size != table->size) { - error("invalid table maximum size"); - } - - for (uint32_t i = 0; i < table->size; ++i) { - uint16_t index = consume_u32v(); - if (index >= module->functions.size()) { - error(pc_ - sizeof(index), "invalid function index"); - break; - } - table->values.push_back(index); - } + consume_bytes(segment->source_size, "segment data"); } // Calculate individual global offsets and total size of globals table. - void CalculateGlobalsOffsets(WasmModule* module) { + void CalculateGlobalOffsets(WasmModule* module) { uint32_t offset = 0; if (module->globals.size() == 0) { module->globals_size = 0; @@ -540,6 +705,30 @@ class ModuleDecoder : public Decoder { module->globals_size = offset; } + // TODO(titzer): this only works without overlapping initializations from + // global bases for entries + void PreinitializeIndirectFunctionTables(WasmModule* module) { + // Fill all tables with invalid entries first. + for (WasmIndirectFunctionTable& table : module->function_tables) { + table.values.resize(table.size); + for (size_t i = 0; i < table.size; i++) { + table.values[i] = kInvalidFunctionIndex; + } + } + for (WasmTableInit& init : module->table_inits) { + if (init.offset.kind != WasmInitExpr::kI32Const) continue; + if (init.table_index >= module->function_tables.size()) continue; + WasmIndirectFunctionTable& table = + module->function_tables[init.table_index]; + for (size_t i = 0; i < init.entries.size(); i++) { + size_t index = i + init.offset.val.i32_const; + if (index < table.values.size()) { + table.values[index] = init.entries[i]; + } + } + } + } + // Verifies the body (code) of a given function. void VerifyFunctionBody(uint32_t func_num, ModuleEnv* menv, WasmFunction* function) { @@ -570,26 +759,18 @@ class ModuleDecoder : public Decoder { } } - // Reads a single 32-bit unsigned integer interpreted as an offset, checking - // the offset is within bounds and advances. - uint32_t consume_offset(const char* name = nullptr) { - uint32_t offset = consume_u32(name ? name : "offset"); - if (offset > static_cast<uint32_t>(limit_ - start_)) { - error(pc_ - sizeof(uint32_t), "offset out of bounds of module"); - } - return offset; - } - // Reads a length-prefixed string, checking that it is within bounds. Returns // the offset of the string, and the length as an out parameter. uint32_t consume_string(uint32_t* length, bool validate_utf8) { *length = consume_u32v("string length"); uint32_t offset = pc_offset(); - TRACE(" +%u %-20s: (%u bytes)\n", offset, "string", *length); - if (validate_utf8 && !unibrow::Utf8::Validate(pc_, *length)) { - error(pc_, "no valid UTF-8 string"); + const byte* string_start = pc_; + // Consume bytes before validation to guarantee that the string is not oob. + consume_bytes(*length, "string"); + if (ok() && validate_utf8 && + !unibrow::Utf8::Validate(string_start, *length)) { + error(string_start, "no valid UTF-8 string"); } - consume_bytes(*length); return offset; } @@ -607,25 +788,134 @@ class ModuleDecoder : public Decoder { } uint32_t consume_func_index(WasmModule* module, WasmFunction** func) { + return consume_index("function index", module->functions, func); + } + + uint32_t consume_global_index(WasmModule* module, WasmGlobal** global) { + return consume_index("global index", module->globals, global); + } + + uint32_t consume_table_index(WasmModule* module, + WasmIndirectFunctionTable** table) { + return consume_index("table index", module->function_tables, table); + } + + template <typename T> + uint32_t consume_index(const char* name, std::vector<T>& vector, T** ptr) { const byte* pos = pc_; - uint32_t func_index = consume_u32v("function index"); - if (func_index >= module->functions.size()) { - error(pos, pos, "function index %u out of bounds (%d functions)", - func_index, static_cast<int>(module->functions.size())); - *func = nullptr; + uint32_t index = consume_u32v(name); + if (index >= vector.size()) { + error(pos, pos, "%s %u out of bounds (%d entries)", name, index, + static_cast<int>(vector.size())); + *ptr = nullptr; return 0; } - *func = &module->functions[func_index]; - return func_index; + *ptr = &vector[index]; + return index; + } + + void consume_resizable_limits(const char* name, const char* units, + uint32_t max_value, uint32_t* initial, + uint32_t* maximum) { + uint32_t flags = consume_u32v("resizable limits flags"); + const byte* pos = pc(); + *initial = consume_u32v("initial size"); + if (*initial > max_value) { + error(pos, pos, + "initial %s size (%u %s) is larger than maximum allowable (%u)", + name, *initial, units, max_value); + } + if (flags & 1) { + pos = pc(); + *maximum = consume_u32v("maximum size"); + if (*maximum > max_value) { + error(pos, pos, + "maximum %s size (%u %s) is larger than maximum allowable (%u)", + name, *maximum, units, max_value); + } + if (*maximum < *initial) { + error(pos, pos, "maximum %s size (%u %s) is less than initial (%u %s)", + name, *maximum, units, *initial, units); + } + } else { + *maximum = 0; + } + } + + bool expect_u8(const char* name, uint8_t expected) { + const byte* pos = pc(); + uint8_t value = consume_u8(name); + if (value != expected) { + error(pos, pos, "expected %s 0x%02x, got 0x%02x", name, expected, value); + return false; + } + return true; + } + + WasmInitExpr consume_init_expr(WasmModule* module, LocalType expected) { + const byte* pos = pc(); + uint8_t opcode = consume_u8("opcode"); + WasmInitExpr expr; + unsigned len = 0; + switch (opcode) { + case kExprGetGlobal: { + GlobalIndexOperand operand(this, pc() - 1); + expr.kind = WasmInitExpr::kGlobalIndex; + expr.val.global_index = operand.index; + len = operand.length; + break; + } + case kExprI32Const: { + ImmI32Operand operand(this, pc() - 1); + expr.kind = WasmInitExpr::kI32Const; + expr.val.i32_const = operand.value; + len = operand.length; + break; + } + case kExprF32Const: { + ImmF32Operand operand(this, pc() - 1); + expr.kind = WasmInitExpr::kF32Const; + expr.val.f32_const = operand.value; + len = operand.length; + break; + } + case kExprI64Const: { + ImmI64Operand operand(this, pc() - 1); + expr.kind = WasmInitExpr::kI64Const; + expr.val.i64_const = operand.value; + len = operand.length; + break; + } + case kExprF64Const: { + ImmF64Operand operand(this, pc() - 1); + expr.kind = WasmInitExpr::kF64Const; + expr.val.f64_const = operand.value; + len = operand.length; + break; + } + default: { + error("invalid opcode in initialization expression"); + expr.kind = WasmInitExpr::kNone; + expr.val.i32_const = 0; + } + } + consume_bytes(len, "init code"); + if (!expect_u8("end opcode", kExprEnd)) { + expr.kind = WasmInitExpr::kNone; + } + if (expected != kAstStmt && TypeOf(module, expr) != kAstI32) { + error(pos, pos, "type error in init expression, expected %s, got %s", + WasmOpcodes::TypeName(expected), + WasmOpcodes::TypeName(TypeOf(module, expr))); + } + return expr; } // Reads a single 8-bit integer, interpreting it as a local type. - LocalType consume_local_type() { - byte val = consume_u8("local type"); + LocalType consume_value_type() { + byte val = consume_u8("value type"); LocalTypeCode t = static_cast<LocalTypeCode>(val); switch (t) { - case kLocalVoid: - return kAstStmt; case kLocalI32: return kAstI32; case kLocalI64: @@ -634,6 +924,8 @@ class ModuleDecoder : public Decoder { return kAstF32; case kLocalF64: return kAstF64; + case kLocalS128: + return kAstS128; default: error(pc_ - 1, "invalid local type"); return kAstStmt; @@ -642,19 +934,12 @@ class ModuleDecoder : public Decoder { // Parses a type entry, which is currently limited to functions only. FunctionSig* consume_sig() { - const byte* pos = pc_; - byte form = consume_u8("type form"); - if (form != kWasmFunctionTypeForm) { - error(pos, pos, "expected function type form (0x%02x), got: 0x%02x", - kWasmFunctionTypeForm, form); - return nullptr; - } + if (!expect_u8("type form", kWasmFunctionTypeForm)) return nullptr; // parse parameter types uint32_t param_count = consume_u32v("param count"); std::vector<LocalType> params; - for (uint32_t i = 0; i < param_count; ++i) { - LocalType param = consume_local_type(); - if (param == kAstStmt) error(pc_ - 1, "invalid void parameter type"); + for (uint32_t i = 0; ok() && i < param_count; ++i) { + LocalType param = consume_value_type(); params.push_back(param); } @@ -667,12 +952,16 @@ class ModuleDecoder : public Decoder { return nullptr; } std::vector<LocalType> returns; - for (uint32_t i = 0; i < return_count; ++i) { - LocalType ret = consume_local_type(); - if (ret == kAstStmt) error(pc_ - 1, "invalid void return type"); + for (uint32_t i = 0; ok() && i < return_count; ++i) { + LocalType ret = consume_value_type(); returns.push_back(ret); } + if (failed()) { + // Decoding failed, return void -> void + return new (module_zone) FunctionSig(0, 0, nullptr); + } + // FunctionSig stores the return types first. LocalType* buffer = module_zone->NewArray<LocalType>(param_count + return_count); @@ -711,7 +1000,7 @@ class FunctionError : public FunctionResult { }; Vector<const byte> FindSection(const byte* module_start, const byte* module_end, - WasmSection::Code code) { + WasmSectionCode code) { Decoder decoder(module_start, module_end); uint32_t magic_word = decoder.consume_u32("wasm magic"); @@ -720,24 +1009,14 @@ Vector<const byte> FindSection(const byte* module_start, const byte* module_end, uint32_t magic_version = decoder.consume_u32("wasm version"); if (magic_version != kWasmVersion) decoder.error("wrong wasm version"); - while (decoder.more() && decoder.ok()) { - // Read the section name. - uint32_t string_length = decoder.consume_u32v("section name length"); - const byte* section_name_start = decoder.pc(); - decoder.consume_bytes(string_length); - if (decoder.failed()) break; - - WasmSection::Code section = - WasmSection::lookup(section_name_start, string_length); - - // Read and check the section size. - uint32_t section_length = decoder.consume_u32v("section length"); - - const byte* section_start = decoder.pc(); - decoder.consume_bytes(section_length); - if (section == code && decoder.ok()) { - return Vector<const uint8_t>(section_start, section_length); + WasmSectionIterator section_iter(decoder); + while (section_iter.more()) { + if (section_iter.section_code() == code) { + return Vector<const uint8_t>(section_iter.section_start(), + section_iter.section_length()); } + decoder.consume_bytes(section_iter.section_length(), "section payload"); + section_iter.advance(); } return Vector<const uint8_t>(); @@ -772,6 +1051,13 @@ FunctionSig* DecodeWasmSignatureForTesting(Zone* zone, const byte* start, return decoder.DecodeFunctionSignature(start); } +WasmInitExpr DecodeWasmInitExprForTesting(const byte* start, const byte* end) { + AccountingAllocator allocator; + Zone zone(&allocator); + ModuleDecoder decoder(&zone, start, end, kWasmOrigin); + return decoder.DecodeInitExpr(start); +} + FunctionResult DecodeWasmFunction(Isolate* isolate, Zone* zone, ModuleEnv* module_env, const byte* function_start, @@ -789,15 +1075,26 @@ FunctionResult DecodeWasmFunction(Isolate* isolate, Zone* zone, return decoder.DecodeSingleFunction(module_env, function); } -FunctionOffsetsResult DecodeWasmFunctionOffsets(const byte* module_start, - const byte* module_end) { +FunctionOffsetsResult DecodeWasmFunctionOffsets( + const byte* module_start, const byte* module_end, + uint32_t num_imported_functions) { + // Find and decode the code section. Vector<const byte> code_section = - FindSection(module_start, module_end, WasmSection::Code::FunctionBodies); + FindSection(module_start, module_end, kCodeSectionCode); Decoder decoder(code_section.start(), code_section.end()); - if (!code_section.start()) decoder.error("no code section"); + FunctionOffsets table; + if (!code_section.start()) { + decoder.error("no code section"); + return decoder.toResult(std::move(table)); + } + + // Reserve entries for the imported functions. + table.reserve(num_imported_functions); + for (uint32_t i = 0; i < num_imported_functions; i++) { + table.push_back(std::make_pair(0, 0)); + } uint32_t functions_count = decoder.consume_u32v("functions count"); - FunctionOffsets table; // Take care of invalid input here. if (functions_count < static_cast<unsigned>(code_section.length()) / 2) table.reserve(functions_count); diff --git a/deps/v8/src/wasm/module-decoder.h b/deps/v8/src/wasm/module-decoder.h index dd6bd3bc86..22a313cec3 100644 --- a/deps/v8/src/wasm/module-decoder.h +++ b/deps/v8/src/wasm/module-decoder.h @@ -12,9 +12,11 @@ namespace v8 { namespace internal { namespace wasm { // Decodes the bytes of a WASM module between {module_start} and {module_end}. -ModuleResult DecodeWasmModule(Isolate* isolate, Zone* zone, - const byte* module_start, const byte* module_end, - bool verify_functions, ModuleOrigin origin); +V8_EXPORT_PRIVATE ModuleResult DecodeWasmModule(Isolate* isolate, Zone* zone, + const byte* module_start, + const byte* module_end, + bool verify_functions, + ModuleOrigin origin); // Exposed for testing. Decodes a single function signature, allocating it // in the given zone. Returns {nullptr} upon failure. @@ -30,8 +32,11 @@ FunctionResult DecodeWasmFunction(Isolate* isolate, Zone* zone, ModuleEnv* env, // Extracts the function offset table from the wasm module bytes. // Returns a vector with <offset, length> entries, or failure if the wasm bytes // are detected as invalid. Note that this validation is not complete. -FunctionOffsetsResult DecodeWasmFunctionOffsets(const byte* module_start, - const byte* module_end); +FunctionOffsetsResult DecodeWasmFunctionOffsets( + const byte* module_start, const byte* module_end, + uint32_t num_imported_functions); + +WasmInitExpr DecodeWasmInitExprForTesting(const byte* start, const byte* end); } // namespace wasm } // namespace internal diff --git a/deps/v8/src/wasm/switch-logic.h b/deps/v8/src/wasm/switch-logic.h index 8cef08b98b..160e0d69b2 100644 --- a/deps/v8/src/wasm/switch-logic.h +++ b/deps/v8/src/wasm/switch-logic.h @@ -5,8 +5,8 @@ #ifndef V8_WASM_SWITCH_LOGIC_H #define V8_WASM_SWITCH_LOGIC_H -#include "src/zone-containers.h" -#include "src/zone.h" +#include "src/zone/zone-containers.h" +#include "src/zone/zone.h" namespace v8 { namespace internal { diff --git a/deps/v8/src/wasm/wasm-debug.cc b/deps/v8/src/wasm/wasm-debug.cc index 54e7100935..42a8e5f2ab 100644 --- a/deps/v8/src/wasm/wasm-debug.cc +++ b/deps/v8/src/wasm/wasm-debug.cc @@ -32,11 +32,15 @@ ByteArray *GetOrCreateFunctionOffsetTable(Handle<WasmDebugInfo> debug_info) { FunctionOffsetsResult function_offsets; { DisallowHeapAllocation no_gc; + Handle<JSObject> wasm_object(debug_info->wasm_object(), isolate); + uint32_t num_imported_functions = + wasm::GetNumImportedFunctions(wasm_object); SeqOneByteString *wasm_bytes = wasm::GetWasmBytes(debug_info->wasm_object()); const byte *bytes_start = wasm_bytes->GetChars(); const byte *bytes_end = bytes_start + wasm_bytes->length(); - function_offsets = wasm::DecodeWasmFunctionOffsets(bytes_start, bytes_end); + function_offsets = wasm::DecodeWasmFunctionOffsets(bytes_start, bytes_end, + num_imported_functions); } DCHECK(function_offsets.ok()); size_t array_size = 2 * kIntSize * function_offsets.val.size(); @@ -179,7 +183,7 @@ Handle<String> WasmDebugInfo::DisassembleFunction( Vector<const uint8_t> bytes_vec = GetFunctionBytes(debug_info, func_index); DisallowHeapAllocation no_gc; - base::AccountingAllocator allocator; + AccountingAllocator allocator; bool ok = PrintAst( &allocator, FunctionBodyForTesting(bytes_vec.start(), bytes_vec.end()), disassembly_os, nullptr); @@ -208,7 +212,7 @@ Handle<FixedArray> WasmDebugInfo::GetFunctionOffsetTable( Vector<const uint8_t> bytes_vec = GetFunctionBytes(debug_info, func_index); DisallowHeapAllocation no_gc; - v8::base::AccountingAllocator allocator; + AccountingAllocator allocator; bool ok = PrintAst( &allocator, FunctionBodyForTesting(bytes_vec.start(), bytes_vec.end()), null_stream, &offset_table_vec); diff --git a/deps/v8/src/wasm/wasm-external-refs.cc b/deps/v8/src/wasm/wasm-external-refs.cc index 09294c2c28..4c4c91b29c 100644 --- a/deps/v8/src/wasm/wasm-external-refs.cc +++ b/deps/v8/src/wasm/wasm-external-refs.cc @@ -206,9 +206,6 @@ uint32_t word64_popcnt_wrapper(uint64_t* input) { void float64_pow_wrapper(double* param0, double* param1) { double x = ReadDoubleValue(param0); double y = ReadDoubleValue(param1); - if (std::isnan(y) || ((x == 1 || x == -1) && std::isinf(y))) { - WriteDoubleValue(param0, std::numeric_limits<double>::quiet_NaN()); - } WriteDoubleValue(param0, Pow(x, y)); } } // namespace wasm diff --git a/deps/v8/src/wasm/wasm-interpreter.cc b/deps/v8/src/wasm/wasm-interpreter.cc index 7e3127dd53..2ac681eff2 100644 --- a/deps/v8/src/wasm/wasm-interpreter.cc +++ b/deps/v8/src/wasm/wasm-interpreter.cc @@ -10,8 +10,8 @@ #include "src/wasm/wasm-external-refs.h" #include "src/wasm/wasm-module.h" -#include "src/base/accounting-allocator.h" -#include "src/zone-containers.h" +#include "src/zone/accounting-allocator.h" +#include "src/zone/zone-containers.h" namespace v8 { namespace internal { @@ -654,6 +654,48 @@ static inline int64_t ExecuteI64ReinterpretF64(double a, TrapReason* trap) { return bit_cast<int64_t>(a); } +static inline int32_t ExecuteGrowMemory(uint32_t delta_pages, + WasmModuleInstance* instance) { + // TODO(ahaas): Move memory allocation to wasm-module.cc for better + // encapsulation. + if (delta_pages > wasm::WasmModule::kMaxMemPages) { + return -1; + } + uint32_t old_size = instance->mem_size; + uint32_t new_size; + byte* new_mem_start; + if (instance->mem_size == 0) { + if (delta_pages > wasm::WasmModule::kMaxMemPages) { + return -1; + } + // TODO(gdeepti): Fix bounds check to take into account size of memtype. + new_size = delta_pages * wasm::WasmModule::kPageSize; + new_mem_start = static_cast<byte*>(calloc(new_size, sizeof(byte))); + if (!new_mem_start) { + return -1; + } + } else { + DCHECK_NOT_NULL(instance->mem_start); + new_size = old_size + delta_pages * wasm::WasmModule::kPageSize; + if (new_size > + wasm::WasmModule::kMaxMemPages * wasm::WasmModule::kPageSize) { + return -1; + } + new_mem_start = static_cast<byte*>(realloc(instance->mem_start, new_size)); + if (!new_mem_start) { + return -1; + } + // Zero initializing uninitialized memory from realloc + memset(new_mem_start + old_size, 0, new_size - old_size); + } + instance->mem_start = new_mem_start; + instance->mem_size = new_size; + // realloc + // update mem_start + // update mem_size + return static_cast<int32_t>(old_size / WasmModule::kPageSize); +} + enum InternalOpcode { #define DECL_INTERNAL_ENUM(name, value) kInternal##name = value, FOREACH_INTERNAL_OPCODE(DECL_INTERNAL_ENUM) @@ -680,54 +722,38 @@ class ControlTransfers : public ZoneObject { public: ControlTransferMap map_; - ControlTransfers(Zone* zone, size_t locals_encoded_size, const byte* start, - const byte* end) + ControlTransfers(Zone* zone, ModuleEnv* env, AstLocalDecls* locals, + const byte* start, const byte* end) : map_(zone) { - // A control reference including from PC, from value depth, and whether - // a value is explicitly passed (e.g. br/br_if/br_table with value). - struct CRef { - const byte* pc; - sp_t value_depth; - bool explicit_value; - }; - // Represents a control flow label. struct CLabel : public ZoneObject { const byte* target; - size_t value_depth; - ZoneVector<CRef> refs; + ZoneVector<const byte*> refs; - CLabel(Zone* zone, size_t v) - : target(nullptr), value_depth(v), refs(zone) {} + explicit CLabel(Zone* zone) : target(nullptr), refs(zone) {} // Bind this label to the given PC. - void Bind(ControlTransferMap* map, const byte* start, const byte* pc, - bool expect_value) { + void Bind(ControlTransferMap* map, const byte* start, const byte* pc) { DCHECK_NULL(target); target = pc; - for (auto from : refs) { - auto pcdiff = static_cast<pcdiff_t>(target - from.pc); - auto spdiff = static_cast<spdiff_t>(from.value_depth - value_depth); - ControlTransfer::StackAction action = ControlTransfer::kNoAction; - if (expect_value && !from.explicit_value) { - action = spdiff == 0 ? ControlTransfer::kPushVoid - : ControlTransfer::kPopAndRepush; - } - pc_t offset = static_cast<size_t>(from.pc - start); - (*map)[offset] = {pcdiff, spdiff, action}; + for (auto from_pc : refs) { + auto pcdiff = static_cast<pcdiff_t>(target - from_pc); + size_t offset = static_cast<size_t>(from_pc - start); + (*map)[offset] = pcdiff; } } // Reference this label from the given location. - void Ref(ControlTransferMap* map, const byte* start, CRef from) { - DCHECK_GE(from.value_depth, value_depth); + void Ref(ControlTransferMap* map, const byte* start, + const byte* from_pc) { if (target) { - auto pcdiff = static_cast<pcdiff_t>(target - from.pc); - auto spdiff = static_cast<spdiff_t>(from.value_depth - value_depth); - pc_t offset = static_cast<size_t>(from.pc - start); - (*map)[offset] = {pcdiff, spdiff, ControlTransfer::kNoAction}; + // Target being bound before a reference means this is a loop. + DCHECK_EQ(kExprLoop, *target); + auto pcdiff = static_cast<pcdiff_t>(target - from_pc); + size_t offset = static_cast<size_t>(from_pc - start); + (*map)[offset] = pcdiff; } else { - refs.push_back(from); + refs.push_back(from_pc); } } }; @@ -738,122 +764,104 @@ class ControlTransfers : public ZoneObject { CLabel* end_label; CLabel* else_label; - void Ref(ControlTransferMap* map, const byte* start, const byte* from_pc, - size_t from_value_depth, bool explicit_value) { - end_label->Ref(map, start, {from_pc, from_value_depth, explicit_value}); + void Ref(ControlTransferMap* map, const byte* start, + const byte* from_pc) { + end_label->Ref(map, start, from_pc); } }; // Compute the ControlTransfer map. - // This works by maintaining a stack of control constructs similar to the + // This algorithm maintains a stack of control constructs similar to the // AST decoder. The {control_stack} allows matching {br,br_if,br_table} // bytecodes with their target, as well as determining whether the current // bytecodes are within the true or false block of an else. - // The value stack depth is tracked as {value_depth} and is needed to - // determine how many values to pop off the stack for explicit and - // implicit control flow. - std::vector<Control> control_stack; - size_t value_depth = 0; - for (BytecodeIterator i(start + locals_encoded_size, end); i.has_next(); - i.next()) { + CLabel* func_label = new (zone) CLabel(zone); + control_stack.push_back({start, func_label, nullptr}); + for (BytecodeIterator i(start, end, locals); i.has_next(); i.next()) { WasmOpcode opcode = i.current(); - TRACE("@%u: control %s (depth = %zu)\n", i.pc_offset(), - WasmOpcodes::OpcodeName(opcode), value_depth); + TRACE("@%u: control %s\n", i.pc_offset(), + WasmOpcodes::OpcodeName(opcode)); switch (opcode) { case kExprBlock: { - TRACE("control @%u $%zu: Block\n", i.pc_offset(), value_depth); - CLabel* label = new (zone) CLabel(zone, value_depth); + TRACE("control @%u: Block\n", i.pc_offset()); + CLabel* label = new (zone) CLabel(zone); control_stack.push_back({i.pc(), label, nullptr}); break; } case kExprLoop: { - TRACE("control @%u $%zu: Loop\n", i.pc_offset(), value_depth); - CLabel* label1 = new (zone) CLabel(zone, value_depth); - CLabel* label2 = new (zone) CLabel(zone, value_depth); - control_stack.push_back({i.pc(), label1, nullptr}); - control_stack.push_back({i.pc(), label2, nullptr}); - label2->Bind(&map_, start, i.pc(), false); + TRACE("control @%u: Loop\n", i.pc_offset()); + CLabel* label = new (zone) CLabel(zone); + control_stack.push_back({i.pc(), label, nullptr}); + label->Bind(&map_, start, i.pc()); break; } case kExprIf: { - TRACE("control @%u $%zu: If\n", i.pc_offset(), value_depth); - value_depth--; - CLabel* end_label = new (zone) CLabel(zone, value_depth); - CLabel* else_label = new (zone) CLabel(zone, value_depth); + TRACE("control @%u: If\n", i.pc_offset()); + CLabel* end_label = new (zone) CLabel(zone); + CLabel* else_label = new (zone) CLabel(zone); control_stack.push_back({i.pc(), end_label, else_label}); - else_label->Ref(&map_, start, {i.pc(), value_depth, false}); + else_label->Ref(&map_, start, i.pc()); break; } case kExprElse: { Control* c = &control_stack.back(); - TRACE("control @%u $%zu: Else\n", i.pc_offset(), value_depth); - c->end_label->Ref(&map_, start, {i.pc(), value_depth, false}); - value_depth = c->end_label->value_depth; + TRACE("control @%u: Else\n", i.pc_offset()); + c->end_label->Ref(&map_, start, i.pc()); DCHECK_NOT_NULL(c->else_label); - c->else_label->Bind(&map_, start, i.pc() + 1, false); + c->else_label->Bind(&map_, start, i.pc() + 1); c->else_label = nullptr; break; } case kExprEnd: { Control* c = &control_stack.back(); - TRACE("control @%u $%zu: End\n", i.pc_offset(), value_depth); + TRACE("control @%u: End\n", i.pc_offset()); if (c->end_label->target) { // only loops have bound labels. DCHECK_EQ(kExprLoop, *c->pc); - control_stack.pop_back(); - c = &control_stack.back(); + } else { + if (c->else_label) c->else_label->Bind(&map_, start, i.pc()); + c->end_label->Bind(&map_, start, i.pc() + 1); } - if (c->else_label) - c->else_label->Bind(&map_, start, i.pc() + 1, true); - c->end_label->Ref(&map_, start, {i.pc(), value_depth, false}); - c->end_label->Bind(&map_, start, i.pc() + 1, true); - value_depth = c->end_label->value_depth + 1; control_stack.pop_back(); break; } case kExprBr: { BreakDepthOperand operand(&i, i.pc()); - TRACE("control @%u $%zu: Br[arity=%u, depth=%u]\n", i.pc_offset(), - value_depth, operand.arity, operand.depth); - value_depth -= operand.arity; - control_stack[control_stack.size() - operand.depth - 1].Ref( - &map_, start, i.pc(), value_depth, operand.arity > 0); - value_depth++; + TRACE("control @%u: Br[depth=%u]\n", i.pc_offset(), operand.depth); + Control* c = &control_stack[control_stack.size() - operand.depth - 1]; + c->Ref(&map_, start, i.pc()); break; } case kExprBrIf: { BreakDepthOperand operand(&i, i.pc()); - TRACE("control @%u $%zu: BrIf[arity=%u, depth=%u]\n", i.pc_offset(), - value_depth, operand.arity, operand.depth); - value_depth -= (operand.arity + 1); - control_stack[control_stack.size() - operand.depth - 1].Ref( - &map_, start, i.pc(), value_depth, operand.arity > 0); - value_depth++; + TRACE("control @%u: BrIf[depth=%u]\n", i.pc_offset(), operand.depth); + Control* c = &control_stack[control_stack.size() - operand.depth - 1]; + c->Ref(&map_, start, i.pc()); break; } case kExprBrTable: { BranchTableOperand operand(&i, i.pc()); - TRACE("control @%u $%zu: BrTable[arity=%u count=%u]\n", i.pc_offset(), - value_depth, operand.arity, operand.table_count); - value_depth -= (operand.arity + 1); - for (uint32_t j = 0; j < operand.table_count + 1; ++j) { - uint32_t target = operand.read_entry(&i, j); - control_stack[control_stack.size() - target - 1].Ref( - &map_, start, i.pc() + j, value_depth, operand.arity > 0); + BranchTableIterator iterator(&i, operand); + TRACE("control @%u: BrTable[count=%u]\n", i.pc_offset(), + operand.table_count); + while (iterator.has_next()) { + uint32_t j = iterator.cur_index(); + uint32_t target = iterator.next(); + Control* c = &control_stack[control_stack.size() - target - 1]; + c->Ref(&map_, start, i.pc() + j); } - value_depth++; break; } default: { - value_depth = value_depth - OpcodeArity(i.pc(), end) + 1; break; } } } + if (!func_label->target) func_label->Bind(&map_, start, end); } - ControlTransfer Lookup(pc_t from) { + pcdiff_t Lookup(pc_t from) { auto result = map_.find(from); if (result == map_.end()) { V8_Fatal(__FILE__, __LINE__, "no control target for pc %zu", from); @@ -899,7 +907,7 @@ class CodeMap { if (function->func_index < interpreter_code_.size()) { InterpreterCode* code = &interpreter_code_[function->func_index]; DCHECK_EQ(function, code->function); - return code; + return Preprocess(code); } return nullptr; } @@ -923,9 +931,9 @@ class CodeMap { if (code->targets == nullptr && code->start) { // Compute the control targets map and the local declarations. CHECK(DecodeLocalDecls(code->locals, code->start, code->end)); - code->targets = - new (zone_) ControlTransfers(zone_, code->locals.decls_encoded_size, - code->orig_start, code->orig_end); + ModuleEnv env = {module_, nullptr, kWasmOrigin}; + code->targets = new (zone_) ControlTransfers( + zone_, &env, &code->locals, code->orig_start, code->orig_end); } return code; } @@ -964,6 +972,7 @@ class ThreadImpl : public WasmInterpreter::Thread { instance_(instance), stack_(zone), frames_(zone), + blocks_(zone), state_(WasmInterpreter::STOPPED), break_pc_(kInvalidPc), trap_reason_(kTrapCount) {} @@ -984,6 +993,9 @@ class ThreadImpl : public WasmInterpreter::Thread { stack_.push_back(args[i]); } frames_.back().ret_pc = InitLocals(code); + blocks_.push_back( + {0, stack_.size(), frames_.size(), + static_cast<uint32_t>(code->function->sig->return_count())}); TRACE(" => PushFrame(#%u @%zu)\n", code->function->func_index, frames_.back().ret_pc); } @@ -1032,11 +1044,11 @@ class ThreadImpl : public WasmInterpreter::Thread { return nullptr; } - virtual WasmVal GetReturnValue() { + virtual WasmVal GetReturnValue(int index) { if (state_ == WasmInterpreter::TRAPPED) return WasmVal(0xdeadbeef); CHECK_EQ(WasmInterpreter::FINISHED, state_); - CHECK_EQ(1, stack_.size()); - return stack_[0]; + CHECK_LT(static_cast<size_t>(index), stack_.size()); + return stack_[index]; } virtual pc_t GetBreakpointPc() { return break_pc_; } @@ -1060,10 +1072,18 @@ class ThreadImpl : public WasmInterpreter::Thread { sp_t llimit() { return plimit() + code->locals.total_local_count; } }; + struct Block { + pc_t pc; + sp_t sp; + size_t fp; + unsigned arity; + }; + CodeMap* codemap_; WasmModuleInstance* instance_; ZoneVector<WasmVal> stack_; ZoneVector<Frame> frames_; + ZoneVector<Block> blocks_; WasmInterpreter::State state_; pc_t break_pc_; TrapReason trap_reason_; @@ -1088,6 +1108,9 @@ class ThreadImpl : public WasmInterpreter::Thread { DCHECK_GE(stack_.size(), arity); // The parameters will overlap the arguments already on the stack. frames_.push_back({code, 0, 0, stack_.size() - arity}); + blocks_.push_back( + {0, stack_.size(), frames_.size(), + static_cast<uint32_t>(code->function->sig->return_count())}); frames_.back().ret_pc = InitLocals(code); TRACE(" => push func#%u @%zu\n", code->function->func_index, frames_.back().ret_pc); @@ -1126,21 +1149,38 @@ class ThreadImpl : public WasmInterpreter::Thread { bool SkipBreakpoint(InterpreterCode* code, pc_t pc) { if (pc == break_pc_) { + // Skip the previously hit breakpoint when resuming. break_pc_ = kInvalidPc; return true; } return false; } - bool DoReturn(InterpreterCode** code, pc_t* pc, pc_t* limit, WasmVal val) { + int LookupTarget(InterpreterCode* code, pc_t pc) { + return static_cast<int>(code->targets->Lookup(pc)); + } + + int DoBreak(InterpreterCode* code, pc_t pc, size_t depth) { + size_t bp = blocks_.size() - depth - 1; + Block* target = &blocks_[bp]; + DoStackTransfer(target->sp, target->arity); + blocks_.resize(bp); + return LookupTarget(code, pc); + } + + bool DoReturn(InterpreterCode** code, pc_t* pc, pc_t* limit, size_t arity) { DCHECK_GT(frames_.size(), 0u); - stack_.resize(frames_.back().sp); + // Pop all blocks for this frame. + while (!blocks_.empty() && blocks_.back().fp == frames_.size()) { + blocks_.pop_back(); + } + + sp_t dest = frames_.back().sp; frames_.pop_back(); if (frames_.size() == 0) { - // A return from the top frame terminates the execution. + // A return from the last frame terminates the execution. state_ = WasmInterpreter::FINISHED; - stack_.clear(); - stack_.push_back(val); + DoStackTransfer(0, arity); TRACE(" => finish\n"); return false; } else { @@ -1149,16 +1189,8 @@ class ThreadImpl : public WasmInterpreter::Thread { *code = top->code; *pc = top->ret_pc; *limit = top->code->end - top->code->start; - if (top->code->start[top->call_pc] == kExprCallIndirect || - (top->code->orig_start && - top->code->orig_start[top->call_pc] == kExprCallIndirect)) { - // UGLY: An indirect call has the additional function index on the - // stack. - stack_.pop_back(); - } TRACE(" => pop func#%u @%zu\n", (*code)->function->func_index, *pc); - - stack_.push_back(val); + DoStackTransfer(dest, arity); return true; } } @@ -1169,31 +1201,21 @@ class ThreadImpl : public WasmInterpreter::Thread { *limit = target->end - target->start; } - // Adjust the program counter {pc} and the stack contents according to the - // code's precomputed control transfer map. Returns the different between - // the new pc and the old pc. - int DoControlTransfer(InterpreterCode* code, pc_t pc) { - auto target = code->targets->Lookup(pc); - switch (target.action) { - case ControlTransfer::kNoAction: - TRACE(" action [sp-%u]\n", target.spdiff); - PopN(target.spdiff); - break; - case ControlTransfer::kPopAndRepush: { - WasmVal val = Pop(); - TRACE(" action [pop x, sp-%u, push x]\n", target.spdiff - 1); - DCHECK_GE(target.spdiff, 1u); - PopN(target.spdiff - 1); - Push(pc, val); - break; - } - case ControlTransfer::kPushVoid: - TRACE(" action [sp-%u, push void]\n", target.spdiff); - PopN(target.spdiff); - Push(pc, WasmVal()); - break; + // Copies {arity} values on the top of the stack down the stack to {dest}, + // dropping the values in-between. + void DoStackTransfer(sp_t dest, size_t arity) { + // before: |---------------| pop_count | arity | + // ^ 0 ^ dest ^ stack_.size() + // + // after: |---------------| arity | + // ^ 0 ^ stack_.size() + DCHECK_LE(dest, stack_.size()); + DCHECK_LE(dest + arity, stack_.size()); + size_t pop_count = stack_.size() - dest - arity; + for (size_t i = 0; i < arity; i++) { + stack_[dest + i] = stack_[dest + pop_count + i]; } - return target.pcdiff; + stack_.resize(stack_.size() - pop_count); } void Execute(InterpreterCode* code, pc_t pc, int max) { @@ -1209,8 +1231,8 @@ class ThreadImpl : public WasmInterpreter::Thread { if (pc >= limit) { // Fell off end of code; do an implicit return. TRACE("@%-3zu: ImplicitReturn\n", pc); - WasmVal val = PopArity(code->function->sig->return_count()); - if (!DoReturn(&code, &pc, &limit, val)) return; + if (!DoReturn(&code, &pc, &limit, code->function->sig->return_count())) + return; decoder.Reset(code->start, code->end); continue; } @@ -1243,27 +1265,37 @@ class ThreadImpl : public WasmInterpreter::Thread { switch (orig) { case kExprNop: - Push(pc, WasmVal()); break; - case kExprBlock: + case kExprBlock: { + BlockTypeOperand operand(&decoder, code->at(pc)); + blocks_.push_back({pc, stack_.size(), frames_.size(), operand.arity}); + len = 1 + operand.length; + break; + } case kExprLoop: { - // Do nothing. + BlockTypeOperand operand(&decoder, code->at(pc)); + blocks_.push_back({pc, stack_.size(), frames_.size(), 0}); + len = 1 + operand.length; break; } case kExprIf: { + BlockTypeOperand operand(&decoder, code->at(pc)); WasmVal cond = Pop(); bool is_true = cond.to<uint32_t>() != 0; + blocks_.push_back({pc, stack_.size(), frames_.size(), operand.arity}); if (is_true) { // fall through to the true block. + len = 1 + operand.length; TRACE(" true => fallthrough\n"); } else { - len = DoControlTransfer(code, pc); + len = LookupTarget(code, pc); TRACE(" false => @%zu\n", pc + len); } break; } case kExprElse: { - len = DoControlTransfer(code, pc); + blocks_.pop_back(); + len = LookupTarget(code, pc); TRACE(" end => @%zu\n", pc + len); break; } @@ -1276,42 +1308,34 @@ class ThreadImpl : public WasmInterpreter::Thread { } case kExprBr: { BreakDepthOperand operand(&decoder, code->at(pc)); - WasmVal val = PopArity(operand.arity); - len = DoControlTransfer(code, pc); + len = DoBreak(code, pc, operand.depth); TRACE(" br => @%zu\n", pc + len); - if (operand.arity > 0) Push(pc, val); break; } case kExprBrIf: { BreakDepthOperand operand(&decoder, code->at(pc)); WasmVal cond = Pop(); - WasmVal val = PopArity(operand.arity); bool is_true = cond.to<uint32_t>() != 0; if (is_true) { - len = DoControlTransfer(code, pc); + len = DoBreak(code, pc, operand.depth); TRACE(" br_if => @%zu\n", pc + len); - if (operand.arity > 0) Push(pc, val); } else { TRACE(" false => fallthrough\n"); len = 1 + operand.length; - Push(pc, WasmVal()); } break; } case kExprBrTable: { BranchTableOperand operand(&decoder, code->at(pc)); uint32_t key = Pop().to<uint32_t>(); - WasmVal val = PopArity(operand.arity); if (key >= operand.table_count) key = operand.table_count; - len = DoControlTransfer(code, pc + key) + key; - TRACE(" br[%u] => @%zu\n", key, pc + len); - if (operand.arity > 0) Push(pc, val); + len = key + DoBreak(code, pc + key, operand.table[key]); + TRACE(" br[%u] => @%zu\n", key, pc + key + len); break; } case kExprReturn: { - ReturnArityOperand operand(&decoder, code->at(pc)); - WasmVal val = PopArity(operand.arity); - if (!DoReturn(&code, &pc, &limit, val)) return; + size_t arity = code->function->sig->return_count(); + if (!DoReturn(&code, &pc, &limit, arity)) return; decoder.Reset(code->start, code->end); continue; } @@ -1320,8 +1344,7 @@ class ThreadImpl : public WasmInterpreter::Thread { return CommitPc(pc); } case kExprEnd: { - len = DoControlTransfer(code, pc); - DCHECK_EQ(1, len); + blocks_.pop_back(); break; } case kExprI8Const: { @@ -1364,10 +1387,21 @@ class ThreadImpl : public WasmInterpreter::Thread { LocalIndexOperand operand(&decoder, code->at(pc)); WasmVal val = Pop(); stack_[frames_.back().sp + operand.index] = val; + len = 1 + operand.length; + break; + } + case kExprTeeLocal: { + LocalIndexOperand operand(&decoder, code->at(pc)); + WasmVal val = Pop(); + stack_[frames_.back().sp + operand.index] = val; Push(pc, val); len = 1 + operand.length; break; } + case kExprDrop: { + Pop(); + break; + } case kExprCallFunction: { CallFunctionOperand operand(&decoder, code->at(pc)); InterpreterCode* target = codemap()->GetCode(operand.index); @@ -1378,9 +1412,7 @@ class ThreadImpl : public WasmInterpreter::Thread { } case kExprCallIndirect: { CallIndirectOperand operand(&decoder, code->at(pc)); - size_t index = stack_.size() - operand.arity - 1; - DCHECK_LT(index, stack_.size()); - uint32_t entry_index = stack_[index].to<uint32_t>(); + uint32_t entry_index = Pop().to<uint32_t>(); // Assume only one table for now. DCHECK_LE(module()->function_tables.size(), 1u); InterpreterCode* target = codemap()->GetIndirectCode(0, entry_index); @@ -1395,10 +1427,6 @@ class ThreadImpl : public WasmInterpreter::Thread { decoder.Reset(code->start, code->end); continue; } - case kExprCallImport: { - UNIMPLEMENTED(); - break; - } case kExprGetGlobal: { GlobalIndexOperand operand(&decoder, code->at(pc)); const WasmGlobal* global = &module()->globals[operand.index]; @@ -1437,14 +1465,13 @@ class ThreadImpl : public WasmInterpreter::Thread { } else { UNREACHABLE(); } - Push(pc, val); len = 1 + operand.length; break; } #define LOAD_CASE(name, ctype, mtype) \ case kExpr##name: { \ - MemoryAccessOperand operand(&decoder, code->at(pc)); \ + MemoryAccessOperand operand(&decoder, code->at(pc), sizeof(ctype)); \ uint32_t index = Pop().to<uint32_t>(); \ size_t effective_mem_size = instance()->mem_size - sizeof(mtype); \ if (operand.offset > effective_mem_size || \ @@ -1476,7 +1503,7 @@ class ThreadImpl : public WasmInterpreter::Thread { #define STORE_CASE(name, ctype, mtype) \ case kExpr##name: { \ - MemoryAccessOperand operand(&decoder, code->at(pc)); \ + MemoryAccessOperand operand(&decoder, code->at(pc), sizeof(ctype)); \ WasmVal val = Pop(); \ uint32_t index = Pop().to<uint32_t>(); \ size_t effective_mem_size = instance()->mem_size - sizeof(mtype); \ @@ -1486,7 +1513,6 @@ class ThreadImpl : public WasmInterpreter::Thread { } \ byte* addr = instance()->mem_start + operand.offset + index; \ WriteLittleEndianValue<mtype>(addr, static_cast<mtype>(val.to<ctype>())); \ - Push(pc, val); \ len = 1 + operand.length; \ break; \ } @@ -1546,9 +1572,14 @@ class ThreadImpl : public WasmInterpreter::Thread { ASMJS_STORE_CASE(F32AsmjsStoreMem, float, float); ASMJS_STORE_CASE(F64AsmjsStoreMem, double, double); #undef ASMJS_STORE_CASE - + case kExprGrowMemory: { + uint32_t delta_pages = Pop().to<uint32_t>(); + Push(pc, WasmVal(ExecuteGrowMemory(delta_pages, instance()))); + break; + } case kExprMemorySize: { - Push(pc, WasmVal(static_cast<uint32_t>(instance()->mem_size))); + Push(pc, WasmVal(static_cast<uint32_t>(instance()->mem_size / + WasmModule::kPageSize))); break; } #define EXECUTE_SIMPLE_BINOP(name, ctype, op) \ @@ -1623,7 +1654,7 @@ class ThreadImpl : public WasmInterpreter::Thread { void Push(pc_t pc, WasmVal val) { // TODO(titzer): store PC as well? - stack_.push_back(val); + if (val.type != kAstStmt) stack_.push_back(val); } void TraceStack(const char* phase, pc_t pc) { @@ -1700,7 +1731,7 @@ class WasmInterpreterInternals : public ZoneObject { // Implementation of the public interface of the interpreter. //============================================================================ WasmInterpreter::WasmInterpreter(WasmModuleInstance* instance, - base::AccountingAllocator* allocator) + AccountingAllocator* allocator) : zone_(allocator), internals_(new (&zone_) WasmInterpreterInternals(&zone_, instance)) {} @@ -1804,7 +1835,7 @@ bool WasmInterpreter::SetFunctionCodeForTesting(const WasmFunction* function, ControlTransferMap WasmInterpreter::ComputeControlTransfersForTesting( Zone* zone, const byte* start, const byte* end) { - ControlTransfers targets(zone, 0, start, end); + ControlTransfers targets(zone, nullptr, nullptr, start, end); return targets.map_; } diff --git a/deps/v8/src/wasm/wasm-interpreter.h b/deps/v8/src/wasm/wasm-interpreter.h index b106a202d2..b61e092e23 100644 --- a/deps/v8/src/wasm/wasm-interpreter.h +++ b/deps/v8/src/wasm/wasm-interpreter.h @@ -6,7 +6,7 @@ #define V8_WASM_INTERPRETER_H_ #include "src/wasm/wasm-opcodes.h" -#include "src/zone-containers.h" +#include "src/zone/zone-containers.h" namespace v8 { namespace base { @@ -28,15 +28,7 @@ typedef uint32_t spdiff_t; const pc_t kInvalidPc = 0x80000000; -// Visible for testing. A {ControlTransfer} helps the interpreter figure out -// the target program counter and stack manipulations for a branch. -struct ControlTransfer { - enum StackAction { kNoAction, kPopAndRepush, kPushVoid }; - pcdiff_t pcdiff; // adjustment to the program counter (positive or negative). - spdiff_t spdiff; // number of elements to pop off the stack. - StackAction action; // action to perform on the stack. -}; -typedef ZoneMap<pc_t, ControlTransfer> ControlTransferMap; +typedef ZoneMap<pc_t, pcdiff_t> ControlTransferMap; // Macro for defining union members. #define FOREACH_UNION_MEMBER(V) \ @@ -102,7 +94,7 @@ class WasmFrame { }; // An interpreter capable of executing WASM. -class WasmInterpreter { +class V8_EXPORT_PRIVATE WasmInterpreter { public: // State machine for a Thread: // +---------------Run()-----------+ @@ -132,15 +124,14 @@ class WasmInterpreter { virtual int GetFrameCount() = 0; virtual const WasmFrame* GetFrame(int index) = 0; virtual WasmFrame* GetMutableFrame(int index) = 0; - virtual WasmVal GetReturnValue() = 0; + virtual WasmVal GetReturnValue(int index = 0) = 0; // Thread-specific breakpoints. bool SetBreakpoint(const WasmFunction* function, int pc, bool enabled); bool GetBreakpoint(const WasmFunction* function, int pc); }; - WasmInterpreter(WasmModuleInstance* instance, - base::AccountingAllocator* allocator); + WasmInterpreter(WasmModuleInstance* instance, AccountingAllocator* allocator); ~WasmInterpreter(); //========================================================================== @@ -190,9 +181,8 @@ class WasmInterpreter { bool SetFunctionCodeForTesting(const WasmFunction* function, const byte* start, const byte* end); - // Computes the control targets for the given bytecode as {pc offset, sp - // offset} - // pairs. Used internally in the interpreter, but exposed for testing. + // Computes the control transfers for the given bytecode. Used internally in + // the interpreter, but exposed for testing. static ControlTransferMap ComputeControlTransfersForTesting(Zone* zone, const byte* start, const byte* end); diff --git a/deps/v8/src/wasm/wasm-js.cc b/deps/v8/src/wasm/wasm-js.cc index 10ae43c78b..254fd7061a 100644 --- a/deps/v8/src/wasm/wasm-js.cc +++ b/deps/v8/src/wasm/wasm-js.cc @@ -9,8 +9,6 @@ #include "src/asmjs/asm-wasm-builder.h" #include "src/assert-scope.h" #include "src/ast/ast.h" -#include "src/ast/scopes.h" -#include "src/compiler.h" #include "src/execution.h" #include "src/factory.h" #include "src/handles.h" @@ -18,7 +16,6 @@ #include "src/objects.h" #include "src/parsing/parse-info.h" -#include "src/wasm/encoder.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-js.h" #include "src/wasm/wasm-module.h" @@ -31,6 +28,13 @@ using v8::internal::wasm::ErrorThrower; namespace v8 { namespace { +i::Handle<i::String> v8_str(i::Isolate* isolate, const char* str) { + return isolate->factory()->NewStringFromAsciiChecked(str); +} +Local<String> v8_str(Isolate* isolate, const char* str) { + return Utils::ToLocal(v8_str(reinterpret_cast<i::Isolate*>(isolate), str)); +} + struct RawBuffer { const byte* start; const byte* end; @@ -80,7 +84,7 @@ void VerifyModule(const v8::FunctionCallbackInfo<v8::Value>& args) { ErrorThrower thrower(isolate, "Wasm.verifyModule()"); if (args.Length() < 1) { - thrower.Error("Argument 0 must be a buffer source"); + thrower.TypeError("Argument 0 must be a buffer source"); return; } RawBuffer buffer = GetRawBufferSource(args[0], &thrower); @@ -104,7 +108,7 @@ void VerifyFunction(const v8::FunctionCallbackInfo<v8::Value>& args) { ErrorThrower thrower(isolate, "Wasm.verifyFunction()"); if (args.Length() < 1) { - thrower.Error("Argument 0 must be a buffer source"); + thrower.TypeError("Argument 0 must be a buffer source"); return; } RawBuffer buffer = GetRawBufferSource(args[0], &thrower); @@ -135,13 +139,11 @@ i::MaybeHandle<i::JSObject> InstantiateModule( // Decode but avoid a redundant pass over function bodies for verification. // Verification will happen during compilation. i::Zone zone(isolate->allocator()); - internal::wasm::ModuleResult result = internal::wasm::DecodeWasmModule( - isolate, &zone, start, end, false, origin); - + i::MaybeHandle<i::JSObject> module_object = + i::wasm::CreateModuleObjectFromBytes(isolate, start, end, thrower, + origin); i::MaybeHandle<i::JSObject> object; - if (result.failed()) { - thrower->Failed("", result); - } else { + if (!module_object.is_null()) { // Success. Instantiate the module and return the object. i::Handle<i::JSObject> ffi = i::Handle<i::JSObject>::null(); if (args.Length() > 1 && args[1]->IsObject()) { @@ -156,19 +158,12 @@ i::MaybeHandle<i::JSObject> InstantiateModule( memory = i::Handle<i::JSArrayBuffer>(i::JSArrayBuffer::cast(*mem_obj)); } - i::MaybeHandle<i::FixedArray> compiled_module = - result.val->CompileFunctions(isolate, thrower); - if (!thrower->error()) { - DCHECK(!compiled_module.is_null()); - object = i::wasm::WasmModule::Instantiate( - isolate, compiled_module.ToHandleChecked(), ffi, memory); - if (!object.is_null()) { - args.GetReturnValue().Set(v8::Utils::ToLocal(object.ToHandleChecked())); - } + object = i::wasm::WasmModule::Instantiate( + isolate, thrower, module_object.ToHandleChecked(), ffi, memory); + if (!object.is_null()) { + args.GetReturnValue().Set(v8::Utils::ToLocal(object.ToHandleChecked())); } } - - if (result.val) delete result.val; return object; } @@ -178,7 +173,7 @@ void InstantiateModule(const v8::FunctionCallbackInfo<v8::Value>& args) { ErrorThrower thrower(isolate, "Wasm.instantiateModule()"); if (args.Length() < 1) { - thrower.Error("Argument 0 must be a buffer source"); + thrower.TypeError("Argument 0 must be a buffer source"); return; } RawBuffer buffer = GetRawBufferSource(args[0], &thrower); @@ -197,20 +192,37 @@ static i::MaybeHandle<i::JSObject> CreateModuleObject( if (buffer.start == nullptr) return i::MaybeHandle<i::JSObject>(); DCHECK(source->IsArrayBuffer() || source->IsTypedArray()); - i::Zone zone(i_isolate->allocator()); - i::wasm::ModuleResult result = i::wasm::DecodeWasmModule( - i_isolate, &zone, buffer.start, buffer.end, false, i::wasm::kWasmOrigin); - std::unique_ptr<const i::wasm::WasmModule> decoded_module(result.val); - if (result.failed()) { - thrower->Failed("", result); - return nothing; - } - i::MaybeHandle<i::FixedArray> compiled_module = - decoded_module->CompileFunctions(i_isolate, thrower); - if (compiled_module.is_null()) return nothing; + return i::wasm::CreateModuleObjectFromBytes( + i_isolate, buffer.start, buffer.end, thrower, + i::wasm::ModuleOrigin::kWasmOrigin); +} + +static bool ValidateModule(v8::Isolate* isolate, + const v8::Local<v8::Value> source, + ErrorThrower* thrower) { + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + i::MaybeHandle<i::JSObject> nothing; + + RawBuffer buffer = GetRawBufferSource(source, thrower); + if (buffer.start == nullptr) return false; - return i::wasm::CreateCompiledModuleObject(i_isolate, - compiled_module.ToHandleChecked()); + DCHECK(source->IsArrayBuffer() || source->IsTypedArray()); + return i::wasm::ValidateModuleBytes(i_isolate, buffer.start, buffer.end, + thrower, + i::wasm::ModuleOrigin::kWasmOrigin); +} + +bool BrandCheck(Isolate* isolate, i::Handle<i::Object> value, + i::Handle<i::Symbol> sym, const char* msg) { + if (value->IsJSObject()) { + i::Handle<i::JSObject> object = i::Handle<i::JSObject>::cast(value); + Maybe<bool> has_brand = i::JSObject::HasOwnProperty(object, sym); + if (has_brand.IsNothing()) return false; + if (has_brand.ToChecked()) return true; + } + v8::Local<v8::Value> e = v8::Exception::TypeError(v8_str(isolate, msg)); + isolate->ThrowException(e); + return false; } void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) { @@ -220,7 +232,7 @@ void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) { "WebAssembly.compile()"); if (args.Length() < 1) { - thrower.Error("Argument 0 must be a buffer source"); + thrower.TypeError("Argument 0 must be a buffer source"); return; } i::MaybeHandle<i::JSObject> module_obj = @@ -238,6 +250,25 @@ void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) { return_value.Set(resolver->GetPromise()); } +void WebAssemblyValidate(const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + ErrorThrower thrower(reinterpret_cast<i::Isolate*>(isolate), + "WebAssembly.validate()"); + + if (args.Length() < 1) { + thrower.TypeError("Argument 0 must be a buffer source"); + return; + } + + v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); + if (ValidateModule(isolate, args[0], &thrower)) { + return_value.Set(v8::True(isolate)); + } else { + return_value.Set(v8::False(isolate)); + } +} + void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); @@ -245,7 +276,7 @@ void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) { "WebAssembly.Module()"); if (args.Length() < 1) { - thrower.Error("Argument 0 must be a buffer source"); + thrower.TypeError("Argument 0 must be a buffer source"); return; } i::MaybeHandle<i::JSObject> module_obj = @@ -264,18 +295,15 @@ void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) { ErrorThrower thrower(i_isolate, "WebAssembly.Instance()"); if (args.Length() < 1) { - thrower.Error( - "Argument 0 must be provided, and must be a WebAssembly.Module object"); + thrower.TypeError("Argument 0 must be a WebAssembly.Module"); return; } Local<Context> context = isolate->GetCurrentContext(); i::Handle<i::Context> i_context = Utils::OpenHandle(*context); - i::Handle<i::Symbol> module_sym(i_context->wasm_module_sym()); - i::MaybeHandle<i::Object> source = - i::Object::GetProperty(Utils::OpenHandle(*args[0]), module_sym); - if (source.is_null() || source.ToHandleChecked()->IsUndefined(i_isolate)) { - thrower.Error("Argument 0 must be a WebAssembly.Module"); + if (!BrandCheck(isolate, Utils::OpenHandle(*args[0]), + i::Handle<i::Symbol>(i_context->wasm_module_sym()), + "Argument 0 must be a WebAssembly.Module")) { return; } @@ -285,13 +313,10 @@ void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) { i::Handle<i::JSObject>::cast(v8::Utils::OpenHandle(*obj)); if (module_obj->GetInternalFieldCount() < 1 || !module_obj->GetInternalField(0)->IsFixedArray()) { - thrower.Error("Argument 0 is an invalid WebAssembly.Module"); + thrower.TypeError("Argument 0 is an invalid WebAssembly.Module"); return; } - i::Handle<i::FixedArray> compiled_code = i::Handle<i::FixedArray>( - i::FixedArray::cast(module_obj->GetInternalField(0))); - i::Handle<i::JSReceiver> ffi = i::Handle<i::JSObject>::null(); if (args.Length() > 1 && args[1]->IsObject()) { Local<Object> obj = Local<Object>::Cast(args[1]); @@ -304,17 +329,211 @@ void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) { i::Handle<i::Object> mem_obj = v8::Utils::OpenHandle(*obj); memory = i::Handle<i::JSArrayBuffer>(i::JSArrayBuffer::cast(*mem_obj)); } - i::MaybeHandle<i::JSObject> instance = - i::wasm::WasmModule::Instantiate(i_isolate, compiled_code, ffi, memory); + i::MaybeHandle<i::JSObject> instance = i::wasm::WasmModule::Instantiate( + i_isolate, &thrower, module_obj, ffi, memory); if (instance.is_null()) { - thrower.Error("Could not instantiate module"); + if (!thrower.error()) thrower.Error("Could not instantiate module"); return; } v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); return_value.Set(Utils::ToLocal(instance.ToHandleChecked())); } + +bool GetIntegerProperty(v8::Isolate* isolate, ErrorThrower* thrower, + Local<Context> context, Local<v8::Object> object, + Local<String> property, int* result, int lower_bound, + int upper_bound) { + v8::MaybeLocal<v8::Value> maybe = object->Get(context, property); + v8::Local<v8::Value> value; + if (maybe.ToLocal(&value)) { + int64_t number; + if (!value->IntegerValue(context).To(&number)) return false; + if (number < static_cast<int64_t>(lower_bound)) { + thrower->RangeError("Property value %" PRId64 + " is below the lower bound %d", + number, lower_bound); + return false; + } + if (number > static_cast<int64_t>(upper_bound)) { + thrower->RangeError("Property value %" PRId64 + " is above the upper bound %d", + number, upper_bound); + return false; + } + *result = static_cast<int>(number); + return true; + } + return false; +} + +void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + ErrorThrower thrower(reinterpret_cast<i::Isolate*>(isolate), + "WebAssembly.Module()"); + if (args.Length() < 1 || !args[0]->IsObject()) { + thrower.TypeError("Argument 0 must be a table descriptor"); + return; + } + Local<Context> context = isolate->GetCurrentContext(); + Local<v8::Object> descriptor = args[0]->ToObject(context).ToLocalChecked(); + // The descriptor's 'element'. + { + v8::MaybeLocal<v8::Value> maybe = + descriptor->Get(context, v8_str(isolate, "element")); + v8::Local<v8::Value> value; + if (!maybe.ToLocal(&value)) return; + v8::Local<v8::String> string; + if (!value->ToString(context).ToLocal(&string)) return; + bool equal; + if (!string->Equals(context, v8_str(isolate, "anyfunc")).To(&equal)) return; + if (!equal) { + thrower.TypeError("Descriptor property 'element' must be 'anyfunc'"); + return; + } + } + const int max_table_size = 1 << 26; + // The descriptor's 'initial'. + int initial; + if (!GetIntegerProperty(isolate, &thrower, context, descriptor, + v8_str(isolate, "initial"), &initial, 0, + max_table_size)) { + return; + } + // The descriptor's 'maximum'. + int maximum = 0; + Local<String> maximum_key = v8_str(isolate, "maximum"); + Maybe<bool> has_maximum = descriptor->Has(context, maximum_key); + + if (has_maximum.IsNothing()) { + // There has been an exception, just return. + return; + } + if (has_maximum.FromJust()) { + if (!GetIntegerProperty(isolate, &thrower, context, descriptor, maximum_key, + &maximum, initial, max_table_size)) { + return; + } + } + + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + i::Handle<i::JSFunction> table_ctor( + i_isolate->native_context()->wasm_table_constructor()); + i::Handle<i::JSObject> table_obj = + i_isolate->factory()->NewJSObject(table_ctor); + i::Handle<i::FixedArray> fixed_array = + i_isolate->factory()->NewFixedArray(initial); + i::Object* null = i_isolate->heap()->null_value(); + for (int i = 0; i < initial; ++i) fixed_array->set(i, null); + table_obj->SetInternalField(0, *fixed_array); + table_obj->SetInternalField( + 1, has_maximum.FromJust() + ? static_cast<i::Object*>(i::Smi::FromInt(maximum)) + : static_cast<i::Object*>(i_isolate->heap()->undefined_value())); + i::Handle<i::Symbol> table_sym(i_isolate->native_context()->wasm_table_sym()); + i::Object::SetProperty(table_obj, table_sym, table_obj, i::STRICT).Check(); + v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); + return_value.Set(Utils::ToLocal(table_obj)); +} + +void WebAssemblyMemory(const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + ErrorThrower thrower(reinterpret_cast<i::Isolate*>(isolate), + "WebAssembly.Module()"); + if (args.Length() < 1 || !args[0]->IsObject()) { + thrower.TypeError("Argument 0 must be a table descriptor"); + return; + } + Local<Context> context = isolate->GetCurrentContext(); + Local<v8::Object> descriptor = args[0]->ToObject(context).ToLocalChecked(); + // The descriptor's 'initial'. + int initial; + if (!GetIntegerProperty(isolate, &thrower, context, descriptor, + v8_str(isolate, "initial"), &initial, 0, 65536)) { + return; + } + // The descriptor's 'maximum'. + int maximum = 0; + Local<String> maximum_key = v8_str(isolate, "maximum"); + Maybe<bool> has_maximum = descriptor->Has(context, maximum_key); + + if (has_maximum.IsNothing()) { + // There has been an exception, just return. + return; + } + if (has_maximum.FromJust()) { + if (!GetIntegerProperty(isolate, &thrower, context, descriptor, maximum_key, + &maximum, initial, 65536)) { + return; + } + } + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + i::Handle<i::JSArrayBuffer> buffer = + i_isolate->factory()->NewJSArrayBuffer(i::SharedFlag::kNotShared); + size_t size = static_cast<size_t>(i::wasm::WasmModule::kPageSize) * + static_cast<size_t>(initial); + i::JSArrayBuffer::SetupAllocatingData(buffer, i_isolate, size); + + i::Handle<i::JSObject> memory_obj = i::WasmJs::CreateWasmMemoryObject( + i_isolate, buffer, has_maximum.FromJust(), maximum); + v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); + return_value.Set(Utils::ToLocal(memory_obj)); +} +void WebAssemblyTableGetLength( + const v8::FunctionCallbackInfo<v8::Value>& args) { + // TODO(rossberg) +} +void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) { + // TODO(rossberg) +} +void WebAssemblyTableGet(const v8::FunctionCallbackInfo<v8::Value>& args) { + // TODO(rossberg) +} +void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) { + // TODO(rossberg) +} +void WebAssemblyMemoryGrow(const v8::FunctionCallbackInfo<v8::Value>& args) { + // TODO(rossberg) +} +void WebAssemblyMemoryGetBuffer( + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Isolate* isolate = args.GetIsolate(); + Local<Context> context = isolate->GetCurrentContext(); + i::Handle<i::Context> i_context = Utils::OpenHandle(*context); + if (!BrandCheck(isolate, Utils::OpenHandle(*args.This()), + i::Handle<i::Symbol>(i_context->wasm_memory_sym()), + "Receiver is not a WebAssembly.Memory")) { + return; + } + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + i::Handle<i::JSObject> receiver = + i::Handle<i::JSObject>::cast(Utils::OpenHandle(*args.This())); + i::Handle<i::Object> buffer(receiver->GetInternalField(0), i_isolate); + DCHECK(buffer->IsJSArrayBuffer()); + v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); + return_value.Set(Utils::ToLocal(buffer)); +} } // namespace +i::Handle<i::JSObject> i::WasmJs::CreateWasmMemoryObject( + i::Isolate* i_isolate, i::Handle<i::JSArrayBuffer> buffer, bool has_maximum, + int maximum) { + i::Handle<i::JSFunction> memory_ctor( + i_isolate->native_context()->wasm_memory_constructor()); + i::Handle<i::JSObject> memory_obj = + i_isolate->factory()->NewJSObject(memory_ctor); + memory_obj->SetInternalField(0, *buffer); + memory_obj->SetInternalField( + 1, has_maximum + ? static_cast<i::Object*>(i::Smi::FromInt(maximum)) + : static_cast<i::Object*>(i_isolate->heap()->undefined_value())); + i::Handle<i::Symbol> memory_sym( + i_isolate->native_context()->wasm_memory_sym()); + i::Object::SetProperty(memory_obj, memory_sym, memory_obj, i::STRICT).Check(); + return memory_obj; +} + // TODO(titzer): we use the API to create the function template because the // internal guts are too ugly to replicate here. static i::Handle<i::FunctionTemplateInfo> NewTemplate(i::Isolate* i_isolate, @@ -325,12 +544,9 @@ static i::Handle<i::FunctionTemplateInfo> NewTemplate(i::Isolate* i_isolate, } namespace internal { -static Handle<String> v8_str(Isolate* isolate, const char* str) { - return isolate->factory()->NewStringFromAsciiChecked(str); -} -static Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object, - const char* str, FunctionCallback func) { +Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object, + const char* str, FunctionCallback func) { Handle<String> name = v8_str(isolate, str); Handle<FunctionTemplateInfo> temp = NewTemplate(isolate, func); Handle<JSFunction> function = @@ -341,6 +557,112 @@ static Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object, return function; } +Handle<JSFunction> InstallGetter(Isolate* isolate, Handle<JSObject> object, + const char* str, FunctionCallback func) { + Handle<String> name = v8_str(isolate, str); + Handle<FunctionTemplateInfo> temp = NewTemplate(isolate, func); + Handle<JSFunction> function = + ApiNatives::InstantiateFunction(temp).ToHandleChecked(); + v8::PropertyAttribute attributes = + static_cast<v8::PropertyAttribute>(v8::DontDelete | v8::ReadOnly); + Utils::ToLocal(object)->SetAccessorProperty(Utils::ToLocal(name), + Utils::ToLocal(function), + Local<Function>(), attributes); + return function; +} + +void WasmJs::InstallWasmModuleSymbolIfNeeded(Isolate* isolate, + Handle<JSGlobalObject> global, + Handle<Context> context) { + if (!context->get(Context::WASM_MODULE_SYM_INDEX)->IsSymbol() || + !context->get(Context::WASM_INSTANCE_SYM_INDEX)->IsSymbol()) { + InstallWasmMapsIfNeeded(isolate, isolate->native_context()); + InstallWasmConstructors(isolate, isolate->global_object(), + isolate->native_context()); + } +} + +void WasmJs::InstallWasmConstructors(Isolate* isolate, + Handle<JSGlobalObject> global, + Handle<Context> context) { + Factory* factory = isolate->factory(); + // Create private symbols. + Handle<Symbol> module_sym = factory->NewPrivateSymbol(); + context->set_wasm_module_sym(*module_sym); + + Handle<Symbol> instance_sym = factory->NewPrivateSymbol(); + context->set_wasm_instance_sym(*instance_sym); + + Handle<Symbol> table_sym = factory->NewPrivateSymbol(); + context->set_wasm_table_sym(*table_sym); + + Handle<Symbol> memory_sym = factory->NewPrivateSymbol(); + context->set_wasm_memory_sym(*memory_sym); + + // Bind the WebAssembly object. + Handle<String> name = v8_str(isolate, "WebAssembly"); + Handle<JSFunction> cons = factory->NewFunction(name); + JSFunction::SetInstancePrototype( + cons, Handle<Object>(context->initial_object_prototype(), isolate)); + cons->shared()->set_instance_class_name(*name); + Handle<JSObject> wasm_object = factory->NewJSObject(cons, TENURED); + PropertyAttributes attributes = static_cast<PropertyAttributes>(DONT_ENUM); + JSObject::AddProperty(global, name, wasm_object, attributes); + + // Setup compile + InstallFunc(isolate, wasm_object, "compile", WebAssemblyCompile); + + // Setup compile + InstallFunc(isolate, wasm_object, "validate", WebAssemblyValidate); + + // Setup Module + Handle<JSFunction> module_constructor = + InstallFunc(isolate, wasm_object, "Module", WebAssemblyModule); + context->set_wasm_module_constructor(*module_constructor); + Handle<JSObject> module_proto = + factory->NewJSObject(module_constructor, TENURED); + i::Handle<i::Map> map = isolate->factory()->NewMap( + i::JS_OBJECT_TYPE, i::JSObject::kHeaderSize + i::kPointerSize); + JSFunction::SetInitialMap(module_constructor, map, module_proto); + JSObject::AddProperty(module_proto, isolate->factory()->constructor_string(), + module_constructor, DONT_ENUM); + + // Setup Instance + Handle<JSFunction> instance_constructor = + InstallFunc(isolate, wasm_object, "Instance", WebAssemblyInstance); + context->set_wasm_instance_constructor(*instance_constructor); + + // Setup Table + Handle<JSFunction> table_constructor = + InstallFunc(isolate, wasm_object, "Table", WebAssemblyTable); + context->set_wasm_table_constructor(*table_constructor); + Handle<JSObject> table_proto = + factory->NewJSObject(table_constructor, TENURED); + map = isolate->factory()->NewMap( + i::JS_OBJECT_TYPE, i::JSObject::kHeaderSize + 2 * i::kPointerSize); + JSFunction::SetInitialMap(table_constructor, map, table_proto); + JSObject::AddProperty(table_proto, isolate->factory()->constructor_string(), + table_constructor, DONT_ENUM); + InstallGetter(isolate, table_proto, "length", WebAssemblyTableGetLength); + InstallFunc(isolate, table_proto, "grow", WebAssemblyTableGrow); + InstallFunc(isolate, table_proto, "get", WebAssemblyTableGet); + InstallFunc(isolate, table_proto, "set", WebAssemblyTableSet); + + // Setup Memory + Handle<JSFunction> memory_constructor = + InstallFunc(isolate, wasm_object, "Memory", WebAssemblyMemory); + context->set_wasm_memory_constructor(*memory_constructor); + Handle<JSObject> memory_proto = + factory->NewJSObject(memory_constructor, TENURED); + map = isolate->factory()->NewMap( + i::JS_OBJECT_TYPE, i::JSObject::kHeaderSize + 2 * i::kPointerSize); + JSFunction::SetInitialMap(memory_constructor, map, memory_proto); + JSObject::AddProperty(memory_proto, isolate->factory()->constructor_string(), + memory_constructor, DONT_ENUM); + InstallFunc(isolate, memory_proto, "grow", WebAssemblyMemoryGrow); + InstallGetter(isolate, memory_proto, "buffer", WebAssemblyMemoryGetBuffer); +} + void WasmJs::Install(Isolate* isolate, Handle<JSGlobalObject> global) { if (!FLAG_expose_wasm && !FLAG_validate_asm) { return; @@ -350,7 +672,7 @@ void WasmJs::Install(Isolate* isolate, Handle<JSGlobalObject> global) { // Setup wasm function map. Handle<Context> context(global->native_context(), isolate); - InstallWasmFunctionMap(isolate, context); + InstallWasmMapsIfNeeded(isolate, context); if (!FLAG_expose_wasm) { return; @@ -383,39 +705,11 @@ void WasmJs::Install(Isolate* isolate, Handle<JSGlobalObject> global) { JSObject::AddProperty(wasm_object, name, value, attributes); } } - - // Create private symbols. - Handle<Symbol> module_sym = isolate->factory()->NewPrivateSymbol(); - Handle<Symbol> instance_sym = isolate->factory()->NewPrivateSymbol(); - context->set_wasm_module_sym(*module_sym); - context->set_wasm_instance_sym(*instance_sym); - - // Bind the WebAssembly object. - Handle<String> name = v8_str(isolate, "WebAssembly"); - Handle<JSFunction> cons = factory->NewFunction(name); - JSFunction::SetInstancePrototype( - cons, Handle<Object>(context->initial_object_prototype(), isolate)); - cons->shared()->set_instance_class_name(*name); - Handle<JSObject> wasm_object = factory->NewJSObject(cons, TENURED); - PropertyAttributes attributes = static_cast<PropertyAttributes>(DONT_ENUM); - JSObject::AddProperty(global, name, wasm_object, attributes); - - // Install static methods on WebAssembly object. - InstallFunc(isolate, wasm_object, "compile", WebAssemblyCompile); - Handle<JSFunction> module_constructor = - InstallFunc(isolate, wasm_object, "Module", WebAssemblyModule); - Handle<JSFunction> instance_constructor = - InstallFunc(isolate, wasm_object, "Instance", WebAssemblyInstance); - i::Handle<i::Map> map = isolate->factory()->NewMap( - i::JS_OBJECT_TYPE, i::JSObject::kHeaderSize + i::kPointerSize); - module_constructor->set_prototype_or_initial_map(*map); - map->SetConstructor(*module_constructor); - - context->set_wasm_module_constructor(*module_constructor); - context->set_wasm_instance_constructor(*instance_constructor); + InstallWasmConstructors(isolate, global, context); } -void WasmJs::InstallWasmFunctionMap(Isolate* isolate, Handle<Context> context) { +void WasmJs::InstallWasmMapsIfNeeded(Isolate* isolate, + Handle<Context> context) { if (!context->get(Context::WASM_FUNCTION_MAP_INDEX)->IsMap()) { // TODO(titzer): Move this to bootstrapper.cc?? // TODO(titzer): Also make one for strict mode functions? diff --git a/deps/v8/src/wasm/wasm-js.h b/deps/v8/src/wasm/wasm-js.h index ded9a1a90b..4f26494624 100644 --- a/deps/v8/src/wasm/wasm-js.h +++ b/deps/v8/src/wasm/wasm-js.h @@ -5,13 +5,8 @@ #ifndef V8_WASM_JS_H_ #define V8_WASM_JS_H_ -#ifndef V8_SHARED #include "src/allocation.h" #include "src/base/hashmap.h" -#else -#include "include/v8.h" -#include "src/base/compiler-specific.h" -#endif // !V8_SHARED namespace v8 { namespace internal { @@ -19,7 +14,19 @@ namespace internal { class WasmJs { public: static void Install(Isolate* isolate, Handle<JSGlobalObject> global_object); - static void InstallWasmFunctionMap(Isolate* isolate, Handle<Context> context); + + V8_EXPORT_PRIVATE static void InstallWasmModuleSymbolIfNeeded( + Isolate* isolate, Handle<JSGlobalObject> global, Handle<Context> context); + + V8_EXPORT_PRIVATE static void InstallWasmMapsIfNeeded( + Isolate* isolate, Handle<Context> context); + static void InstallWasmConstructors(Isolate* isolate, + Handle<JSGlobalObject> global, + Handle<Context> context); + + static Handle<JSObject> CreateWasmMemoryObject(Isolate* isolate, + Handle<JSArrayBuffer> buffer, + bool has_maximum, int maximum); }; } // namespace internal diff --git a/deps/v8/src/wasm/wasm-macro-gen.h b/deps/v8/src/wasm/wasm-macro-gen.h index abd57d505a..fd10a3929a 100644 --- a/deps/v8/src/wasm/wasm-macro-gen.h +++ b/deps/v8/src/wasm/wasm-macro-gen.h @@ -7,7 +7,7 @@ #include "src/wasm/wasm-opcodes.h" -#include "src/zone-containers.h" +#include "src/zone/zone-containers.h" #define U32_LE(v) \ static_cast<byte>(v), static_cast<byte>((v) >> 8), \ @@ -17,17 +17,17 @@ #define WASM_MODULE_HEADER U32_LE(kWasmMagic), U32_LE(kWasmVersion) -#define SIG_INDEX(v) U16_LE(v) -// TODO(binji): make SIG_INDEX match this. #define IMPORT_SIG_INDEX(v) U32V_1(v) #define FUNC_INDEX(v) U32V_1(v) +#define TABLE_INDEX(v) U32V_1(v) #define NO_NAME U32V_1(0) #define NAME_LENGTH(v) U32V_1(v) +#define ENTRY_COUNT(v) U32V_1(v) #define ZERO_ALIGNMENT 0 #define ZERO_OFFSET 0 -#define BR_TARGET(v) U32_LE(v) +#define BR_TARGET(v) U32V_1(v) #define MASK_7 ((1 << 7) - 1) #define MASK_14 ((1 << 14) - 1) @@ -62,36 +62,76 @@ #define ARITY_0 0 #define ARITY_1 1 +#define ARITY_2 2 #define DEPTH_0 0 #define DEPTH_1 1 +#define DEPTH_2 2 +#define ARITY_2 2 + +#define WASM_BLOCK(...) kExprBlock, kLocalVoid, __VA_ARGS__, kExprEnd + +#define WASM_BLOCK_T(t, ...) \ + kExprBlock, static_cast<byte>(WasmOpcodes::LocalTypeCodeFor(t)), \ + __VA_ARGS__, kExprEnd + +#define WASM_BLOCK_TT(t1, t2, ...) \ + kExprBlock, kMultivalBlock, 0, \ + static_cast<byte>(WasmOpcodes::LocalTypeCodeFor(t1)), \ + static_cast<byte>(WasmOpcodes::LocalTypeCodeFor(t2)), __VA_ARGS__, \ + kExprEnd + +#define WASM_BLOCK_I(...) kExprBlock, kLocalI32, __VA_ARGS__, kExprEnd +#define WASM_BLOCK_L(...) kExprBlock, kLocalI64, __VA_ARGS__, kExprEnd +#define WASM_BLOCK_F(...) kExprBlock, kLocalF32, __VA_ARGS__, kExprEnd +#define WASM_BLOCK_D(...) kExprBlock, kLocalF64, __VA_ARGS__, kExprEnd + +#define WASM_INFINITE_LOOP kExprLoop, kLocalVoid, kExprBr, DEPTH_0, kExprEnd + +#define WASM_LOOP(...) kExprLoop, kLocalVoid, __VA_ARGS__, kExprEnd +#define WASM_LOOP_I(...) kExprLoop, kLocalI32, __VA_ARGS__, kExprEnd +#define WASM_LOOP_L(...) kExprLoop, kLocalI64, __VA_ARGS__, kExprEnd +#define WASM_LOOP_F(...) kExprLoop, kLocalF32, __VA_ARGS__, kExprEnd +#define WASM_LOOP_D(...) kExprLoop, kLocalF64, __VA_ARGS__, kExprEnd + +#define WASM_IF(cond, tstmt) cond, kExprIf, kLocalVoid, tstmt, kExprEnd -#define WASM_BLOCK(...) kExprBlock, __VA_ARGS__, kExprEnd -#define WASM_INFINITE_LOOP kExprLoop, kExprBr, ARITY_0, DEPTH_0, kExprEnd -#define WASM_LOOP(...) kExprLoop, __VA_ARGS__, kExprEnd -#define WASM_IF(cond, tstmt) cond, kExprIf, tstmt, kExprEnd #define WASM_IF_ELSE(cond, tstmt, fstmt) \ - cond, kExprIf, tstmt, kExprElse, fstmt, kExprEnd + cond, kExprIf, kLocalVoid, tstmt, kExprElse, fstmt, kExprEnd + +#define WASM_IF_ELSE_T(t, cond, tstmt, fstmt) \ + cond, kExprIf, static_cast<byte>(WasmOpcodes::LocalTypeCodeFor(t)), tstmt, \ + kExprElse, fstmt, kExprEnd + +#define WASM_IF_ELSE_TT(t1, t2, cond, tstmt, fstmt) \ + cond, kExprIf, kMultivalBlock, 0, \ + static_cast<byte>(WasmOpcodes::LocalTypeCodeFor(t1)), \ + static_cast<byte>(WasmOpcodes::LocalTypeCodeFor(t2)), tstmt, kExprElse, \ + fstmt, kExprEnd + +#define WASM_IF_ELSE_I(cond, tstmt, fstmt) \ + cond, kExprIf, kLocalI32, tstmt, kExprElse, fstmt, kExprEnd +#define WASM_IF_ELSE_L(cond, tstmt, fstmt) \ + cond, kExprIf, kLocalI64, tstmt, kExprElse, fstmt, kExprEnd +#define WASM_IF_ELSE_F(cond, tstmt, fstmt) \ + cond, kExprIf, kLocalF32, tstmt, kExprElse, fstmt, kExprEnd +#define WASM_IF_ELSE_D(cond, tstmt, fstmt) \ + cond, kExprIf, kLocalF64, tstmt, kExprElse, fstmt, kExprEnd + #define WASM_SELECT(tval, fval, cond) tval, fval, cond, kExprSelect -#define WASM_BR(depth) kExprBr, ARITY_0, static_cast<byte>(depth) -#define WASM_BR_IF(depth, cond) \ - cond, kExprBrIf, ARITY_0, static_cast<byte>(depth) -#define WASM_BRV(depth, val) val, kExprBr, ARITY_1, static_cast<byte>(depth) -#define WASM_BRV_IF(depth, val, cond) \ - val, cond, kExprBrIf, ARITY_1, static_cast<byte>(depth) -#define WASM_BREAK(depth) kExprBr, ARITY_0, static_cast<byte>(depth + 1) -#define WASM_CONTINUE(depth) kExprBr, ARITY_0, static_cast<byte>(depth) -#define WASM_BREAKV(depth, val) \ - val, kExprBr, ARITY_1, static_cast<byte>(depth + 1) -#define WASM_RETURN0 kExprReturn, ARITY_0 -#define WASM_RETURN1(val) val, kExprReturn, ARITY_1 -#define WASM_RETURNN(count, ...) __VA_ARGS__, kExprReturn, count + +#define WASM_RETURN0 kExprReturn +#define WASM_RETURN1(val) val, kExprReturn +#define WASM_RETURNN(count, ...) __VA_ARGS__, kExprReturn + +#define WASM_BR(depth) kExprBr, static_cast<byte>(depth) +#define WASM_BR_IF(depth, cond) cond, kExprBrIf, static_cast<byte>(depth) +#define WASM_BR_IFD(depth, val, cond) \ + val, cond, kExprBrIf, static_cast<byte>(depth), kExprDrop +#define WASM_CONTINUE(depth) kExprBr, static_cast<byte>(depth) #define WASM_UNREACHABLE kExprUnreachable #define WASM_BR_TABLE(key, count, ...) \ - key, kExprBrTable, ARITY_0, U32V_1(count), __VA_ARGS__ - -#define WASM_BR_TABLEV(val, key, count, ...) \ - val, key, kExprBrTable, ARITY_1, U32V_1(count), __VA_ARGS__ + key, kExprBrTable, U32V_1(count), __VA_ARGS__ #define WASM_CASE(x) static_cast<byte>(x), static_cast<byte>(x >> 8) #define WASM_CASE_BR(x) static_cast<byte>(x), static_cast<byte>(0x80 | (x) >> 8) @@ -343,6 +383,8 @@ class LocalDeclEncoder { static_cast<byte>(bit_cast<uint64_t>(val) >> 56) #define WASM_GET_LOCAL(index) kExprGetLocal, static_cast<byte>(index) #define WASM_SET_LOCAL(index, val) val, kExprSetLocal, static_cast<byte>(index) +#define WASM_TEE_LOCAL(index, val) val, kExprTeeLocal, static_cast<byte>(index) +#define WASM_DROP kExprDrop #define WASM_GET_GLOBAL(index) kExprGetGlobal, static_cast<byte>(index) #define WASM_SET_GLOBAL(index, val) \ val, kExprSetGlobal, static_cast<byte>(index) @@ -374,49 +416,25 @@ class LocalDeclEncoder { v8::internal::wasm::WasmOpcodes::LoadStoreOpcodeOf(type, true)), \ alignment, ZERO_OFFSET -#define WASM_CALL_FUNCTION0(index) \ - kExprCallFunction, 0, static_cast<byte>(index) -#define WASM_CALL_FUNCTION1(index, a) \ - a, kExprCallFunction, 1, static_cast<byte>(index) -#define WASM_CALL_FUNCTION2(index, a, b) \ - a, b, kExprCallFunction, 2, static_cast<byte>(index) -#define WASM_CALL_FUNCTION3(index, a, b, c) \ - a, b, c, kExprCallFunction, 3, static_cast<byte>(index) -#define WASM_CALL_FUNCTION4(index, a, b, c, d) \ - a, b, c, d, kExprCallFunction, 4, static_cast<byte>(index) -#define WASM_CALL_FUNCTION5(index, a, b, c, d, e) \ - kExprCallFunction, 5, static_cast<byte>(index) -#define WASM_CALL_FUNCTIONN(arity, index, ...) \ - __VA_ARGS__, kExprCallFunction, arity, static_cast<byte>(index) - -#define WASM_CALL_IMPORT0(index) kExprCallImport, 0, static_cast<byte>(index) -#define WASM_CALL_IMPORT1(index, a) \ - a, kExprCallImport, 1, static_cast<byte>(index) -#define WASM_CALL_IMPORT2(index, a, b) \ - a, b, kExprCallImport, 2, static_cast<byte>(index) -#define WASM_CALL_IMPORT3(index, a, b, c) \ - a, b, c, kExprCallImport, 3, static_cast<byte>(index) -#define WASM_CALL_IMPORT4(index, a, b, c, d) \ - a, b, c, d, kExprCallImport, 4, static_cast<byte>(index) -#define WASM_CALL_IMPORT5(index, a, b, c, d, e) \ - a, b, c, d, e, kExprCallImport, 5, static_cast<byte>(index) -#define WASM_CALL_IMPORTN(arity, index, ...) \ - __VA_ARGS__, kExprCallImport, U32V_1(arity), static_cast<byte>(index), +#define WASM_CALL_FUNCTION0(index) kExprCallFunction, static_cast<byte>(index) +#define WASM_CALL_FUNCTION(index, ...) \ + __VA_ARGS__, kExprCallFunction, static_cast<byte>(index) +// TODO(titzer): change usages of these macros to put func last. #define WASM_CALL_INDIRECT0(index, func) \ - func, kExprCallIndirect, 0, static_cast<byte>(index) + func, kExprCallIndirect, static_cast<byte>(index) #define WASM_CALL_INDIRECT1(index, func, a) \ - func, a, kExprCallIndirect, 1, static_cast<byte>(index) + a, func, kExprCallIndirect, static_cast<byte>(index) #define WASM_CALL_INDIRECT2(index, func, a, b) \ - func, a, b, kExprCallIndirect, 2, static_cast<byte>(index) + a, b, func, kExprCallIndirect, static_cast<byte>(index) #define WASM_CALL_INDIRECT3(index, func, a, b, c) \ - func, a, b, c, kExprCallIndirect, 3, static_cast<byte>(index) + a, b, c, func, kExprCallIndirect, static_cast<byte>(index) #define WASM_CALL_INDIRECT4(index, func, a, b, c, d) \ - func, a, b, c, d, kExprCallIndirect, 4, static_cast<byte>(index) + a, b, c, d, func, kExprCallIndirect, static_cast<byte>(index) #define WASM_CALL_INDIRECT5(index, func, a, b, c, d, e) \ - func, a, b, c, d, e, kExprCallIndirect, 5, static_cast<byte>(index) + a, b, c, d, e, func, kExprCallIndirect, static_cast<byte>(index) #define WASM_CALL_INDIRECTN(arity, index, func, ...) \ - func, __VA_ARGS__, kExprCallIndirect, U32V_1(arity), static_cast<byte>(index) + __VA_ARGS__, func, kExprCallIndirect, static_cast<byte>(index) #define WASM_NOT(x) x, kExprI32Eqz #define WASM_SEQ(...) __VA_ARGS__ @@ -424,11 +442,16 @@ class LocalDeclEncoder { //------------------------------------------------------------------------------ // Constructs that are composed of multiple bytecodes. //------------------------------------------------------------------------------ -#define WASM_WHILE(x, y) \ - kExprLoop, x, kExprIf, y, kExprBr, ARITY_1, DEPTH_1, kExprEnd, kExprEnd +#define WASM_WHILE(x, y) \ + kExprLoop, kLocalVoid, x, kExprIf, kLocalVoid, y, kExprBr, DEPTH_1, \ + kExprEnd, kExprEnd #define WASM_INC_LOCAL(index) \ kExprGetLocal, static_cast<byte>(index), kExprI8Const, 1, kExprI32Add, \ - kExprSetLocal, static_cast<byte>(index) + kExprTeeLocal, static_cast<byte>(index) +#define WASM_INC_LOCAL_BYV(index, count) \ + kExprGetLocal, static_cast<byte>(index), kExprI8Const, \ + static_cast<byte>(count), kExprI32Add, kExprTeeLocal, \ + static_cast<byte>(index) #define WASM_INC_LOCAL_BY(index, count) \ kExprGetLocal, static_cast<byte>(index), kExprI8Const, \ static_cast<byte>(count), kExprI32Add, kExprSetLocal, \ @@ -580,11 +603,17 @@ class LocalDeclEncoder { #define WASM_I64_REINTERPRET_F64(x) x, kExprI64ReinterpretF64 //------------------------------------------------------------------------------ +// Memory Operations. +//------------------------------------------------------------------------------ +#define WASM_GROW_MEMORY(x) x, kExprGrowMemory +#define WASM_MEMORY_SIZE kExprMemorySize + +//------------------------------------------------------------------------------ // Simd Operations. //------------------------------------------------------------------------------ #define WASM_SIMD_I32x4_SPLAT(x) x, kSimdPrefix, kExprI32x4Splat & 0xff -#define WASM_SIMD_I32x4_EXTRACT_LANE(x, y) \ - x, y, kSimdPrefix, kExprI32x4ExtractLane & 0xff +#define WASM_SIMD_I32x4_EXTRACT_LANE(lane, x) \ + x, kSimdPrefix, kExprI32x4ExtractLane & 0xff, static_cast<byte>(lane) #define SIG_ENTRY_v_v kWasmFunctionTypeForm, 0, 0 #define SIZEOF_SIG_ENTRY_v_v 3 @@ -605,4 +634,13 @@ class LocalDeclEncoder { #define SIZEOF_SIG_ENTRY_x_xx 6 #define SIZEOF_SIG_ENTRY_x_xxx 7 +#define WASM_BRV(depth, val) val, kExprBr, static_cast<byte>(depth) +#define WASM_BRV_IF(depth, val, cond) \ + val, cond, kExprBrIf, static_cast<byte>(depth) +#define WASM_BRV_IFD(depth, val, cond) \ + val, cond, kExprBrIf, static_cast<byte>(depth), kExprDrop +#define WASM_IFB(cond, ...) cond, kExprIf, kLocalVoid, __VA_ARGS__, kExprEnd +#define WASM_BR_TABLEV(val, key, count, ...) \ + val, key, kExprBrTable, U32V_1(count), __VA_ARGS__ + #endif // V8_WASM_MACRO_GEN_H_ diff --git a/deps/v8/src/wasm/encoder.cc b/deps/v8/src/wasm/wasm-module-builder.cc index ef0bddc836..084f5a0c1a 100644 --- a/deps/v8/src/wasm/encoder.cc +++ b/deps/v8/src/wasm/wasm-module-builder.cc @@ -6,12 +6,12 @@ #include "src/handles.h" #include "src/v8.h" -#include "src/zone-containers.h" +#include "src/zone/zone-containers.h" #include "src/wasm/ast-decoder.h" -#include "src/wasm/encoder.h" #include "src/wasm/leb-helper.h" #include "src/wasm/wasm-macro-gen.h" +#include "src/wasm/wasm-module-builder.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-opcodes.h" @@ -30,15 +30,11 @@ namespace v8 { namespace internal { namespace wasm { -// Emit a section name and the size as a padded varint that can be patched +// Emit a section code and the size as a padded varint that can be patched // later. -size_t EmitSection(WasmSection::Code code, ZoneBuffer& buffer) { - // Emit the section name. - const char* name = WasmSection::getName(code); - TRACE("emit section: %s\n", name); - size_t length = WasmSection::getNameLength(code); - buffer.write_size(length); // Section name string size. - buffer.write(reinterpret_cast<const byte*>(name), length); +size_t EmitSection(WasmSectionCode code, ZoneBuffer& buffer) { + // Emit the section code. + buffer.write_u8(code); // Emit a placeholder for the length. return buffer.reserve_u32v(); @@ -55,8 +51,14 @@ WasmFunctionBuilder::WasmFunctionBuilder(WasmModuleBuilder* builder) locals_(builder->zone()), signature_index_(0), exported_(0), + func_index_(static_cast<uint32_t>(builder->functions_.size())), body_(builder->zone()), - name_(builder->zone()) {} + name_(builder->zone()), + i32_temps_(builder->zone()), + i64_temps_(builder->zone()), + f32_temps_(builder->zone()), + f64_temps_(builder->zone()), + direct_calls_(builder->zone()) {} void WasmFunctionBuilder::EmitVarInt(uint32_t val) { byte buffer[8]; @@ -86,6 +88,10 @@ void WasmFunctionBuilder::EmitSetLocal(uint32_t local_index) { EmitWithVarInt(kExprSetLocal, local_index); } +void WasmFunctionBuilder::EmitTeeLocal(uint32_t local_index) { + EmitWithVarInt(kExprTeeLocal, local_index); +} + void WasmFunctionBuilder::EmitCode(const byte* code, uint32_t code_size) { for (size_t i = 0; i < code_size; ++i) { body_.push_back(code[i]); @@ -124,6 +130,15 @@ void WasmFunctionBuilder::EmitI32Const(int32_t value) { } } +void WasmFunctionBuilder::EmitDirectCallIndex(uint32_t index) { + DirectCallIndex call; + call.offset = body_.size(); + call.direct_index = index; + direct_calls_.push_back(call); + byte code[] = {U32V_5(0)}; + EmitCode(code, sizeof(code)); +} + void WasmFunctionBuilder::SetExported() { exported_ = true; } void WasmFunctionBuilder::SetName(const char* name, int name_length) { @@ -139,14 +154,15 @@ void WasmFunctionBuilder::WriteSignature(ZoneBuffer& buffer) const { buffer.write_u32v(signature_index_); } -void WasmFunctionBuilder::WriteExport(ZoneBuffer& buffer, - uint32_t func_index) const { +void WasmFunctionBuilder::WriteExport(ZoneBuffer& buffer) const { if (exported_) { - buffer.write_u32v(func_index); buffer.write_size(name_.size()); if (name_.size() > 0) { buffer.write(reinterpret_cast<const byte*>(&name_[0]), name_.size()); } + buffer.write_u8(kExternalFunction); + buffer.write_u32v(func_index_ + + static_cast<uint32_t>(builder_->imports_.size())); } } @@ -158,24 +174,16 @@ void WasmFunctionBuilder::WriteBody(ZoneBuffer& buffer) const { locals_.Emit(*ptr); (*ptr) += locals_size; // UGLY: manual bump of position pointer if (body_.size() > 0) { + size_t base = buffer.offset(); buffer.write(&body_[0], body_.size()); + for (DirectCallIndex call : direct_calls_) { + buffer.patch_u32v( + base + call.offset, + call.direct_index + static_cast<uint32_t>(builder_->imports_.size())); + } } } -WasmDataSegmentEncoder::WasmDataSegmentEncoder(Zone* zone, const byte* data, - uint32_t size, uint32_t dest) - : data_(zone), dest_(dest) { - for (size_t i = 0; i < size; ++i) { - data_.push_back(data[i]); - } -} - -void WasmDataSegmentEncoder::Write(ZoneBuffer& buffer) const { - buffer.write_u32v(dest_); - buffer.write_u32v(static_cast<uint32_t>(data_.size())); - buffer.write(&data_[0], data_.size()); -} - WasmModuleBuilder::WasmModuleBuilder(Zone* zone) : zone_(zone), signatures_(zone), @@ -187,23 +195,22 @@ WasmModuleBuilder::WasmModuleBuilder(Zone* zone) signature_map_(zone), start_function_index_(-1) {} -uint32_t WasmModuleBuilder::AddFunction() { +WasmFunctionBuilder* WasmModuleBuilder::AddFunction(FunctionSig* sig) { functions_.push_back(new (zone_) WasmFunctionBuilder(this)); - return static_cast<uint32_t>(functions_.size() - 1); + // Add the signature if one was provided here. + if (sig) functions_.back()->SetSignature(sig); + return functions_.back(); } -WasmFunctionBuilder* WasmModuleBuilder::FunctionAt(size_t index) { - if (functions_.size() > index) { - return functions_.at(index); - } else { - return nullptr; +void WasmModuleBuilder::AddDataSegment(const byte* data, uint32_t size, + uint32_t dest) { + data_segments_.push_back({ZoneVector<byte>(zone()), dest}); + ZoneVector<byte>& vec = data_segments_.back().data; + for (uint32_t i = 0; i < size; i++) { + vec.push_back(data[i]); } } -void WasmModuleBuilder::AddDataSegment(WasmDataSegmentEncoder* data) { - data_segments_.push_back(data); -} - bool WasmModuleBuilder::CompareFunctionSigs::operator()(FunctionSig* a, FunctionSig* b) const { if (a->return_count() < b->return_count()) return true; @@ -243,12 +250,13 @@ uint32_t WasmModuleBuilder::AddImport(const char* name, int name_length, return static_cast<uint32_t>(imports_.size() - 1); } -void WasmModuleBuilder::MarkStartFunction(uint32_t index) { - start_function_index_ = index; +void WasmModuleBuilder::MarkStartFunction(WasmFunctionBuilder* function) { + start_function_index_ = function->func_index(); } -uint32_t WasmModuleBuilder::AddGlobal(LocalType type, bool exported) { - globals_.push_back(std::make_pair(type, exported)); +uint32_t WasmModuleBuilder::AddGlobal(LocalType type, bool exported, + bool mutability) { + globals_.push_back({type, exported, mutability}); return static_cast<uint32_t>(globals_.size() - 1); } @@ -262,7 +270,7 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { // == Emit signatures ======================================================== if (signatures_.size() > 0) { - size_t start = EmitSection(WasmSection::Code::Signatures, buffer); + size_t start = EmitSection(kTypeSectionCode, buffer); buffer.write_size(signatures_.size()); for (FunctionSig* sig : signatures_) { @@ -279,86 +287,128 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { FixupSection(buffer, start); } - // == Emit globals =========================================================== - if (globals_.size() > 0) { - size_t start = EmitSection(WasmSection::Code::Globals, buffer); - buffer.write_size(globals_.size()); - - for (auto global : globals_) { - buffer.write_u32v(0); // Length of the global name. - buffer.write_u8(WasmOpcodes::LocalTypeCodeFor(global.first)); - buffer.write_u8(global.second); - } - FixupSection(buffer, start); - } - // == Emit imports =========================================================== if (imports_.size() > 0) { - size_t start = EmitSection(WasmSection::Code::ImportTable, buffer); + size_t start = EmitSection(kImportSectionCode, buffer); buffer.write_size(imports_.size()); for (auto import : imports_) { - buffer.write_u32v(import.sig_index); - buffer.write_u32v(import.name_length); - buffer.write(reinterpret_cast<const byte*>(import.name), + buffer.write_u32v(import.name_length); // module name length + buffer.write(reinterpret_cast<const byte*>(import.name), // module name import.name_length); - buffer.write_u32v(0); + buffer.write_u32v(0); // field name length + buffer.write_u8(kExternalFunction); + buffer.write_u32v(import.sig_index); } FixupSection(buffer, start); } // == Emit function signatures =============================================== + bool has_names = false; if (functions_.size() > 0) { - size_t start = EmitSection(WasmSection::Code::FunctionSignatures, buffer); + size_t start = EmitSection(kFunctionSectionCode, buffer); buffer.write_size(functions_.size()); for (auto function : functions_) { function->WriteSignature(buffer); if (function->exported()) exports++; + if (function->name_.size() > 0) has_names = true; } FixupSection(buffer, start); } // == emit function table ==================================================== if (indirect_functions_.size() > 0) { - size_t start = EmitSection(WasmSection::Code::FunctionTable, buffer); + size_t start = EmitSection(kTableSectionCode, buffer); + buffer.write_u8(1); // table count + buffer.write_u8(kWasmAnyFunctionTypeForm); + buffer.write_u8(kResizableMaximumFlag); + buffer.write_size(indirect_functions_.size()); buffer.write_size(indirect_functions_.size()); - - for (auto index : indirect_functions_) { - buffer.write_u32v(index); - } FixupSection(buffer, start); } // == emit memory declaration ================================================ { - size_t start = EmitSection(WasmSection::Code::Memory, buffer); + size_t start = EmitSection(kMemorySectionCode, buffer); + buffer.write_u8(1); // memory count + buffer.write_u32v(kResizableMaximumFlag); buffer.write_u32v(16); // min memory size buffer.write_u32v(16); // max memory size - buffer.write_u8(0); // memory export - static_assert(kDeclMemorySize == 3, "memory size must match emit above"); + FixupSection(buffer, start); + } + + // == Emit globals =========================================================== + if (globals_.size() > 0) { + size_t start = EmitSection(kGlobalSectionCode, buffer); + buffer.write_size(globals_.size()); + + for (auto global : globals_) { + buffer.write_u8(WasmOpcodes::LocalTypeCodeFor(global.type)); + buffer.write_u8(global.mutability ? 1 : 0); + switch (global.type) { + case kAstI32: { + static const byte code[] = {WASM_I32V_1(0)}; + buffer.write(code, sizeof(code)); + break; + } + case kAstF32: { + static const byte code[] = {WASM_F32(0)}; + buffer.write(code, sizeof(code)); + break; + } + case kAstI64: { + static const byte code[] = {WASM_I64V_1(0)}; + buffer.write(code, sizeof(code)); + break; + } + case kAstF64: { + static const byte code[] = {WASM_F64(0.0)}; + buffer.write(code, sizeof(code)); + break; + } + default: + UNREACHABLE(); + } + buffer.write_u8(kExprEnd); + } FixupSection(buffer, start); } // == emit exports =========================================================== if (exports > 0) { - size_t start = EmitSection(WasmSection::Code::ExportTable, buffer); + size_t start = EmitSection(kExportSectionCode, buffer); buffer.write_u32v(exports); - uint32_t index = 0; - for (auto function : functions_) { - function->WriteExport(buffer, index++); - } + for (auto function : functions_) function->WriteExport(buffer); FixupSection(buffer, start); } // == emit start function index ============================================== if (start_function_index_ >= 0) { - size_t start = EmitSection(WasmSection::Code::StartFunction, buffer); - buffer.write_u32v(start_function_index_); + size_t start = EmitSection(kStartSectionCode, buffer); + buffer.write_u32v(start_function_index_ + + static_cast<uint32_t>(imports_.size())); + FixupSection(buffer, start); + } + + // == emit function table elements =========================================== + if (indirect_functions_.size() > 0) { + size_t start = EmitSection(kElementSectionCode, buffer); + buffer.write_u8(1); // count of entries + buffer.write_u8(0); // table index + buffer.write_u8(kExprI32Const); // offset + buffer.write_u32v(0); + buffer.write_u8(kExprEnd); + buffer.write_size(indirect_functions_.size()); // element count + + for (auto index : indirect_functions_) { + buffer.write_u32v(index + static_cast<uint32_t>(imports_.size())); + } + FixupSection(buffer, start); } // == emit code ============================================================== if (functions_.size() > 0) { - size_t start = EmitSection(WasmSection::Code::FunctionBodies, buffer); + size_t start = EmitSection(kCodeSectionCode, buffer); buffer.write_size(functions_.size()); for (auto function : functions_) { function->WriteBody(buffer); @@ -368,11 +418,38 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { // == emit data segments ===================================================== if (data_segments_.size() > 0) { - size_t start = EmitSection(WasmSection::Code::DataSegments, buffer); + size_t start = EmitSection(kDataSectionCode, buffer); buffer.write_size(data_segments_.size()); for (auto segment : data_segments_) { - segment->Write(buffer); + buffer.write_u8(0); // linear memory segment + buffer.write_u8(kExprI32Const); // initializer expression for dest + buffer.write_u32v(segment.dest); + buffer.write_u8(kExprEnd); + buffer.write_u32v(static_cast<uint32_t>(segment.data.size())); + buffer.write(&segment.data[0], segment.data.size()); + } + FixupSection(buffer, start); + } + + // == Emit names ============================================================= + if (has_names) { + // Emit the section code. + buffer.write_u8(kUnknownSectionCode); + // Emit a placeholder for the length. + size_t start = buffer.reserve_u32v(); + // Emit the section string. + buffer.write_size(4); + buffer.write(reinterpret_cast<const byte*>("name"), 4); + // Emit the names. + buffer.write_size(functions_.size()); + for (auto function : functions_) { + buffer.write_size(function->name_.size()); + if (function->name_.size() > 0) { + buffer.write(reinterpret_cast<const byte*>(&function->name_[0]), + function->name_.size()); + } + buffer.write_u8(0); } FixupSection(buffer, start); } diff --git a/deps/v8/src/wasm/encoder.h b/deps/v8/src/wasm/wasm-module-builder.h index eb8aa64abd..dcaf6c8e86 100644 --- a/deps/v8/src/wasm/encoder.h +++ b/deps/v8/src/wasm/wasm-module-builder.h @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef V8_WASM_ENCODER_H_ -#define V8_WASM_ENCODER_H_ +#ifndef V8_WASM_WASM_MODULE_BUILDER_H_ +#define V8_WASM_WASM_MODULE_BUILDER_H_ #include "src/signature.h" -#include "src/zone-containers.h" +#include "src/zone/zone-containers.h" #include "src/wasm/leb-helper.h" #include "src/wasm/wasm-macro-gen.h" @@ -90,13 +90,14 @@ class ZoneBuffer : public ZoneObject { void EnsureSpace(size_t size) { if ((pos_ + size) > end_) { - size_t new_size = 4096 + (end_ - buffer_) * 3; + size_t new_size = 4096 + size + (end_ - buffer_) * 3; byte* new_buffer = reinterpret_cast<byte*>(zone_->New(new_size)); memcpy(new_buffer, buffer_, (pos_ - buffer_)); pos_ = new_buffer + (pos_ - buffer_); buffer_ = new_buffer; end_ = new_buffer + new_size; } + DCHECK(pos_ + size <= end_); } byte** pos_ptr() { return &pos_; } @@ -110,7 +111,7 @@ class ZoneBuffer : public ZoneObject { class WasmModuleBuilder; -class WasmFunctionBuilder : public ZoneObject { +class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject { public: // Building methods. void SetSignature(FunctionSig* sig); @@ -120,61 +121,102 @@ class WasmFunctionBuilder : public ZoneObject { void Emit(WasmOpcode opcode); void EmitGetLocal(uint32_t index); void EmitSetLocal(uint32_t index); + void EmitTeeLocal(uint32_t index); void EmitI32Const(int32_t val); void EmitWithU8(WasmOpcode opcode, const byte immediate); void EmitWithU8U8(WasmOpcode opcode, const byte imm1, const byte imm2); void EmitWithVarInt(WasmOpcode opcode, uint32_t immediate); + void EmitDirectCallIndex(uint32_t index); void SetExported(); void SetName(const char* name, int name_length); - bool exported() { return exported_; } - // Writing methods. void WriteSignature(ZoneBuffer& buffer) const; - void WriteExport(ZoneBuffer& buffer, uint32_t func_index) const; + void WriteExport(ZoneBuffer& buffer) const; void WriteBody(ZoneBuffer& buffer) const; + bool exported() { return exported_; } + uint32_t func_index() { return func_index_; } + FunctionSig* signature(); + private: explicit WasmFunctionBuilder(WasmModuleBuilder* builder); friend class WasmModuleBuilder; + friend class WasmTemporary; + + struct DirectCallIndex { + size_t offset; + uint32_t direct_index; + }; + WasmModuleBuilder* builder_; LocalDeclEncoder locals_; uint32_t signature_index_; bool exported_; + uint32_t func_index_; ZoneVector<uint8_t> body_; ZoneVector<char> name_; + ZoneVector<uint32_t> i32_temps_; + ZoneVector<uint32_t> i64_temps_; + ZoneVector<uint32_t> f32_temps_; + ZoneVector<uint32_t> f64_temps_; + ZoneVector<DirectCallIndex> direct_calls_; }; -// TODO(titzer): kill! -class WasmDataSegmentEncoder : public ZoneObject { +class WasmTemporary { public: - WasmDataSegmentEncoder(Zone* zone, const byte* data, uint32_t size, - uint32_t dest); - void Write(ZoneBuffer& buffer) const; + WasmTemporary(WasmFunctionBuilder* builder, LocalType type) { + switch (type) { + case kAstI32: + temporary_ = &builder->i32_temps_; + break; + case kAstI64: + temporary_ = &builder->i64_temps_; + break; + case kAstF32: + temporary_ = &builder->f32_temps_; + break; + case kAstF64: + temporary_ = &builder->f64_temps_; + break; + default: + UNREACHABLE(); + temporary_ = nullptr; + } + if (temporary_->size() == 0) { + // Allocate a new temporary. + index_ = builder->AddLocal(type); + } else { + // Reuse a previous temporary. + index_ = temporary_->back(); + temporary_->pop_back(); + } + } + ~WasmTemporary() { + temporary_->push_back(index_); // return the temporary to the list. + } + uint32_t index() { return index_; } private: - ZoneVector<byte> data_; - uint32_t dest_; -}; - -struct WasmFunctionImport { - uint32_t sig_index; - const char* name; - int name_length; + ZoneVector<uint32_t>* temporary_; + uint32_t index_; }; -class WasmModuleBuilder : public ZoneObject { +class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { public: explicit WasmModuleBuilder(Zone* zone); // Building methods. - uint32_t AddFunction(); - uint32_t AddGlobal(LocalType type, bool exported); - WasmFunctionBuilder* FunctionAt(size_t index); - void AddDataSegment(WasmDataSegmentEncoder* data); + uint32_t AddImport(const char* name, int name_length, FunctionSig* sig); + void SetImportName(uint32_t index, const char* name, int name_length) { + imports_[index].name = name; + imports_[index].name_length = name_length; + } + WasmFunctionBuilder* AddFunction(FunctionSig* sig = nullptr); + uint32_t AddGlobal(LocalType type, bool exported, bool mutability = true); + void AddDataSegment(const byte* data, uint32_t size, uint32_t dest); uint32_t AddSignature(FunctionSig* sig); void AddIndirectFunction(uint32_t index); - void MarkStartFunction(uint32_t index); - uint32_t AddImport(const char* name, int name_length, FunctionSig* sig); + void MarkStartFunction(WasmFunctionBuilder* builder); // Writing methods. void WriteTo(ZoneBuffer& buffer) const; @@ -186,20 +228,44 @@ class WasmModuleBuilder : public ZoneObject { Zone* zone() { return zone_; } + FunctionSig* GetSignature(uint32_t index) { return signatures_[index]; } + private: + struct WasmFunctionImport { + uint32_t sig_index; + const char* name; + int name_length; + }; + + struct WasmGlobal { + LocalType type; + bool exported; + bool mutability; + }; + + struct WasmDataSegment { + ZoneVector<byte> data; + uint32_t dest; + }; + + friend class WasmFunctionBuilder; Zone* zone_; ZoneVector<FunctionSig*> signatures_; ZoneVector<WasmFunctionImport> imports_; ZoneVector<WasmFunctionBuilder*> functions_; - ZoneVector<WasmDataSegmentEncoder*> data_segments_; + ZoneVector<WasmDataSegment> data_segments_; ZoneVector<uint32_t> indirect_functions_; - ZoneVector<std::pair<LocalType, bool>> globals_; + ZoneVector<WasmGlobal> globals_; SignatureMap signature_map_; int start_function_index_; }; +inline FunctionSig* WasmFunctionBuilder::signature() { + return builder_->signatures_[signature_index_]; +} + } // namespace wasm } // namespace internal } // namespace v8 -#endif // V8_WASM_ENCODER_H_ +#endif // V8_WASM_WASM_MODULE_BUILDER_H_ diff --git a/deps/v8/src/wasm/wasm-module.cc b/deps/v8/src/wasm/wasm-module.cc index 94bf998e53..f4cf505f5a 100644 --- a/deps/v8/src/wasm/wasm-module.cc +++ b/deps/v8/src/wasm/wasm-module.cc @@ -18,6 +18,7 @@ #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-debug.h" #include "src/wasm/wasm-function-name-table.h" +#include "src/wasm/wasm-js.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-result.h" @@ -27,179 +28,56 @@ namespace v8 { namespace internal { namespace wasm { -enum JSFunctionExportInternalField { - kInternalModuleInstance, - kInternalArity, - kInternalSignature -}; +#define TRACE(...) \ + do { \ + if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \ + } while (false) -static const int kPlaceholderMarker = 1000000000; +#define TRACE_CHAIN(instance) \ + do { \ + instance->PrintInstancesChain(); \ + } while (false) -static const char* wasmSections[] = { -#define F(enumerator, order, string) string, - FOR_EACH_WASM_SECTION_TYPE(F) -#undef F - "<unknown>" // entry for "Max" -}; +namespace { -static uint8_t wasmSectionsLengths[]{ -#define F(enumerator, order, string) sizeof(string) - 1, - FOR_EACH_WASM_SECTION_TYPE(F) -#undef F - 9 // entry for "Max" -}; +static const int kPlaceholderMarker = 1000000000; -static uint8_t wasmSectionsOrders[]{ -#define F(enumerator, order, string) order, - FOR_EACH_WASM_SECTION_TYPE(F) -#undef F - 0 // entry for "Max" +enum JSFunctionExportInternalField { + kInternalModuleInstance, + kInternalArity, + kInternalSignature }; -static_assert(sizeof(wasmSections) / sizeof(wasmSections[0]) == - (size_t)WasmSection::Code::Max + 1, - "expected enum WasmSection::Code to be monotonic from 0"); - -WasmSection::Code WasmSection::begin() { return (WasmSection::Code)0; } -WasmSection::Code WasmSection::end() { return WasmSection::Code::Max; } -WasmSection::Code WasmSection::next(WasmSection::Code code) { - return (WasmSection::Code)(1 + (uint32_t)code); -} - -const char* WasmSection::getName(WasmSection::Code code) { - return wasmSections[(size_t)code]; -} - -size_t WasmSection::getNameLength(WasmSection::Code code) { - return wasmSectionsLengths[(size_t)code]; -} - -int WasmSection::getOrder(WasmSection::Code code) { - return wasmSectionsOrders[(size_t)code]; -} - -WasmSection::Code WasmSection::lookup(const byte* string, uint32_t length) { - // TODO(jfb) Linear search, it may be better to do a common-prefix search. - for (Code i = begin(); i != end(); i = next(i)) { - if (getNameLength(i) == length && 0 == memcmp(getName(i), string, length)) { - return i; - } - } - return Code::Max; -} - -std::ostream& operator<<(std::ostream& os, const WasmModule& module) { - os << "WASM module with "; - os << (module.min_mem_pages * module.kPageSize) << " min mem"; - os << (module.max_mem_pages * module.kPageSize) << " max mem"; - os << module.functions.size() << " functions"; - os << module.functions.size() << " globals"; - os << module.functions.size() << " data segments"; - return os; -} - -std::ostream& operator<<(std::ostream& os, const WasmFunction& function) { - os << "WASM function with signature " << *function.sig; - - os << " code bytes: " - << (function.code_end_offset - function.code_start_offset); - return os; -} - -std::ostream& operator<<(std::ostream& os, const WasmFunctionName& pair) { - os << "#" << pair.function_->func_index << ":"; - if (pair.function_->name_offset > 0) { - if (pair.module_) { - WasmName name = pair.module_->GetName(pair.function_->name_offset, - pair.function_->name_length); - os.write(name.start(), name.length()); - } else { - os << "+" << pair.function_->func_index; - } - } else { - os << "?"; - } - return os; -} - -Handle<JSFunction> WrapExportCodeAsJSFunction( - Isolate* isolate, Handle<Code> export_code, Handle<String> name, int arity, - MaybeHandle<ByteArray> maybe_signature, Handle<JSObject> module_instance) { - Handle<SharedFunctionInfo> shared = - isolate->factory()->NewSharedFunctionInfo(name, export_code, false); - shared->set_length(arity); - shared->set_internal_formal_parameter_count(arity); - Handle<JSFunction> function = isolate->factory()->NewFunction( - isolate->wasm_function_map(), name, export_code); - function->set_shared(*shared); - - function->SetInternalField(kInternalModuleInstance, *module_instance); - // add another Internal Field as the function arity - function->SetInternalField(kInternalArity, Smi::FromInt(arity)); - // add another Internal Field as the signature of the foreign function - Handle<ByteArray> signature; - if (maybe_signature.ToHandle(&signature)) { - function->SetInternalField(kInternalSignature, *signature); - } - return function; -} - -namespace { // Internal constants for the layout of the module object. -const int kWasmModuleFunctionTable = 0; -const int kWasmModuleCodeTable = 1; -const int kWasmMemArrayBuffer = 2; -const int kWasmGlobalsArrayBuffer = 3; -// TODO(clemensh): Remove function name array, extract names from module bytes. -const int kWasmFunctionNamesArray = 4; -const int kWasmModuleBytesString = 5; -const int kWasmDebugInfo = 6; -const int kWasmModuleInternalFieldCount = 7; - -// TODO(mtrofin): Unnecessary once we stop using JS Heap for wasm code. -// For now, each field is expected to have the type commented by its side. -// The elements typed as "maybe" are optional. The others are mandatory. Since -// the compiled module is either obtained from the current v8 instance, or from -// a snapshot produced by a compatible (==identical) v8 instance, we simply -// fail at instantiation time, in the face of invalid data. -enum CompiledWasmObjectFields { - kFunctions, // FixedArray of Code - kImportData, // maybe FixedArray of FixedArray respecting the - // WasmImportMetadata structure. - kExports, // maybe FixedArray of FixedArray of WasmExportMetadata - // structure - kStartupFunction, // maybe FixedArray of WasmExportMetadata structure - kTableOfIndirectFunctionTables, // maybe FixedArray of FixedArray of - // WasmIndirectFunctionTableMetadata - kModuleBytes, // maybe String - kFunctionNameTable, // maybe ByteArray - kMinRequiredMemory, // Smi. an uint32_t - // The following 2 are either together present or absent: - kDataSegmentsInfo, // maybe FixedArray of FixedArray respecting the - // WasmSegmentInfo structure - kDataSegments, // maybe ByteArray. - - kGlobalsSize, // Smi. an uint32_t - kExportMem, // Smi. bool - kOrigin, // Smi. ModuleOrigin - kCompiledWasmObjectTableSize // Sentinel value. +enum WasmInstanceObjectFields { + kWasmCompiledModule = 0, + kWasmModuleFunctionTable, + kWasmModuleCodeTable, + kWasmMemArrayBuffer, + kWasmGlobalsArrayBuffer, + // TODO(clemensh): Remove function name array, extract names from module + // bytes. + kWasmFunctionNamesArray, + kWasmModuleBytesString, + kWasmDebugInfo, + kWasmNumImportedFunctions, + kWasmModuleInternalFieldCount }; -enum WasmImportMetadata { - kModuleName, // String - kFunctionName, // maybe String - kOutputCount, // Smi. an uint32_t - kSignature, // ByteArray. A copy of the data in FunctionSig - kWasmImportDataTableSize // Sentinel value. +enum WasmImportData { + kModuleName, // String + kFunctionName, // maybe String + kOutputCount, // Smi. an uint32_t + kSignature, // ByteArray. A copy of the data in FunctionSig + kWasmImportDataSize // Sentinel value. }; -enum WasmExportMetadata { - kExportCode, // Code - kExportName, // String - kExportArity, // Smi, an int - kExportedFunctionIndex, // Smi, an uint32_t - kExportedSignature, // ByteArray. A copy of the data in FunctionSig - kWasmExportMetadataTableSize // Sentinel value. +enum WasmExportData { + kExportName, // String + kExportArity, // Smi, an int + kExportedFunctionIndex, // Smi, an uint32_t + kExportedSignature, // ByteArray. A copy of the data in FunctionSig + kWasmExportDataSize // Sentinel value. }; enum WasmSegmentInfo { @@ -208,31 +86,26 @@ enum WasmSegmentInfo { kWasmSegmentInfoSize // Sentinel value. }; -enum WasmIndirectFunctionTableMetadata { - kSize, // Smi. an uint32_t - kTable, // FixedArray of indirect function table - kWasmIndirectFunctionTableMetadataSize // Sentinel value. +enum WasmIndirectFunctionTableData { + kSize, // Smi. an uint32_t + kTable, // FixedArray of indirect function table + kWasmIndirectFunctionTableDataSize // Sentinel value. }; uint32_t GetMinModuleMemSize(const WasmModule* module) { return WasmModule::kPageSize * module->min_mem_pages; } -void LoadDataSegments(Handle<FixedArray> compiled_module, Address mem_addr, - size_t mem_size) { - Isolate* isolate = compiled_module->GetIsolate(); - MaybeHandle<ByteArray> maybe_data = - compiled_module->GetValue<ByteArray>(isolate, kDataSegments); - MaybeHandle<FixedArray> maybe_segments = - compiled_module->GetValue<FixedArray>(isolate, kDataSegmentsInfo); +void LoadDataSegments(Handle<WasmCompiledModule> compiled_module, + Address mem_addr, size_t mem_size) { + CHECK(compiled_module->has_data_segments() == + compiled_module->has_data_segments_info()); - // We either have both or neither. - CHECK(maybe_data.is_null() == maybe_segments.is_null()); // If we have neither, we're done. - if (maybe_data.is_null()) return; + if (!compiled_module->has_data_segments()) return; - Handle<ByteArray> data = maybe_data.ToHandleChecked(); - Handle<FixedArray> segments = maybe_segments.ToHandleChecked(); + Handle<ByteArray> data = compiled_module->data_segments(); + Handle<FixedArray> segments = compiled_module->data_segments_info(); uint32_t last_extraction_pos = 0; for (int i = 0; i < segments->length(); ++i) { @@ -250,12 +123,11 @@ void LoadDataSegments(Handle<FixedArray> compiled_module, Address mem_addr, } void SaveDataSegmentInfo(Factory* factory, const WasmModule* module, - Handle<FixedArray> compiled_module) { + Handle<WasmCompiledModule> compiled_module) { Handle<FixedArray> segments = factory->NewFixedArray( static_cast<int>(module->data_segments.size()), TENURED); uint32_t data_size = 0; for (const WasmDataSegment& segment : module->data_segments) { - if (!segment.init) continue; if (segment.source_size == 0) continue; data_size += segment.source_size; } @@ -264,11 +136,12 @@ void SaveDataSegmentInfo(Factory* factory, const WasmModule* module, uint32_t last_insertion_pos = 0; for (uint32_t i = 0; i < module->data_segments.size(); ++i) { const WasmDataSegment& segment = module->data_segments[i]; - if (!segment.init) continue; if (segment.source_size == 0) continue; Handle<ByteArray> js_segment = factory->NewByteArray(kWasmSegmentInfoSize * sizeof(uint32_t), TENURED); - js_segment->set_int(kDestAddr, segment.dest_addr); + // TODO(titzer): add support for global offsets for dest_addr + CHECK_EQ(WasmInitExpr::kI32Const, segment.dest_addr.kind); + js_segment->set_int(kDestAddr, segment.dest_addr.val.i32_const); js_segment->set_int(kSourceSize, segment.source_size); segments->set(i, *js_segment); data->copy_in(last_insertion_pos, @@ -276,8 +149,8 @@ void SaveDataSegmentInfo(Factory* factory, const WasmModule* module, segment.source_size); last_insertion_pos += segment.source_size; } - compiled_module->set(kDataSegmentsInfo, *segments); - compiled_module->set(kDataSegments, *data); + compiled_module->set_data_segments_info(segments); + compiled_module->set_data_segments(data); } void PatchFunctionTable(Handle<Code> code, @@ -315,8 +188,9 @@ Handle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size) { return buffer; } -void RelocateInstanceCode(Handle<JSObject> instance, Address start, - uint32_t prev_size, uint32_t new_size) { +void RelocateInstanceCode(Handle<JSObject> instance, Address old_start, + Address start, uint32_t prev_size, + uint32_t new_size) { Handle<FixedArray> functions = Handle<FixedArray>( FixedArray::cast(instance->GetInternalField(kWasmModuleCodeTable))); for (int i = 0; i < functions->length(); ++i) { @@ -325,7 +199,7 @@ void RelocateInstanceCode(Handle<JSObject> instance, Address start, int mask = (1 << RelocInfo::WASM_MEMORY_REFERENCE) | (1 << RelocInfo::WASM_MEMORY_SIZE_REFERENCE); for (RelocIterator it(*function, mask); !it.done(); it.next()) { - it.rinfo()->update_wasm_memory_reference(nullptr, start, prev_size, + it.rinfo()->update_wasm_memory_reference(old_start, start, prev_size, new_size); } } @@ -347,7 +221,8 @@ Handle<JSArrayBuffer> AllocateMemory(ErrorThrower* thrower, Isolate* isolate, return mem_buffer; } -void RelocateGlobals(Handle<JSObject> instance, Address globals_start) { +void RelocateGlobals(Handle<JSObject> instance, Address old_start, + Address globals_start) { Handle<FixedArray> functions = Handle<FixedArray>( FixedArray::cast(instance->GetInternalField(kWasmModuleCodeTable))); uint32_t function_count = static_cast<uint32_t>(functions->length()); @@ -356,7 +231,7 @@ void RelocateGlobals(Handle<JSObject> instance, Address globals_start) { AllowDeferredHandleDereference embedding_raw_address; int mask = 1 << RelocInfo::WASM_GLOBAL_REFERENCE; for (RelocIterator it(*function, mask); !it.done(); it.next()) { - it.rinfo()->update_wasm_global_reference(nullptr, globals_start); + it.rinfo()->update_wasm_global_reference(old_start, globals_start); } } } @@ -375,64 +250,41 @@ Handle<Code> CreatePlaceholder(Factory* factory, uint32_t index, return code; } -// TODO(mtrofin): remove when we stop relying on placeholders. -void InitializePlaceholders(Factory* factory, - std::vector<Handle<Code>>* placeholders, - size_t size) { - DCHECK(placeholders->empty()); - placeholders->reserve(size); - - for (uint32_t i = 0; i < size; ++i) { - placeholders->push_back(CreatePlaceholder(factory, i, Code::WASM_FUNCTION)); - } -} - bool LinkFunction(Handle<Code> unlinked, - const std::vector<Handle<Code>>& code_targets, - Code::Kind kind) { + std::vector<Handle<Code>>& code_table) { bool modified = false; - int mode_mask = RelocInfo::kCodeTargetMask; + int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (RelocIterator it(*unlinked, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsCodeTarget(mode)) { Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); - if (target->kind() == kind && - target->constant_pool_offset() >= kPlaceholderMarker) { - // Patch direct calls to placeholder code objects. - uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; - CHECK(index < code_targets.size()); - Handle<Code> new_target = code_targets[index]; - if (target != *new_target) { - it.rinfo()->set_target_address(new_target->instruction_start(), - UPDATE_WRITE_BARRIER, - SKIP_ICACHE_FLUSH); - modified = true; + if (target->constant_pool_offset() < kPlaceholderMarker) continue; + switch (target->kind()) { + case Code::WASM_FUNCTION: // fall through + case Code::WASM_TO_JS_FUNCTION: // fall through + case Code::JS_TO_WASM_FUNCTION: { + // Patch direct calls to placeholder code objects. + uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; + Handle<Code> new_target = code_table[index]; + if (target != *new_target) { + it.rinfo()->set_target_address(new_target->instruction_start(), + UPDATE_WRITE_BARRIER, + SKIP_ICACHE_FLUSH); + modified = true; + } + break; } + default: + break; } } } return modified; } -void LinkModuleFunctions(Isolate* isolate, - std::vector<Handle<Code>>& functions) { - for (size_t i = 0; i < functions.size(); ++i) { - Handle<Code> code = functions[i]; - LinkFunction(code, functions, Code::WASM_FUNCTION); - } -} - -void LinkImports(Isolate* isolate, std::vector<Handle<Code>>& functions, - const std::vector<Handle<Code>>& imports) { - for (uint32_t i = 0; i < functions.size(); ++i) { - Handle<Code> code = functions[i]; - LinkFunction(code, imports, Code::WASM_TO_JS_FUNCTION); - } -} - -void FlushAssemblyCache(Isolate* isolate, Handle<FixedArray> functions) { +void FlushICache(Isolate* isolate, Handle<FixedArray> functions) { for (int i = 0; i < functions->length(); ++i) { Handle<Code> code = functions->GetValueChecked<Code>(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), @@ -440,84 +292,6 @@ void FlushAssemblyCache(Isolate* isolate, Handle<FixedArray> functions) { } } -} // namespace - -WasmModule::WasmModule(byte* module_start) - : module_start(module_start), - module_end(nullptr), - min_mem_pages(0), - max_mem_pages(0), - mem_export(false), - mem_external(false), - start_function_index(-1), - origin(kWasmOrigin), - globals_size(0), - pending_tasks(new base::Semaphore(0)) {} - -static MaybeHandle<JSFunction> ReportFFIError( - ErrorThrower& thrower, const char* error, uint32_t index, - Handle<String> module_name, MaybeHandle<String> function_name) { - Handle<String> function_name_handle; - if (function_name.ToHandle(&function_name_handle)) { - thrower.Error("Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", - index, module_name->length(), module_name->ToCString().get(), - function_name_handle->length(), - function_name_handle->ToCString().get(), error); - } else { - thrower.Error("Import #%d module=\"%.*s\" error: %s", index, - module_name->length(), module_name->ToCString().get(), error); - } - thrower.Error("Import "); - return MaybeHandle<JSFunction>(); -} - -static MaybeHandle<JSReceiver> LookupFunction( - ErrorThrower& thrower, Factory* factory, Handle<JSReceiver> ffi, - uint32_t index, Handle<String> module_name, - MaybeHandle<String> function_name) { - if (ffi.is_null()) { - return ReportFFIError(thrower, "FFI is not an object", index, module_name, - function_name); - } - - // Look up the module first. - MaybeHandle<Object> result = Object::GetProperty(ffi, module_name); - if (result.is_null()) { - return ReportFFIError(thrower, "module not found", index, module_name, - function_name); - } - - Handle<Object> module = result.ToHandleChecked(); - - if (!module->IsJSReceiver()) { - return ReportFFIError(thrower, "module is not an object or function", index, - module_name, function_name); - } - - Handle<Object> function; - if (!function_name.is_null()) { - // Look up the function in the module. - MaybeHandle<Object> result = - Object::GetProperty(module, function_name.ToHandleChecked()); - if (result.is_null()) { - return ReportFFIError(thrower, "function not found", index, module_name, - function_name); - } - function = result.ToHandleChecked(); - } else { - // No function specified. Use the "default export". - function = module; - } - - if (!function->IsCallable()) { - return ReportFFIError(thrower, "not a callable", index, module_name, - function_name); - } - - return Handle<JSReceiver>::cast(function); -} - -namespace { // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( @@ -530,7 +304,7 @@ bool FetchAndExecuteCompilationUnit( DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; - // - 1 because AtomicIntrement returns the value after the atomic increment. + // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; @@ -539,10 +313,8 @@ bool FetchAndExecuteCompilationUnit( compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); - { - base::LockGuard<base::Mutex> guard(result_mutex); - executed_units->push(unit); - } + base::LockGuard<base::Mutex> guard(result_mutex); + executed_units->push(unit); } return true; } @@ -585,11 +357,6 @@ static void RecordStats(Isolate* isolate, Code* code) { code->relocation_info()->length()); } -static void RecordStats(Isolate* isolate, - const std::vector<Handle<Code>>& functions) { - for (Handle<Code> c : functions) RecordStats(isolate, *c); -} - static void RecordStats(Isolate* isolate, Handle<FixedArray> functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { @@ -597,16 +364,27 @@ static void RecordStats(Isolate* isolate, Handle<FixedArray> functions) { } } -Handle<FixedArray> GetImportsMetadata(Factory* factory, - const WasmModule* module) { +Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, + JSObject* owner) { + Address old_address = nullptr; + Object* stored_value = owner->GetInternalField(kWasmGlobalsArrayBuffer); + if (stored_value != undefined) { + old_address = static_cast<Address>( + JSArrayBuffer::cast(stored_value)->backing_store()); + } + return old_address; +} + +Handle<FixedArray> GetImportsData(Factory* factory, const WasmModule* module) { Handle<FixedArray> ret = factory->NewFixedArray( static_cast<int>(module->import_table.size()), TENURED); for (size_t i = 0; i < module->import_table.size(); ++i) { const WasmImport& import = module->import_table[i]; + if (import.kind != kExternalFunction) continue; WasmName module_name = module->GetNameOrNull(import.module_name_offset, import.module_name_length); - WasmName function_name = module->GetNameOrNull(import.function_name_offset, - import.function_name_length); + WasmName function_name = module->GetNameOrNull(import.field_name_offset, + import.field_name_length); Handle<String> module_name_string = factory->InternalizeUtf8String(module_name); @@ -614,116 +392,172 @@ Handle<FixedArray> GetImportsMetadata(Factory* factory, function_name.is_empty() ? Handle<String>::null() : factory->InternalizeUtf8String(function_name); - Handle<ByteArray> sig = - factory->NewByteArray(static_cast<int>(import.sig->parameter_count() + - import.sig->return_count()), - TENURED); - sig->copy_in(0, reinterpret_cast<const byte*>(import.sig->raw_data()), + FunctionSig* fsig = module->functions[import.index].sig; + Handle<ByteArray> sig = factory->NewByteArray( + static_cast<int>(fsig->parameter_count() + fsig->return_count()), + TENURED); + sig->copy_in(0, reinterpret_cast<const byte*>(fsig->raw_data()), sig->length()); Handle<FixedArray> encoded_import = - factory->NewFixedArray(kWasmImportDataTableSize, TENURED); + factory->NewFixedArray(kWasmImportDataSize, TENURED); encoded_import->set(kModuleName, *module_name_string); if (!function_name_string.is_null()) { encoded_import->set(kFunctionName, *function_name_string); } - encoded_import->set( - kOutputCount, - Smi::FromInt(static_cast<int>(import.sig->return_count()))); + encoded_import->set(kOutputCount, + Smi::FromInt(static_cast<int>(fsig->return_count()))); encoded_import->set(kSignature, *sig); ret->set(static_cast<int>(i), *encoded_import); } return ret; } -bool CompileWrappersToImportedFunctions(Isolate* isolate, - const Handle<JSReceiver> ffi, - std::vector<Handle<Code>>& imports, - Handle<FixedArray> import_data, - ErrorThrower* thrower) { - uint32_t import_count = static_cast<uint32_t>(import_data->length()); - if (import_count > 0) { - imports.reserve(import_count); - for (uint32_t index = 0; index < import_count; ++index) { - Handle<FixedArray> data = - import_data->GetValueChecked<FixedArray>(isolate, index); - Handle<String> module_name = - data->GetValueChecked<String>(isolate, kModuleName); - MaybeHandle<String> function_name = - data->GetValue<String>(isolate, kFunctionName); - - // TODO(mtrofin): this is an uint32_t, actually. We should rationalize - // it when we rationalize signed/unsigned stuff. - int ret_count = Smi::cast(data->get(kOutputCount))->value(); - CHECK(ret_count >= 0); - Handle<ByteArray> sig_data = - data->GetValueChecked<ByteArray>(isolate, kSignature); - int sig_data_size = sig_data->length(); - int param_count = sig_data_size - ret_count; - CHECK(param_count >= 0); - - MaybeHandle<JSReceiver> function = LookupFunction( - *thrower, isolate->factory(), ffi, index, module_name, function_name); - if (function.is_null()) return false; - Handle<Code> code; - Handle<JSReceiver> target = function.ToHandleChecked(); - bool isMatch = false; - Handle<Code> export_wrapper_code; - if (target->IsJSFunction()) { - Handle<JSFunction> func = Handle<JSFunction>::cast(target); - export_wrapper_code = handle(func->code()); - if (export_wrapper_code->kind() == Code::JS_TO_WASM_FUNCTION) { - int exported_param_count = - Smi::cast(func->GetInternalField(kInternalArity))->value(); - Handle<ByteArray> exportedSig = Handle<ByteArray>( - ByteArray::cast(func->GetInternalField(kInternalSignature))); - if (exported_param_count == param_count && - exportedSig->length() == sig_data->length() && - memcmp(exportedSig->data(), sig_data->data(), - exportedSig->length()) == 0) { - isMatch = true; - } - } +static MaybeHandle<JSFunction> ReportFFIError( + ErrorThrower* thrower, const char* error, uint32_t index, + Handle<String> module_name, MaybeHandle<String> function_name) { + Handle<String> function_name_handle; + if (function_name.ToHandle(&function_name_handle)) { + thrower->Error("Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", + index, module_name->length(), module_name->ToCString().get(), + function_name_handle->length(), + function_name_handle->ToCString().get(), error); + } else { + thrower->Error("Import #%d module=\"%.*s\" error: %s", index, + module_name->length(), module_name->ToCString().get(), + error); + } + thrower->Error("Import "); + return MaybeHandle<JSFunction>(); +} + +static MaybeHandle<JSReceiver> LookupFunction( + ErrorThrower* thrower, Factory* factory, Handle<JSReceiver> ffi, + uint32_t index, Handle<String> module_name, + MaybeHandle<String> function_name) { + if (ffi.is_null()) { + return ReportFFIError(thrower, "FFI is not an object", index, module_name, + function_name); + } + + // Look up the module first. + MaybeHandle<Object> result = Object::GetProperty(ffi, module_name); + if (result.is_null()) { + return ReportFFIError(thrower, "module not found", index, module_name, + function_name); + } + + Handle<Object> module = result.ToHandleChecked(); + + if (!module->IsJSReceiver()) { + return ReportFFIError(thrower, "module is not an object or function", index, + module_name, function_name); + } + + Handle<Object> function; + if (!function_name.is_null()) { + // Look up the function in the module. + MaybeHandle<Object> result = + Object::GetProperty(module, function_name.ToHandleChecked()); + if (result.is_null()) { + return ReportFFIError(thrower, "function not found", index, module_name, + function_name); + } + function = result.ToHandleChecked(); + } else { + // No function specified. Use the "default export". + function = module; + } + + if (!function->IsCallable()) { + return ReportFFIError(thrower, "not a callable", index, module_name, + function_name); + } + + return Handle<JSReceiver>::cast(function); +} + +Handle<Code> CompileImportWrapper(Isolate* isolate, + const Handle<JSReceiver> ffi, int index, + Handle<FixedArray> import_data, + ErrorThrower* thrower) { + Handle<FixedArray> data = + import_data->GetValueChecked<FixedArray>(isolate, index); + Handle<String> module_name = + data->GetValueChecked<String>(isolate, kModuleName); + MaybeHandle<String> function_name = + data->GetValue<String>(isolate, kFunctionName); + + // TODO(mtrofin): this is an uint32_t, actually. We should rationalize + // it when we rationalize signed/unsigned stuff. + int ret_count = Smi::cast(data->get(kOutputCount))->value(); + CHECK_GE(ret_count, 0); + Handle<ByteArray> sig_data = + data->GetValueChecked<ByteArray>(isolate, kSignature); + int sig_data_size = sig_data->length(); + int param_count = sig_data_size - ret_count; + CHECK(param_count >= 0); + + MaybeHandle<JSReceiver> function = LookupFunction( + thrower, isolate->factory(), ffi, index, module_name, function_name); + if (function.is_null()) return Handle<Code>::null(); + Handle<Code> code; + Handle<JSReceiver> target = function.ToHandleChecked(); + bool isMatch = false; + Handle<Code> export_wrapper_code; + if (target->IsJSFunction()) { + Handle<JSFunction> func = Handle<JSFunction>::cast(target); + export_wrapper_code = handle(func->code()); + if (export_wrapper_code->kind() == Code::JS_TO_WASM_FUNCTION) { + int exported_param_count = + Smi::cast(func->GetInternalField(kInternalArity))->value(); + Handle<ByteArray> exportedSig = Handle<ByteArray>( + ByteArray::cast(func->GetInternalField(kInternalSignature))); + if (exported_param_count == param_count && + exportedSig->length() == sig_data->length() && + memcmp(exportedSig->data(), sig_data->data(), + exportedSig->length()) == 0) { + isMatch = true; } - if (isMatch) { - int wasm_count = 0; - int const mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); - for (RelocIterator it(*export_wrapper_code, mask); !it.done(); - it.next()) { - RelocInfo* rinfo = it.rinfo(); - Address target_address = rinfo->target_address(); - Code* target = Code::GetCodeFromTargetAddress(target_address); - if (target->kind() == Code::WASM_FUNCTION) { - ++wasm_count; - code = handle(target); - } - } - DCHECK(wasm_count == 1); - } else { - // Copy the signature to avoid a raw pointer into a heap object when - // GC can happen. - Zone zone(isolate->allocator()); - MachineRepresentation* reps = - zone.NewArray<MachineRepresentation>(sig_data_size); - memcpy(reps, sig_data->data(), - sizeof(MachineRepresentation) * sig_data_size); - FunctionSig sig(ret_count, param_count, reps); - - code = compiler::CompileWasmToJSWrapper(isolate, target, &sig, index, - module_name, function_name); + } + } + if (isMatch) { + int wasm_count = 0; + int const mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); + for (RelocIterator it(*export_wrapper_code, mask); !it.done(); it.next()) { + RelocInfo* rinfo = it.rinfo(); + Address target_address = rinfo->target_address(); + Code* target = Code::GetCodeFromTargetAddress(target_address); + if (target->kind() == Code::WASM_FUNCTION) { + ++wasm_count; + code = handle(target); } - imports.push_back(code); } + DCHECK(wasm_count == 1); + return code; + } else { + // Copy the signature to avoid a raw pointer into a heap object when + // GC can happen. + Zone zone(isolate->allocator()); + MachineRepresentation* reps = + zone.NewArray<MachineRepresentation>(sig_data_size); + memcpy(reps, sig_data->data(), + sizeof(MachineRepresentation) * sig_data_size); + FunctionSig sig(ret_count, param_count, reps); + + return compiler::CompileWasmToJSWrapper(isolate, target, &sig, index, + module_name, function_name); } - return true; } void InitializeParallelCompilation( Isolate* isolate, const std::vector<WasmFunction>& functions, std::vector<compiler::WasmCompilationUnit*>& compilation_units, - ModuleEnv& module_env, ErrorThrower& thrower) { + ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { - compilation_units[i] = new compiler::WasmCompilationUnit( - &thrower, isolate, &module_env, &functions[i], i); + const WasmFunction* func = &functions[i]; + compilation_units[i] = + func->imported ? nullptr : new compiler::WasmCompilationUnit( + thrower, isolate, &module_env, func, i); } } @@ -812,7 +646,7 @@ void CompileInParallel(Isolate* isolate, const WasmModule* module, // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, - *module_env, *thrower); + *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; @@ -853,8 +687,8 @@ void CompileSequentially(Isolate* isolate, const WasmModule* module, for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; + if (func.imported) continue; // Imports are compiled at instantiation time. - DCHECK_EQ(i, func.func_index); WasmName str = module->GetName(func.name_offset, func.name_length); Handle<Code> code = Handle<Code>::null(); // Compile the function. @@ -870,190 +704,321 @@ void CompileSequentially(Isolate* isolate, const WasmModule* module, } } -void SetDebugSupport(Factory* factory, Handle<FixedArray> compiled_module, - Handle<JSObject> js_object) { - Isolate* isolate = compiled_module->GetIsolate(); - MaybeHandle<String> module_bytes_string = - compiled_module->GetValue<String>(isolate, kModuleBytes); - if (!module_bytes_string.is_null()) { - js_object->SetInternalField(kWasmModuleBytesString, - *module_bytes_string.ToHandleChecked()); - } - Handle<FixedArray> functions = Handle<FixedArray>( - FixedArray::cast(js_object->GetInternalField(kWasmModuleCodeTable))); +void PatchDirectCalls(Handle<FixedArray> old_functions, + Handle<FixedArray> new_functions, int start) { + DCHECK_EQ(new_functions->length(), old_functions->length()); - for (int i = FLAG_skip_compiling_wasm_funcs; i < functions->length(); ++i) { - Handle<Code> code = functions->GetValueChecked<Code>(isolate, i); - DCHECK(code->deoptimization_data() == nullptr || - code->deoptimization_data()->length() == 0); - Handle<FixedArray> deopt_data = factory->NewFixedArray(2, TENURED); - if (!js_object.is_null()) { - deopt_data->set(0, *js_object); + DisallowHeapAllocation no_gc; + std::map<Code*, Code*> old_to_new_code; + for (int i = 0; i < new_functions->length(); ++i) { + old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), + Code::cast(new_functions->get(i)))); + } + int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); + AllowDeferredHandleDereference embedding_raw_address; + for (int i = start; i < new_functions->length(); ++i) { + Code* wasm_function = Code::cast(new_functions->get(i)); + for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { + Code* old_code = + Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); + if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || + old_code->kind() == Code::WASM_FUNCTION) { + auto found = old_to_new_code.find(old_code); + DCHECK(found != old_to_new_code.end()); + Code* new_code = found->second; + if (new_code != old_code) { + it.rinfo()->set_target_address(new_code->instruction_start(), + UPDATE_WRITE_BARRIER, + SKIP_ICACHE_FLUSH); + } + } } - deopt_data->set(1, Smi::FromInt(static_cast<int>(i))); - deopt_data->set_length(2); - code->set_deoptimization_data(*deopt_data); } +} - MaybeHandle<ByteArray> function_name_table = - compiled_module->GetValue<ByteArray>(isolate, kFunctionNameTable); - if (!function_name_table.is_null()) { - js_object->SetInternalField(kWasmFunctionNamesArray, - *function_name_table.ToHandleChecked()); +static void ResetCompiledModule(Isolate* isolate, JSObject* owner, + WasmCompiledModule* compiled_module) { + TRACE("Resetting %d\n", compiled_module->instance_id()); + Object* undefined = *isolate->factory()->undefined_value(); + uint32_t old_mem_size = compiled_module->has_heap() + ? compiled_module->mem_size() + : compiled_module->default_mem_size(); + uint32_t default_mem_size = compiled_module->default_mem_size(); + Object* mem_start = compiled_module->ptr_to_heap(); + Address old_mem_address = nullptr; + Address globals_start = + GetGlobalStartAddressFromCodeTemplate(undefined, owner); + + if (old_mem_size > 0) { + CHECK_NE(mem_start, undefined); + old_mem_address = + static_cast<Address>(JSArrayBuffer::cast(mem_start)->backing_store()); + } + int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | + RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | + RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE); + + Object* fct_obj = compiled_module->ptr_to_code_table(); + if (fct_obj != nullptr && fct_obj != undefined && + (old_mem_size > 0 || globals_start != nullptr)) { + FixedArray* functions = FixedArray::cast(fct_obj); + for (int i = 0; i < functions->length(); ++i) { + Code* code = Code::cast(functions->get(i)); + bool changed = false; + for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { + RelocInfo::Mode mode = it.rinfo()->rmode(); + if (RelocInfo::IsWasmMemoryReference(mode) || + RelocInfo::IsWasmMemorySizeReference(mode)) { + it.rinfo()->update_wasm_memory_reference( + old_mem_address, nullptr, old_mem_size, default_mem_size); + changed = true; + } else { + CHECK(RelocInfo::IsWasmGlobalReference(mode)); + it.rinfo()->update_wasm_global_reference(globals_start, nullptr); + changed = true; + } + } + if (changed) { + Assembler::FlushICache(isolate, code->instruction_start(), + code->instruction_size()); + } + } } + compiled_module->reset_heap(); } -bool SetupGlobals(Isolate* isolate, Handle<FixedArray> compiled_module, - Handle<JSObject> instance, ErrorThrower* thrower) { - uint32_t globals_size = static_cast<uint32_t>( - Smi::cast(compiled_module->get(kGlobalsSize))->value()); - if (globals_size > 0) { - Handle<JSArrayBuffer> globals_buffer = - NewArrayBuffer(isolate, globals_size); - if (globals_buffer.is_null()) { - thrower->Error("Out of memory: wasm globals"); - return false; +static void InstanceFinalizer(const v8::WeakCallbackInfo<void>& data) { + JSObject** p = reinterpret_cast<JSObject**>(data.GetParameter()); + JSObject* owner = *p; + WasmCompiledModule* compiled_module = + WasmCompiledModule::cast(owner->GetInternalField(kWasmCompiledModule)); + TRACE("Finalizing %d {\n", compiled_module->instance_id()); + Isolate* isolate = reinterpret_cast<Isolate*>(data.GetIsolate()); + DCHECK(compiled_module->has_weak_module_object()); + WeakCell* weak_module_obj = compiled_module->ptr_to_weak_module_object(); + + // weak_module_obj may have been cleared, meaning the module object + // was GC-ed. In that case, there won't be any new instances created, + // and we don't need to maintain the links between instances. + if (!weak_module_obj->cleared()) { + JSObject* module_obj = JSObject::cast(weak_module_obj->value()); + WasmCompiledModule* current_template = + WasmCompiledModule::cast(module_obj->GetInternalField(0)); + + TRACE("chain before {\n"); + TRACE_CHAIN(current_template); + TRACE("}\n"); + + DCHECK(!current_template->has_weak_prev_instance()); + WeakCell* next = compiled_module->ptr_to_weak_next_instance(); + WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); + + if (current_template == compiled_module) { + if (next == nullptr) { + ResetCompiledModule(isolate, owner, compiled_module); + } else { + DCHECK(next->value()->IsFixedArray()); + module_obj->SetInternalField(0, next->value()); + DCHECK_NULL(prev); + WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); + } + } else { + DCHECK(!(prev == nullptr && next == nullptr)); + // the only reason prev or next would be cleared is if the + // respective objects got collected, but if that happened, + // we would have relinked the list. + if (prev != nullptr) { + DCHECK(!prev->cleared()); + if (next == nullptr) { + WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); + } else { + WasmCompiledModule::cast(prev->value()) + ->set_ptr_to_weak_next_instance(next); + } + } + if (next != nullptr) { + DCHECK(!next->cleared()); + if (prev == nullptr) { + WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); + } else { + WasmCompiledModule::cast(next->value()) + ->set_ptr_to_weak_prev_instance(prev); + } + } } - RelocateGlobals(instance, - static_cast<Address>(globals_buffer->backing_store())); - instance->SetInternalField(kWasmGlobalsArrayBuffer, *globals_buffer); + TRACE("chain after {\n"); + TRACE_CHAIN(WasmCompiledModule::cast(module_obj->GetInternalField(0))); + TRACE("}\n"); } - return true; + compiled_module->reset_weak_owning_instance(); + GlobalHandles::Destroy(reinterpret_cast<Object**>(p)); + TRACE("}\n"); } -bool SetupInstanceHeap(Isolate* isolate, Handle<FixedArray> compiled_module, - Handle<JSObject> instance, Handle<JSArrayBuffer> memory, - ErrorThrower* thrower) { - uint32_t min_mem_pages = static_cast<uint32_t>( - Smi::cast(compiled_module->get(kMinRequiredMemory))->value()); - isolate->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); - // TODO(wasm): re-enable counter for max_mem_pages when we use that field. +Handle<FixedArray> SetupIndirectFunctionTable( + Isolate* isolate, Handle<FixedArray> wasm_functions, + Handle<FixedArray> indirect_table_template, + Handle<FixedArray> tables_to_replace) { + Factory* factory = isolate->factory(); + Handle<FixedArray> cloned_indirect_tables = + factory->CopyFixedArray(indirect_table_template); + for (int i = 0; i < cloned_indirect_tables->length(); ++i) { + Handle<FixedArray> orig_metadata = + cloned_indirect_tables->GetValueChecked<FixedArray>(isolate, i); + Handle<FixedArray> cloned_metadata = factory->CopyFixedArray(orig_metadata); + cloned_indirect_tables->set(i, *cloned_metadata); - if (memory.is_null() && min_mem_pages > 0) { - memory = AllocateMemory(thrower, isolate, min_mem_pages); - if (memory.is_null()) { - return false; + Handle<FixedArray> orig_table = + cloned_metadata->GetValueChecked<FixedArray>(isolate, kTable); + Handle<FixedArray> cloned_table = factory->CopyFixedArray(orig_table); + cloned_metadata->set(kTable, *cloned_table); + // Patch the cloned code to refer to the cloned kTable. + Handle<FixedArray> table_to_replace = + tables_to_replace->GetValueChecked<FixedArray>(isolate, i) + ->GetValueChecked<FixedArray>(isolate, kTable); + for (int fct_index = 0; fct_index < wasm_functions->length(); ++fct_index) { + Handle<Code> wasm_function = + wasm_functions->GetValueChecked<Code>(isolate, fct_index); + PatchFunctionTable(wasm_function, table_to_replace, cloned_table); } } + return cloned_indirect_tables; +} - if (!memory.is_null()) { - instance->SetInternalField(kWasmMemArrayBuffer, *memory); - Address mem_start = static_cast<Address>(memory->backing_store()); - uint32_t mem_size = static_cast<uint32_t>(memory->byte_length()->Number()); - RelocateInstanceCode(instance, mem_start, - WasmModule::kPageSize * min_mem_pages, mem_size); - LoadDataSegments(compiled_module, mem_start, mem_size); +} // namespace + +const char* SectionName(WasmSectionCode code) { + switch (code) { + case kUnknownSectionCode: + return "Unknown"; + case kTypeSectionCode: + return "Type"; + case kImportSectionCode: + return "Import"; + case kFunctionSectionCode: + return "Function"; + case kTableSectionCode: + return "Table"; + case kMemorySectionCode: + return "Memory"; + case kGlobalSectionCode: + return "Global"; + case kExportSectionCode: + return "Export"; + case kStartSectionCode: + return "Start"; + case kCodeSectionCode: + return "Code"; + case kElementSectionCode: + return "Element"; + case kDataSectionCode: + return "Data"; + case kNameSectionCode: + return "Name"; + default: + return "<unknown>"; } - return true; } -bool SetupImports(Isolate* isolate, Handle<FixedArray> compiled_module, - Handle<JSObject> instance, ErrorThrower* thrower, - Handle<JSReceiver> ffi) { - //------------------------------------------------------------------------- - // Compile wrappers to imported functions. - //------------------------------------------------------------------------- - std::vector<Handle<Code>> import_code; - MaybeHandle<FixedArray> maybe_import_data = - compiled_module->GetValue<FixedArray>(isolate, kImportData); - Handle<FixedArray> import_data; - if (maybe_import_data.ToHandle(&import_data)) { - if (!CompileWrappersToImportedFunctions(isolate, ffi, import_code, - import_data, thrower)) { - return false; +std::ostream& operator<<(std::ostream& os, const WasmModule& module) { + os << "WASM module with "; + os << (module.min_mem_pages * module.kPageSize) << " min mem"; + os << (module.max_mem_pages * module.kPageSize) << " max mem"; + os << module.functions.size() << " functions"; + os << module.functions.size() << " globals"; + os << module.functions.size() << " data segments"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const WasmFunction& function) { + os << "WASM function with signature " << *function.sig; + + os << " code bytes: " + << (function.code_end_offset - function.code_start_offset); + return os; +} + +std::ostream& operator<<(std::ostream& os, const WasmFunctionName& pair) { + os << "#" << pair.function_->func_index << ":"; + if (pair.function_->name_offset > 0) { + if (pair.module_) { + WasmName name = pair.module_->GetName(pair.function_->name_offset, + pair.function_->name_length); + os.write(name.start(), name.length()); + } else { + os << "+" << pair.function_->func_index; } + } else { + os << "?"; } + return os; +} - RecordStats(isolate, import_code); +Handle<JSFunction> WrapExportCodeAsJSFunction( + Isolate* isolate, Handle<Code> export_code, Handle<String> name, int arity, + MaybeHandle<ByteArray> maybe_signature, Handle<JSObject> module_instance) { + Handle<SharedFunctionInfo> shared = + isolate->factory()->NewSharedFunctionInfo(name, export_code, false); + shared->set_length(arity); + shared->set_internal_formal_parameter_count(arity); + Handle<JSFunction> function = isolate->factory()->NewFunction( + isolate->wasm_function_map(), name, export_code); + function->set_shared(*shared); - Handle<FixedArray> code_table = Handle<FixedArray>( - FixedArray::cast(instance->GetInternalField(kWasmModuleCodeTable))); - // TODO(mtrofin): get the code off std::vector and on FixedArray, for - // consistency. - std::vector<Handle<Code>> function_code(code_table->length()); - for (int i = 0; i < code_table->length(); ++i) { - Handle<Code> code = Handle<Code>(Code::cast(code_table->get(i))); - function_code[i] = code; + function->SetInternalField(kInternalModuleInstance, *module_instance); + // add another Internal Field as the function arity + function->SetInternalField(kInternalArity, Smi::FromInt(arity)); + // add another Internal Field as the signature of the foreign function + Handle<ByteArray> signature; + if (maybe_signature.ToHandle(&signature)) { + function->SetInternalField(kInternalSignature, *signature); } - - LinkImports(isolate, function_code, import_code); - return true; + return function; } -bool SetupExportsObject(Handle<FixedArray> compiled_module, Isolate* isolate, - Handle<JSObject> instance, ErrorThrower* thrower) { - Factory* factory = isolate->factory(); - bool mem_export = - static_cast<bool>(Smi::cast(compiled_module->get(kExportMem))->value()); - ModuleOrigin origin = static_cast<ModuleOrigin>( - Smi::cast(compiled_module->get(kOrigin))->value()); - - MaybeHandle<FixedArray> maybe_exports = - compiled_module->GetValue<FixedArray>(isolate, kExports); - if (!maybe_exports.is_null() || mem_export) { - PropertyDescriptor desc; - desc.set_writable(false); +Object* GetOwningWasmInstance(Code* code) { + DCHECK(code->kind() == Code::WASM_FUNCTION); + DisallowHeapAllocation no_gc; + FixedArray* deopt_data = code->deoptimization_data(); + DCHECK_NOT_NULL(deopt_data); + DCHECK(deopt_data->length() == 2); + Object* weak_link = deopt_data->get(0); + if (!weak_link->IsWeakCell()) return nullptr; + WeakCell* cell = WeakCell::cast(weak_link); + return cell->value(); +} - Handle<JSObject> exports_object = instance; - if (origin == kWasmOrigin) { - // Create the "exports" object. - Handle<JSFunction> object_function = Handle<JSFunction>( - isolate->native_context()->object_function(), isolate); - exports_object = factory->NewJSObject(object_function, TENURED); - Handle<String> exports_name = factory->InternalizeUtf8String("exports"); - JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); - } - Handle<FixedArray> exports; - if (maybe_exports.ToHandle(&exports)) { - int exports_size = exports->length(); - for (int i = 0; i < exports_size; ++i) { - if (thrower->error()) return false; - Handle<FixedArray> export_metadata = - exports->GetValueChecked<FixedArray>(isolate, i); - Handle<Code> export_code = - export_metadata->GetValueChecked<Code>(isolate, kExportCode); - RecordStats(isolate, *export_code); - Handle<String> name = - export_metadata->GetValueChecked<String>(isolate, kExportName); - int arity = Smi::cast(export_metadata->get(kExportArity))->value(); - MaybeHandle<ByteArray> signature = - export_metadata->GetValue<ByteArray>(isolate, kExportedSignature); - Handle<JSFunction> function = WrapExportCodeAsJSFunction( - isolate, export_code, name, arity, signature, instance); - desc.set_value(function); - Maybe<bool> status = JSReceiver::DefineOwnProperty( - isolate, exports_object, name, &desc, Object::THROW_ON_ERROR); - if (!status.IsJust()) { - thrower->Error("export of %.*s failed.", name->length(), - name->ToCString().get()); - return false; - } - } - } - if (mem_export) { - // Export the memory as a named property. - Handle<String> name = factory->InternalizeUtf8String("memory"); - Handle<JSArrayBuffer> memory = Handle<JSArrayBuffer>( - JSArrayBuffer::cast(instance->GetInternalField(kWasmMemArrayBuffer))); - JSObject::AddProperty(exports_object, name, memory, READ_ONLY); - } - } - return true; +uint32_t GetNumImportedFunctions(Handle<JSObject> wasm_object) { + return static_cast<uint32_t>( + Smi::cast(wasm_object->GetInternalField(kWasmNumImportedFunctions)) + ->value()); } -} // namespace +WasmModule::WasmModule(byte* module_start) + : module_start(module_start), + module_end(nullptr), + min_mem_pages(0), + max_mem_pages(0), + mem_export(false), + start_function_index(-1), + origin(kWasmOrigin), + globals_size(0), + num_imported_functions(0), + num_declared_functions(0), + num_exported_functions(0), + pending_tasks(new base::Semaphore(0)) {} -MaybeHandle<FixedArray> WasmModule::CompileFunctions( +MaybeHandle<WasmCompiledModule> WasmModule::CompileFunctions( Isolate* isolate, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); - MaybeHandle<FixedArray> nothing; + MaybeHandle<WasmCompiledModule> nothing; - WasmModuleInstance temp_instance_for_compilation(this); - temp_instance_for_compilation.context = isolate->native_context(); - temp_instance_for_compilation.mem_size = GetMinModuleMemSize(this); - temp_instance_for_compilation.mem_start = nullptr; - temp_instance_for_compilation.globals_start = nullptr; + WasmModuleInstance temp_instance(this); + temp_instance.context = isolate->native_context(); + temp_instance.mem_size = GetMinModuleMemSize(this); + temp_instance.mem_start = nullptr; + temp_instance.globals_start = nullptr; MaybeHandle<FixedArray> indirect_table = function_tables.size() @@ -1062,10 +1027,10 @@ MaybeHandle<FixedArray> WasmModule::CompileFunctions( : MaybeHandle<FixedArray>(); for (uint32_t i = 0; i < function_tables.size(); ++i) { Handle<FixedArray> values = wasm::BuildFunctionTable(isolate, i, this); - temp_instance_for_compilation.function_tables[i] = values; + temp_instance.function_tables[i] = values; Handle<FixedArray> metadata = isolate->factory()->NewFixedArray( - kWasmIndirectFunctionTableMetadataSize, TENURED); + kWasmIndirectFunctionTableDataSize, TENURED); metadata->set(kSize, Smi::FromInt(function_tables[i].size)); metadata->set(kTable, *values); indirect_table.ToHandleChecked()->set(i, *metadata); @@ -1076,61 +1041,90 @@ MaybeHandle<FixedArray> WasmModule::CompileFunctions( ModuleEnv module_env; module_env.module = this; - module_env.instance = &temp_instance_for_compilation; + module_env.instance = &temp_instance; module_env.origin = origin; - InitializePlaceholders(factory, &module_env.placeholders, functions.size()); - Handle<FixedArray> compiled_functions = - factory->NewFixedArray(static_cast<int>(functions.size()), TENURED); + // The {code_table} array contains import wrappers and functions (which + // are both included in {functions.size()}, and export wrappers. + int code_table_size = + static_cast<int>(functions.size() + num_exported_functions); + Handle<FixedArray> code_table = + factory->NewFixedArray(static_cast<int>(code_table_size), TENURED); - temp_instance_for_compilation.import_code.resize(import_table.size()); - for (uint32_t i = 0; i < import_table.size(); ++i) { - temp_instance_for_compilation.import_code[i] = - CreatePlaceholder(factory, i, Code::WASM_TO_JS_FUNCTION); + // Initialize the code table with placeholders. + for (uint32_t i = 0; i < functions.size(); i++) { + Code::Kind kind = Code::WASM_FUNCTION; + if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; + Handle<Code> placeholder = CreatePlaceholder(factory, i, kind); + code_table->set(static_cast<int>(i), *placeholder); + temp_instance.function_code[i] = placeholder; } + isolate->counters()->wasm_functions_per_module()->AddSample( static_cast<int>(functions.size())); - if (FLAG_wasm_num_compilation_tasks != 0) { - CompileInParallel(isolate, this, - temp_instance_for_compilation.function_code, thrower, - &module_env); + if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { + // Avoid a race condition by collecting results into a second vector. + std::vector<Handle<Code>> results; + results.reserve(temp_instance.function_code.size()); + for (size_t i = 0; i < temp_instance.function_code.size(); i++) { + results.push_back(temp_instance.function_code[i]); + } + CompileInParallel(isolate, this, results, thrower, &module_env); + + for (size_t i = 0; i < results.size(); i++) { + temp_instance.function_code[i] = results[i]; + } } else { - CompileSequentially(isolate, this, - temp_instance_for_compilation.function_code, thrower, + CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; - i < temp_instance_for_compilation.function_code.size(); ++i) { - Code* code = *temp_instance_for_compilation.function_code[i]; - compiled_functions->set(static_cast<int>(i), code); + i < temp_instance.function_code.size(); ++i) { + Code* code = *temp_instance.function_code[i]; + code_table->set(static_cast<int>(i), code); + } + + // Link the functions in the module. + for (size_t i = FLAG_skip_compiling_wasm_funcs; + i < temp_instance.function_code.size(); ++i) { + Handle<Code> code = temp_instance.function_code[i]; + bool modified = LinkFunction(code, temp_instance.function_code); + if (modified) { + // TODO(mtrofin): do we need to flush the cache here? + Assembler::FlushICache(isolate, code->instruction_start(), + code->instruction_size()); + } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. - Handle<FixedArray> ret = - factory->NewFixedArray(kCompiledWasmObjectTableSize, TENURED); - ret->set(kFunctions, *compiled_functions); + Handle<WasmCompiledModule> ret = WasmCompiledModule::New( + isolate, min_mem_pages, globals_size, mem_export, origin); + ret->set_code_table(code_table); if (!indirect_table.is_null()) { - ret->set(kTableOfIndirectFunctionTables, *indirect_table.ToHandleChecked()); + ret->set_indirect_function_tables(indirect_table.ToHandleChecked()); } - Handle<FixedArray> import_data = GetImportsMetadata(factory, this); - ret->set(kImportData, *import_data); + Handle<FixedArray> import_data = GetImportsData(factory, this); + ret->set_import_data(import_data); - // Compile export functions. - int export_size = static_cast<int>(export_table.size()); - Handle<Code> startup_fct; + // Compile exported function wrappers. + int export_size = static_cast<int>(num_exported_functions); if (export_size > 0) { Handle<FixedArray> exports = factory->NewFixedArray(export_size, TENURED); - for (int i = 0; i < export_size; ++i) { - Handle<FixedArray> export_metadata = - factory->NewFixedArray(kWasmExportMetadataTableSize, TENURED); - const WasmExport& exp = export_table[i]; - FunctionSig* funcSig = functions[exp.func_index].sig; + int index = -1; + + for (const WasmExport& exp : export_table) { + if (exp.kind != kExternalFunction) + continue; // skip non-function exports. + index++; + Handle<FixedArray> export_data = + factory->NewFixedArray(kWasmExportDataSize, TENURED); + FunctionSig* funcSig = functions[exp.index].sig; Handle<ByteArray> exportedSig = factory->NewByteArray(static_cast<int>(funcSig->parameter_count() + funcSig->return_count()), @@ -1138,45 +1132,34 @@ MaybeHandle<FixedArray> WasmModule::CompileFunctions( exportedSig->copy_in(0, reinterpret_cast<const byte*>(funcSig->raw_data()), exportedSig->length()); - export_metadata->set(kExportedSignature, *exportedSig); + export_data->set(kExportedSignature, *exportedSig); WasmName str = GetName(exp.name_offset, exp.name_length); Handle<String> name = factory->InternalizeUtf8String(str); - Handle<Code> code = - temp_instance_for_compilation.function_code[exp.func_index]; + Handle<Code> code = code_table->GetValueChecked<Code>(isolate, exp.index); Handle<Code> export_code = compiler::CompileJSToWasmWrapper( - isolate, &module_env, code, exp.func_index); + isolate, &module_env, code, exp.index); if (thrower->error()) return nothing; - export_metadata->set(kExportCode, *export_code); - export_metadata->set(kExportName, *name); - export_metadata->set( - kExportArity, Smi::FromInt(static_cast<int>( - functions[exp.func_index].sig->parameter_count()))); - export_metadata->set(kExportedFunctionIndex, - Smi::FromInt(static_cast<int>(exp.func_index))); - exports->set(i, *export_metadata); - if (exp.func_index == start_function_index) { - startup_fct = export_code; - } + export_data->set(kExportName, *name); + export_data->set(kExportArity, + Smi::FromInt(static_cast<int>( + functions[exp.index].sig->parameter_count()))); + export_data->set(kExportedFunctionIndex, + Smi::FromInt(static_cast<int>(exp.index))); + exports->set(index, *export_data); + code_table->set(static_cast<int>(functions.size() + index), *export_code); } - ret->set(kExports, *exports); + ret->set_exports(exports); } - // Compile startup function, if we haven't already. + // Record data for startup function. if (start_function_index >= 0) { - uint32_t index = static_cast<uint32_t>(start_function_index); HandleScope scope(isolate); - if (startup_fct.is_null()) { - Handle<Code> code = temp_instance_for_compilation.function_code[index]; - DCHECK_EQ(0, functions[index].sig->parameter_count()); - startup_fct = - compiler::CompileJSToWasmWrapper(isolate, &module_env, code, index); - } - Handle<FixedArray> metadata = - factory->NewFixedArray(kWasmExportMetadataTableSize, TENURED); - metadata->set(kExportCode, *startup_fct); - metadata->set(kExportArity, Smi::FromInt(0)); - metadata->set(kExportedFunctionIndex, Smi::FromInt(start_function_index)); - ret->set(kStartupFunction, *metadata); + Handle<FixedArray> startup_data = + factory->NewFixedArray(kWasmExportDataSize, TENURED); + startup_data->set(kExportArity, Smi::FromInt(0)); + startup_data->set(kExportedFunctionIndex, + Smi::FromInt(start_function_index)); + ret->set_startup_function(startup_data); } // TODO(wasm): saving the module bytes for debugging is wasteful. We should @@ -1189,213 +1172,334 @@ MaybeHandle<FixedArray> WasmModule::CompileFunctions( Handle<String> module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); - ret->set(kModuleBytes, *module_bytes_string); + ret->set_module_bytes(module_bytes_string); } Handle<ByteArray> function_name_table = BuildFunctionNamesTable(isolate, module_env.module); - ret->set(kFunctionNameTable, *function_name_table); - ret->set(kMinRequiredMemory, Smi::FromInt(min_mem_pages)); + ret->set_function_names(function_name_table); if (data_segments.size() > 0) SaveDataSegmentInfo(factory, this, ret); - ret->set(kGlobalsSize, Smi::FromInt(globals_size)); - ret->set(kExportMem, Smi::FromInt(mem_export)); - ret->set(kOrigin, Smi::FromInt(origin)); + DCHECK_EQ(ret->default_mem_size(), temp_instance.mem_size); return ret; } -void PatchJSWrapper(Isolate* isolate, Handle<Code> wrapper, - Handle<Code> new_target) { - AllowDeferredHandleDereference embedding_raw_address; - bool seen = false; - for (RelocIterator it(*wrapper, 1 << RelocInfo::CODE_TARGET); !it.done(); - it.next()) { - Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); - if (target->kind() == Code::WASM_FUNCTION) { - DCHECK(!seen); - seen = true; - it.rinfo()->set_target_address(new_target->instruction_start(), - UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); +// Instantiates a WASM module, creating a WebAssembly.Instance from a +// WebAssembly.Module. +MaybeHandle<JSObject> WasmModule::Instantiate(Isolate* isolate, + ErrorThrower* thrower, + Handle<JSObject> module_object, + Handle<JSReceiver> ffi, + Handle<JSArrayBuffer> memory) { + MaybeHandle<JSObject> nothing; + HistogramTimerScope wasm_instantiate_module_time_scope( + isolate->counters()->wasm_instantiate_module_time()); + Factory* factory = isolate->factory(); + + //-------------------------------------------------------------------------- + // Reuse the compiled module (if no owner), otherwise clone. + //-------------------------------------------------------------------------- + Handle<WasmCompiledModule> compiled_module; + Handle<FixedArray> code_table; + Handle<FixedArray> old_code_table; + Handle<JSObject> owner; + // If we don't clone, this will be null(). Otherwise, this will + // be a weak link to the original. If we lose the original to GC, + // this will be a cleared. We'll link the instances chain last. + MaybeHandle<WeakCell> link_to_original; + + TRACE("Starting new module instantiation\n"); + { + Handle<WasmCompiledModule> original( + WasmCompiledModule::cast(module_object->GetInternalField(0)), isolate); + // Always make a new copy of the code_table, since the old_code_table + // may still have placeholders for imports. + old_code_table = original->code_table(); + code_table = factory->CopyFixedArray(old_code_table); + + if (original->has_weak_owning_instance()) { + WeakCell* tmp = original->ptr_to_weak_owning_instance(); + DCHECK(!tmp->cleared()); + // There is already an owner, clone everything. + owner = Handle<JSObject>(JSObject::cast(tmp->value()), isolate); + // Insert the latest clone in front. + TRACE("Cloning from %d\n", original->instance_id()); + compiled_module = WasmCompiledModule::Clone(isolate, original); + // Replace the strong reference to point to the new instance here. + // This allows any of the other instances, including the original, + // to be collected. + module_object->SetInternalField(0, *compiled_module); + compiled_module->set_weak_module_object(original->weak_module_object()); + link_to_original = factory->NewWeakCell(original); + // Don't link to original here. We remember the original + // as a weak link. If that link isn't clear by the time we finish + // instantiating this instance, then we link it at that time. + compiled_module->reset_weak_next_instance(); + + // Clone the code for WASM functions and exports. + for (int i = 0; i < code_table->length(); ++i) { + Handle<Code> orig_code = code_table->GetValueChecked<Code>(isolate, i); + switch (orig_code->kind()) { + case Code::WASM_TO_JS_FUNCTION: + // Imports will be overwritten with newly compiled wrappers. + break; + case Code::JS_TO_WASM_FUNCTION: + case Code::WASM_FUNCTION: { + Handle<Code> code = factory->CopyCode(orig_code); + code_table->set(i, *code); + break; + } + default: + UNREACHABLE(); + } + } + RecordStats(isolate, code_table); + } else { + // There was no owner, so we can reuse the original. + compiled_module = original; + TRACE("Reusing existing instance %d\n", compiled_module->instance_id()); } + compiled_module->set_code_table(code_table); } - CHECK(seen); - Assembler::FlushICache(isolate, wrapper->instruction_start(), - wrapper->instruction_size()); -} -Handle<FixedArray> SetupIndirectFunctionTable( - Isolate* isolate, Handle<FixedArray> wasm_functions, - Handle<FixedArray> indirect_table_template) { - Factory* factory = isolate->factory(); - Handle<FixedArray> cloned_indirect_tables = - factory->CopyFixedArray(indirect_table_template); - for (int i = 0; i < cloned_indirect_tables->length(); ++i) { - Handle<FixedArray> orig_metadata = - cloned_indirect_tables->GetValueChecked<FixedArray>(isolate, i); - Handle<FixedArray> cloned_metadata = factory->CopyFixedArray(orig_metadata); - cloned_indirect_tables->set(i, *cloned_metadata); + //-------------------------------------------------------------------------- + // Allocate the instance object. + //-------------------------------------------------------------------------- + Handle<Map> map = factory->NewMap( + JS_OBJECT_TYPE, + JSObject::kHeaderSize + kWasmModuleInternalFieldCount * kPointerSize); + Handle<JSObject> instance = factory->NewJSObjectFromMap(map, TENURED); + instance->SetInternalField(kWasmModuleCodeTable, *code_table); - Handle<FixedArray> orig_table = - cloned_metadata->GetValueChecked<FixedArray>(isolate, kTable); - Handle<FixedArray> cloned_table = factory->CopyFixedArray(orig_table); - cloned_metadata->set(kTable, *cloned_table); - // Patch the cloned code to refer to the cloned kTable. - for (int i = 0; i < wasm_functions->length(); ++i) { - Handle<Code> wasm_function = - wasm_functions->GetValueChecked<Code>(isolate, i); - PatchFunctionTable(wasm_function, orig_table, cloned_table); - } + //-------------------------------------------------------------------------- + // Set up the memory for the new instance. + //-------------------------------------------------------------------------- + MaybeHandle<JSArrayBuffer> old_memory; + // TODO(titzer): handle imported memory properly. + + uint32_t min_mem_pages = compiled_module->min_memory_pages(); + isolate->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); + // TODO(wasm): re-enable counter for max_mem_pages when we use that field. + + if (memory.is_null() && min_mem_pages > 0) { + memory = AllocateMemory(thrower, isolate, min_mem_pages); + if (memory.is_null()) return nothing; // failed to allocate memory } - return cloned_indirect_tables; -} -Handle<FixedArray> CloneModuleForInstance(Isolate* isolate, - Handle<FixedArray> original) { - Factory* factory = isolate->factory(); - Handle<FixedArray> clone = factory->CopyFixedArray(original); - - // Clone each wasm code object. - Handle<FixedArray> orig_wasm_functions = - original->GetValueChecked<FixedArray>(isolate, kFunctions); - Handle<FixedArray> clone_wasm_functions = - factory->CopyFixedArray(orig_wasm_functions); - clone->set(kFunctions, *clone_wasm_functions); - for (int i = 0; i < clone_wasm_functions->length(); ++i) { - Handle<Code> orig_code = - clone_wasm_functions->GetValueChecked<Code>(isolate, i); - Handle<Code> cloned_code = factory->CopyCode(orig_code); - clone_wasm_functions->set(i, *cloned_code); - } - - MaybeHandle<FixedArray> maybe_orig_exports = - original->GetValue<FixedArray>(isolate, kExports); - Handle<FixedArray> orig_exports; - if (maybe_orig_exports.ToHandle(&orig_exports)) { - Handle<FixedArray> cloned_exports = factory->CopyFixedArray(orig_exports); - clone->set(kExports, *cloned_exports); - for (int i = 0; i < orig_exports->length(); ++i) { - Handle<FixedArray> export_metadata = - orig_exports->GetValueChecked<FixedArray>(isolate, i); - Handle<FixedArray> clone_metadata = - factory->CopyFixedArray(export_metadata); - cloned_exports->set(i, *clone_metadata); - Handle<Code> orig_code = - export_metadata->GetValueChecked<Code>(isolate, kExportCode); - Handle<Code> cloned_code = factory->CopyCode(orig_code); - clone_metadata->set(kExportCode, *cloned_code); - // TODO(wasm): This is actually a uint32_t, but since FixedArray indexes - // in int, we are taking the risk of invalid values. - int exported_fct_index = - Smi::cast(export_metadata->get(kExportedFunctionIndex))->value(); - CHECK_GE(exported_fct_index, 0); - CHECK_LT(exported_fct_index, clone_wasm_functions->length()); - Handle<Code> new_target = clone_wasm_functions->GetValueChecked<Code>( - isolate, exported_fct_index); - PatchJSWrapper(isolate, cloned_code, new_target); + if (!memory.is_null()) { + instance->SetInternalField(kWasmMemArrayBuffer, *memory); + Address mem_start = static_cast<Address>(memory->backing_store()); + uint32_t mem_size = static_cast<uint32_t>(memory->byte_length()->Number()); + LoadDataSegments(compiled_module, mem_start, mem_size); + + uint32_t old_mem_size = compiled_module->has_heap() + ? compiled_module->mem_size() + : compiled_module->default_mem_size(); + Address old_mem_start = + compiled_module->has_heap() + ? static_cast<Address>(compiled_module->heap()->backing_store()) + : nullptr; + RelocateInstanceCode(instance, old_mem_start, mem_start, old_mem_size, + mem_size); + compiled_module->set_heap(memory); + } + + //-------------------------------------------------------------------------- + // Set up the globals for the new instance. + //-------------------------------------------------------------------------- + MaybeHandle<JSArrayBuffer> old_globals; + MaybeHandle<JSArrayBuffer> globals; + uint32_t globals_size = compiled_module->globals_size(); + if (globals_size > 0) { + Handle<JSArrayBuffer> global_buffer = NewArrayBuffer(isolate, globals_size); + globals = global_buffer; + if (globals.is_null()) { + thrower->Error("Out of memory: wasm globals"); + return nothing; + } + Address old_address = + owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( + *isolate->factory()->undefined_value(), + JSObject::cast(*owner)); + RelocateGlobals(instance, old_address, + static_cast<Address>(global_buffer->backing_store())); + instance->SetInternalField(kWasmGlobalsArrayBuffer, *global_buffer); + } + + //-------------------------------------------------------------------------- + // Compile the import wrappers for the new instance. + //-------------------------------------------------------------------------- + // TODO(titzer): handle imported globals and function tables. + int num_imported_functions = 0; + if (compiled_module->has_import_data()) { + Handle<FixedArray> import_data = compiled_module->import_data(); + num_imported_functions = import_data->length(); + for (int index = 0; index < num_imported_functions; index++) { + Handle<Code> import_wrapper = + CompileImportWrapper(isolate, ffi, index, import_data, thrower); + if (thrower->error()) return nothing; + code_table->set(index, *import_wrapper); + RecordStats(isolate, *import_wrapper); } } - MaybeHandle<FixedArray> maybe_startup = - original->GetValue<FixedArray>(isolate, kStartupFunction); - if (!maybe_startup.is_null()) { - Handle<FixedArray> startup_metadata = - factory->CopyFixedArray(maybe_startup.ToHandleChecked()); - Handle<Code> startup_fct_clone = factory->CopyCode( - startup_metadata->GetValueChecked<Code>(isolate, kExportCode)); - startup_metadata->set(kExportCode, *startup_fct_clone); - clone->set(kStartupFunction, *startup_metadata); - // TODO(wasm): see todo above about int vs size_t indexing in FixedArray. - int startup_fct_index = - Smi::cast(startup_metadata->get(kExportedFunctionIndex))->value(); - CHECK_GE(startup_fct_index, 0); - CHECK_LT(startup_fct_index, clone_wasm_functions->length()); - Handle<Code> new_target = - clone_wasm_functions->GetValueChecked<Code>(isolate, startup_fct_index); - PatchJSWrapper(isolate, startup_fct_clone, new_target); - } - return clone; -} + //-------------------------------------------------------------------------- + // Set up the debug support for the new instance. + //-------------------------------------------------------------------------- + // TODO(wasm): avoid referencing this stuff from the instance, use it off + // the compiled module instead. See the following 3 assignments: + if (compiled_module->has_module_bytes()) { + instance->SetInternalField(kWasmModuleBytesString, + compiled_module->ptr_to_module_bytes()); + } -// Instantiates a wasm module as a JSObject. -// * allocates a backing store of {mem_size} bytes. -// * installs a named property "memory" for that buffer if exported -// * installs named properties on the object for exported functions -// * compiles wasm code to machine code -MaybeHandle<JSObject> WasmModule::Instantiate( - Isolate* isolate, Handle<FixedArray> compiled_module, - Handle<JSReceiver> ffi, Handle<JSArrayBuffer> memory) { - HistogramTimerScope wasm_instantiate_module_time_scope( - isolate->counters()->wasm_instantiate_module_time()); - ErrorThrower thrower(isolate, "WasmModule::Instantiate()"); - Factory* factory = isolate->factory(); + if (compiled_module->has_function_names()) { + instance->SetInternalField(kWasmFunctionNamesArray, + compiled_module->ptr_to_function_names()); + } - compiled_module = CloneModuleForInstance(isolate, compiled_module); + { + Handle<Object> handle = factory->NewNumber(num_imported_functions); + instance->SetInternalField(kWasmNumImportedFunctions, *handle); + } + + //-------------------------------------------------------------------------- + // Set up the runtime support for the new instance. + //-------------------------------------------------------------------------- + Handle<WeakCell> weak_link = isolate->factory()->NewWeakCell(instance); + + for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; + i < code_table->length(); ++i) { + Handle<Code> code = code_table->GetValueChecked<Code>(isolate, i); + if (code->kind() == Code::WASM_FUNCTION) { + Handle<FixedArray> deopt_data = + isolate->factory()->NewFixedArray(2, TENURED); + deopt_data->set(0, *weak_link); + deopt_data->set(1, Smi::FromInt(static_cast<int>(i))); + deopt_data->set_length(2); + code->set_deoptimization_data(*deopt_data); + } + } - // These fields are compulsory. - Handle<FixedArray> code_table = - compiled_module->GetValueChecked<FixedArray>(isolate, kFunctions); + //-------------------------------------------------------------------------- + // Set up the indirect function tables for the new instance. + //-------------------------------------------------------------------------- + { + std::vector<Handle<Code>> functions( + static_cast<size_t>(code_table->length())); + for (int i = 0; i < code_table->length(); ++i) { + functions[i] = code_table->GetValueChecked<Code>(isolate, i); + } - std::vector<Handle<Code>> functions( - static_cast<size_t>(code_table->length())); - for (int i = 0; i < code_table->length(); ++i) { - functions[static_cast<size_t>(i)] = - code_table->GetValueChecked<Code>(isolate, i); + if (compiled_module->has_indirect_function_tables()) { + Handle<FixedArray> indirect_tables_template = + compiled_module->indirect_function_tables(); + Handle<FixedArray> to_replace = + owner.is_null() ? indirect_tables_template + : handle(FixedArray::cast(owner->GetInternalField( + kWasmModuleFunctionTable))); + Handle<FixedArray> indirect_tables = SetupIndirectFunctionTable( + isolate, code_table, indirect_tables_template, to_replace); + for (int i = 0; i < indirect_tables->length(); ++i) { + Handle<FixedArray> metadata = + indirect_tables->GetValueChecked<FixedArray>(isolate, i); + uint32_t size = Smi::cast(metadata->get(kSize))->value(); + Handle<FixedArray> table = + metadata->GetValueChecked<FixedArray>(isolate, kTable); + PopulateFunctionTable(table, size, &functions); + } + instance->SetInternalField(kWasmModuleFunctionTable, *indirect_tables); + } } - LinkModuleFunctions(isolate, functions); - RecordStats(isolate, code_table); + //-------------------------------------------------------------------------- + // Set up the exports object for the new instance. + //-------------------------------------------------------------------------- + bool mem_export = compiled_module->export_memory(); + ModuleOrigin origin = compiled_module->origin(); - MaybeHandle<JSObject> nothing; + if (compiled_module->has_exports() || mem_export) { + PropertyDescriptor desc; + desc.set_writable(false); - Handle<Map> map = factory->NewMap( - JS_OBJECT_TYPE, - JSObject::kHeaderSize + kWasmModuleInternalFieldCount * kPointerSize); - Handle<JSObject> js_object = factory->NewJSObjectFromMap(map, TENURED); - js_object->SetInternalField(kWasmModuleCodeTable, *code_table); - - if (!(SetupInstanceHeap(isolate, compiled_module, js_object, memory, - &thrower) && - SetupGlobals(isolate, compiled_module, js_object, &thrower) && - SetupImports(isolate, compiled_module, js_object, &thrower, ffi) && - SetupExportsObject(compiled_module, isolate, js_object, &thrower))) { - return nothing; + Handle<JSObject> exports_object = instance; + if (origin == kWasmOrigin) { + // Create the "exports" object. + Handle<JSFunction> object_function = Handle<JSFunction>( + isolate->native_context()->object_function(), isolate); + exports_object = factory->NewJSObject(object_function, TENURED); + Handle<String> exports_name = factory->InternalizeUtf8String("exports"); + JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); + } + int first_export = -1; + // TODO(wasm): another iteration over the code objects. + for (int i = 0; i < code_table->length(); i++) { + Handle<Code> code = code_table->GetValueChecked<Code>(isolate, i); + if (code->kind() == Code::JS_TO_WASM_FUNCTION) { + first_export = i; + break; + } + } + if (compiled_module->has_exports()) { + Handle<FixedArray> exports = compiled_module->exports(); + int export_size = exports->length(); + for (int i = 0; i < export_size; ++i) { + Handle<FixedArray> export_data = + exports->GetValueChecked<FixedArray>(isolate, i); + Handle<String> name = + export_data->GetValueChecked<String>(isolate, kExportName); + int arity = Smi::cast(export_data->get(kExportArity))->value(); + MaybeHandle<ByteArray> signature = + export_data->GetValue<ByteArray>(isolate, kExportedSignature); + Handle<Code> export_code = + code_table->GetValueChecked<Code>(isolate, first_export + i); + Handle<JSFunction> function = WrapExportCodeAsJSFunction( + isolate, export_code, name, arity, signature, instance); + desc.set_value(function); + Maybe<bool> status = JSReceiver::DefineOwnProperty( + isolate, exports_object, name, &desc, Object::THROW_ON_ERROR); + if (!status.IsJust()) { + thrower->Error("export of %.*s failed.", name->length(), + name->ToCString().get()); + return nothing; + } + } + } + if (mem_export) { + // Export the memory as a named property. + Handle<JSArrayBuffer> buffer = Handle<JSArrayBuffer>( + JSArrayBuffer::cast(instance->GetInternalField(kWasmMemArrayBuffer))); + Handle<Object> memory_object = + WasmJs::CreateWasmMemoryObject(isolate, buffer, false, 0); + // TODO(titzer): export the memory with the correct name. + Handle<String> name = factory->InternalizeUtf8String("memory"); + JSObject::AddProperty(exports_object, name, memory_object, READ_ONLY); + } } - SetDebugSupport(factory, compiled_module, js_object); - - FlushAssemblyCache(isolate, code_table); - - MaybeHandle<FixedArray> maybe_indirect_tables = - compiled_module->GetValue<FixedArray>(isolate, - kTableOfIndirectFunctionTables); - Handle<FixedArray> indirect_tables_template; - if (maybe_indirect_tables.ToHandle(&indirect_tables_template)) { - Handle<FixedArray> indirect_tables = SetupIndirectFunctionTable( - isolate, code_table, indirect_tables_template); - for (int i = 0; i < indirect_tables->length(); ++i) { - Handle<FixedArray> metadata = - indirect_tables->GetValueChecked<FixedArray>(isolate, i); - uint32_t size = Smi::cast(metadata->get(kSize))->value(); - Handle<FixedArray> table = - metadata->GetValueChecked<FixedArray>(isolate, kTable); - wasm::PopulateFunctionTable(table, size, &functions); - } - js_object->SetInternalField(kWasmModuleFunctionTable, *indirect_tables); + if (num_imported_functions > 0 || !owner.is_null()) { + // If the code was cloned, or new imports were compiled, patch. + PatchDirectCalls(old_code_table, code_table, num_imported_functions); } + FlushICache(isolate, code_table); + + //-------------------------------------------------------------------------- // Run the start function if one was specified. - MaybeHandle<FixedArray> maybe_startup_fct = - compiled_module->GetValue<FixedArray>(isolate, kStartupFunction); - Handle<FixedArray> metadata; - if (maybe_startup_fct.ToHandle(&metadata)) { + //-------------------------------------------------------------------------- + if (compiled_module->has_startup_function()) { + Handle<FixedArray> startup_data = compiled_module->startup_function(); HandleScope scope(isolate); + int32_t start_index = + startup_data->GetValueChecked<Smi>(isolate, kExportedFunctionIndex) + ->value(); Handle<Code> startup_code = - metadata->GetValueChecked<Code>(isolate, kExportCode); - int arity = Smi::cast(metadata->get(kExportArity))->value(); + code_table->GetValueChecked<Code>(isolate, start_index); + int arity = Smi::cast(startup_data->get(kExportArity))->value(); MaybeHandle<ByteArray> startup_signature = - metadata->GetValue<ByteArray>(isolate, kExportedSignature); + startup_data->GetValue<ByteArray>(isolate, kExportedSignature); Handle<JSFunction> startup_fct = WrapExportCodeAsJSFunction( isolate, startup_code, factory->InternalizeUtf8String("start"), arity, - startup_signature, js_object); + startup_signature, instance); RecordStats(isolate, *startup_code); // Call the JS function. Handle<Object> undefined = isolate->factory()->undefined_value(); @@ -1403,35 +1507,86 @@ MaybeHandle<JSObject> WasmModule::Instantiate( Execution::Call(isolate, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { - thrower.Error("WASM.instantiateModule(): start function failed"); + thrower->Error("WASM.instantiateModule(): start function failed"); return nothing; } } - DCHECK(wasm::IsWasmObject(*js_object)); - return js_object; + DCHECK(wasm::IsWasmObject(*instance)); + + { + Handle<WeakCell> link_to_owner = factory->NewWeakCell(instance); + + Handle<Object> global_handle = isolate->global_handles()->Create(*instance); + Handle<WeakCell> link_to_clone = factory->NewWeakCell(compiled_module); + { + DisallowHeapAllocation no_gc; + compiled_module->set_weak_owning_instance(link_to_owner); + Handle<WeakCell> next; + if (link_to_original.ToHandle(&next) && !next->cleared()) { + WasmCompiledModule* original = WasmCompiledModule::cast(next->value()); + DCHECK(original->has_weak_owning_instance()); + DCHECK(!original->weak_owning_instance()->cleared()); + compiled_module->set_weak_next_instance(next); + original->set_weak_prev_instance(link_to_clone); + } + + compiled_module->set_weak_owning_instance(link_to_owner); + instance->SetInternalField(kWasmCompiledModule, *compiled_module); + GlobalHandles::MakeWeak(global_handle.location(), + global_handle.location(), &InstanceFinalizer, + v8::WeakCallbackType::kFinalizer); + } + } + TRACE("Finishing instance %d\n", compiled_module->instance_id()); + TRACE_CHAIN(WasmCompiledModule::cast(module_object->GetInternalField(0))); + return instance; } -// TODO(mtrofin): remove this once we move to WASM_DIRECT_CALL -Handle<Code> ModuleEnv::GetCodeOrPlaceholder(uint32_t index) const { - DCHECK(IsValidFunction(index)); - if (!placeholders.empty()) return placeholders[index]; - DCHECK_NOT_NULL(instance); - return instance->function_code[index]; +#if DEBUG +uint32_t WasmCompiledModule::instance_id_counter_ = 0; +#endif + +Handle<WasmCompiledModule> WasmCompiledModule::New(Isolate* isolate, + uint32_t min_memory_pages, + uint32_t globals_size, + bool export_memory, + ModuleOrigin origin) { + Handle<FixedArray> ret = + isolate->factory()->NewFixedArray(PropertyIndices::Count, TENURED); + // Globals size is expected to fit into an int without overflow. This is not + // supported by the spec at the moment, however, we don't support array + // buffer sizes over 1g, so, for now, we avoid alocating a HeapNumber for + // the globals size. The CHECK guards this assumption. + CHECK_GE(static_cast<int>(globals_size), 0); + ret->set(kID_min_memory_pages, + Smi::FromInt(static_cast<int>(min_memory_pages))); + ret->set(kID_globals_size, Smi::FromInt(static_cast<int>(globals_size))); + ret->set(kID_export_memory, Smi::FromInt(static_cast<int>(export_memory))); + ret->set(kID_origin, Smi::FromInt(static_cast<int>(origin))); + WasmCompiledModule::cast(*ret)->Init(); + return handle(WasmCompiledModule::cast(*ret)); } -Handle<Code> ModuleEnv::GetImportCode(uint32_t index) { - DCHECK(IsValidImport(index)); - return instance ? instance->import_code[index] : Handle<Code>::null(); +void WasmCompiledModule::Init() { +#if DEBUG + set(kID_instance_id, Smi::FromInt(instance_id_counter_++)); + TRACE("New compiled module id: %d\n", instance_id()); +#endif } -compiler::CallDescriptor* ModuleEnv::GetCallDescriptor(Zone* zone, - uint32_t index) { - DCHECK(IsValidFunction(index)); - // Always make a direct call to whatever is in the table at that location. - // A wrapper will be generated for FFI calls. - const WasmFunction* function = &module->functions[index]; - return GetWasmCallDescriptor(zone, function->sig); +void WasmCompiledModule::PrintInstancesChain() { +#if DEBUG + if (!FLAG_trace_wasm_instances) return; + for (WasmCompiledModule* current = this; current != nullptr;) { + PrintF("->%d", current->instance_id()); + if (current->ptr_to_weak_next_instance() == nullptr) break; + CHECK(!current->ptr_to_weak_next_instance()->cleared()); + current = + WasmCompiledModule::cast(current->ptr_to_weak_next_instance()->value()); + } + PrintF("\n"); +#endif } Handle<Object> GetWasmFunctionNameOrNull(Isolate* isolate, Handle<Object> wasm, @@ -1577,93 +1732,188 @@ int GetNumberOfFunctions(JSObject* wasm) { return ByteArray::cast(func_names_obj)->get_int(0); } -Handle<JSObject> CreateCompiledModuleObject( - Isolate* isolate, Handle<FixedArray> compiled_module) { - Handle<JSFunction> module_cons( - isolate->native_context()->wasm_module_constructor()); - Handle<JSObject> module_obj = isolate->factory()->NewJSObject(module_cons); +Handle<JSObject> CreateCompiledModuleObject(Isolate* isolate, + Handle<FixedArray> compiled_module, + ModuleOrigin origin) { + Handle<JSObject> module_obj; + if (origin == ModuleOrigin::kWasmOrigin) { + Handle<JSFunction> module_cons( + isolate->native_context()->wasm_module_constructor()); + module_obj = isolate->factory()->NewJSObject(module_cons); + } else { + DCHECK(origin == ModuleOrigin::kAsmJsOrigin); + Handle<Map> map = isolate->factory()->NewMap( + JS_OBJECT_TYPE, JSObject::kHeaderSize + kPointerSize); + module_obj = isolate->factory()->NewJSObjectFromMap(map, TENURED); + } module_obj->SetInternalField(0, *compiled_module); - Handle<Symbol> module_sym(isolate->native_context()->wasm_module_sym()); - Object::SetProperty(module_obj, module_sym, module_obj, STRICT).Check(); + if (origin == ModuleOrigin::kWasmOrigin) { + Handle<Symbol> module_sym(isolate->native_context()->wasm_module_sym()); + Object::SetProperty(module_obj, module_sym, module_obj, STRICT).Check(); + } + Handle<WeakCell> link_to_module = isolate->factory()->NewWeakCell(module_obj); + WasmCompiledModule::cast(*compiled_module) + ->set_weak_module_object(link_to_module); return module_obj; } -namespace testing { - -int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start, - const byte* module_end, bool asm_js) { - HandleScope scope(isolate); +MaybeHandle<JSObject> CreateModuleObjectFromBytes(Isolate* isolate, + const byte* start, + const byte* end, + ErrorThrower* thrower, + ModuleOrigin origin) { + MaybeHandle<JSObject> nothing; Zone zone(isolate->allocator()); - ErrorThrower thrower(isolate, "CompileAndRunWasmModule"); - - // Decode the module, but don't verify function bodies, since we'll - // be compiling them anyway. - ModuleResult decoding_result = - DecodeWasmModule(isolate, &zone, module_start, module_end, false, - asm_js ? kAsmJsOrigin : kWasmOrigin); - - std::unique_ptr<const WasmModule> module(decoding_result.val); - if (decoding_result.failed()) { - // Module verification failed. throw. - thrower.Error("WASM.compileRun() failed: %s", - decoding_result.error_msg.get()); - return -1; + ModuleResult result = + DecodeWasmModule(isolate, &zone, start, end, false, origin); + std::unique_ptr<const WasmModule> decoded_module(result.val); + if (result.failed()) { + thrower->Failed("Wasm decoding failed", result); + return nothing; } + MaybeHandle<FixedArray> compiled_module = + decoded_module->CompileFunctions(isolate, thrower); + if (compiled_module.is_null()) return nothing; - if (module->import_table.size() > 0) { - thrower.Error("Not supported: module has imports."); - } - if (module->export_table.size() == 0) { - thrower.Error("Not supported: module has no exports."); + return CreateCompiledModuleObject(isolate, compiled_module.ToHandleChecked(), + origin); +} + +bool ValidateModuleBytes(Isolate* isolate, const byte* start, const byte* end, + ErrorThrower* thrower, ModuleOrigin origin) { + Zone zone(isolate->allocator()); + ModuleResult result = + DecodeWasmModule(isolate, &zone, start, end, false, origin); + if (result.ok()) { + DCHECK_NOT_NULL(result.val); + delete result.val; + return true; } + return false; +} - if (thrower.error()) return -1; - MaybeHandle<FixedArray> compiled_module = - module->CompileFunctions(isolate, &thrower); +MaybeHandle<JSArrayBuffer> GetInstanceMemory(Isolate* isolate, + Handle<JSObject> instance) { + Object* mem = instance->GetInternalField(kWasmMemArrayBuffer); + DCHECK(IsWasmObject(*instance)); + if (mem->IsUndefined(isolate)) return MaybeHandle<JSArrayBuffer>(); + return Handle<JSArrayBuffer>(JSArrayBuffer::cast(mem)); +} - if (compiled_module.is_null()) return -1; - Handle<JSObject> instance = - WasmModule::Instantiate(isolate, compiled_module.ToHandleChecked(), - Handle<JSReceiver>::null(), - Handle<JSArrayBuffer>::null()) - .ToHandleChecked(); +void SetInstanceMemory(Handle<JSObject> instance, JSArrayBuffer* buffer) { + DisallowHeapAllocation no_gc; + DCHECK(IsWasmObject(*instance)); + instance->SetInternalField(kWasmMemArrayBuffer, buffer); + WasmCompiledModule* module = + WasmCompiledModule::cast(instance->GetInternalField(kWasmCompiledModule)); + module->set_ptr_to_heap(buffer); +} - return CallFunction(isolate, instance, &thrower, "main", 0, nullptr); +int32_t GetInstanceMemorySize(Isolate* isolate, Handle<JSObject> instance) { + MaybeHandle<JSArrayBuffer> maybe_mem_buffer = + GetInstanceMemory(isolate, instance); + Handle<JSArrayBuffer> buffer; + if (!maybe_mem_buffer.ToHandle(&buffer)) { + return 0; + } else { + return buffer->byte_length()->Number() / WasmModule::kPageSize; + } } -int32_t CallFunction(Isolate* isolate, Handle<JSObject> instance, - ErrorThrower* thrower, const char* name, int argc, - Handle<Object> argv[]) { - Handle<Name> exports = isolate->factory()->InternalizeUtf8String("exports"); - Handle<JSObject> exports_object = Handle<JSObject>::cast( - JSObject::GetProperty(instance, exports).ToHandleChecked()); - Handle<Name> main_name = isolate->factory()->NewStringFromAsciiChecked(name); - PropertyDescriptor desc; - Maybe<bool> property_found = JSReceiver::GetOwnPropertyDescriptor( - isolate, exports_object, main_name, &desc); - if (!property_found.FromMaybe(false)) return -1; - - Handle<JSFunction> main_export = Handle<JSFunction>::cast(desc.value()); - - // Call the JS function. - Handle<Object> undefined = isolate->factory()->undefined_value(); - MaybeHandle<Object> retval = - Execution::Call(isolate, main_export, undefined, argc, argv); - - // The result should be a number. - if (retval.is_null()) { - thrower->Error("WASM.compileRun() failed: Invocation was null"); +int32_t GrowInstanceMemory(Isolate* isolate, Handle<JSObject> instance, + uint32_t pages) { + Address old_mem_start = nullptr; + uint32_t old_size = 0, new_size = 0; + + MaybeHandle<JSArrayBuffer> maybe_mem_buffer = + GetInstanceMemory(isolate, instance); + Handle<JSArrayBuffer> old_buffer; + if (!maybe_mem_buffer.ToHandle(&old_buffer)) { + // If module object does not have linear memory associated with it, + // Allocate new array buffer of given size. + // TODO(gdeepti): Fix bounds check to take into account size of memtype. + new_size = pages * WasmModule::kPageSize; + // The code generated in the wasm compiler guarantees this precondition. + DCHECK(pages <= WasmModule::kMaxMemPages); + } else { + old_mem_start = static_cast<Address>(old_buffer->backing_store()); + old_size = old_buffer->byte_length()->Number(); + // If the old memory was zero-sized, we should have been in the + // "undefined" case above. + DCHECK_NOT_NULL(old_mem_start); + DCHECK_NE(0, old_size); + DCHECK(old_size + pages * WasmModule::kPageSize <= + std::numeric_limits<uint32_t>::max()); + new_size = old_size + pages * WasmModule::kPageSize; + } + + if (new_size <= old_size || + WasmModule::kMaxMemPages * WasmModule::kPageSize <= new_size) { return -1; } - Handle<Object> result = retval.ToHandleChecked(); - if (result->IsSmi()) { - return Smi::cast(*result)->value(); + Handle<JSArrayBuffer> buffer = NewArrayBuffer(isolate, new_size); + if (buffer.is_null()) return -1; + Address new_mem_start = static_cast<Address>(buffer->backing_store()); + if (old_size != 0) { + memcpy(new_mem_start, old_mem_start, old_size); } - if (result->IsHeapNumber()) { - return static_cast<int32_t>(HeapNumber::cast(*result)->value()); + SetInstanceMemory(instance, *buffer); + if (!UpdateWasmModuleMemory(instance, old_mem_start, new_mem_start, old_size, + new_size)) { + return -1; } - thrower->Error("WASM.compileRun() failed: Return value should be number"); - return -1; + DCHECK(old_size % WasmModule::kPageSize == 0); + return (old_size / WasmModule::kPageSize); +} + +namespace testing { + +void ValidateInstancesChain(Isolate* isolate, Handle<JSObject> module_obj, + int instance_count) { + CHECK_GE(instance_count, 0); + DisallowHeapAllocation no_gc; + WasmCompiledModule* compiled_module = + WasmCompiledModule::cast(module_obj->GetInternalField(0)); + CHECK_EQ( + JSObject::cast(compiled_module->ptr_to_weak_module_object()->value()), + *module_obj); + Object* prev = nullptr; + int found_instances = compiled_module->has_weak_owning_instance() ? 1 : 0; + WasmCompiledModule* current_instance = compiled_module; + while (current_instance->has_weak_next_instance()) { + CHECK((prev == nullptr && !current_instance->has_weak_prev_instance()) || + current_instance->ptr_to_weak_prev_instance()->value() == prev); + CHECK_EQ(current_instance->ptr_to_weak_module_object()->value(), + *module_obj); + CHECK( + IsWasmObject(current_instance->ptr_to_weak_owning_instance()->value())); + prev = current_instance; + current_instance = WasmCompiledModule::cast( + current_instance->ptr_to_weak_next_instance()->value()); + ++found_instances; + CHECK_LE(found_instances, instance_count); + } + CHECK_EQ(found_instances, instance_count); +} + +void ValidateModuleState(Isolate* isolate, Handle<JSObject> module_obj) { + DisallowHeapAllocation no_gc; + WasmCompiledModule* compiled_module = + WasmCompiledModule::cast(module_obj->GetInternalField(0)); + CHECK(compiled_module->has_weak_module_object()); + CHECK_EQ(compiled_module->ptr_to_weak_module_object()->value(), *module_obj); + CHECK(!compiled_module->has_weak_prev_instance()); + CHECK(!compiled_module->has_weak_next_instance()); + CHECK(!compiled_module->has_weak_owning_instance()); +} + +void ValidateOrphanedInstance(Isolate* isolate, Handle<JSObject> instance) { + DisallowHeapAllocation no_gc; + CHECK(IsWasmObject(*instance)); + WasmCompiledModule* compiled_module = + WasmCompiledModule::cast(instance->GetInternalField(kWasmCompiledModule)); + CHECK(compiled_module->has_weak_module_object()); + CHECK(compiled_module->ptr_to_weak_module_object()->cleared()); } } // namespace testing diff --git a/deps/v8/src/wasm/wasm-module.h b/deps/v8/src/wasm/wasm-module.h index 0c3df51d76..ac75042392 100644 --- a/deps/v8/src/wasm/wasm-module.h +++ b/deps/v8/src/wasm/wasm-module.h @@ -27,84 +27,71 @@ const size_t kMaxModuleSize = 1024 * 1024 * 1024; const size_t kMaxFunctionSize = 128 * 1024; const size_t kMaxStringSize = 256; const uint32_t kWasmMagic = 0x6d736100; -const uint32_t kWasmVersion = 0x0b; +const uint32_t kWasmVersion = 0x0c; + const uint8_t kWasmFunctionTypeForm = 0x40; +const uint8_t kWasmAnyFunctionTypeForm = 0x20; + +enum WasmSectionCode { + kUnknownSectionCode = 0, // code for unknown sections + kTypeSectionCode = 1, // Function signature declarations + kImportSectionCode = 2, // Import declarations + kFunctionSectionCode = 3, // Function declarations + kTableSectionCode = 4, // Indirect function table and other tables + kMemorySectionCode = 5, // Memory attributes + kGlobalSectionCode = 6, // Global declarations + kExportSectionCode = 7, // Exports + kStartSectionCode = 8, // Start function declaration + kElementSectionCode = 9, // Elements section + kCodeSectionCode = 10, // Function code + kDataSectionCode = 11, // Data segments + kNameSectionCode = 12, // Name section (encoded as a string) +}; + +inline bool IsValidSectionCode(uint8_t byte) { + return kTypeSectionCode <= byte && byte <= kDataSectionCode; +} -// WebAssembly sections are named as strings in the binary format, but -// internally V8 uses an enum to handle them. -// -// Entries have the form F(enumerator, string). -#define FOR_EACH_WASM_SECTION_TYPE(F) \ - F(Signatures, 1, "type") \ - F(ImportTable, 2, "import") \ - F(FunctionSignatures, 3, "function") \ - F(FunctionTable, 4, "table") \ - F(Memory, 5, "memory") \ - F(ExportTable, 6, "export") \ - F(StartFunction, 7, "start") \ - F(FunctionBodies, 8, "code") \ - F(DataSegments, 9, "data") \ - F(Names, 10, "name") \ - F(Globals, 0, "global") \ - F(End, 0, "end") - -// Contants for the above section types: {LEB128 length, characters...}. -#define WASM_SECTION_MEMORY 6, 'm', 'e', 'm', 'o', 'r', 'y' -#define WASM_SECTION_SIGNATURES 4, 't', 'y', 'p', 'e' -#define WASM_SECTION_GLOBALS 6, 'g', 'l', 'o', 'b', 'a', 'l' -#define WASM_SECTION_DATA_SEGMENTS 4, 'd', 'a', 't', 'a' -#define WASM_SECTION_FUNCTION_TABLE 5, 't', 'a', 'b', 'l', 'e' -#define WASM_SECTION_END 3, 'e', 'n', 'd' -#define WASM_SECTION_START_FUNCTION 5, 's', 't', 'a', 'r', 't' -#define WASM_SECTION_IMPORT_TABLE 6, 'i', 'm', 'p', 'o', 'r', 't' -#define WASM_SECTION_EXPORT_TABLE 6, 'e', 'x', 'p', 'o', 'r', 't' -#define WASM_SECTION_FUNCTION_SIGNATURES \ - 8, 'f', 'u', 'n', 'c', 't', 'i', 'o', 'n' -#define WASM_SECTION_FUNCTION_BODIES 4, 'c', 'o', 'd', 'e' -#define WASM_SECTION_NAMES 4, 'n', 'a', 'm', 'e' - -// Constants for the above section headers' size (LEB128 + characters). -#define WASM_SECTION_MEMORY_SIZE ((size_t)7) -#define WASM_SECTION_SIGNATURES_SIZE ((size_t)5) -#define WASM_SECTION_GLOBALS_SIZE ((size_t)7) -#define WASM_SECTION_DATA_SEGMENTS_SIZE ((size_t)5) -#define WASM_SECTION_FUNCTION_TABLE_SIZE ((size_t)6) -#define WASM_SECTION_END_SIZE ((size_t)4) -#define WASM_SECTION_START_FUNCTION_SIZE ((size_t)6) -#define WASM_SECTION_IMPORT_TABLE_SIZE ((size_t)7) -#define WASM_SECTION_EXPORT_TABLE_SIZE ((size_t)7) -#define WASM_SECTION_FUNCTION_SIGNATURES_SIZE ((size_t)9) -#define WASM_SECTION_FUNCTION_BODIES_SIZE ((size_t)5) -#define WASM_SECTION_NAMES_SIZE ((size_t)5) +const char* SectionName(WasmSectionCode code); class WasmDebugInfo; -struct WasmSection { - enum class Code : uint32_t { -#define F(enumerator, order, string) enumerator, - FOR_EACH_WASM_SECTION_TYPE(F) -#undef F - Max - }; - static WasmSection::Code begin(); - static WasmSection::Code end(); - static WasmSection::Code next(WasmSection::Code code); - static const char* getName(Code code); - static int getOrder(Code code); - static size_t getNameLength(Code code); - static WasmSection::Code lookup(const byte* string, uint32_t length); +// Constants for fixed-size elements within a module. +static const uint32_t kMaxReturnCount = 1; +static const uint8_t kResizableMaximumFlag = 1; +static const int32_t kInvalidFunctionIndex = -1; + +enum WasmExternalKind { + kExternalFunction = 0, + kExternalTable = 1, + kExternalMemory = 2, + kExternalGlobal = 3 }; -enum WasmFunctionDeclBit { - kDeclFunctionName = 0x01, - kDeclFunctionExport = 0x08 +// Representation of an initializer expression. +struct WasmInitExpr { + enum WasmInitKind { + kNone, + kGlobalIndex, + kI32Const, + kI64Const, + kF32Const, + kF64Const + } kind; + + union { + int32_t i32_const; + int64_t i64_const; + float f32_const; + double f64_const; + uint32_t global_index; + } val; }; -// Constants for fixed-size elements within a module. -static const size_t kDeclMemorySize = 3; -static const size_t kDeclDataSegmentSize = 13; - -static const uint32_t kMaxReturnCount = 1; +#define NO_INIT \ + { \ + WasmInitExpr::kNone, { 0u } \ + } // Static representation of a WASM function. struct WasmFunction { @@ -115,54 +102,69 @@ struct WasmFunction { uint32_t name_length; // length in bytes of the name. uint32_t code_start_offset; // offset in the module bytes of code start. uint32_t code_end_offset; // offset in the module bytes of code end. -}; - -// Static representation of an imported WASM function. -struct WasmImport { - FunctionSig* sig; // signature of the function. - uint32_t sig_index; // index into the signature table. - uint32_t module_name_offset; // offset in module bytes of the module name. - uint32_t module_name_length; // length in bytes of the module name. - uint32_t function_name_offset; // offset in module bytes of the import name. - uint32_t function_name_length; // length in bytes of the import name. -}; - -// Static representation of an exported WASM function. -struct WasmExport { - uint32_t func_index; // index into the function table. - uint32_t name_offset; // offset in module bytes of the name to export. - uint32_t name_length; // length in bytes of the exported name. + bool imported; + bool exported; }; // Static representation of a wasm global variable. struct WasmGlobal { - uint32_t name_offset; // offset in the module bytes of the name, if any. - uint32_t name_length; // length in bytes of the global name. LocalType type; // type of the global. - uint32_t offset; // offset from beginning of globals area. - bool exported; // true if this global is exported. + bool mutability; // {true} if mutable. + WasmInitExpr init; // the initialization expression of the global. + uint32_t offset; // offset into global memory. + bool imported; // true if imported. + bool exported; // true if exported. }; // Static representation of a wasm data segment. struct WasmDataSegment { - uint32_t dest_addr; // destination memory address of the data. + WasmInitExpr dest_addr; // destination memory address of the data. uint32_t source_offset; // start offset in the module bytes. uint32_t source_size; // end offset in the module bytes. - bool init; // true if loaded upon instantiation. }; // Static representation of a wasm indirect call table. struct WasmIndirectFunctionTable { - uint32_t size; // initial table size. - uint32_t max_size; // maximum table size. - std::vector<uint16_t> values; // function table. + uint32_t size; // initial table size. + uint32_t max_size; // maximum table size. + std::vector<int32_t> values; // function table, -1 indicating invalid. + bool imported; // true if imported. + bool exported; // true if exported. +}; + +// Static representation of how to initialize a table. +struct WasmTableInit { + uint32_t table_index; + WasmInitExpr offset; + std::vector<uint32_t> entries; +}; + +// Static representation of a WASM import. +struct WasmImport { + uint32_t module_name_length; // length in bytes of the module name. + uint32_t module_name_offset; // offset in module bytes of the module name. + uint32_t field_name_length; // length in bytes of the import name. + uint32_t field_name_offset; // offset in module bytes of the import name. + WasmExternalKind kind; // kind of the import. + uint32_t index; // index into the respective space. +}; + +// Static representation of a WASM export. +struct WasmExport { + uint32_t name_length; // length in bytes of the exported name. + uint32_t name_offset; // offset in module bytes of the name to export. + WasmExternalKind kind; // kind of the export. + uint32_t index; // index into the respective space. }; enum ModuleOrigin { kWasmOrigin, kAsmJsOrigin }; +class WasmCompiledModule; + // Static representation of a module. struct WasmModule { static const uint32_t kPageSize = 0x10000; // Page size, 64kb. + static const uint32_t kMaxLegalPages = 65536; // Maximum legal pages static const uint32_t kMinMemPages = 1; // Minimum memory size = 64kb static const uint32_t kMaxMemPages = 16384; // Maximum memory size = 1gb @@ -171,7 +173,6 @@ struct WasmModule { uint32_t min_mem_pages; // minimum size of the memory in 64k pages. uint32_t max_mem_pages; // maximum size of the memory in 64k pages. bool mem_export; // true if the memory is exported. - bool mem_external; // true if the memory is external. // TODO(wasm): reconcile start function index being an int with // the fact that we index on uint32_t, so we may technically not be // able to represent some start_function_index -es. @@ -180,12 +181,16 @@ struct WasmModule { std::vector<WasmGlobal> globals; // globals in this module. uint32_t globals_size; // size of globals table. + uint32_t num_imported_functions; // number of imported functions. + uint32_t num_declared_functions; // number of declared functions. + uint32_t num_exported_functions; // number of exported functions. std::vector<FunctionSig*> signatures; // signatures in this module. std::vector<WasmFunction> functions; // functions in this module. std::vector<WasmDataSegment> data_segments; // data segments in this module. std::vector<WasmIndirectFunctionTable> function_tables; // function tables. std::vector<WasmImport> import_table; // import table. std::vector<WasmExport> export_table; // export table. + std::vector<WasmTableInit> table_inits; // initializations of tables // We store the semaphore here to extend its lifetime. In <libc-2.21, which we // use on the try bots, semaphore::Wait() can return while some compilation // tasks are still executing semaphore::Signal(). If the semaphore is cleaned @@ -233,13 +238,12 @@ struct WasmModule { } // Creates a new instantiation of the module in the given isolate. - static MaybeHandle<JSObject> Instantiate(Isolate* isolate, - Handle<FixedArray> compiled_module, - Handle<JSReceiver> ffi, - Handle<JSArrayBuffer> memory); + V8_EXPORT_PRIVATE static MaybeHandle<JSObject> Instantiate( + Isolate* isolate, ErrorThrower* thrower, Handle<JSObject> module_object, + Handle<JSReceiver> ffi, Handle<JSArrayBuffer> memory); - MaybeHandle<FixedArray> CompileFunctions(Isolate* isolate, - ErrorThrower* thrower) const; + MaybeHandle<WasmCompiledModule> CompileFunctions(Isolate* isolate, + ErrorThrower* thrower) const; private: DISALLOW_COPY_AND_ASSIGN(WasmModule); @@ -255,7 +259,6 @@ struct WasmModuleInstance { Handle<JSArrayBuffer> globals_buffer; // Handle to array buffer of globals. std::vector<Handle<FixedArray>> function_tables; // indirect function tables. std::vector<Handle<Code>> function_code; // code objects for each function. - std::vector<Handle<Code>> import_code; // code objects for each import. // -- raw memory ------------------------------------------------------------ byte* mem_start; // start of linear memory. uint32_t mem_size; // size of the linear memory. @@ -266,7 +269,6 @@ struct WasmModuleInstance { : module(m), function_tables(m->function_tables.size()), function_code(m->functions.size()), - import_code(m->import_table.size()), mem_start(nullptr), mem_size(0), globals_start(nullptr) {} @@ -278,9 +280,6 @@ struct ModuleEnv { const WasmModule* module; WasmModuleInstance* instance; ModuleOrigin origin; - // TODO(mtrofin): remove this once we introduce WASM_DIRECT_CALL - // reloc infos. - std::vector<Handle<Code>> placeholders; bool IsValidGlobal(uint32_t index) const { return module && index < module->globals.size(); @@ -291,9 +290,6 @@ struct ModuleEnv { bool IsValidSignature(uint32_t index) const { return module && index < module->signatures.size(); } - bool IsValidImport(uint32_t index) const { - return module && index < module->import_table.size(); - } bool IsValidTable(uint32_t index) const { return module && index < module->function_tables.size(); } @@ -305,10 +301,6 @@ struct ModuleEnv { DCHECK(IsValidFunction(index)); return module->functions[index].sig; } - FunctionSig* GetImportSignature(uint32_t index) { - DCHECK(IsValidImport(index)); - return module->import_table[index].sig; - } FunctionSig* GetSignature(uint32_t index) { DCHECK(IsValidSignature(index)); return module->signatures[index]; @@ -320,14 +312,15 @@ struct ModuleEnv { bool asm_js() { return origin == kAsmJsOrigin; } - Handle<Code> GetCodeOrPlaceholder(uint32_t index) const; - Handle<Code> GetImportCode(uint32_t index); + Handle<Code> GetFunctionCode(uint32_t index) { + DCHECK_NOT_NULL(instance); + return instance->function_code[index]; + } static compiler::CallDescriptor* GetWasmCallDescriptor(Zone* zone, FunctionSig* sig); static compiler::CallDescriptor* GetI32WasmCallDescriptor( Zone* zone, compiler::CallDescriptor* descriptor); - compiler::CallDescriptor* GetCallDescriptor(Zone* zone, uint32_t index); }; // A helper for printing out the names of functions. @@ -347,6 +340,128 @@ typedef Result<WasmFunction*> FunctionResult; typedef std::vector<std::pair<int, int>> FunctionOffsets; typedef Result<FunctionOffsets> FunctionOffsetsResult; +class WasmCompiledModule : public FixedArray { + public: + static WasmCompiledModule* cast(Object* fixed_array) { + return reinterpret_cast<WasmCompiledModule*>(fixed_array); + } + +#define WCM_OBJECT_OR_WEAK(TYPE, NAME, ID) \ + Handle<TYPE> NAME() const { return handle(ptr_to_##NAME()); } \ + \ + MaybeHandle<TYPE> maybe_##NAME() const { \ + if (has_##NAME()) return NAME(); \ + return MaybeHandle<TYPE>(); \ + } \ + \ + TYPE* ptr_to_##NAME() const { \ + Object* obj = get(ID); \ + if (!obj->Is##TYPE()) return nullptr; \ + return TYPE::cast(obj); \ + } \ + \ + void set_##NAME(Handle<TYPE> value) { set_ptr_to_##NAME(*value); } \ + \ + void set_ptr_to_##NAME(TYPE* value) { set(ID, value); } \ + \ + bool has_##NAME() const { return get(ID)->Is##TYPE(); } \ + \ + void reset_##NAME() { set_undefined(ID); } + +#define WCM_OBJECT(TYPE, NAME) WCM_OBJECT_OR_WEAK(TYPE, NAME, kID_##NAME) + +#define WCM_SMALL_NUMBER(TYPE, NAME) \ + TYPE NAME() const { \ + return static_cast<TYPE>(Smi::cast(get(kID_##NAME))->value()); \ + } + +#define WCM_WEAK_LINK(TYPE, NAME) \ + WCM_OBJECT_OR_WEAK(WeakCell, weak_##NAME, kID_##NAME); \ + \ + Handle<TYPE> NAME() const { \ + return handle(TYPE::cast(weak_##NAME()->value())); \ + } + +#define CORE_WCM_PROPERTY_TABLE(MACRO) \ + MACRO(OBJECT, FixedArray, code_table) \ + MACRO(OBJECT, FixedArray, import_data) \ + MACRO(OBJECT, FixedArray, exports) \ + MACRO(OBJECT, FixedArray, startup_function) \ + MACRO(OBJECT, FixedArray, indirect_function_tables) \ + MACRO(OBJECT, String, module_bytes) \ + MACRO(OBJECT, ByteArray, function_names) \ + MACRO(SMALL_NUMBER, uint32_t, min_memory_pages) \ + MACRO(OBJECT, FixedArray, data_segments_info) \ + MACRO(OBJECT, ByteArray, data_segments) \ + MACRO(SMALL_NUMBER, uint32_t, globals_size) \ + MACRO(OBJECT, JSArrayBuffer, heap) \ + MACRO(SMALL_NUMBER, bool, export_memory) \ + MACRO(SMALL_NUMBER, ModuleOrigin, origin) \ + MACRO(WEAK_LINK, WasmCompiledModule, next_instance) \ + MACRO(WEAK_LINK, WasmCompiledModule, prev_instance) \ + MACRO(WEAK_LINK, JSObject, owning_instance) \ + MACRO(WEAK_LINK, JSObject, module_object) + +#if DEBUG +#define DEBUG_ONLY_TABLE(MACRO) MACRO(SMALL_NUMBER, uint32_t, instance_id) +#else +#define DEBUG_ONLY_TABLE(IGNORE) + uint32_t instance_id() const { return -1; } +#endif + +#define WCM_PROPERTY_TABLE(MACRO) \ + CORE_WCM_PROPERTY_TABLE(MACRO) \ + DEBUG_ONLY_TABLE(MACRO) + + private: + enum PropertyIndices { +#define INDICES(IGNORE1, IGNORE2, NAME) kID_##NAME, + WCM_PROPERTY_TABLE(INDICES) Count +#undef INDICES + }; + + public: + static Handle<WasmCompiledModule> New(Isolate* isolate, + uint32_t min_memory_pages, + uint32_t globals_size, + bool export_memory, + ModuleOrigin origin); + + static Handle<WasmCompiledModule> Clone(Isolate* isolate, + Handle<WasmCompiledModule> module) { + Handle<WasmCompiledModule> ret = Handle<WasmCompiledModule>::cast( + isolate->factory()->CopyFixedArray(module)); + ret->Init(); + ret->reset_weak_owning_instance(); + ret->reset_weak_next_instance(); + ret->reset_weak_prev_instance(); + return ret; + } + + uint32_t mem_size() const { + DCHECK(has_heap()); + return heap()->byte_length()->Number(); + } + + uint32_t default_mem_size() const { + return min_memory_pages() * WasmModule::kPageSize; + } + +#define DECLARATION(KIND, TYPE, NAME) WCM_##KIND(TYPE, NAME) + WCM_PROPERTY_TABLE(DECLARATION) +#undef DECLARATION + + void PrintInstancesChain(); + + private: +#if DEBUG + static uint32_t instance_id_counter_; +#endif + void Init(); + + DISALLOW_IMPLICIT_CONSTRUCTORS(WasmCompiledModule); +}; + // Extract a function name from the given wasm object. // Returns "<WASM UNNAMED>" if the function is unnamed or the name is not a // valid UTF-8 string. @@ -399,18 +514,38 @@ void PopulateFunctionTable(Handle<FixedArray> table, uint32_t table_size, const std::vector<Handle<Code>>* code_table); Handle<JSObject> CreateCompiledModuleObject(Isolate* isolate, - Handle<FixedArray> compiled_module); + Handle<FixedArray> compiled_module, + ModuleOrigin origin); + +V8_EXPORT_PRIVATE MaybeHandle<JSObject> CreateModuleObjectFromBytes( + Isolate* isolate, const byte* start, const byte* end, ErrorThrower* thrower, + ModuleOrigin origin); + +V8_EXPORT_PRIVATE bool ValidateModuleBytes(Isolate* isolate, const byte* start, + const byte* end, + ErrorThrower* thrower, + ModuleOrigin origin); + +// Get the number of imported functions for a WASM instance. +uint32_t GetNumImportedFunctions(Handle<JSObject> wasm_object); + +// Assumed to be called with a code object associated to a wasm module instance. +// Intended to be called from runtime functions. +// Returns nullptr on failing to get owning instance. +Object* GetOwningWasmInstance(Code* code); + +int32_t GetInstanceMemorySize(Isolate* isolate, Handle<JSObject> instance); + +int32_t GrowInstanceMemory(Isolate* isolate, Handle<JSObject> instance, + uint32_t pages); namespace testing { -// Decode, verify, and run the function labeled "main" in the -// given encoded module. The module should have no imports. -int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start, - const byte* module_end, bool asm_js = false); +void ValidateInstancesChain(Isolate* isolate, Handle<JSObject> module_obj, + int instance_count); +void ValidateModuleState(Isolate* isolate, Handle<JSObject> module_obj); +void ValidateOrphanedInstance(Isolate* isolate, Handle<JSObject> instance); -int32_t CallFunction(Isolate* isolate, Handle<JSObject> instance, - ErrorThrower* thrower, const char* name, int argc, - Handle<Object> argv[]); } // namespace testing } // namespace wasm } // namespace internal diff --git a/deps/v8/src/wasm/wasm-opcodes.cc b/deps/v8/src/wasm/wasm-opcodes.cc index 8f54207661..cd2dde4748 100644 --- a/deps/v8/src/wasm/wasm-opcodes.cc +++ b/deps/v8/src/wasm/wasm-opcodes.cc @@ -38,6 +38,18 @@ const char* WasmOpcodes::ShortOpcodeName(WasmOpcode opcode) { return "Unknown"; } +bool WasmOpcodes::IsPrefixOpcode(WasmOpcode opcode) { + switch (opcode) { +#define CHECK_PREFIX(name, opcode) \ + case k##name##Prefix: \ + return true; + FOREACH_PREFIX(CHECK_PREFIX) +#undef CHECK_PREFIX + default: + return false; + } +} + std::ostream& operator<<(std::ostream& os, const FunctionSig& sig) { if (sig.return_count() == 0) os << "v"; for (size_t i = 0; i < sig.return_count(); ++i) { @@ -74,6 +86,7 @@ static const FunctionSig* kSimdExprSigs[] = { nullptr, FOREACH_SIMD_SIGNATURE(DECLARE_SIMD_SIG_ENTRY)}; static byte kSimpleExprSigTable[256]; +static byte kSimpleAsmjsExprSigTable[256]; static byte kSimdExprSigTable[256]; // Initialize the signature table. @@ -81,14 +94,16 @@ static void InitSigTables() { #define SET_SIG_TABLE(name, opcode, sig) \ kSimpleExprSigTable[opcode] = static_cast<int>(kSigEnum_##sig) + 1; FOREACH_SIMPLE_OPCODE(SET_SIG_TABLE); - FOREACH_SIMPLE_MEM_OPCODE(SET_SIG_TABLE); - FOREACH_ASMJS_COMPAT_OPCODE(SET_SIG_TABLE); #undef SET_SIG_TABLE +#define SET_ASMJS_SIG_TABLE(name, opcode, sig) \ + kSimpleAsmjsExprSigTable[opcode] = static_cast<int>(kSigEnum_##sig) + 1; + FOREACH_ASMJS_COMPAT_OPCODE(SET_ASMJS_SIG_TABLE); +#undef SET_ASMJS_SIG_TABLE byte simd_index; #define SET_SIG_TABLE(name, opcode, sig) \ simd_index = opcode & 0xff; \ kSimdExprSigTable[simd_index] = static_cast<int>(kSigEnum_##sig) + 1; - FOREACH_SIMD_OPCODE(SET_SIG_TABLE) + FOREACH_SIMD_0_OPERAND_OPCODE(SET_SIG_TABLE) #undef SET_SIG_TABLE } @@ -102,6 +117,10 @@ class SigTable { return const_cast<FunctionSig*>( kSimpleExprSigs[kSimpleExprSigTable[static_cast<byte>(opcode)]]); } + FunctionSig* AsmjsSignature(WasmOpcode opcode) const { + return const_cast<FunctionSig*>( + kSimpleExprSigs[kSimpleAsmjsExprSigTable[static_cast<byte>(opcode)]]); + } FunctionSig* SimdSignature(WasmOpcode opcode) const { return const_cast<FunctionSig*>( kSimdExprSigs[kSimdExprSigTable[static_cast<byte>(opcode & 0xff)]]); @@ -118,6 +137,10 @@ FunctionSig* WasmOpcodes::Signature(WasmOpcode opcode) { } } +FunctionSig* WasmOpcodes::AsmjsSignature(WasmOpcode opcode) { + return sig_table.Get().AsmjsSignature(opcode); +} + // TODO(titzer): pull WASM_64 up to a common header. #if !V8_TARGET_ARCH_32_BIT || V8_TARGET_ARCH_X64 #define WASM_64 1 diff --git a/deps/v8/src/wasm/wasm-opcodes.h b/deps/v8/src/wasm/wasm-opcodes.h index 4d66e567ef..03827b2035 100644 --- a/deps/v8/src/wasm/wasm-opcodes.h +++ b/deps/v8/src/wasm/wasm-opcodes.h @@ -22,6 +22,9 @@ enum LocalTypeCode { kLocalS128 = 5 }; +// Type code for multi-value block types. +static const uint8_t kMultivalBlock = 0x41; + // We reuse the internal machine type to represent WebAssembly AST types. // A typedef improves readability without adding a whole new type system. typedef MachineRepresentation LocalType; @@ -44,7 +47,7 @@ const WasmCodePosition kNoCodePosition = -1; // Control expressions and blocks. #define FOREACH_CONTROL_OPCODE(V) \ - V(Nop, 0x00, _) \ + V(Unreachable, 0x00, _) \ V(Block, 0x01, _) \ V(Loop, 0x02, _) \ V(If, 0x03, _) \ @@ -54,13 +57,10 @@ const WasmCodePosition kNoCodePosition = -1; V(BrIf, 0x07, _) \ V(BrTable, 0x08, _) \ V(Return, 0x09, _) \ - V(Unreachable, 0x0a, _) \ + V(Nop, 0x0a, _) \ V(Throw, 0xfa, _) \ - V(TryCatch, 0xfb, _) \ - V(TryCatchFinally, 0xfc, _) \ - V(TryFinally, 0xfd, _) \ + V(Try, 0xfb, _) \ V(Catch, 0xfe, _) \ - V(Finally, 0xff, _) \ V(End, 0x0F, _) // Constants, locals, globals, and calls. @@ -71,9 +71,10 @@ const WasmCodePosition kNoCodePosition = -1; V(F32Const, 0x13, _) \ V(GetLocal, 0x14, _) \ V(SetLocal, 0x15, _) \ + V(TeeLocal, 0x19, _) \ + V(Drop, 0x0b, _) \ V(CallFunction, 0x16, _) \ V(CallIndirect, 0x17, _) \ - V(CallImport, 0x18, _) \ V(I8Const, 0xcb, _) \ V(GetGlobal, 0xbb, _) \ V(SetGlobal, 0xbc, _) @@ -273,141 +274,144 @@ const WasmCodePosition kNoCodePosition = -1; V(I32AsmjsSConvertF64, 0xe2, i_d) \ V(I32AsmjsUConvertF64, 0xe3, i_d) -#define FOREACH_SIMD_OPCODE(V) \ - V(F32x4Splat, 0xe500, s_f) \ - V(F32x4ExtractLane, 0xe501, f_si) \ - V(F32x4ReplaceLane, 0xe502, s_sif) \ - V(F32x4Abs, 0xe503, s_s) \ - V(F32x4Neg, 0xe504, s_s) \ - V(F32x4Sqrt, 0xe505, s_s) \ - V(F32x4RecipApprox, 0xe506, s_s) \ - V(F32x4SqrtApprox, 0xe507, s_s) \ - V(F32x4Add, 0xe508, s_ss) \ - V(F32x4Sub, 0xe509, s_ss) \ - V(F32x4Mul, 0xe50a, s_ss) \ - V(F32x4Div, 0xe50b, s_ss) \ - V(F32x4Min, 0xe50c, s_ss) \ - V(F32x4Max, 0xe50d, s_ss) \ - V(F32x4MinNum, 0xe50e, s_ss) \ - V(F32x4MaxNum, 0xe50f, s_ss) \ - V(F32x4Eq, 0xe510, s_ss) \ - V(F32x4Ne, 0xe511, s_ss) \ - V(F32x4Lt, 0xe512, s_ss) \ - V(F32x4Le, 0xe513, s_ss) \ - V(F32x4Gt, 0xe514, s_ss) \ - V(F32x4Ge, 0xe515, s_ss) \ - V(F32x4Select, 0xe516, s_sss) \ - V(F32x4Swizzle, 0xe517, s_s) \ - V(F32x4Shuffle, 0xe518, s_ss) \ - V(F32x4FromInt32x4, 0xe519, s_s) \ - V(F32x4FromUint32x4, 0xe51a, s_s) \ - V(I32x4Splat, 0xe51b, s_i) \ - V(I32x4ExtractLane, 0xe51c, i_si) \ - V(I32x4ReplaceLane, 0xe51d, s_sii) \ - V(I32x4Neg, 0xe51e, s_s) \ - V(I32x4Add, 0xe51f, s_ss) \ - V(I32x4Sub, 0xe520, s_ss) \ - V(I32x4Mul, 0xe521, s_ss) \ - V(I32x4Min_s, 0xe522, s_ss) \ - V(I32x4Max_s, 0xe523, s_ss) \ - V(I32x4Shl, 0xe524, s_si) \ - V(I32x4Shr_s, 0xe525, s_si) \ - V(I32x4Eq, 0xe526, s_ss) \ - V(I32x4Ne, 0xe527, s_ss) \ - V(I32x4Lt_s, 0xe528, s_ss) \ - V(I32x4Le_s, 0xe529, s_ss) \ - V(I32x4Gt_s, 0xe52a, s_ss) \ - V(I32x4Ge_s, 0xe52b, s_ss) \ - V(I32x4Select, 0xe52c, s_sss) \ - V(I32x4Swizzle, 0xe52d, s_s) \ - V(I32x4Shuffle, 0xe52e, s_ss) \ - V(I32x4FromFloat32x4, 0xe52f, s_s) \ - V(I32x4Min_u, 0xe530, s_ss) \ - V(I32x4Max_u, 0xe531, s_ss) \ - V(I32x4Shr_u, 0xe532, s_ss) \ - V(I32x4Lt_u, 0xe533, s_ss) \ - V(I32x4Le_u, 0xe534, s_ss) \ - V(I32x4Gt_u, 0xe535, s_ss) \ - V(I32x4Ge_u, 0xe536, s_ss) \ - V(Ui32x4FromFloat32x4, 0xe537, s_s) \ - V(I16x8Splat, 0xe538, s_i) \ - V(I16x8ExtractLane, 0xe539, i_si) \ - V(I16x8ReplaceLane, 0xe53a, s_sii) \ - V(I16x8Neg, 0xe53b, s_s) \ - V(I16x8Add, 0xe53c, s_ss) \ - V(I16x8AddSaturate_s, 0xe53d, s_ss) \ - V(I16x8Sub, 0xe53e, s_ss) \ - V(I16x8SubSaturate_s, 0xe53f, s_ss) \ - V(I16x8Mul, 0xe540, s_ss) \ - V(I16x8Min_s, 0xe541, s_ss) \ - V(I16x8Max_s, 0xe542, s_ss) \ - V(I16x8Shl, 0xe543, s_si) \ - V(I16x8Shr_s, 0xe544, s_si) \ - V(I16x8Eq, 0xe545, s_ss) \ - V(I16x8Ne, 0xe546, s_ss) \ - V(I16x8Lt_s, 0xe547, s_ss) \ - V(I16x8Le_s, 0xe548, s_ss) \ - V(I16x8Gt_s, 0xe549, s_ss) \ - V(I16x8Ge_s, 0xe54a, s_ss) \ - V(I16x8Select, 0xe54b, s_sss) \ - V(I16x8Swizzle, 0xe54c, s_s) \ - V(I16x8Shuffle, 0xe54d, s_ss) \ - V(I16x8AddSaturate_u, 0xe54e, s_ss) \ - V(I16x8SubSaturate_u, 0xe54f, s_ss) \ - V(I16x8Min_u, 0xe550, s_ss) \ - V(I16x8Max_u, 0xe551, s_ss) \ - V(I16x8Shr_u, 0xe552, s_si) \ - V(I16x8Lt_u, 0xe553, s_ss) \ - V(I16x8Le_u, 0xe554, s_ss) \ - V(I16x8Gt_u, 0xe555, s_ss) \ - V(I16x8Ge_u, 0xe556, s_ss) \ - V(I8x16Splat, 0xe557, s_i) \ - V(I8x16ExtractLane, 0xe558, i_si) \ - V(I8x16ReplaceLane, 0xe559, s_sii) \ - V(I8x16Neg, 0xe55a, s_s) \ - V(I8x16Add, 0xe55b, s_ss) \ - V(I8x16AddSaturate_s, 0xe55c, s_ss) \ - V(I8x16Sub, 0xe55d, s_ss) \ - V(I8x16SubSaturate_s, 0xe55e, s_ss) \ - V(I8x16Mul, 0xe55f, s_ss) \ - V(I8x16Min_s, 0xe560, s_ss) \ - V(I8x16Max_s, 0xe561, s_ss) \ - V(I8x16Shl, 0xe562, s_si) \ - V(I8x16Shr_s, 0xe563, s_si) \ - V(I8x16Eq, 0xe564, s_ss) \ - V(I8x16Neq, 0xe565, s_ss) \ - V(I8x16Lt_s, 0xe566, s_ss) \ - V(I8x16Le_s, 0xe567, s_ss) \ - V(I8x16Gt_s, 0xe568, s_ss) \ - V(I8x16Ge_s, 0xe569, s_ss) \ - V(I8x16Select, 0xe56a, s_sss) \ - V(I8x16Swizzle, 0xe56b, s_s) \ - V(I8x16Shuffle, 0xe56c, s_ss) \ - V(I8x16AddSaturate_u, 0xe56d, s_ss) \ - V(I8x16Sub_saturate_u, 0xe56e, s_ss) \ - V(I8x16Min_u, 0xe56f, s_ss) \ - V(I8x16Max_u, 0xe570, s_ss) \ - V(I8x16Shr_u, 0xe571, s_ss) \ - V(I8x16Lt_u, 0xe572, s_ss) \ - V(I8x16Le_u, 0xe573, s_ss) \ - V(I8x16Gt_u, 0xe574, s_ss) \ - V(I8x16Ge_u, 0xe575, s_ss) \ - V(S128And, 0xe576, s_ss) \ - V(S128Ior, 0xe577, s_ss) \ - V(S128Xor, 0xe578, s_ss) \ +#define FOREACH_SIMD_0_OPERAND_OPCODE(V) \ + V(F32x4Splat, 0xe500, s_f) \ + V(F32x4ReplaceLane, 0xe502, s_sif) \ + V(F32x4Abs, 0xe503, s_s) \ + V(F32x4Neg, 0xe504, s_s) \ + V(F32x4Sqrt, 0xe505, s_s) \ + V(F32x4RecipApprox, 0xe506, s_s) \ + V(F32x4SqrtApprox, 0xe507, s_s) \ + V(F32x4Add, 0xe508, s_ss) \ + V(F32x4Sub, 0xe509, s_ss) \ + V(F32x4Mul, 0xe50a, s_ss) \ + V(F32x4Div, 0xe50b, s_ss) \ + V(F32x4Min, 0xe50c, s_ss) \ + V(F32x4Max, 0xe50d, s_ss) \ + V(F32x4MinNum, 0xe50e, s_ss) \ + V(F32x4MaxNum, 0xe50f, s_ss) \ + V(F32x4Eq, 0xe510, s_ss) \ + V(F32x4Ne, 0xe511, s_ss) \ + V(F32x4Lt, 0xe512, s_ss) \ + V(F32x4Le, 0xe513, s_ss) \ + V(F32x4Gt, 0xe514, s_ss) \ + V(F32x4Ge, 0xe515, s_ss) \ + V(F32x4Select, 0xe516, s_sss) \ + V(F32x4Swizzle, 0xe517, s_s) \ + V(F32x4Shuffle, 0xe518, s_ss) \ + V(F32x4FromInt32x4, 0xe519, s_s) \ + V(F32x4FromUint32x4, 0xe51a, s_s) \ + V(I32x4Splat, 0xe51b, s_i) \ + V(I32x4ReplaceLane, 0xe51d, s_sii) \ + V(I32x4Neg, 0xe51e, s_s) \ + V(I32x4Add, 0xe51f, s_ss) \ + V(I32x4Sub, 0xe520, s_ss) \ + V(I32x4Mul, 0xe521, s_ss) \ + V(I32x4Min_s, 0xe522, s_ss) \ + V(I32x4Max_s, 0xe523, s_ss) \ + V(I32x4Shl, 0xe524, s_si) \ + V(I32x4Shr_s, 0xe525, s_si) \ + V(I32x4Eq, 0xe526, s_ss) \ + V(I32x4Ne, 0xe527, s_ss) \ + V(I32x4Lt_s, 0xe528, s_ss) \ + V(I32x4Le_s, 0xe529, s_ss) \ + V(I32x4Gt_s, 0xe52a, s_ss) \ + V(I32x4Ge_s, 0xe52b, s_ss) \ + V(I32x4Select, 0xe52c, s_sss) \ + V(I32x4Swizzle, 0xe52d, s_s) \ + V(I32x4Shuffle, 0xe52e, s_ss) \ + V(I32x4FromFloat32x4, 0xe52f, s_s) \ + V(I32x4Min_u, 0xe530, s_ss) \ + V(I32x4Max_u, 0xe531, s_ss) \ + V(I32x4Shr_u, 0xe532, s_ss) \ + V(I32x4Lt_u, 0xe533, s_ss) \ + V(I32x4Le_u, 0xe534, s_ss) \ + V(I32x4Gt_u, 0xe535, s_ss) \ + V(I32x4Ge_u, 0xe536, s_ss) \ + V(Ui32x4FromFloat32x4, 0xe537, s_s) \ + V(I16x8Splat, 0xe538, s_i) \ + V(I16x8ReplaceLane, 0xe53a, s_sii) \ + V(I16x8Neg, 0xe53b, s_s) \ + V(I16x8Add, 0xe53c, s_ss) \ + V(I16x8AddSaturate_s, 0xe53d, s_ss) \ + V(I16x8Sub, 0xe53e, s_ss) \ + V(I16x8SubSaturate_s, 0xe53f, s_ss) \ + V(I16x8Mul, 0xe540, s_ss) \ + V(I16x8Min_s, 0xe541, s_ss) \ + V(I16x8Max_s, 0xe542, s_ss) \ + V(I16x8Shl, 0xe543, s_si) \ + V(I16x8Shr_s, 0xe544, s_si) \ + V(I16x8Eq, 0xe545, s_ss) \ + V(I16x8Ne, 0xe546, s_ss) \ + V(I16x8Lt_s, 0xe547, s_ss) \ + V(I16x8Le_s, 0xe548, s_ss) \ + V(I16x8Gt_s, 0xe549, s_ss) \ + V(I16x8Ge_s, 0xe54a, s_ss) \ + V(I16x8Select, 0xe54b, s_sss) \ + V(I16x8Swizzle, 0xe54c, s_s) \ + V(I16x8Shuffle, 0xe54d, s_ss) \ + V(I16x8AddSaturate_u, 0xe54e, s_ss) \ + V(I16x8SubSaturate_u, 0xe54f, s_ss) \ + V(I16x8Min_u, 0xe550, s_ss) \ + V(I16x8Max_u, 0xe551, s_ss) \ + V(I16x8Shr_u, 0xe552, s_si) \ + V(I16x8Lt_u, 0xe553, s_ss) \ + V(I16x8Le_u, 0xe554, s_ss) \ + V(I16x8Gt_u, 0xe555, s_ss) \ + V(I16x8Ge_u, 0xe556, s_ss) \ + V(I8x16Splat, 0xe557, s_i) \ + V(I8x16ReplaceLane, 0xe559, s_sii) \ + V(I8x16Neg, 0xe55a, s_s) \ + V(I8x16Add, 0xe55b, s_ss) \ + V(I8x16AddSaturate_s, 0xe55c, s_ss) \ + V(I8x16Sub, 0xe55d, s_ss) \ + V(I8x16SubSaturate_s, 0xe55e, s_ss) \ + V(I8x16Mul, 0xe55f, s_ss) \ + V(I8x16Min_s, 0xe560, s_ss) \ + V(I8x16Max_s, 0xe561, s_ss) \ + V(I8x16Shl, 0xe562, s_si) \ + V(I8x16Shr_s, 0xe563, s_si) \ + V(I8x16Eq, 0xe564, s_ss) \ + V(I8x16Neq, 0xe565, s_ss) \ + V(I8x16Lt_s, 0xe566, s_ss) \ + V(I8x16Le_s, 0xe567, s_ss) \ + V(I8x16Gt_s, 0xe568, s_ss) \ + V(I8x16Ge_s, 0xe569, s_ss) \ + V(I8x16Select, 0xe56a, s_sss) \ + V(I8x16Swizzle, 0xe56b, s_s) \ + V(I8x16Shuffle, 0xe56c, s_ss) \ + V(I8x16AddSaturate_u, 0xe56d, s_ss) \ + V(I8x16Sub_saturate_u, 0xe56e, s_ss) \ + V(I8x16Min_u, 0xe56f, s_ss) \ + V(I8x16Max_u, 0xe570, s_ss) \ + V(I8x16Shr_u, 0xe571, s_ss) \ + V(I8x16Lt_u, 0xe572, s_ss) \ + V(I8x16Le_u, 0xe573, s_ss) \ + V(I8x16Gt_u, 0xe574, s_ss) \ + V(I8x16Ge_u, 0xe575, s_ss) \ + V(S128And, 0xe576, s_ss) \ + V(S128Ior, 0xe577, s_ss) \ + V(S128Xor, 0xe578, s_ss) \ V(S128Not, 0xe579, s_s) +#define FOREACH_SIMD_1_OPERAND_OPCODE(V) \ + V(F32x4ExtractLane, 0xe501, _) \ + V(I32x4ExtractLane, 0xe51c, _) \ + V(I16x8ExtractLane, 0xe539, _) \ + V(I8x16ExtractLane, 0xe558, _) + // All opcodes. -#define FOREACH_OPCODE(V) \ - FOREACH_CONTROL_OPCODE(V) \ - FOREACH_MISC_OPCODE(V) \ - FOREACH_SIMPLE_OPCODE(V) \ - FOREACH_SIMPLE_MEM_OPCODE(V) \ - FOREACH_STORE_MEM_OPCODE(V) \ - FOREACH_LOAD_MEM_OPCODE(V) \ - FOREACH_MISC_MEM_OPCODE(V) \ - FOREACH_ASMJS_COMPAT_OPCODE(V) \ - FOREACH_SIMD_OPCODE(V) +#define FOREACH_OPCODE(V) \ + FOREACH_CONTROL_OPCODE(V) \ + FOREACH_MISC_OPCODE(V) \ + FOREACH_SIMPLE_OPCODE(V) \ + FOREACH_SIMPLE_MEM_OPCODE(V) \ + FOREACH_STORE_MEM_OPCODE(V) \ + FOREACH_LOAD_MEM_OPCODE(V) \ + FOREACH_MISC_MEM_OPCODE(V) \ + FOREACH_ASMJS_COMPAT_OPCODE(V) \ + FOREACH_SIMD_0_OPERAND_OPCODE(V) \ + FOREACH_SIMD_1_OPERAND_OPCODE(V) // All signatures. #define FOREACH_SIGNATURE(V) \ @@ -443,12 +447,10 @@ const WasmCodePosition kNoCodePosition = -1; #define FOREACH_SIMD_SIGNATURE(V) \ V(s_s, kAstS128, kAstS128) \ V(s_f, kAstS128, kAstF32) \ - V(f_si, kAstF32, kAstS128, kAstI32) \ V(s_sif, kAstS128, kAstS128, kAstI32, kAstF32) \ V(s_ss, kAstS128, kAstS128, kAstS128) \ V(s_sss, kAstS128, kAstS128, kAstS128, kAstS128) \ V(s_i, kAstS128, kAstI32) \ - V(i_si, kAstI32, kAstS128, kAstI32) \ V(s_sii, kAstS128, kAstS128, kAstI32, kAstI32) \ V(s_si, kAstS128, kAstS128, kAstI32) @@ -489,6 +491,8 @@ class WasmOpcodes { static const char* OpcodeName(WasmOpcode opcode); static const char* ShortOpcodeName(WasmOpcode opcode); static FunctionSig* Signature(WasmOpcode opcode); + static FunctionSig* AsmjsSignature(WasmOpcode opcode); + static bool IsPrefixOpcode(WasmOpcode opcode); static int TrapReasonToMessageId(TrapReason reason); static const char* TrapReasonMessage(TrapReason reason); @@ -497,6 +501,8 @@ class WasmOpcodes { return 1 << ElementSizeLog2Of(type.representation()); } + static byte MemSize(LocalType type) { return 1 << ElementSizeLog2Of(type); } + static LocalTypeCode LocalTypeCodeFor(LocalType type) { switch (type) { case kAstI32: @@ -507,10 +513,10 @@ class WasmOpcodes { return kLocalF32; case kAstF64: return kLocalF64; - case kAstStmt: - return kLocalVoid; case kAstS128: return kLocalS128; + case kAstStmt: + return kLocalVoid; default: UNREACHABLE(); return kLocalVoid; diff --git a/deps/v8/src/wasm/wasm-result.cc b/deps/v8/src/wasm/wasm-result.cc index 30268ac8ad..7d251f03df 100644 --- a/deps/v8/src/wasm/wasm-result.cc +++ b/deps/v8/src/wasm/wasm-result.cc @@ -27,15 +27,13 @@ std::ostream& operator<<(std::ostream& os, const ErrorCode& error_code) { return os; } -void ErrorThrower::Error(const char* format, ...) { +void ErrorThrower::Format(i::Handle<i::JSFunction> constructor, + const char* format, va_list args) { // Only report the first error. if (error()) return; char buffer[256]; - va_list arguments; - va_start(arguments, format); - base::OS::VSNPrintF(buffer, 255, format, arguments); - va_end(arguments); + base::OS::VSNPrintF(buffer, 255, format, args); std::ostringstream str; if (context_ != nullptr) { @@ -43,12 +41,39 @@ void ErrorThrower::Error(const char* format, ...) { } str << buffer; - message_ = isolate_->factory()->NewStringFromAsciiChecked(str.str().c_str()); + i::Handle<i::String> message = + isolate_->factory()->NewStringFromAsciiChecked(str.str().c_str()); + exception_ = isolate_->factory()->NewError(constructor, message); +} + +void ErrorThrower::Error(const char* format, ...) { + if (error()) return; + va_list arguments; + va_start(arguments, format); + Format(isolate_->error_function(), format, arguments); + va_end(arguments); +} + +void ErrorThrower::TypeError(const char* format, ...) { + if (error()) return; + va_list arguments; + va_start(arguments, format); + Format(isolate_->type_error_function(), format, arguments); + va_end(arguments); +} + +void ErrorThrower::RangeError(const char* format, ...) { + if (error()) return; + va_list arguments; + va_start(arguments, format); + CHECK(*isolate_->range_error_function() != *isolate_->type_error_function()); + Format(isolate_->range_error_function(), format, arguments); + va_end(arguments); } ErrorThrower::~ErrorThrower() { if (error() && !isolate_->has_pending_exception()) { - isolate_->ScheduleThrow(*message_); + isolate_->ScheduleThrow(*exception_); } } } // namespace wasm diff --git a/deps/v8/src/wasm/wasm-result.h b/deps/v8/src/wasm/wasm-result.h index f16c15906d..ecc54e5b7a 100644 --- a/deps/v8/src/wasm/wasm-result.h +++ b/deps/v8/src/wasm/wasm-result.h @@ -22,19 +22,7 @@ namespace wasm { // Error codes for programmatic checking of the decoder's verification. enum ErrorCode { kSuccess, - kError, // TODO(titzer): remove me - kOutOfMemory, // decoder ran out of memory - kEndOfCode, // end of code reached prematurely - kInvalidOpcode, // found invalid opcode - kUnreachableCode, // found unreachable code - kImproperContinue, // improperly nested continue - kImproperBreak, // improperly nested break - kReturnCount, // return count mismatch - kTypeError, // type mismatch - kInvalidLocalIndex, // invalid local - kInvalidGlobalIndex, // invalid global - kInvalidFunctionIndex, // invalid function - kInvalidMemType // invalid memory type + kError, // TODO(titzer): introduce real error codes }; // The overall result of decoding a function or a module. @@ -97,33 +85,37 @@ std::ostream& operator<<(std::ostream& os, const Result<T>& result) { std::ostream& operator<<(std::ostream& os, const ErrorCode& error_code); // A helper for generating error messages that bubble up to JS exceptions. -class ErrorThrower { +class V8_EXPORT_PRIVATE ErrorThrower { public: - ErrorThrower(Isolate* isolate, const char* context) + ErrorThrower(i::Isolate* isolate, const char* context) : isolate_(isolate), context_(context) {} ~ErrorThrower(); PRINTF_FORMAT(2, 3) void Error(const char* fmt, ...); + PRINTF_FORMAT(2, 3) void TypeError(const char* fmt, ...); + PRINTF_FORMAT(2, 3) void RangeError(const char* fmt, ...); template <typename T> void Failed(const char* error, Result<T>& result) { std::ostringstream str; str << error << result; - return Error("%s", str.str().c_str()); + Error("%s", str.str().c_str()); } - i::Handle<i::String> Reify() { - auto result = message_; - message_ = i::Handle<i::String>(); + i::Handle<i::Object> Reify() { + i::Handle<i::Object> result = exception_; + exception_ = i::Handle<i::Object>::null(); return result; } - bool error() const { return !message_.is_null(); } + bool error() const { return !exception_.is_null(); } private: - Isolate* isolate_; + void Format(i::Handle<i::JSFunction> constructor, const char* fmt, va_list); + + i::Isolate* isolate_; const char* context_; - i::Handle<i::String> message_; + i::Handle<i::Object> exception_; }; } // namespace wasm } // namespace internal |