diff options
author | Michaël Zasso <targos@protonmail.com> | 2017-03-21 10:16:54 +0100 |
---|---|---|
committer | Michaël Zasso <targos@protonmail.com> | 2017-03-25 09:44:10 +0100 |
commit | c459d8ea5d402c702948c860d9497b2230ff7e8a (patch) | |
tree | 56c282fc4d40e5cb613b47cf7be3ea0526ed5b6f /deps/v8/src/wasm | |
parent | e0bc5a7361b1d29c3ed034155fd779ce6f44fb13 (diff) | |
download | android-node-v8-c459d8ea5d402c702948c860d9497b2230ff7e8a.tar.gz android-node-v8-c459d8ea5d402c702948c860d9497b2230ff7e8a.tar.bz2 android-node-v8-c459d8ea5d402c702948c860d9497b2230ff7e8a.zip |
deps: update V8 to 5.7.492.69
PR-URL: https://github.com/nodejs/node/pull/11752
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Diffstat (limited to 'deps/v8/src/wasm')
27 files changed, 4582 insertions, 2291 deletions
diff --git a/deps/v8/src/wasm/OWNERS b/deps/v8/src/wasm/OWNERS index 2822c29819..4f54661aeb 100644 --- a/deps/v8/src/wasm/OWNERS +++ b/deps/v8/src/wasm/OWNERS @@ -2,6 +2,7 @@ set noparent ahaas@chromium.org bradnelson@chromium.org +clemensh@chromium.org mtrofin@chromium.org rossberg@chromium.org titzer@chromium.org diff --git a/deps/v8/src/wasm/decoder.h b/deps/v8/src/wasm/decoder.h index fc8f110b73..afe8701779 100644 --- a/deps/v8/src/wasm/decoder.h +++ b/deps/v8/src/wasm/decoder.h @@ -34,7 +34,12 @@ class Decoder { Decoder(const byte* start, const byte* end) : start_(start), pc_(start), - limit_(end), + end_(end), + error_pc_(nullptr), + error_pt_(nullptr) {} + Decoder(const byte* start, const byte* pc, const byte* end) + : start_(start), + pc_(pc), end_(end), error_pc_(nullptr), error_pt_(nullptr) {} @@ -44,7 +49,7 @@ class Decoder { inline bool check(const byte* base, unsigned offset, unsigned length, const char* msg) { DCHECK_GE(base, start_); - if ((base + offset + length) > limit_) { + if ((base + offset + length) > end_) { error(base, base + offset, "%s", msg); return false; } @@ -185,22 +190,27 @@ class Decoder { // 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 DEBUG + if (name) { + // Only trace if the name is not null. + TRACE(" +%d %-20s: %d bytes\n", static_cast<int>(pc_ - start_), name, + size); + } +#endif if (checkAvailable(size)) { pc_ += size; } else { - pc_ = limit_; + pc_ = end_; } } - // Check that at least {size} bytes exist between {pc_} and {limit_}. + // Check that at least {size} bytes exist between {pc_} and {end_}. bool checkAvailable(int size) { intptr_t pc_overflow_value = std::numeric_limits<intptr_t>::max() - size; if (size < 0 || (intptr_t)pc_ > pc_overflow_value) { error(pc_, nullptr, "reading %d bytes would underflow/overflow", size); return false; - } else if (pc_ < start_ || limit_ < (pc_ + size)) { + } else if (pc_ < start_ || end_ < (pc_ + size)) { error(pc_, nullptr, "expected %d bytes, fell off end", size); return false; } else { @@ -241,11 +251,11 @@ class Decoder { template <typename T> T traceOffEnd() { T t = 0; - for (const byte* ptr = pc_; ptr < limit_; ptr++) { + for (const byte* ptr = pc_; ptr < end_; ptr++) { TRACE("%02x ", *ptr); } TRACE("<end>\n"); - pc_ = limit_; + pc_ = end_; return t; } @@ -272,7 +282,6 @@ class Decoder { void Reset(const byte* start, const byte* end) { start_ = start; pc_ = start; - limit_ = end; end_ = end; error_pc_ = nullptr; error_pt_ = nullptr; @@ -281,16 +290,16 @@ class Decoder { bool ok() const { return error_msg_ == nullptr; } bool failed() const { return !ok(); } - bool more() const { return pc_ < limit_; } + bool more() const { return pc_ < end_; } - const byte* start() { return start_; } - const byte* pc() { return pc_; } - uint32_t pc_offset() { return static_cast<uint32_t>(pc_ - start_); } + const byte* start() const { return start_; } + const byte* pc() const { return pc_; } + uint32_t pc_offset() const { return static_cast<uint32_t>(pc_ - start_); } + const byte* end() const { return end_; } protected: const byte* start_; const byte* pc_; - const byte* limit_; const byte* end_; const byte* error_pc_; const byte* error_pt_; @@ -308,7 +317,7 @@ class Decoder { const int kMaxLength = (sizeof(IntType) * 8 + 6) / 7; const byte* ptr = base + offset; const byte* end = ptr + kMaxLength; - if (end > limit_) end = limit_; + if (end > end_) end = end_; int shift = 0; byte b = 0; IntType result = 0; @@ -358,7 +367,7 @@ class Decoder { const int kMaxLength = (sizeof(IntType) * 8 + 6) / 7; const byte* pos = pc_; const byte* end = pc_ + kMaxLength; - if (end > limit_) end = limit_; + if (end > end_) end = end_; IntType result = 0; int shift = 0; diff --git a/deps/v8/src/wasm/ast-decoder.cc b/deps/v8/src/wasm/function-body-decoder.cc index ff6af34a02..04a2806237 100644 --- a/deps/v8/src/wasm/ast-decoder.cc +++ b/deps/v8/src/wasm/function-body-decoder.cc @@ -9,8 +9,8 @@ #include "src/handles.h" #include "src/zone/zone-containers.h" -#include "src/wasm/ast-decoder.h" #include "src/wasm/decoder.h" +#include "src/wasm/function-body-decoder.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-opcodes.h" @@ -31,16 +31,14 @@ namespace wasm { #define TRACE(...) #endif -#define CHECK_PROTOTYPE_OPCODE(flag) \ - if (module_ && module_->origin == kAsmJsOrigin) { \ - error("Opcode not supported for asmjs modules"); \ - } \ - if (!FLAG_##flag) { \ - error("Invalid opcode (enable with --" #flag ")"); \ - break; \ +#define CHECK_PROTOTYPE_OPCODE(flag) \ + if (module_ != nullptr && module_->origin == kAsmJsOrigin) { \ + error("Opcode not supported for asmjs modules"); \ + } \ + if (!FLAG_##flag) { \ + 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. @@ -70,7 +68,7 @@ struct SsaEnv { struct Value { const byte* pc; TFNode* node; - LocalType type; + ValueType type; }; struct TryInfo : public ZoneObject { @@ -87,9 +85,9 @@ struct MergeValues { Value first; } vals; // Either multiple values or a single value. - Value& first() { - DCHECK_GT(arity, 0u); - return arity == 1 ? vals.first : vals.array[0]; + Value& operator[](size_t i) { + DCHECK_GT(arity, i); + return arity == 1 ? vals.first : vals.array[i]; } }; @@ -101,11 +99,12 @@ enum ControlKind { kControlIf, kControlBlock, kControlLoop, kControlTry }; 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. + size_t 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. + bool unreachable; // The current block has been ended. // Values merged into the end of this control construct. MergeValues merge; @@ -116,30 +115,30 @@ struct Control { inline bool is_try() const { return kind == kControlTry; } // Named constructors. - static Control Block(const byte* pc, int stack_depth, SsaEnv* end_env, + static Control Block(const byte* pc, size_t stack_depth, SsaEnv* end_env, int32_t previous_catch) { - return {pc, kControlBlock, stack_depth, end_env, - nullptr, nullptr, previous_catch, {0, {NO_VALUE}}}; + return {pc, kControlBlock, stack_depth, end_env, nullptr, + nullptr, previous_catch, false, {0, {NO_VALUE}}}; } - static Control If(const byte* pc, int stack_depth, SsaEnv* end_env, + static Control If(const byte* pc, size_t stack_depth, SsaEnv* end_env, SsaEnv* false_env, int32_t previous_catch) { - return {pc, kControlIf, stack_depth, end_env, - false_env, nullptr, previous_catch, {0, {NO_VALUE}}}; + return {pc, kControlIf, stack_depth, end_env, false_env, + nullptr, previous_catch, false, {0, {NO_VALUE}}}; } - static Control Loop(const byte* pc, int stack_depth, SsaEnv* end_env, + static Control Loop(const byte* pc, size_t stack_depth, SsaEnv* end_env, int32_t previous_catch) { - return {pc, kControlLoop, stack_depth, end_env, - nullptr, nullptr, previous_catch, {0, {NO_VALUE}}}; + return {pc, kControlLoop, stack_depth, end_env, nullptr, + nullptr, previous_catch, false, {0, {NO_VALUE}}}; } - static Control Try(const byte* pc, int stack_depth, SsaEnv* end_env, + static Control Try(const byte* pc, size_t stack_depth, SsaEnv* end_env, 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}}}; + return {pc, kControlTry, stack_depth, end_env, nullptr, + try_info, previous_catch, false, {0, {NO_VALUE}}}; } }; @@ -164,24 +163,123 @@ struct LaneOperand { // lengths, etc. class WasmDecoder : public Decoder { public: - WasmDecoder(ModuleEnv* module, FunctionSig* sig, const byte* start, + WasmDecoder(const WasmModule* module, FunctionSig* sig, const byte* start, const byte* end) : Decoder(start, end), module_(module), sig_(sig), - total_locals_(0), local_types_(nullptr) {} - ModuleEnv* module_; + const WasmModule* module_; FunctionSig* sig_; - size_t total_locals_; - ZoneVector<LocalType>* local_types_; + + ZoneVector<ValueType>* local_types_; + + size_t total_locals() const { + return local_types_ == nullptr ? 0 : local_types_->size(); + } + + static bool DecodeLocals(Decoder* decoder, const FunctionSig* sig, + ZoneVector<ValueType>* type_list) { + DCHECK_NOT_NULL(type_list); + // Initialize from signature. + if (sig != nullptr) { + type_list->reserve(sig->parameter_count()); + for (size_t i = 0; i < sig->parameter_count(); ++i) { + type_list->push_back(sig->GetParam(i)); + } + } + // Decode local declarations, if any. + uint32_t entries = decoder->consume_u32v("local decls count"); + if (decoder->failed()) return false; + + TRACE("local decls count: %u\n", entries); + while (entries-- > 0 && decoder->ok() && decoder->more()) { + uint32_t count = decoder->consume_u32v("local count"); + if (decoder->failed()) return false; + + if ((count + type_list->size()) > kMaxNumWasmLocals) { + decoder->error(decoder->pc() - 1, "local count too large"); + return false; + } + byte code = decoder->consume_u8("local type"); + if (decoder->failed()) return false; + + ValueType type; + switch (code) { + case kLocalI32: + type = kWasmI32; + break; + case kLocalI64: + type = kWasmI64; + break; + case kLocalF32: + type = kWasmF32; + break; + case kLocalF64: + type = kWasmF64; + break; + case kLocalS128: + type = kWasmS128; + break; + default: + decoder->error(decoder->pc() - 1, "invalid local type"); + return false; + } + type_list->insert(type_list->end(), count, type); + } + DCHECK(decoder->ok()); + return true; + } + + static BitVector* AnalyzeLoopAssignment(Decoder* decoder, const byte* pc, + int locals_count, Zone* zone) { + if (pc >= decoder->end()) return nullptr; + if (*pc != kExprLoop) return nullptr; + + BitVector* assigned = new (zone) BitVector(locals_count, zone); + int depth = 0; + // Iteratively process all AST nodes nested inside the loop. + while (pc < decoder->end() && decoder->ok()) { + WasmOpcode opcode = static_cast<WasmOpcode>(*pc); + unsigned length = 1; + switch (opcode) { + case kExprLoop: + case kExprIf: + case kExprBlock: + case kExprTry: + length = OpcodeLength(decoder, pc); + depth++; + break; + case kExprSetLocal: // fallthru + case kExprTeeLocal: { + LocalIndexOperand operand(decoder, pc); + if (assigned->length() > 0 && + operand.index < static_cast<uint32_t>(assigned->length())) { + // Unverified code might have an out-of-bounds index. + assigned->Add(operand.index); + } + length = 1 + operand.length; + break; + } + case kExprEnd: + depth--; + break; + default: + length = OpcodeLength(decoder, pc); + break; + } + if (depth <= 0) break; + pc += length; + } + return decoder->ok() ? assigned : nullptr; + } inline bool Validate(const byte* pc, LocalIndexOperand& operand) { - if (operand.index < total_locals_) { + if (operand.index < total_locals()) { if (local_types_) { operand.type = local_types_->at(operand.index); } else { - operand.type = kAstStmt; + operand.type = kWasmStmt; } return true; } @@ -190,9 +288,8 @@ class WasmDecoder : public Decoder { } inline bool Validate(const byte* pc, GlobalIndexOperand& operand) { - ModuleEnv* m = module_; - if (m && m->module && operand.index < m->module->globals.size()) { - operand.global = &m->module->globals[operand.index]; + if (module_ != nullptr && operand.index < module_->globals.size()) { + operand.global = &module_->globals[operand.index]; operand.type = operand.global->type; return true; } @@ -201,9 +298,8 @@ class WasmDecoder : public Decoder { } inline bool Complete(const byte* pc, CallFunctionOperand& operand) { - ModuleEnv* m = module_; - if (m && m->module && operand.index < m->module->functions.size()) { - operand.sig = m->module->functions[operand.index].sig; + if (module_ != nullptr && operand.index < module_->functions.size()) { + operand.sig = module_->functions[operand.index].sig; return true; } return false; @@ -218,17 +314,15 @@ class WasmDecoder : public Decoder { } inline bool Complete(const byte* pc, CallIndirectOperand& operand) { - ModuleEnv* m = module_; - if (m && m->module && operand.index < m->module->signatures.size()) { - operand.sig = m->module->signatures[operand.index]; + if (module_ != nullptr && operand.index < module_->signatures.size()) { + operand.sig = module_->signatures[operand.index]; return true; } return false; } inline bool Validate(const byte* pc, CallIndirectOperand& operand) { - uint32_t table_index = 0; - if (!module_->IsValidTable(table_index)) { + if (module_ == nullptr || module_->function_tables.empty()) { error("function table has to exist to execute call_indirect"); return false; } @@ -264,33 +358,33 @@ class WasmDecoder : public Decoder { } } - unsigned OpcodeLength(const byte* pc) { + static unsigned OpcodeLength(Decoder* decoder, const byte* pc) { switch (static_cast<byte>(*pc)) { #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: FOREACH_LOAD_MEM_OPCODE(DECLARE_OPCODE_CASE) FOREACH_STORE_MEM_OPCODE(DECLARE_OPCODE_CASE) #undef DECLARE_OPCODE_CASE { - MemoryAccessOperand operand(this, pc, UINT32_MAX); + MemoryAccessOperand operand(decoder, pc, UINT32_MAX); return 1 + operand.length; } case kExprBr: case kExprBrIf: { - BreakDepthOperand operand(this, pc); + BreakDepthOperand operand(decoder, pc); return 1 + operand.length; } case kExprSetGlobal: case kExprGetGlobal: { - GlobalIndexOperand operand(this, pc); + GlobalIndexOperand operand(decoder, pc); return 1 + operand.length; } case kExprCallFunction: { - CallFunctionOperand operand(this, pc); + CallFunctionOperand operand(decoder, pc); return 1 + operand.length; } case kExprCallIndirect: { - CallIndirectOperand operand(this, pc); + CallIndirectOperand operand(decoder, pc); return 1 + operand.length; } @@ -298,7 +392,7 @@ class WasmDecoder : public Decoder { case kExprIf: // fall thru case kExprLoop: case kExprBlock: { - BlockTypeOperand operand(this, pc); + BlockTypeOperand operand(decoder, pc); return 1 + operand.length; } @@ -306,35 +400,33 @@ class WasmDecoder : public Decoder { case kExprTeeLocal: case kExprGetLocal: case kExprCatch: { - LocalIndexOperand operand(this, pc); + LocalIndexOperand operand(decoder, pc); return 1 + operand.length; } case kExprBrTable: { - BranchTableOperand operand(this, pc); - BranchTableIterator iterator(this, operand); + BranchTableOperand operand(decoder, pc); + BranchTableIterator iterator(decoder, operand); return 1 + iterator.length(); } case kExprI32Const: { - ImmI32Operand operand(this, pc); + ImmI32Operand operand(decoder, pc); return 1 + operand.length; } case kExprI64Const: { - ImmI64Operand operand(this, pc); + ImmI64Operand operand(decoder, pc); return 1 + operand.length; } case kExprGrowMemory: case kExprMemorySize: { - MemoryIndexOperand operand(this, pc); + MemoryIndexOperand operand(decoder, pc); return 1 + operand.length; } - case kExprI8Const: - return 2; case kExprF32Const: return 5; case kExprF64Const: return 9; case kSimdPrefix: { - byte simd_index = checked_read_u8(pc, 1, "simd_index"); + byte simd_index = decoder->checked_read_u8(pc, 1, "simd_index"); WasmOpcode opcode = static_cast<WasmOpcode>(kSimdPrefix << 8 | simd_index); switch (opcode) { @@ -351,7 +443,7 @@ class WasmDecoder : public Decoder { return 3; } default: - error("invalid SIMD opcode"); + decoder->error(pc, "invalid SIMD opcode"); return 2; } } @@ -363,24 +455,24 @@ class WasmDecoder : public Decoder { static const int32_t kNullCatch = -1; -// The full WASM decoder for bytecode. Both verifies bytecode and generates -// a TurboFan IR graph. +// The full WASM decoder for bytecode. Verifies bytecode and, optionally, +// generates a TurboFan IR graph. class WasmFullDecoder : public WasmDecoder { public: + WasmFullDecoder(Zone* zone, const wasm::WasmModule* module, + const FunctionBody& body) + : WasmFullDecoder(zone, module, nullptr, body) {} + WasmFullDecoder(Zone* zone, TFBuilder* builder, const FunctionBody& body) - : WasmDecoder(body.module, body.sig, body.start, body.end), - zone_(zone), - builder_(builder), - base_(body.base), - local_type_vec_(zone), - stack_(zone), - control_(zone), - last_end_found_(false), - current_catch_(kNullCatch) { - local_types_ = &local_type_vec_; - } + : WasmFullDecoder(zone, builder->module_env() == nullptr + ? nullptr + : builder->module_env()->module, + builder, body) {} bool Decode() { + if (FLAG_wasm_code_fuzzer_gen_test) { + PrintRawWasmCode(start_, end_); + } base::ElapsedTimer decode_timer; if (FLAG_trace_wasm_decode_time) { decode_timer.Start(); @@ -393,47 +485,21 @@ class WasmFullDecoder : public WasmDecoder { return false; } - DecodeLocalDecls(); + DCHECK_EQ(0, local_types_->size()); + WasmDecoder::DecodeLocals(this, sig_, local_types_); InitSsaEnv(); DecodeFunctionBody(); 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"); + // Generate a better error message whether the unterminated control + // structure is the function body block or an innner structure. + if (control_.size() > 1) { + error(pc_, control_.back().pc, "unterminated control structure"); + } else { + error("function body must end with \"end\" opcode."); + } return TraceFailed(); } @@ -441,7 +507,6 @@ class WasmFullDecoder : public WasmDecoder { error("function body must end with \"end\" opcode."); return false; } -#endif if (FLAG_trace_wasm_decode_time) { double ms = decode_timer.Elapsed().InMillisecondsF(); @@ -459,36 +524,21 @@ class WasmFullDecoder : public WasmDecoder { return false; } - bool DecodeLocalDecls(AstLocalDecls& decls) { - DecodeLocalDecls(); - if (failed()) return false; - decls.decls_encoded_size = pc_offset(); - decls.local_types.reserve(local_type_vec_.size()); - for (size_t pos = 0; pos < local_type_vec_.size();) { - uint32_t count = 0; - LocalType type = local_type_vec_[pos]; - while (pos < local_type_vec_.size() && local_type_vec_[pos] == type) { - pos++; - count++; - } - decls.local_types.push_back(std::pair<LocalType, uint32_t>(type, count)); - } - decls.total_local_count = static_cast<uint32_t>(local_type_vec_.size()); - return true; - } - - BitVector* AnalyzeLoopAssignmentForTesting(const byte* pc, - size_t num_locals) { - total_locals_ = num_locals; - local_type_vec_.reserve(num_locals); - if (num_locals > local_type_vec_.size()) { - local_type_vec_.insert(local_type_vec_.end(), - num_locals - local_type_vec_.size(), kAstI32); - } - return AnalyzeLoopAssignment(pc); + private: + WasmFullDecoder(Zone* zone, const wasm::WasmModule* module, + TFBuilder* builder, const FunctionBody& body) + : WasmDecoder(module, body.sig, body.start, body.end), + zone_(zone), + builder_(builder), + base_(body.base), + local_type_vec_(zone), + stack_(zone), + control_(zone), + last_end_found_(false), + current_catch_(kNullCatch) { + local_types_ = &local_type_vec_; } - private: static const size_t kErrorMsgSize = 128; Zone* zone_; @@ -497,7 +547,7 @@ class WasmFullDecoder : public WasmDecoder { SsaEnv* ssa_env_; - ZoneVector<LocalType> local_type_vec_; // types of local variables. + ZoneVector<ValueType> 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_; @@ -521,11 +571,11 @@ class WasmFullDecoder : public WasmDecoder { // Initialize local variables. uint32_t index = 0; while (index < sig_->parameter_count()) { - ssa_env->locals[index] = builder_->Param(index, local_type_vec_[index]); + ssa_env->locals[index] = builder_->Param(index); index++; } while (index < local_type_vec_.size()) { - LocalType type = local_type_vec_[index]; + ValueType type = local_type_vec_[index]; TFNode* node = DefaultValue(type); while (index < local_type_vec_.size() && local_type_vec_[index] == type) { @@ -533,27 +583,28 @@ class WasmFullDecoder : public WasmDecoder { ssa_env->locals[index++] = node; } } - builder_->set_module(module_); } ssa_env->control = start; ssa_env->effect = start; SetEnv("initial", ssa_env); if (builder_) { - builder_->StackCheck(position()); + // The function-prologue stack check is associated with position 0, which + // is never a position of any instruction in the function. + builder_->StackCheck(0); } } - TFNode* DefaultValue(LocalType type) { + TFNode* DefaultValue(ValueType type) { switch (type) { - case kAstI32: + case kWasmI32: return builder_->Int32Constant(0); - case kAstI64: + case kWasmI64: return builder_->Int64Constant(0); - case kAstF32: + case kWasmF32: return builder_->Float32Constant(0); - case kAstF64: + case kWasmF64: return builder_->Float64Constant(0); - case kAstS128: + case kWasmS128: return builder_->CreateS128Value(0); default: UNREACHABLE(); @@ -572,58 +623,19 @@ class WasmFullDecoder : public WasmDecoder { return bytes; } - // Decodes the locals declarations, if any, populating {local_type_vec_}. - void DecodeLocalDecls() { - DCHECK_EQ(0u, local_type_vec_.size()); - // Initialize {local_type_vec} from signature. - if (sig_) { - local_type_vec_.reserve(sig_->parameter_count()); - for (size_t i = 0; i < sig_->parameter_count(); ++i) { - local_type_vec_.push_back(sig_->GetParam(i)); - } - } - // 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) { - case kLocalI32: - type = kAstI32; - break; - case kLocalI64: - type = kAstI64; - break; - case kLocalF32: - type = kAstF32; - break; - case kLocalF64: - type = kAstF64; - break; - case kLocalS128: - type = kAstS128; - break; - default: - error(pc_ - 1, "invalid local type"); - return; - } - local_type_vec_.insert(local_type_vec_.end(), count, type); + bool CheckHasMemory() { + if (!module_->has_memory) { + error(pc_ - 1, "memory instruction with no memory"); } - total_locals_ = local_type_vec_.size(); + return module_->has_memory; } // Decodes the body of a function. void DecodeFunctionBody() { TRACE("wasm-decode %p...%p (module+%d, %d bytes) %s\n", reinterpret_cast<const void*>(start_), - reinterpret_cast<const void*>(limit_), baserel(pc_), - static_cast<int>(limit_ - start_), builder_ ? "graph building" : ""); + reinterpret_cast<const void*>(end_), baserel(pc_), + static_cast<int>(end_ - start_), builder_ ? "graph building" : ""); { // Set up initial function block. @@ -643,9 +655,7 @@ class WasmFullDecoder : public WasmDecoder { } } - if (pc_ >= limit_) return; // Nothing to do. - - while (true) { // decoding loop. + while (pc_ < end_) { // decoding loop. unsigned len = 1; WasmOpcode opcode = static_cast<WasmOpcode>(*pc_); if (!WasmOpcodes::IsPrefixOpcode(opcode)) { @@ -673,8 +683,12 @@ class WasmFullDecoder : public WasmDecoder { } case kExprThrow: { CHECK_PROTOTYPE_OPCODE(wasm_eh_prototype); - Value value = Pop(0, kAstI32); + Value value = Pop(0, kWasmI32); BUILD(Throw, value.node); + // TODO(titzer): Throw should end control, but currently we build a + // (reachable) runtime call instead of connecting it directly to + // end. + // EndControl(); break; } case kExprTry: { @@ -710,9 +724,7 @@ class WasmFullDecoder : public WasmDecoder { break; } - if (ssa_env_->go()) { - MergeValuesInto(c); - } + FallThruTo(c); stack_.resize(c->stack_depth); DCHECK_NOT_NULL(c->try_info); @@ -746,7 +758,7 @@ class WasmFullDecoder : public WasmDecoder { case kExprIf: { // Condition on top of stack. Split environments for branches. BlockTypeOperand operand(this, pc_); - Value cond = Pop(0, kAstI32); + Value cond = Pop(0, kWasmI32); TFNode* if_true = nullptr; TFNode* if_false = nullptr; BUILD(BranchNoHint, cond.node, &if_true, &if_false); @@ -776,8 +788,8 @@ class WasmFullDecoder : public WasmDecoder { break; } FallThruTo(c); - // Switch to environment for false branch. stack_.resize(c->stack_depth); + // Switch to environment for false branch. SetEnv("if_else:false", c->false_env); c->false_env = nullptr; // record that an else is already seen break; @@ -791,7 +803,8 @@ class WasmFullDecoder : public WasmDecoder { Control* c = &control_.back(); if (c->is_loop()) { // A loop just leaves the values on the stack. - TypeCheckLoopFallThru(c); + TypeCheckFallThru(c); + if (c->unreachable) PushEndValues(c); PopControl(); SetEnv("loop:end", ssa_env_); break; @@ -800,8 +813,7 @@ class WasmFullDecoder : public WasmDecoder { if (c->false_env != nullptr) { // End the true branch of a one-armed if. Goto(c->false_env, c->end_env); - if (ssa_env_->go() && - static_cast<int>(stack_.size()) != c->stack_depth) { + if (!c->unreachable && stack_.size() != c->stack_depth) { error("end of if expected empty stack"); stack_.resize(c->stack_depth); } @@ -824,23 +836,13 @@ class WasmFullDecoder : public WasmDecoder { } FallThruTo(c); SetEnv(name, c->end_env); + PushEndValues(c); - // 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]); - } - } - - PopControl(); - - if (control_.empty()) { - // If the last (implicit) control was popped, check we are at end. + if (control_.size() == 1) { + // If at the last (implicit) control, check we are at end. if (pc_ + 1 != end_) { error(pc_, pc_ + 1, "trailing code after function end"); + break; } last_end_found_ = true; if (ssa_env_->go()) { @@ -848,25 +850,18 @@ class WasmFullDecoder : public WasmDecoder { TRACE(" @%-8d #xx:%-20s|", startrel(pc_), "ImplicitReturn"); DoReturn(); TRACE("\n"); + } else { + TypeCheckFallThru(c); } - return; } + PopControl(); break; } case kExprSelect: { - Value cond = Pop(2, kAstI32); + Value cond = Pop(2, kWasmI32); Value fval = Pop(); - Value tval = Pop(); - if (tval.type == kAstStmt || tval.type != fval.type) { - if (tval.type != kAstEnd && fval.type != kAstEnd) { - error("type mismatch in select"); - break; - } - } + Value tval = Pop(0, fval.type); if (build()) { - DCHECK(tval.type != kAstEnd); - DCHECK(fval.type != kAstEnd); - DCHECK(cond.type != kAstEnd); TFNode* controls[2]; builder_->BranchNoHint(cond.node, &controls[0], &controls[1]); TFNode* merge = builder_->Merge(2, controls); @@ -875,7 +870,7 @@ class WasmFullDecoder : public WasmDecoder { Push(tval.type, phi); ssa_env_->control = merge; } else { - Push(tval.type, nullptr); + Push(tval.type == kWasmVar ? fval.type : tval.type, nullptr); } break; } @@ -890,7 +885,7 @@ class WasmFullDecoder : public WasmDecoder { } case kExprBrIf: { BreakDepthOperand operand(this, pc_); - Value cond = Pop(0, kAstI32); + Value cond = Pop(0, kWasmI32); if (ok() && Validate(pc_, operand, control_)) { SsaEnv* fenv = ssa_env_; SsaEnv* tenv = Split(fenv); @@ -907,7 +902,7 @@ class WasmFullDecoder : public WasmDecoder { BranchTableOperand operand(this, pc_); BranchTableIterator iterator(this, operand); if (Validate(pc_, operand, control_.size())) { - Value key = Pop(0, kAstI32); + Value key = Pop(0, kWasmI32); if (failed()) break; SsaEnv* break_env = ssa_env_; @@ -917,6 +912,7 @@ class WasmFullDecoder : public WasmDecoder { SsaEnv* copy = Steal(break_env); ssa_env_ = copy; + MergeValues* merge = nullptr; while (ok() && iterator.has_next()) { uint32_t i = iterator.cur_index(); const byte* pos = iterator.pc(); @@ -930,6 +926,27 @@ class WasmFullDecoder : public WasmDecoder { ? BUILD(IfDefault, sw) : BUILD(IfValue, i, sw); BreakTo(target); + + // Check that label types match up. + Control* c = &control_[control_.size() - target - 1]; + if (i == 0) { + merge = &c->merge; + } else if (merge->arity != c->merge.arity) { + error(pos, pos, + "inconsistent arity in br_table target %d" + " (previous was %u, this one %u)", + i, merge->arity, c->merge.arity); + } else if (control_.back().unreachable) { + for (uint32_t j = 0; ok() && j < merge->arity; ++j) { + if ((*merge)[j].type != c->merge[j].type) { + error(pos, pos, + "type error in br_table target %d operand %d" + " (previous expected %s, this one %s)", + i, j, WasmOpcodes::TypeName((*merge)[j].type), + WasmOpcodes::TypeName(c->merge[j].type)); + } + } + } } if (failed()) break; } else { @@ -946,6 +963,7 @@ class WasmFullDecoder : public WasmDecoder { ssa_env_ = break_env; } len = 1 + iterator.length(); + EndControl(); break; } case kExprReturn: { @@ -957,33 +975,27 @@ class WasmFullDecoder : public WasmDecoder { EndControl(); break; } - case kExprI8Const: { - ImmI8Operand operand(this, pc_); - Push(kAstI32, BUILD(Int32Constant, operand.value)); - len = 1 + operand.length; - break; - } case kExprI32Const: { ImmI32Operand operand(this, pc_); - Push(kAstI32, BUILD(Int32Constant, operand.value)); + Push(kWasmI32, BUILD(Int32Constant, operand.value)); len = 1 + operand.length; break; } case kExprI64Const: { ImmI64Operand operand(this, pc_); - Push(kAstI64, BUILD(Int64Constant, operand.value)); + Push(kWasmI64, BUILD(Int64Constant, operand.value)); len = 1 + operand.length; break; } case kExprF32Const: { ImmF32Operand operand(this, pc_); - Push(kAstF32, BUILD(Float32Constant, operand.value)); + Push(kWasmF32, BUILD(Float32Constant, operand.value)); len = 1 + operand.length; break; } case kExprF64Const: { ImmF64Operand operand(this, pc_); - Push(kAstF64, BUILD(Float64Constant, operand.value)); + Push(kWasmF64, BUILD(Float64Constant, operand.value)); len = 1 + operand.length; break; } @@ -1045,79 +1057,81 @@ class WasmFullDecoder : public WasmDecoder { break; } case kExprI32LoadMem8S: - len = DecodeLoadMem(kAstI32, MachineType::Int8()); + len = DecodeLoadMem(kWasmI32, MachineType::Int8()); break; case kExprI32LoadMem8U: - len = DecodeLoadMem(kAstI32, MachineType::Uint8()); + len = DecodeLoadMem(kWasmI32, MachineType::Uint8()); break; case kExprI32LoadMem16S: - len = DecodeLoadMem(kAstI32, MachineType::Int16()); + len = DecodeLoadMem(kWasmI32, MachineType::Int16()); break; case kExprI32LoadMem16U: - len = DecodeLoadMem(kAstI32, MachineType::Uint16()); + len = DecodeLoadMem(kWasmI32, MachineType::Uint16()); break; case kExprI32LoadMem: - len = DecodeLoadMem(kAstI32, MachineType::Int32()); + len = DecodeLoadMem(kWasmI32, MachineType::Int32()); break; case kExprI64LoadMem8S: - len = DecodeLoadMem(kAstI64, MachineType::Int8()); + len = DecodeLoadMem(kWasmI64, MachineType::Int8()); break; case kExprI64LoadMem8U: - len = DecodeLoadMem(kAstI64, MachineType::Uint8()); + len = DecodeLoadMem(kWasmI64, MachineType::Uint8()); break; case kExprI64LoadMem16S: - len = DecodeLoadMem(kAstI64, MachineType::Int16()); + len = DecodeLoadMem(kWasmI64, MachineType::Int16()); break; case kExprI64LoadMem16U: - len = DecodeLoadMem(kAstI64, MachineType::Uint16()); + len = DecodeLoadMem(kWasmI64, MachineType::Uint16()); break; case kExprI64LoadMem32S: - len = DecodeLoadMem(kAstI64, MachineType::Int32()); + len = DecodeLoadMem(kWasmI64, MachineType::Int32()); break; case kExprI64LoadMem32U: - len = DecodeLoadMem(kAstI64, MachineType::Uint32()); + len = DecodeLoadMem(kWasmI64, MachineType::Uint32()); break; case kExprI64LoadMem: - len = DecodeLoadMem(kAstI64, MachineType::Int64()); + len = DecodeLoadMem(kWasmI64, MachineType::Int64()); break; case kExprF32LoadMem: - len = DecodeLoadMem(kAstF32, MachineType::Float32()); + len = DecodeLoadMem(kWasmF32, MachineType::Float32()); break; case kExprF64LoadMem: - len = DecodeLoadMem(kAstF64, MachineType::Float64()); + len = DecodeLoadMem(kWasmF64, MachineType::Float64()); break; case kExprI32StoreMem8: - len = DecodeStoreMem(kAstI32, MachineType::Int8()); + len = DecodeStoreMem(kWasmI32, MachineType::Int8()); break; case kExprI32StoreMem16: - len = DecodeStoreMem(kAstI32, MachineType::Int16()); + len = DecodeStoreMem(kWasmI32, MachineType::Int16()); break; case kExprI32StoreMem: - len = DecodeStoreMem(kAstI32, MachineType::Int32()); + len = DecodeStoreMem(kWasmI32, MachineType::Int32()); break; case kExprI64StoreMem8: - len = DecodeStoreMem(kAstI64, MachineType::Int8()); + len = DecodeStoreMem(kWasmI64, MachineType::Int8()); break; case kExprI64StoreMem16: - len = DecodeStoreMem(kAstI64, MachineType::Int16()); + len = DecodeStoreMem(kWasmI64, MachineType::Int16()); break; case kExprI64StoreMem32: - len = DecodeStoreMem(kAstI64, MachineType::Int32()); + len = DecodeStoreMem(kWasmI64, MachineType::Int32()); break; case kExprI64StoreMem: - len = DecodeStoreMem(kAstI64, MachineType::Int64()); + len = DecodeStoreMem(kWasmI64, MachineType::Int64()); break; case kExprF32StoreMem: - len = DecodeStoreMem(kAstF32, MachineType::Float32()); + len = DecodeStoreMem(kWasmF32, MachineType::Float32()); break; case kExprF64StoreMem: - len = DecodeStoreMem(kAstF64, MachineType::Float64()); + len = DecodeStoreMem(kWasmF64, MachineType::Float64()); break; case kExprGrowMemory: { + if (!CheckHasMemory()) break; MemoryIndexOperand operand(this, pc_); + DCHECK_NOT_NULL(module_); if (module_->origin != kAsmJsOrigin) { - Value val = Pop(0, kAstI32); - Push(kAstI32, BUILD(GrowMemory, val.node)); + Value val = Pop(0, kWasmI32); + Push(kWasmI32, BUILD(GrowMemory, val.node)); } else { error("grow_memory is not supported for asmjs modules"); } @@ -1125,8 +1139,9 @@ class WasmFullDecoder : public WasmDecoder { break; } case kExprMemorySize: { + if (!CheckHasMemory()) break; MemoryIndexOperand operand(this, pc_); - Push(kAstI32, BUILD(CurrentMemoryPages)); + Push(kWasmI32, BUILD(CurrentMemoryPages)); len = 1 + operand.length; break; } @@ -1144,7 +1159,7 @@ class WasmFullDecoder : public WasmDecoder { case kExprCallIndirect: { CallIndirectOperand operand(this, pc_); if (Validate(pc_, operand)) { - Value index = Pop(0, kAstI32); + Value index = Pop(0, kWasmI32); TFNode** buffer = PopArgs(operand.sig); if (buffer) buffer[0] = index.node; TFNode** rets = nullptr; @@ -1165,7 +1180,7 @@ class WasmFullDecoder : public WasmDecoder { break; } case kAtomicPrefix: { - if (!module_ || module_->origin != kAsmJsOrigin) { + if (module_ == nullptr || module_->origin != kAsmJsOrigin) { error("Atomics are allowed only in AsmJs modules"); break; } @@ -1184,7 +1199,7 @@ class WasmFullDecoder : public WasmDecoder { } default: { // Deal with special asmjs opcodes. - if (module_ && module_->origin == kAsmJsOrigin) { + if (module_ != nullptr && module_->origin == kAsmJsOrigin) { sig = WasmOpcodes::AsmjsSignature(opcode); if (sig) { BuildSimpleOperator(opcode, sig); @@ -1199,6 +1214,35 @@ class WasmFullDecoder : public WasmDecoder { #if DEBUG if (FLAG_trace_wasm_decoder) { + PrintF(" "); + for (size_t i = 0; i < control_.size(); ++i) { + Control* c = &control_[i]; + enum ControlKind { + kControlIf, + kControlBlock, + kControlLoop, + kControlTry + }; + switch (c->kind) { + case kControlIf: + PrintF("I"); + break; + case kControlBlock: + PrintF("B"); + break; + case kControlLoop: + PrintF("L"); + break; + case kControlTry: + PrintF("T"); + break; + default: + break; + } + PrintF("%u", c->merge.arity); + if (c->unreachable) PrintF("*"); + } + PrintF(" | "); for (size_t i = 0; i < stack_.size(); ++i) { Value& val = stack_[i]; WasmOpcode opcode = static_cast<WasmOpcode>(*val.pc); @@ -1228,20 +1272,23 @@ class WasmFullDecoder : public WasmDecoder { default: break; } + if (val.node == nullptr) PrintF("?"); } PrintF("\n"); } #endif pc_ += len; - if (pc_ >= limit_) { - // End of code reached or exceeded. - if (pc_ > limit_ && ok()) error("Beyond end of code"); - return; - } } // end decode loop + if (pc_ > end_ && ok()) error("Beyond end of code"); } - void EndControl() { ssa_env_->Kill(SsaEnv::kControlEnd); } + void EndControl() { + ssa_env_->Kill(SsaEnv::kControlEnd); + if (!control_.empty()) { + stack_.resize(control_.back().stack_depth); + control_.back().unreachable = true; + } + } void SetBlockType(Control* c, BlockTypeOperand& operand) { c->merge.arity = operand.arity; @@ -1273,77 +1320,96 @@ class WasmFullDecoder : public WasmDecoder { } } - LocalType GetReturnType(FunctionSig* sig) { - return sig->return_count() == 0 ? kAstStmt : sig->GetReturn(); + ValueType GetReturnType(FunctionSig* sig) { + return sig->return_count() == 0 ? kWasmStmt : sig->GetReturn(); } void PushBlock(SsaEnv* end_env) { - const int stack_depth = static_cast<int>(stack_.size()); control_.emplace_back( - Control::Block(pc_, stack_depth, end_env, current_catch_)); + Control::Block(pc_, stack_.size(), 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, current_catch_)); + Control::Loop(pc_, stack_.size(), 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, current_catch_)); + Control::If(pc_, stack_.size(), end_env, false_env, current_catch_)); } 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, zone_, + control_.emplace_back(Control::Try(pc_, stack_.size(), 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) { + int DecodeLoadMem(ValueType type, MachineType mem_type) { + if (!CheckHasMemory()) return 0; MemoryAccessOperand operand(this, pc_, ElementSizeLog2Of(mem_type.representation())); - Value index = Pop(0, kAstI32); + Value index = Pop(0, kWasmI32); TFNode* node = BUILD(LoadMem, type, mem_type, index.node, operand.offset, operand.alignment, position()); Push(type, node); return 1 + operand.length; } - int DecodeStoreMem(LocalType type, MachineType mem_type) { + int DecodeStoreMem(ValueType type, MachineType mem_type) { + if (!CheckHasMemory()) return 0; MemoryAccessOperand operand(this, pc_, ElementSizeLog2Of(mem_type.representation())); Value val = Pop(1, type); - Value index = Pop(0, kAstI32); + Value index = Pop(0, kWasmI32); BUILD(StoreMem, mem_type, index.node, operand.offset, operand.alignment, val.node, position()); return 1 + operand.length; } - unsigned ExtractLane(WasmOpcode opcode, LocalType type) { + unsigned ExtractLane(WasmOpcode opcode, ValueType type) { LaneOperand operand(this, pc_); if (Validate(pc_, operand)) { - TFNode* input = Pop(0, LocalType::kSimd128).node; - TFNode* node = BUILD(SimdExtractLane, opcode, operand.lane, input); + compiler::NodeVector inputs(1, zone_); + inputs[0] = Pop(0, ValueType::kSimd128).node; + TFNode* node = BUILD(SimdLaneOp, opcode, operand.lane, inputs); Push(type, node); } return operand.length; } + unsigned ReplaceLane(WasmOpcode opcode, ValueType type) { + LaneOperand operand(this, pc_); + if (Validate(pc_, operand)) { + compiler::NodeVector inputs(2, zone_); + inputs[1] = Pop(1, type).node; + inputs[0] = Pop(0, ValueType::kSimd128).node; + TFNode* node = BUILD(SimdLaneOp, opcode, operand.lane, inputs); + Push(ValueType::kSimd128, node); + } + return operand.length; + } + unsigned DecodeSimdOpcode(WasmOpcode opcode) { unsigned len = 0; switch (opcode) { case kExprI32x4ExtractLane: { - len = ExtractLane(opcode, LocalType::kWord32); + len = ExtractLane(opcode, ValueType::kWord32); break; } case kExprF32x4ExtractLane: { - len = ExtractLane(opcode, LocalType::kFloat32); + len = ExtractLane(opcode, ValueType::kFloat32); + break; + } + case kExprI32x4ReplaceLane: { + len = ReplaceLane(opcode, ValueType::kWord32); + break; + } + case kExprF32x4ReplaceLane: { + len = ReplaceLane(opcode, ValueType::kFloat32); break; } default: { @@ -1381,12 +1447,25 @@ class WasmFullDecoder : public WasmDecoder { EndControl(); } - void Push(LocalType type, TFNode* node) { - if (type != kAstStmt && type != kAstEnd) { + void Push(ValueType type, TFNode* node) { + if (type != kWasmStmt) { stack_.push_back({pc_, node, type}); } } + void PushEndValues(Control* c) { + DCHECK_EQ(c, &control_.back()); + 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]); + } + } + DCHECK_EQ(c->stack_depth + c->merge.arity, stack_.size()); + } + 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. @@ -1399,31 +1478,24 @@ class WasmFullDecoder : public WasmDecoder { return WasmOpcodes::ShortOpcodeName(static_cast<WasmOpcode>(*pc)); } - Value Pop(int index, LocalType expected) { - if (!ssa_env_->go()) { - // Unreachable code is essentially not typechecked. - return {pc_, nullptr, expected}; - } + Value Pop(int index, ValueType expected) { Value val = Pop(); - if (val.type != expected) { - if (val.type != kAstEnd) { - error(pc_, val.pc, "%s[%d] expected type %s, found %s of type %s", - SafeOpcodeNameAt(pc_), index, WasmOpcodes::TypeName(expected), - SafeOpcodeNameAt(val.pc), WasmOpcodes::TypeName(val.type)); - } + if (val.type != expected && val.type != kWasmVar && expected != kWasmVar) { + error(pc_, val.pc, "%s[%d] expected type %s, found %s of type %s", + SafeOpcodeNameAt(pc_), index, WasmOpcodes::TypeName(expected), + SafeOpcodeNameAt(val.pc), WasmOpcodes::TypeName(val.type)); } return val; } 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}; - error(pc_, pc_, "%s found empty stack", SafeOpcodeNameAt(pc_)); + // Popping past the current control start in reachable code. + Value val = {pc_, nullptr, kWasmVar}; + if (!control_.back().unreachable) { + error(pc_, pc_, "%s found empty stack", SafeOpcodeNameAt(pc_)); + } return val; } Value val = stack_.back(); @@ -1431,22 +1503,6 @@ class WasmFullDecoder : public WasmDecoder { return val; } - Value PopUpTo(int stack_depth) { - if (!ssa_env_->go()) { - // Unreachable code is essentially not typechecked. - return {pc_, nullptr, kAstEnd}; - } - if (stack_depth == static_cast<int>(stack_.size())) { - Value val = {pc_, nullptr, kAstStmt}; - return val; - } else { - DCHECK_LE(stack_depth, static_cast<int>(stack_.size())); - Value val = Pop(); - stack_.resize(stack_depth); - return val; - } - } - int baserel(const byte* ptr) { return base_ ? static_cast<int>(ptr - base_) : 0; } @@ -1454,17 +1510,17 @@ class WasmFullDecoder : public WasmDecoder { int startrel(const byte* ptr) { return static_cast<int>(ptr - start_); } 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_, c->end_env); } else { // Merge the value(s) into the end of the block. - if (c->stack_depth + c->merge.arity > stack_.size()) { + size_t expected = control_.back().stack_depth + c->merge.arity; + if (stack_.size() < expected && !control_.back().unreachable) { error( pc_, pc_, - "expected at least %d values on the stack for br to @%d, found %d", + "expected at least %u 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; @@ -1474,37 +1530,41 @@ class WasmFullDecoder : public WasmDecoder { } void FallThruTo(Control* c) { - if (!ssa_env_->go()) return; + DCHECK_EQ(c, &control_.back()); // Merge the value(s) into the end of the block. - int arity = static_cast<int>(c->merge.arity); - if (c->stack_depth + arity != static_cast<int>(stack_.size())) { - error(pc_, pc_, "expected %d elements on the stack for fallthru to @%d", - arity, startrel(c->pc)); + size_t expected = c->stack_depth + c->merge.arity; + if (stack_.size() == expected || + (stack_.size() < expected && c->unreachable)) { + MergeValuesInto(c); + c->unreachable = false; return; } - MergeValuesInto(c); + error(pc_, pc_, "expected %u elements on the stack for fallthru to @%d", + c->merge.arity, startrel(c->pc)); } - inline Value& GetMergeValueFromStack(Control* c, int i) { + inline Value& GetMergeValueFromStack(Control* c, size_t i) { return stack_[stack_.size() - c->merge.arity + i]; } - void TypeCheckLoopFallThru(Control* c) { - if (!ssa_env_->go()) return; + void TypeCheckFallThru(Control* c) { + DCHECK_EQ(c, &control_.back()); // Fallthru must match arity exactly. int arity = static_cast<int>(c->merge.arity); - if (c->stack_depth + arity != static_cast<int>(stack_.size())) { + if (c->stack_depth + arity < stack_.size() || + (c->stack_depth + arity != stack_.size() && !c->unreachable)) { 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++) { + size_t avail = stack_.size() - c->stack_depth; + for (size_t i = avail >= c->merge.arity ? 0 : c->merge.arity - avail; + 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]; + Value& old = c->merge[i]; if (val.type != old.type) { - error(pc_, pc_, "type error in merge[%d] (expected %s, got %s)", i, + error(pc_, pc_, "type error in merge[%zu] (expected %s, got %s)", i, WasmOpcodes::TypeName(old.type), WasmOpcodes::TypeName(val.type)); return; } @@ -1514,23 +1574,24 @@ class WasmFullDecoder : public WasmDecoder { void MergeValuesInto(Control* c) { SsaEnv* target = c->end_env; bool first = target->state == SsaEnv::kUnreachable; + bool reachable = ssa_env_->go(); Goto(ssa_env_, target); - for (unsigned i = 0; i < c->merge.arity; i++) { + size_t avail = stack_.size() - control_.back().stack_depth; + for (size_t i = avail >= c->merge.arity ? 0 : c->merge.arity - avail; + 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, + Value& old = c->merge[i]; + if (val.type != old.type && val.type != kWasmVar) { + error(pc_, pc_, "type error in merge[%zu] (expected %s, got %s)", i, WasmOpcodes::TypeName(old.type), WasmOpcodes::TypeName(val.type)); return; } - if (builder_) { + if (builder_ && reachable) { + DCHECK_NOT_NULL(val.node); old.node = first ? val.node : CreateOrMergeIntoPhi(old.type, target->control, old.node, val.node); - } else { - old.node = nullptr; } } } @@ -1555,13 +1616,13 @@ class WasmFullDecoder : public WasmDecoder { break; } } - PrintF(" env = %p, state = %c, reason = %s", static_cast<void*>(env), + PrintF("{set_env = %p, state = %c, reason = %s", static_cast<void*>(env), state, reason); if (env && env->control) { PrintF(", control = "); compiler::WasmGraphBuilder::PrintDebugName(env->control); } - PrintF("\n"); + PrintF("}"); } #endif ssa_env_ = env; @@ -1602,7 +1663,7 @@ class WasmFullDecoder : public WasmDecoder { } else { DCHECK_EQ(SsaEnv::kMerged, try_info->catch_env->state); try_info->exception = - CreateOrMergeIntoPhi(kAstI32, try_info->catch_env->control, + CreateOrMergeIntoPhi(kWasmI32, try_info->catch_env->control, try_info->exception, if_exception); } @@ -1686,7 +1747,7 @@ class WasmFullDecoder : public WasmDecoder { return from->Kill(); } - TFNode* CreateOrMergeIntoPhi(LocalType type, TFNode* merge, TFNode* tnode, + TFNode* CreateOrMergeIntoPhi(ValueType type, TFNode* merge, TFNode* tnode, TFNode* fnode) { DCHECK_NOT_NULL(builder_); if (builder_->IsPhiWithMerge(tnode, merge)) { @@ -1710,7 +1771,8 @@ class WasmFullDecoder : public WasmDecoder { env->effect = builder_->EffectPhi(1, &env->effect, env->control); builder_->Terminate(env->effect, env->control); if (FLAG_wasm_loop_assignment_analysis) { - BitVector* assigned = AnalyzeLoopAssignment(pc); + BitVector* assigned = AnalyzeLoopAssignment( + this, pc, static_cast<int>(total_locals()), zone_); if (failed()) return env; if (assigned != nullptr) { // Only introduce phis for variables assigned in this loop. @@ -1789,52 +1851,10 @@ class WasmFullDecoder : public WasmDecoder { } virtual void onFirstError() { - limit_ = start_; // Terminate decoding loop. + end_ = start_; // Terminate decoding loop. builder_ = nullptr; // Don't build any more nodes. TRACE(" !%s\n", error_msg_.get()); } - BitVector* AnalyzeLoopAssignment(const byte* pc) { - if (pc >= limit_) return nullptr; - if (*pc != kExprLoop) return nullptr; - - BitVector* assigned = - new (zone_) BitVector(static_cast<int>(local_type_vec_.size()), zone_); - int depth = 0; - // Iteratively process all AST nodes nested inside the loop. - while (pc < limit_ && ok()) { - WasmOpcode opcode = static_cast<WasmOpcode>(*pc); - unsigned length = 1; - switch (opcode) { - case kExprLoop: - case kExprIf: - case kExprBlock: - case kExprTry: - length = OpcodeLength(pc); - depth++; - break; - case kExprSetLocal: // fallthru - case kExprTeeLocal: { - LocalIndexOperand operand(this, pc); - if (assigned->length() > 0 && - operand.index < static_cast<uint32_t>(assigned->length())) { - // Unverified code might have an out-of-bounds index. - assigned->Add(operand.index); - } - length = 1 + operand.length; - break; - } - case kExprEnd: - depth--; - break; - default: - length = OpcodeLength(pc); - break; - } - if (depth <= 0) break; - pc += length; - } - return ok() ? assigned : nullptr; - } inline wasm::WasmCodePosition position() { int offset = static_cast<int>(pc_ - start_); @@ -1865,30 +1885,33 @@ class WasmFullDecoder : public WasmDecoder { } }; -bool DecodeLocalDecls(AstLocalDecls& decls, const byte* start, +bool DecodeLocalDecls(BodyLocalDecls* decls, const byte* start, const byte* end) { - AccountingAllocator allocator; - Zone tmp(&allocator, ZONE_NAME); - FunctionBody body = {nullptr, nullptr, nullptr, start, end}; - WasmFullDecoder decoder(&tmp, nullptr, body); - return decoder.DecodeLocalDecls(decls); + Decoder decoder(start, end); + if (WasmDecoder::DecodeLocals(&decoder, nullptr, &decls->type_list)) { + DCHECK(decoder.ok()); + decls->encoded_size = decoder.pc_offset(); + return true; + } + return false; } BytecodeIterator::BytecodeIterator(const byte* start, const byte* end, - AstLocalDecls* decls) + BodyLocalDecls* decls) : Decoder(start, end) { if (decls != nullptr) { - if (DecodeLocalDecls(*decls, start, end)) { - pc_ += decls->decls_encoded_size; + if (DecodeLocalDecls(decls, start, end)) { + pc_ += decls->encoded_size; if (pc_ > end_) pc_ = end_; } } } DecodeResult VerifyWasmCode(AccountingAllocator* allocator, + const wasm::WasmModule* module, FunctionBody& body) { Zone zone(allocator, ZONE_NAME); - WasmFullDecoder decoder(&zone, nullptr, body); + WasmFullDecoder decoder(&zone, module, body); decoder.Decode(); return decoder.toResult<DecodeStruct*>(nullptr); } @@ -1902,21 +1925,35 @@ DecodeResult BuildTFGraph(AccountingAllocator* allocator, TFBuilder* builder, } unsigned OpcodeLength(const byte* pc, const byte* end) { - WasmDecoder decoder(nullptr, nullptr, pc, end); - return decoder.OpcodeLength(pc); + Decoder decoder(pc, end); + return WasmDecoder::OpcodeLength(&decoder, pc); } -void PrintAstForDebugging(const byte* start, const byte* end) { +void PrintRawWasmCode(const byte* start, const byte* end) { AccountingAllocator allocator; - OFStream os(stdout); - PrintAst(&allocator, FunctionBodyForTesting(start, end), os, nullptr); + PrintRawWasmCode(&allocator, FunctionBodyForTesting(start, end), nullptr); } -bool PrintAst(AccountingAllocator* allocator, const FunctionBody& body, - std::ostream& os, - std::vector<std::tuple<uint32_t, int, int>>* offset_table) { +namespace { +const char* RawOpcodeName(WasmOpcode opcode) { + switch (opcode) { +#define DECLARE_NAME_CASE(name, opcode, sig) \ + case kExpr##name: \ + return "kExpr" #name; + FOREACH_OPCODE(DECLARE_NAME_CASE) +#undef DECLARE_NAME_CASE + default: + break; + } + return "Unknown"; +} +} // namespace + +bool PrintRawWasmCode(AccountingAllocator* allocator, const FunctionBody& body, + const wasm::WasmModule* module) { + OFStream os(stdout); Zone zone(allocator, ZONE_NAME); - WasmFullDecoder decoder(&zone, nullptr, body); + WasmFullDecoder decoder(&zone, module, body); int line_nr = 0; // Print the function signature. @@ -1926,14 +1963,22 @@ bool PrintAst(AccountingAllocator* allocator, const FunctionBody& body, } // Print the local declarations. - AstLocalDecls decls(&zone); + BodyLocalDecls decls(&zone); BytecodeIterator i(body.start, body.end, &decls); - if (body.start != i.pc()) { + if (body.start != i.pc() && !FLAG_wasm_code_fuzzer_gen_test) { os << "// locals: "; - for (auto p : decls.local_types) { - LocalType type = p.first; - uint32_t count = p.second; - os << " " << count << " " << WasmOpcodes::TypeName(type); + if (!decls.type_list.empty()) { + ValueType type = decls.type_list[0]; + uint32_t count = 0; + for (size_t pos = 0; pos < decls.type_list.size(); ++pos) { + if (decls.type_list[pos] == type) { + ++count; + } else { + os << " " << count << " " << WasmOpcodes::TypeName(type); + type = decls.type_list[pos]; + count = 1; + } + } } os << std::endl; ++line_nr; @@ -1949,25 +1994,22 @@ bool PrintAst(AccountingAllocator* allocator, const FunctionBody& body, ++line_nr; unsigned control_depth = 0; for (; i.has_next(); i.next()) { - unsigned length = decoder.OpcodeLength(i.pc()); + unsigned length = WasmDecoder::OpcodeLength(&decoder, i.pc()); WasmOpcode opcode = i.current(); if (opcode == kExprElse) control_depth--; int num_whitespaces = control_depth < 32 ? 2 * control_depth : 64; - if (offset_table) { - offset_table->push_back( - std::make_tuple(i.pc_offset(), line_nr, num_whitespaces)); - } // 64 whitespaces const char* padding = " "; os.write(padding, num_whitespaces); - os << "k" << WasmOpcodes::OpcodeName(opcode) << ","; + + os << RawOpcodeName(opcode) << ","; for (size_t j = 1; j < length; ++j) { - os << " " << AsHex(i.pc()[j], 2) << ","; + os << " 0x" << AsHex(i.pc()[j], 2) << ","; } switch (opcode) { @@ -2024,7 +2066,7 @@ bool PrintAst(AccountingAllocator* allocator, const FunctionBody& body, } default: break; - } + } os << std::endl; ++line_nr; } @@ -2034,9 +2076,9 @@ bool PrintAst(AccountingAllocator* allocator, const FunctionBody& body, BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, size_t num_locals, const byte* start, const byte* end) { - FunctionBody body = {nullptr, nullptr, nullptr, start, end}; - WasmFullDecoder decoder(zone, nullptr, body); - return decoder.AnalyzeLoopAssignmentForTesting(start, num_locals); + Decoder decoder(start, end); + return WasmDecoder::AnalyzeLoopAssignment(&decoder, start, + static_cast<int>(num_locals), zone); } } // namespace wasm diff --git a/deps/v8/src/wasm/ast-decoder.h b/deps/v8/src/wasm/function-body-decoder.h index 9ce323efcb..1115b1a450 100644 --- a/deps/v8/src/wasm/ast-decoder.h +++ b/deps/v8/src/wasm/function-body-decoder.h @@ -2,10 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef V8_WASM_AST_DECODER_H_ -#define V8_WASM_AST_DECODER_H_ +#ifndef V8_WASM_FUNCTION_BODY_DECODER_H_ +#define V8_WASM_FUNCTION_BODY_DECODER_H_ + +#include <iterator> #include "src/base/compiler-specific.h" +#include "src/base/iterator.h" #include "src/globals.h" #include "src/signature.h" #include "src/wasm/decoder.h" @@ -29,12 +32,12 @@ struct WasmGlobal; // Helpers for decoding different kinds of operands which follow bytecodes. struct LocalIndexOperand { uint32_t index; - LocalType type; + ValueType type; unsigned length; inline LocalIndexOperand(Decoder* decoder, const byte* pc) { index = decoder->checked_read_u32v(pc, 1, &length, "local index"); - type = kAstStmt; + type = kWasmStmt; } }; @@ -67,7 +70,9 @@ struct ImmF32Operand { float value; unsigned length; inline ImmF32Operand(Decoder* decoder, const byte* pc) { - value = bit_cast<float>(decoder->checked_read_u32(pc, 1, "immf32")); + // Avoid bit_cast because it might not preserve the signalling bit of a NaN. + uint32_t tmp = decoder->checked_read_u32(pc, 1, "immf32"); + memcpy(&value, &tmp, sizeof(value)); length = 4; } }; @@ -76,21 +81,23 @@ struct ImmF64Operand { double value; unsigned length; inline ImmF64Operand(Decoder* decoder, const byte* pc) { - value = bit_cast<double>(decoder->checked_read_u64(pc, 1, "immf64")); + // Avoid bit_cast because it might not preserve the signalling bit of a NaN. + uint64_t tmp = decoder->checked_read_u64(pc, 1, "immf64"); + memcpy(&value, &tmp, sizeof(value)); length = 8; } }; struct GlobalIndexOperand { uint32_t index; - LocalType type; + ValueType 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; + type = kWasmStmt; } }; @@ -101,12 +108,12 @@ struct BlockTypeOperand { inline BlockTypeOperand(Decoder* decoder, const byte* pc) { uint8_t val = decoder->checked_read_u8(pc, 1, "block type"); - LocalType type = kAstStmt; + ValueType type = kWasmStmt; length = 1; arity = 0; types = nullptr; if (decode_local_type(val, &type)) { - arity = type == kAstStmt ? 0 : 1; + arity = type == kWasmStmt ? 0 : 1; types = pc + 1; } else { // Handle multi-value blocks. @@ -132,7 +139,7 @@ struct BlockTypeOperand { uint32_t offset = 1 + 1 + len + i; val = decoder->checked_read_u8(pc, offset, "block type"); decode_local_type(val, &type); - if (type == kAstStmt) { + if (type == kWasmStmt) { decoder->error(pc, pc + offset, "invalid block type"); return; } @@ -141,34 +148,34 @@ struct BlockTypeOperand { } // 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)) { + bool decode_local_type(uint8_t val, ValueType* result) { + switch (static_cast<ValueTypeCode>(val)) { case kLocalVoid: - *result = kAstStmt; + *result = kWasmStmt; return true; case kLocalI32: - *result = kAstI32; + *result = kWasmI32; return true; case kLocalI64: - *result = kAstI64; + *result = kWasmI64; return true; case kLocalF32: - *result = kAstF32; + *result = kWasmF32; return true; case kLocalF64: - *result = kAstF64; + *result = kWasmF64; return true; case kLocalS128: - *result = kAstS128; + *result = kWasmS128; return true; default: - *result = kAstStmt; + *result = kWasmStmt; return false; } } - LocalType read_entry(unsigned index) { + ValueType read_entry(unsigned index) { DCHECK_LT(index, arity); - LocalType result; + ValueType result; CHECK(decode_local_type(types[index], &result)); return result; } @@ -243,10 +250,6 @@ struct BranchTableOperand { } table = pc + 1 + len1; } - inline uint32_t read_entry(Decoder* decoder, unsigned i) { - DCHECK(i <= table_count); - return table ? decoder->read_u32(table + i * sizeof(uint32_t)) : 0; - } }; // A helper to iterate over a branch table. @@ -309,11 +312,10 @@ struct MemoryAccessOperand { }; typedef compiler::WasmGraphBuilder TFBuilder; -struct ModuleEnv; // forward declaration of module interface. +struct WasmModule; // forward declaration of module interface. // All of the various data structures necessary to decode a function body. struct FunctionBody { - ModuleEnv* module; // module environment FunctionSig* sig; // function signature const byte* base; // base of the module bytes, for error reporting const byte* start; // start of the function body @@ -322,7 +324,7 @@ struct FunctionBody { static inline FunctionBody FunctionBodyForTesting(const byte* start, const byte* end) { - return {nullptr, nullptr, start, start, end}; + return {nullptr, start, start, end}; } struct DecodeStruct { @@ -334,48 +336,42 @@ inline std::ostream& operator<<(std::ostream& os, const DecodeStruct& tree) { } V8_EXPORT_PRIVATE DecodeResult VerifyWasmCode(AccountingAllocator* allocator, + const wasm::WasmModule* module, 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); +bool PrintRawWasmCode(AccountingAllocator* allocator, const FunctionBody& body, + const wasm::WasmModule* module); // A simplified form of AST printing, e.g. from a debugger. -void PrintAstForDebugging(const byte* start, const byte* end); +void PrintRawWasmCode(const byte* start, const byte* end); inline DecodeResult VerifyWasmCode(AccountingAllocator* allocator, - ModuleEnv* module, FunctionSig* sig, + const WasmModule* module, FunctionSig* sig, const byte* start, const byte* end) { - FunctionBody body = {module, sig, nullptr, start, end}; - return VerifyWasmCode(allocator, body); + FunctionBody body = {sig, nullptr, start, end}; + return VerifyWasmCode(allocator, module, body); } inline DecodeResult BuildTFGraph(AccountingAllocator* allocator, - TFBuilder* builder, ModuleEnv* module, - FunctionSig* sig, const byte* start, - const byte* end) { - FunctionBody body = {module, sig, nullptr, start, end}; + TFBuilder* builder, FunctionSig* sig, + const byte* start, const byte* end) { + FunctionBody body = {sig, nullptr, start, end}; return BuildTFGraph(allocator, builder, body); } -struct AstLocalDecls { +struct BodyLocalDecls { // The size of the encoded declarations. - uint32_t decls_encoded_size; // size of encoded declarations + uint32_t encoded_size; // size of encoded declarations - // Total number of locals. - uint32_t total_local_count; - - // List of {local type, count} pairs. - ZoneVector<std::pair<LocalType, uint32_t>> local_types; + ZoneVector<ValueType> type_list; // Constructor initializes the vector. - explicit AstLocalDecls(Zone* zone) - : decls_encoded_size(0), total_local_count(0), local_types(zone) {} + explicit BodyLocalDecls(Zone* zone) : encoded_size(0), type_list(zone) {} }; -V8_EXPORT_PRIVATE bool DecodeLocalDecls(AstLocalDecls& decls, const byte* start, - const byte* end); +V8_EXPORT_PRIVATE bool DecodeLocalDecls(BodyLocalDecls* decls, + const byte* start, const byte* end); V8_EXPORT_PRIVATE BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, size_t num_locals, const byte* start, @@ -386,41 +382,77 @@ V8_EXPORT_PRIVATE unsigned OpcodeLength(const byte* pc, const byte* end); // A simple forward iterator for bytecodes. class V8_EXPORT_PRIVATE BytecodeIterator : public NON_EXPORTED_BASE(Decoder) { - public: - // If one wants to iterate over the bytecode without looking at {pc_offset()}. - class iterator { + // Base class for both iterators defined below. + class iterator_base { public: - inline iterator& operator++() { + inline iterator_base& operator++() { DCHECK_LT(ptr_, end_); ptr_ += OpcodeLength(ptr_, end_); return *this; } + inline bool operator==(const iterator_base& that) { + return this->ptr_ == that.ptr_; + } + inline bool operator!=(const iterator_base& that) { + return this->ptr_ != that.ptr_; + } + + protected: + const byte* ptr_; + const byte* end_; + iterator_base(const byte* ptr, const byte* end) : ptr_(ptr), end_(end) {} + }; + + public: + // If one wants to iterate over the bytecode without looking at {pc_offset()}. + class opcode_iterator + : public iterator_base, + public std::iterator<std::input_iterator_tag, WasmOpcode> { + public: inline WasmOpcode operator*() { DCHECK_LT(ptr_, end_); return static_cast<WasmOpcode>(*ptr_); } - inline bool operator==(const iterator& that) { - return this->ptr_ == that.ptr_; - } - inline bool operator!=(const iterator& that) { - return this->ptr_ != that.ptr_; + + private: + friend class BytecodeIterator; + opcode_iterator(const byte* ptr, const byte* end) + : iterator_base(ptr, end) {} + }; + // If one wants to iterate over the instruction offsets without looking at + // opcodes. + class offset_iterator + : public iterator_base, + public std::iterator<std::input_iterator_tag, uint32_t> { + public: + inline uint32_t operator*() { + DCHECK_LT(ptr_, end_); + return static_cast<uint32_t>(ptr_ - start_); } private: + const byte* start_; friend class BytecodeIterator; - const byte* ptr_; - const byte* end_; - iterator(const byte* ptr, const byte* end) : ptr_(ptr), end_(end) {} + offset_iterator(const byte* start, const byte* ptr, const byte* end) + : iterator_base(ptr, end), start_(start) {} }; // Create a new {BytecodeIterator}. If the {decls} pointer is non-null, // assume the bytecode starts with local declarations and decode them. // Otherwise, do not decode local decls. BytecodeIterator(const byte* start, const byte* end, - AstLocalDecls* decls = nullptr); + BodyLocalDecls* decls = nullptr); - inline iterator begin() const { return iterator(pc_, end_); } - inline iterator end() const { return iterator(end_, end_); } + base::iterator_range<opcode_iterator> opcodes() { + return base::iterator_range<opcode_iterator>(opcode_iterator(pc_, end_), + opcode_iterator(end_, end_)); + } + + base::iterator_range<offset_iterator> offsets() { + return base::iterator_range<offset_iterator>( + offset_iterator(start_, pc_, end_), + offset_iterator(start_, end_, end_)); + } WasmOpcode current() { return static_cast<WasmOpcode>( @@ -441,4 +473,4 @@ class V8_EXPORT_PRIVATE BytecodeIterator : public NON_EXPORTED_BASE(Decoder) { } // namespace internal } // namespace v8 -#endif // V8_WASM_AST_DECODER_H_ +#endif // V8_WASM_FUNCTION_BODY_DECODER_H_ diff --git a/deps/v8/src/wasm/module-decoder.cc b/deps/v8/src/wasm/module-decoder.cc index c8eace3c10..056fc2f64d 100644 --- a/deps/v8/src/wasm/module-decoder.cc +++ b/deps/v8/src/wasm/module-decoder.cc @@ -12,6 +12,7 @@ #include "src/v8.h" #include "src/wasm/decoder.h" +#include "src/wasm/wasm-limits.h" namespace v8 { namespace internal { @@ -31,25 +32,25 @@ namespace { const char* kNameString = "name"; const size_t kNameStringLength = 4; -LocalType TypeOf(const WasmModule* module, const WasmInitExpr& expr) { +ValueType TypeOf(const WasmModule* module, const WasmInitExpr& expr) { switch (expr.kind) { case WasmInitExpr::kNone: - return kAstStmt; + return kWasmStmt; case WasmInitExpr::kGlobalIndex: return expr.val.global_index < module->globals.size() ? module->globals[expr.val.global_index].type - : kAstStmt; + : kWasmStmt; case WasmInitExpr::kI32Const: - return kAstI32; + return kWasmI32; case WasmInitExpr::kI64Const: - return kAstI64; + return kWasmI64; case WasmInitExpr::kF32Const: - return kAstF32; + return kWasmF32; case WasmInitExpr::kF64Const: - return kAstF64; + return kWasmF64; default: UNREACHABLE(); - return kAstStmt; + return kWasmStmt; } } @@ -179,17 +180,17 @@ class ModuleDecoder : public Decoder { ModuleOrigin origin) : Decoder(module_start, module_end), module_zone(zone), origin_(origin) { result_.start = start_; - if (limit_ < start_) { + if (end_ < start_) { error(start_, "end is less than start"); - limit_ = start_; + end_ = start_; } } virtual void onFirstError() { - pc_ = limit_; // On error, terminate section decoding loop. + pc_ = end_; // On error, terminate section decoding loop. } - static void DumpModule(WasmModule* module, const ModuleResult& result) { + void DumpModule(const ModuleResult& result) { std::string path; if (FLAG_dump_wasm_module_path) { path = FLAG_dump_wasm_module_path; @@ -199,7 +200,7 @@ class ModuleDecoder : public Decoder { } } // File are named `HASH.{ok,failed}.wasm`. - size_t hash = base::hash_range(module->module_start, module->module_end); + size_t hash = base::hash_range(start_, end_); char buf[32] = {'\0'}; #if V8_OS_WIN && _MSC_VER < 1900 #define snprintf sprintf_s @@ -208,17 +209,15 @@ class ModuleDecoder : public Decoder { result.ok() ? "ok" : "failed"); std::string name(buf); if (FILE* wasm_file = base::OS::FOpen((path + name).c_str(), "wb")) { - fwrite(module->module_start, module->module_end - module->module_start, 1, - wasm_file); + fwrite(start_, end_ - start_, 1, wasm_file); fclose(wasm_file); } } // Decodes an entire module. - ModuleResult DecodeModule(WasmModule* module, bool verify_functions = true) { + ModuleResult DecodeModule(bool verify_functions = true) { pc_ = start_; - module->module_start = start_; - module->module_end = limit_; + WasmModule* module = new WasmModule(module_zone); module->min_mem_pages = 0; module->max_mem_pages = 0; module->mem_export = false; @@ -249,8 +248,8 @@ class ModuleDecoder : public Decoder { // ===== Type section ==================================================== if (section_iter.section_code() == kTypeSectionCode) { - uint32_t signatures_count = consume_u32v("signatures count"); - module->signatures.reserve(SafeReserve(signatures_count)); + uint32_t signatures_count = consume_count("types count", kV8MaxWasmTypes); + module->signatures.reserve(signatures_count); for (uint32_t i = 0; ok() && i < signatures_count; ++i) { TRACE("DecodeSignature[%d] module+%d\n", i, static_cast<int>(pc_ - start_)); @@ -262,8 +261,9 @@ class ModuleDecoder : public Decoder { // ===== 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)); + uint32_t import_table_count = + consume_count("imports count", kV8MaxWasmImports); + module->import_table.reserve(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_)); @@ -280,9 +280,6 @@ class ModuleDecoder : public Decoder { 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->field_name_offset = consume_string(&import->field_name_length, true); @@ -307,6 +304,7 @@ class ModuleDecoder : public Decoder { } case kExternalTable: { // ===== Imported table ========================================== + if (!AddTable(module)) break; import->index = static_cast<uint32_t>(module->function_tables.size()); module->function_tables.push_back({0, 0, false, @@ -314,30 +312,29 @@ class ModuleDecoder : public Decoder { false, SignatureMap()}); expect_u8("element type", kWasmAnyFunctionTypeForm); WasmIndirectFunctionTable* table = &module->function_tables.back(); - consume_resizable_limits( - "element count", "elements", WasmModule::kV8MaxTableSize, - &table->min_size, &table->has_max, WasmModule::kV8MaxTableSize, - &table->max_size); + consume_resizable_limits("element count", "elements", + kV8MaxWasmTableSize, &table->min_size, + &table->has_max, kV8MaxWasmTableSize, + &table->max_size); break; } case kExternalMemory: { // ===== Imported memory ========================================= - bool has_max = false; - consume_resizable_limits("memory", "pages", WasmModule::kV8MaxPages, - &module->min_mem_pages, &has_max, - WasmModule::kSpecMaxPages, - &module->max_mem_pages); - module->has_memory = true; + if (!AddMemory(module)) break; + consume_resizable_limits( + "memory", "pages", kV8MaxWasmMemoryPages, + &module->min_mem_pages, &module->has_max_mem, + kSpecMaxWasmMemoryPages, &module->max_mem_pages); break; } case kExternalGlobal: { // ===== Imported global ========================================= import->index = static_cast<uint32_t>(module->globals.size()); module->globals.push_back( - {kAstStmt, false, WasmInitExpr(), 0, true, false}); + {kWasmStmt, false, WasmInitExpr(), 0, true, false}); WasmGlobal* global = &module->globals.back(); global->type = consume_value_type(); - global->mutability = consume_u8("mutability") != 0; + global->mutability = consume_mutability(); if (global->mutability) { error("mutable globals cannot be imported"); } @@ -353,8 +350,9 @@ class ModuleDecoder : public Decoder { // ===== Function section ================================================ if (section_iter.section_code() == kFunctionSectionCode) { - uint32_t functions_count = consume_u32v("functions count"); - module->functions.reserve(SafeReserve(functions_count)); + uint32_t functions_count = + consume_count("functions count", kV8MaxWasmFunctions); + module->functions.reserve(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()); @@ -375,63 +373,47 @@ class ModuleDecoder : public Decoder { // ===== 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); - } - if (module->function_tables.size() < 1) { - module->function_tables.push_back({0, 0, false, std::vector<int32_t>(), - false, false, SignatureMap()}); - } + uint32_t table_count = consume_count("table count", kV8MaxWasmTables); for (uint32_t i = 0; ok() && i < table_count; i++) { + if (!AddTable(module)) break; + module->function_tables.push_back({0, 0, false, std::vector<int32_t>(), + false, false, SignatureMap()}); WasmIndirectFunctionTable* table = &module->function_tables.back(); expect_u8("table type", kWasmAnyFunctionTypeForm); - consume_resizable_limits("table elements", "elements", - WasmModule::kV8MaxTableSize, &table->min_size, - &table->has_max, WasmModule::kV8MaxTableSize, - &table->max_size); + consume_resizable_limits( + "table elements", "elements", kV8MaxWasmTableSize, &table->min_size, + &table->has_max, kV8MaxWasmTableSize, &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); - } + uint32_t memory_count = consume_count("memory count", kV8MaxWasmMemories); for (uint32_t i = 0; ok() && i < memory_count; i++) { - bool has_max = false; - consume_resizable_limits( - "memory", "pages", WasmModule::kV8MaxPages, &module->min_mem_pages, - &has_max, WasmModule::kSpecMaxPages, &module->max_mem_pages); + if (!AddMemory(module)) break; + consume_resizable_limits("memory", "pages", kV8MaxWasmMemoryPages, + &module->min_mem_pages, &module->has_max_mem, + kSpecMaxWasmMemoryPages, + &module->max_mem_pages); } - module->has_memory = true; section_iter.advance(); } // ===== Global section ================================================== if (section_iter.section_code() == kGlobalSectionCode) { - uint32_t globals_count = consume_u32v("globals count"); + uint32_t globals_count = + consume_count("globals count", kV8MaxWasmGlobals); uint32_t imported_globals = static_cast<uint32_t>(module->globals.size()); - if (!IsWithinLimit(std::numeric_limits<int32_t>::max(), globals_count, - imported_globals)) { - error(pos, pos, "too many imported+defined globals: %u + %u", - imported_globals, globals_count); - } - module->globals.reserve(SafeReserve(imported_globals + globals_count)); + module->globals.reserve(imported_globals + 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, WasmInitExpr(), 0, false, false}); + {kWasmStmt, false, WasmInitExpr(), 0, false, false}); WasmGlobal* global = &module->globals.back(); DecodeGlobalInModule(module, i + imported_globals, global); } @@ -440,8 +422,9 @@ class ModuleDecoder : public Decoder { // ===== 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)); + uint32_t export_table_count = + consume_count("exports count", kV8MaxWasmImports); + module->export_table.reserve(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_)); @@ -473,7 +456,11 @@ class ModuleDecoder : public Decoder { } case kExternalMemory: { uint32_t index = consume_u32v("memory index"); - if (index != 0) error("invalid memory index != 0"); + // TODO(titzer): This should become more regular + // once we support multiple memories. + if (!module->has_memory || index != 0) { + error("invalid memory index != 0"); + } module->mem_export = true; break; } @@ -493,8 +480,8 @@ class ModuleDecoder : public Decoder { break; } } - // Check for duplicate exports. - if (ok() && module->export_table.size() > 1) { + // Check for duplicate exports (except for asm.js). + if (ok() && origin_ != kAsmJsOrigin && 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) { @@ -538,7 +525,8 @@ class ModuleDecoder : public Decoder { // ===== Elements section ================================================ if (section_iter.section_code() == kElementSectionCode) { - uint32_t element_count = consume_u32v("element count"); + uint32_t element_count = + consume_count("element count", kV8MaxWasmTableSize); for (uint32_t i = 0; ok() && i < element_count; ++i) { const byte* pos = pc(); uint32_t table_index = consume_u32v("table index"); @@ -551,19 +539,18 @@ class ModuleDecoder : public Decoder { } else { table = &module->function_tables[table_index]; } - WasmInitExpr offset = consume_init_expr(module, kAstI32); - uint32_t num_elem = consume_u32v("number of elements"); + WasmInitExpr offset = consume_init_expr(module, kWasmI32); + uint32_t num_elem = + consume_count("number of elements", kV8MaxWasmTableEntries); 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; uint32_t index = consume_func_index(module, &func); init->entries.push_back(index); if (table && index < module->functions.size()) { // Canonicalize signature indices during decoding. - // TODO(titzer): suboptimal, redundant when verifying only. table->map.FindOrInsert(module->functions[index].sig); } } @@ -587,10 +574,8 @@ class ModuleDecoder : public Decoder { function->code_start_offset = pc_offset(); function->code_end_offset = pc_offset() + size; if (verify_functions) { - ModuleEnv module_env; - module_env.module = module; - module_env.origin = module->origin; - + ModuleBytesEnv module_env(module, nullptr, + ModuleWireBytes(start_, end_)); VerifyFunctionBody(i + module->num_imported_functions, &module_env, function); } @@ -601,8 +586,9 @@ class ModuleDecoder : public Decoder { // ===== 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)); + uint32_t data_segments_count = + consume_count("data segments count", kV8MaxWasmDataSegments); + module->data_segments.reserve(data_segments_count); for (uint32_t i = 0; ok() && i < data_segments_count; ++i) { if (!module->has_memory) { error("cannot load data without memory"); @@ -623,22 +609,29 @@ class ModuleDecoder : public Decoder { // ===== Name section ==================================================== if (section_iter.section_code() == kNameSectionCode) { - uint32_t functions_count = consume_u32v("functions count"); + // TODO(titzer): find a way to report name errors as warnings. + // Use an inner decoder so that errors don't fail the outer decoder. + Decoder inner(start_, pc_, end_); + uint32_t functions_count = inner.consume_u32v("functions count"); - for (uint32_t i = 0; ok() && i < functions_count; ++i) { + for (uint32_t i = 0; inner.ok() && i < functions_count; ++i) { uint32_t function_name_length = 0; - uint32_t name_offset = consume_string(&function_name_length, false); + uint32_t name_offset = + consume_string(inner, &function_name_length, false); uint32_t func_index = i; - if (func_index < module->functions.size()) { + if (inner.ok() && func_index < module->functions.size()) { module->functions[func_index].name_offset = name_offset; module->functions[func_index].name_length = function_name_length; } - uint32_t local_names_count = consume_u32v("local names count"); + uint32_t local_names_count = inner.consume_u32v("local names count"); for (uint32_t j = 0; ok() && j < local_names_count; j++) { - skip_string(); + uint32_t length = inner.consume_u32v("string length"); + inner.consume_bytes(length, "string"); } } + // Skip the whole names section in the outer decoder. + consume_bytes(section_iter.payload_length(), nullptr); section_iter.advance(); } @@ -656,25 +649,19 @@ class ModuleDecoder : public Decoder { if (verify_functions && result.ok()) { result.MoveFrom(result_); // Copy error code and location. } - if (FLAG_dump_wasm_module) DumpModule(module, result); + if (FLAG_dump_wasm_module) DumpModule(result); return result; } - uint32_t SafeReserve(uint32_t count) { - // Avoid OOM by only reserving up to a certain size. - const uint32_t kMaxReserve = 20000; - return count < kMaxReserve ? count : kMaxReserve; - } - // Decodes a single anonymous function starting at {start_}. - FunctionResult DecodeSingleFunction(ModuleEnv* module_env, + FunctionResult DecodeSingleFunction(ModuleBytesEnv* module_env, WasmFunction* function) { pc_ = start_; function->sig = consume_sig(); // read signature function->name_offset = 0; // ---- name function->name_length = 0; // ---- name length function->code_start_offset = off(pc_); // ---- code start - function->code_end_offset = off(limit_); // ---- code end + function->code_end_offset = off(end_); // ---- code end if (ok()) VerifyFunctionBody(0, module_env, function); @@ -693,7 +680,7 @@ class ModuleDecoder : public Decoder { WasmInitExpr DecodeInitExpr(const byte* start) { pc_ = start; - return consume_init_expr(nullptr, kAstStmt); + return consume_init_expr(nullptr, kWasmStmt); } private: @@ -703,13 +690,32 @@ class ModuleDecoder : public Decoder { uint32_t off(const byte* ptr) { return static_cast<uint32_t>(ptr - start_); } + bool AddTable(WasmModule* module) { + if (module->function_tables.size() > 0) { + error("At most one table is supported"); + return false; + } else { + return true; + } + } + + bool AddMemory(WasmModule* module) { + if (module->has_memory) { + error("At most one memory is supported"); + return false; + } else { + module->has_memory = true; + return true; + } + } + // Decodes a single global entry inside a module starting at {pc_}. void DecodeGlobalInModule(WasmModule* module, uint32_t index, WasmGlobal* global) { global->type = consume_value_type(); - global->mutability = consume_u8("mutability") != 0; + global->mutability = consume_mutability(); const byte* pos = pc(); - global->init = consume_init_expr(module, kAstStmt); + global->init = consume_init_expr(module, kWasmStmt); switch (global->init.kind) { case WasmInitExpr::kGlobalIndex: { uint32_t other_index = global->init.val.global_index; @@ -747,12 +753,12 @@ class ModuleDecoder : public Decoder { void DecodeDataSegmentInModule(WasmModule* module, WasmDataSegment* segment) { const byte* start = pc_; expect_u8("linear memory index", 0); - segment->dest_addr = consume_init_expr(module, kAstI32); + segment->dest_addr = consume_init_expr(module, kWasmI32); segment->source_size = consume_u32v("source size"); segment->source_offset = static_cast<uint32_t>(pc_ - start_); // Validate the data is in the module. - uint32_t module_limit = static_cast<uint32_t>(limit_ - start_); + uint32_t module_limit = static_cast<uint32_t>(end_ - start_); if (!IsWithinLimit(module_limit, segment->source_offset, segment->source_size)) { error(start, "segment out of bounds of module"); @@ -779,17 +785,19 @@ class ModuleDecoder : public Decoder { } // Verifies the body (code) of a given function. - void VerifyFunctionBody(uint32_t func_num, ModuleEnv* menv, + void VerifyFunctionBody(uint32_t func_num, ModuleBytesEnv* menv, WasmFunction* function) { if (FLAG_trace_wasm_decoder || FLAG_trace_wasm_decode_time) { OFStream os(stdout); os << "Verifying WASM function " << WasmFunctionName(function, menv) << std::endl; } - FunctionBody body = {menv, function->sig, start_, + FunctionBody body = {function->sig, start_, start_ + function->code_start_offset, start_ + function->code_end_offset}; - DecodeResult result = VerifyWasmCode(module_zone->allocator(), body); + DecodeResult result = + VerifyWasmCode(module_zone->allocator(), + menv == nullptr ? nullptr : menv->module, body); if (result.failed()) { // Wrap the error message from the function decoder. std::ostringstream str; @@ -808,27 +816,26 @@ class ModuleDecoder : public Decoder { } } + uint32_t consume_string(uint32_t* length, bool validate_utf8) { + return consume_string(*this, length, validate_utf8); + } + // 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(); - const byte* string_start = pc_; + uint32_t consume_string(Decoder& decoder, uint32_t* length, + bool validate_utf8) { + *length = decoder.consume_u32v("string length"); + uint32_t offset = decoder.pc_offset(); + const byte* string_start = decoder.pc(); // Consume bytes before validation to guarantee that the string is not oob. - if (*length > 0) consume_bytes(*length, "string"); - if (ok() && validate_utf8 && + if (*length > 0) decoder.consume_bytes(*length, "string"); + if (decoder.ok() && validate_utf8 && !unibrow::Utf8::Validate(string_start, *length)) { - error(string_start, "no valid UTF-8 string"); + decoder.error(string_start, "no valid UTF-8 string"); } return offset; } - // Skips over a length-prefixed string, but checks that it is within bounds. - void skip_string() { - uint32_t length = consume_u32v("string length"); - consume_bytes(length, "string"); - } - uint32_t consume_sig_index(WasmModule* module, FunctionSig** sig) { const byte* pos = pc_; uint32_t sig_index = consume_u32v("signature index"); @@ -842,6 +849,17 @@ class ModuleDecoder : public Decoder { return sig_index; } + uint32_t consume_count(const char* name, size_t maximum) { + const byte* p = pc_; + uint32_t count = consume_u32v(name); + if (count > maximum) { + error(p, p, "%s of %u exceeds internal limit of %zu", name, count, + maximum); + return static_cast<uint32_t>(maximum); + } + return count; + } + uint32_t consume_func_index(WasmModule* module, WasmFunction** func) { return consume_index("function index", module->functions, func); } @@ -912,7 +930,7 @@ class ModuleDecoder : public Decoder { return true; } - WasmInitExpr consume_init_expr(WasmModule* module, LocalType expected) { + WasmInitExpr consume_init_expr(WasmModule* module, ValueType expected) { const byte* pos = pc(); uint8_t opcode = consume_u8("opcode"); WasmInitExpr expr; @@ -978,7 +996,7 @@ class ModuleDecoder : public Decoder { if (!expect_u8("end opcode", kExprEnd)) { expr.kind = WasmInitExpr::kNone; } - if (expected != kAstStmt && TypeOf(module, expr) != kAstI32) { + if (expected != kWasmStmt && TypeOf(module, expr) != kWasmI32) { error(pos, pos, "type error in init expression, expected %s, got %s", WasmOpcodes::TypeName(expected), WasmOpcodes::TypeName(TypeOf(module, expr))); @@ -986,29 +1004,36 @@ class ModuleDecoder : public Decoder { return expr; } + // Read a mutability flag + bool consume_mutability() { + byte val = consume_u8("mutability"); + if (val > 1) error(pc_ - 1, "invalid mutability"); + return val != 0; + } + // Reads a single 8-bit integer, interpreting it as a local type. - LocalType consume_value_type() { + ValueType consume_value_type() { byte val = consume_u8("value type"); - LocalTypeCode t = static_cast<LocalTypeCode>(val); + ValueTypeCode t = static_cast<ValueTypeCode>(val); switch (t) { case kLocalI32: - return kAstI32; + return kWasmI32; case kLocalI64: - return kAstI64; + return kWasmI64; case kLocalF32: - return kAstF32; + return kWasmF32; case kLocalF64: - return kAstF64; + return kWasmF64; case kLocalS128: if (origin_ != kAsmJsOrigin && FLAG_wasm_simd_prototype) { - return kAstS128; + return kWasmS128; } else { error(pc_ - 1, "invalid local type"); - return kAstStmt; + return kWasmStmt; } default: error(pc_ - 1, "invalid local type"); - return kAstStmt; + return kWasmStmt; } } @@ -1016,35 +1041,32 @@ class ModuleDecoder : public Decoder { FunctionSig* consume_sig() { if (!expect_u8("type form", kWasmFunctionTypeForm)) return nullptr; // parse parameter types - uint32_t param_count = consume_u32v("param count"); - std::vector<LocalType> params; + uint32_t param_count = + consume_count("param count", kV8MaxWasmFunctionParams); + if (failed()) return nullptr; + std::vector<ValueType> params; for (uint32_t i = 0; ok() && i < param_count; ++i) { - LocalType param = consume_value_type(); + ValueType param = consume_value_type(); params.push_back(param); } // parse return types - const byte* pt = pc_; - uint32_t return_count = consume_u32v("return count"); - if (return_count > kMaxReturnCount) { - error(pt, pt, "return count of %u exceeds maximum of %u", return_count, - kMaxReturnCount); - return nullptr; - } - std::vector<LocalType> returns; + const size_t max_return_count = FLAG_wasm_mv_prototype + ? kV8MaxWasmFunctionMultiReturns + : kV8MaxWasmFunctionReturns; + uint32_t return_count = consume_count("return count", max_return_count); + if (failed()) return nullptr; + std::vector<ValueType> returns; for (uint32_t i = 0; ok() && i < return_count; ++i) { - LocalType ret = consume_value_type(); + ValueType ret = consume_value_type(); returns.push_back(ret); } - if (failed()) { - // Decoding failed, return void -> void - return new (module_zone) FunctionSig(0, 0, nullptr); - } + if (failed()) return nullptr; // FunctionSig stores the return types first. - LocalType* buffer = - module_zone->NewArray<LocalType>(param_count + return_count); + ValueType* buffer = + module_zone->NewArray<ValueType>(param_count + return_count); uint32_t b = 0; for (uint32_t i = 0; i < return_count; ++i) buffer[b++] = returns[i]; for (uint32_t i = 0; i < param_count; ++i) buffer[b++] = params[i]; @@ -1113,16 +1135,16 @@ ModuleResult DecodeWasmModule(Isolate* isolate, const byte* module_start, isolate->counters()->wasm_decode_module_time()); size_t size = module_end - module_start; if (module_start > module_end) return ModuleError("start > end"); - if (size >= kMaxModuleSize) return ModuleError("size > maximum module size"); + if (size >= kV8MaxWasmModuleSize) + return ModuleError("size > maximum module size"); // TODO(bradnelson): Improve histogram handling of size_t. isolate->counters()->wasm_module_size_bytes()->AddSample( static_cast<int>(size)); // Signatures are stored in zone memory, which have the same lifetime // as the {module}. Zone* zone = new Zone(isolate->allocator(), ZONE_NAME); - WasmModule* module = new WasmModule(zone, module_start); ModuleDecoder decoder(zone, module_start, module_end, origin); - ModuleResult result = decoder.DecodeModule(module, verify_functions); + ModuleResult result = decoder.DecodeModule(verify_functions); // TODO(bradnelson): Improve histogram handling of size_t. // TODO(titzer): this isn't accurate, since it doesn't count the data // allocated on the C++ heap. @@ -1146,14 +1168,14 @@ WasmInitExpr DecodeWasmInitExprForTesting(const byte* start, const byte* end) { } FunctionResult DecodeWasmFunction(Isolate* isolate, Zone* zone, - ModuleEnv* module_env, + ModuleBytesEnv* module_env, const byte* function_start, const byte* function_end) { HistogramTimerScope wasm_decode_function_time_scope( isolate->counters()->wasm_decode_function_time()); size_t size = function_end - function_start; if (function_start > function_end) return FunctionError("start > end"); - if (size > kMaxFunctionSize) + if (size > kV8MaxWasmFunctionSize) return FunctionError("size > maximum function size"); isolate->counters()->wasm_function_size_bytes()->AddSample( static_cast<int>(size)); @@ -1208,22 +1230,31 @@ AsmJsOffsetsResult DecodeAsmJsOffsets(const byte* tables_start, for (uint32_t i = 0; i < functions_count && decoder.ok(); ++i) { uint32_t size = decoder.consume_u32v("table size"); if (size == 0) { - table.push_back(std::vector<std::pair<int, int>>()); + table.push_back(std::vector<AsmJsOffsetEntry>()); continue; } if (!decoder.checkAvailable(size)) { decoder.error("illegal asm function offset table size"); } const byte* table_end = decoder.pc() + size; - uint32_t locals_size = decoder.consume_u32("locals size"); + uint32_t locals_size = decoder.consume_u32v("locals size"); + int function_start_position = decoder.consume_u32v("function start pos"); int last_byte_offset = locals_size; - int last_asm_position = 0; - std::vector<std::pair<int, int>> func_asm_offsets; + int last_asm_position = function_start_position; + std::vector<AsmJsOffsetEntry> func_asm_offsets; func_asm_offsets.reserve(size / 4); // conservative estimation + // Add an entry for the stack check, associated with position 0. + func_asm_offsets.push_back( + {0, function_start_position, function_start_position}); while (decoder.ok() && decoder.pc() < table_end) { last_byte_offset += decoder.consume_u32v("byte offset delta"); - last_asm_position += decoder.consume_i32v("asm position delta"); - func_asm_offsets.push_back({last_byte_offset, last_asm_position}); + int call_position = + last_asm_position + decoder.consume_i32v("call position delta"); + int to_number_position = + call_position + decoder.consume_i32v("to_number position delta"); + last_asm_position = to_number_position; + func_asm_offsets.push_back( + {last_byte_offset, call_position, to_number_position}); } if (decoder.pc() != table_end) { decoder.error("broken asm offset table"); @@ -1235,6 +1266,36 @@ AsmJsOffsetsResult DecodeAsmJsOffsets(const byte* tables_start, return decoder.toResult(std::move(table)); } +std::vector<CustomSectionOffset> DecodeCustomSections(const byte* start, + const byte* end) { + Decoder decoder(start, end); + decoder.consume_bytes(4, "wasm magic"); + decoder.consume_bytes(4, "wasm version"); + + std::vector<CustomSectionOffset> result; + + while (decoder.more()) { + byte section_code = decoder.consume_u8("section code"); + uint32_t section_length = decoder.consume_u32v("section length"); + uint32_t section_start = decoder.pc_offset(); + if (section_code != 0) { + // Skip known sections. + decoder.consume_bytes(section_length, "section bytes"); + continue; + } + uint32_t name_length = decoder.consume_u32v("name length"); + uint32_t name_offset = decoder.pc_offset(); + decoder.consume_bytes(name_length, "section name"); + uint32_t payload_offset = decoder.pc_offset(); + uint32_t payload_length = section_length - (payload_offset - section_start); + decoder.consume_bytes(payload_length); + result.push_back({section_start, name_offset, name_length, payload_offset, + payload_length, section_length}); + } + + return result; +} + } // namespace wasm } // namespace internal } // namespace v8 diff --git a/deps/v8/src/wasm/module-decoder.h b/deps/v8/src/wasm/module-decoder.h index 7cf5cfe3c1..982fbc9189 100644 --- a/deps/v8/src/wasm/module-decoder.h +++ b/deps/v8/src/wasm/module-decoder.h @@ -6,7 +6,7 @@ #define V8_WASM_MODULE_DECODER_H_ #include "src/globals.h" -#include "src/wasm/ast-decoder.h" +#include "src/wasm/function-body-decoder.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-result.h" @@ -18,7 +18,12 @@ typedef Result<const WasmModule*> ModuleResult; typedef Result<WasmFunction*> FunctionResult; typedef std::vector<std::pair<int, int>> FunctionOffsets; typedef Result<FunctionOffsets> FunctionOffsetsResult; -typedef std::vector<std::vector<std::pair<int, int>>> AsmJsOffsets; +struct AsmJsOffsetEntry { + int byte_offset; + int source_position_call; + int source_position_number_conversion; +}; +typedef std::vector<std::vector<AsmJsOffsetEntry>> AsmJsOffsets; typedef Result<AsmJsOffsets> AsmJsOffsetsResult; // Decodes the bytes of a WASM module between {module_start} and {module_end}. @@ -37,7 +42,8 @@ V8_EXPORT_PRIVATE FunctionSig* DecodeWasmSignatureForTesting(Zone* zone, // Decodes the bytes of a WASM function between // {function_start} and {function_end}. V8_EXPORT_PRIVATE FunctionResult DecodeWasmFunction(Isolate* isolate, - Zone* zone, ModuleEnv* env, + Zone* zone, + ModuleBytesEnv* env, const byte* function_start, const byte* function_end); @@ -50,6 +56,18 @@ FunctionOffsetsResult DecodeWasmFunctionOffsets(const byte* module_start, V8_EXPORT_PRIVATE WasmInitExpr DecodeWasmInitExprForTesting(const byte* start, const byte* end); +struct CustomSectionOffset { + uint32_t section_start; + uint32_t name_offset; + uint32_t name_length; + uint32_t payload_offset; + uint32_t payload_length; + uint32_t section_length; +}; + +V8_EXPORT_PRIVATE std::vector<CustomSectionOffset> DecodeCustomSections( + const byte* start, const byte* end); + // Extracts the mapping from wasm byte offset to asm.js source position per // function. // Returns a vector of vectors with <byte_offset, source_position> entries, or diff --git a/deps/v8/src/wasm/wasm-debug.cc b/deps/v8/src/wasm/wasm-debug.cc index 11c2ef8aa5..6cb865d59c 100644 --- a/deps/v8/src/wasm/wasm-debug.cc +++ b/deps/v8/src/wasm/wasm-debug.cc @@ -2,144 +2,273 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "src/assembler-inl.h" #include "src/assert-scope.h" +#include "src/compiler/wasm-compiler.h" #include "src/debug/debug.h" #include "src/factory.h" +#include "src/frames-inl.h" #include "src/isolate.h" #include "src/wasm/module-decoder.h" +#include "src/wasm/wasm-interpreter.h" +#include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects.h" +#include "src/zone/accounting-allocator.h" using namespace v8::internal; using namespace v8::internal::wasm; namespace { -enum { - kWasmDebugInfoWasmObj, - kWasmDebugInfoWasmBytesHash, - kWasmDebugInfoAsmJsOffsets, - kWasmDebugInfoNumEntries -}; +class InterpreterHandle { + AccountingAllocator allocator_; + WasmInstance instance_; + WasmInterpreter interpreter_; -// TODO(clemensh): Move asm.js offset tables to the compiled module. -FixedArray *GetAsmJsOffsetTables(Handle<WasmDebugInfo> debug_info, - Isolate *isolate) { - Object *offset_tables = debug_info->get(kWasmDebugInfoAsmJsOffsets); - if (!offset_tables->IsUndefined(isolate)) { - return FixedArray::cast(offset_tables); + public: + // Initialize in the right order, using helper methods to make this possible. + // WasmInterpreter has to be allocated in place, since it is not movable. + InterpreterHandle(Isolate* isolate, WasmDebugInfo* debug_info) + : instance_(debug_info->wasm_instance()->compiled_module()->module()), + interpreter_(GetBytesEnv(&instance_, debug_info), &allocator_) { + Handle<JSArrayBuffer> mem_buffer = + handle(debug_info->wasm_instance()->memory_buffer(), isolate); + if (mem_buffer->IsUndefined(isolate)) { + DCHECK_EQ(0, instance_.module->min_mem_pages); + instance_.mem_start = nullptr; + instance_.mem_size = 0; + } else { + instance_.mem_start = + reinterpret_cast<byte*>(mem_buffer->backing_store()); + CHECK(mem_buffer->byte_length()->ToUint32(&instance_.mem_size)); + } } - Handle<JSObject> wasm_instance(debug_info->wasm_instance(), isolate); - Handle<WasmCompiledModule> compiled_module(GetCompiledModule(*wasm_instance), - isolate); - DCHECK(compiled_module->has_asm_js_offset_tables()); - - AsmJsOffsetsResult asm_offsets; - { - Handle<ByteArray> asm_offset_tables = - compiled_module->asm_js_offset_tables(); - DisallowHeapAllocation no_gc; - const byte *bytes_start = asm_offset_tables->GetDataStartAddress(); - const byte *bytes_end = bytes_start + asm_offset_tables->length(); - asm_offsets = wasm::DecodeAsmJsOffsets(bytes_start, bytes_end); + static ModuleBytesEnv GetBytesEnv(WasmInstance* instance, + WasmDebugInfo* debug_info) { + // Return raw pointer into heap. The WasmInterpreter will make its own copy + // of this data anyway, and there is no heap allocation in-between. + SeqOneByteString* bytes_str = + debug_info->wasm_instance()->compiled_module()->module_bytes(); + Vector<const byte> bytes(bytes_str->GetChars(), bytes_str->length()); + return ModuleBytesEnv(instance->module, instance, bytes); } - // Wasm bytes must be valid and must contain asm.js offset table. - DCHECK(asm_offsets.ok()); - DCHECK_GE(static_cast<size_t>(kMaxInt), asm_offsets.val.size()); - int num_functions = static_cast<int>(asm_offsets.val.size()); - DCHECK_EQ( - wasm::GetNumberOfFunctions(handle(debug_info->wasm_instance())), - static_cast<int>(num_functions + - compiled_module->module()->num_imported_functions)); - Handle<FixedArray> all_tables = - isolate->factory()->NewFixedArray(num_functions); - debug_info->set(kWasmDebugInfoAsmJsOffsets, *all_tables); - for (int func = 0; func < num_functions; ++func) { - std::vector<std::pair<int, int>> &func_asm_offsets = asm_offsets.val[func]; - if (func_asm_offsets.empty()) continue; - size_t array_size = 2 * kIntSize * func_asm_offsets.size(); - CHECK_LE(array_size, static_cast<size_t>(kMaxInt)); - ByteArray *arr = - *isolate->factory()->NewByteArray(static_cast<int>(array_size)); - all_tables->set(func, arr); - int idx = 0; - for (std::pair<int, int> p : func_asm_offsets) { - // Byte offsets must be strictly monotonously increasing: - DCHECK(idx == 0 || p.first > arr->get_int(idx - 2)); - arr->set_int(idx++, p.first); - arr->set_int(idx++, p.second); + + WasmInterpreter* interpreter() { return &interpreter_; } + const WasmModule* module() { return instance_.module; } + + void Execute(uint32_t func_index, uint8_t* arg_buffer) { + DCHECK_GE(module()->functions.size(), func_index); + FunctionSig* sig = module()->functions[func_index].sig; + DCHECK_GE(kMaxInt, sig->parameter_count()); + int num_params = static_cast<int>(sig->parameter_count()); + ScopedVector<WasmVal> wasm_args(num_params); + uint8_t* arg_buf_ptr = arg_buffer; + for (int i = 0; i < num_params; ++i) { + uint32_t param_size = 1 << ElementSizeLog2Of(sig->GetParam(i)); +#define CASE_ARG_TYPE(type, ctype) \ + case type: \ + DCHECK_EQ(param_size, sizeof(ctype)); \ + wasm_args[i] = WasmVal(*reinterpret_cast<ctype*>(arg_buf_ptr)); \ + break; + switch (sig->GetParam(i)) { + CASE_ARG_TYPE(kWasmI32, uint32_t) + CASE_ARG_TYPE(kWasmI64, uint64_t) + CASE_ARG_TYPE(kWasmF32, float) + CASE_ARG_TYPE(kWasmF64, double) +#undef CASE_ARG_TYPE + default: + UNREACHABLE(); + } + arg_buf_ptr += param_size; } - DCHECK_EQ(arr->length(), idx * kIntSize); + + WasmInterpreter::Thread* thread = interpreter_.GetThread(0); + // We do not support reentering an already running interpreter at the moment + // (like INTERPRETER -> JS -> WASM -> INTERPRETER). + DCHECK(thread->state() == WasmInterpreter::STOPPED || + thread->state() == WasmInterpreter::FINISHED); + thread->Reset(); + thread->PushFrame(&module()->functions[func_index], wasm_args.start()); + WasmInterpreter::State state; + do { + state = thread->Run(); + switch (state) { + case WasmInterpreter::State::PAUSED: { + // We hit a breakpoint. + // TODO(clemensh): Handle this. + } break; + case WasmInterpreter::State::FINISHED: + // Perfect, just break the switch and exit the loop. + break; + case WasmInterpreter::State::TRAPPED: + // TODO(clemensh): Generate appropriate JS exception. + UNIMPLEMENTED(); + break; + // STOPPED and RUNNING should never occur here. + case WasmInterpreter::State::STOPPED: + case WasmInterpreter::State::RUNNING: + default: + UNREACHABLE(); + } + } while (state != WasmInterpreter::State::FINISHED); + + // Copy back the return value + DCHECK_GE(kV8MaxWasmFunctionReturns, sig->return_count()); + // TODO(wasm): Handle multi-value returns. + DCHECK_EQ(1, kV8MaxWasmFunctionReturns); + if (sig->return_count()) { + WasmVal ret_val = thread->GetReturnValue(0); +#define CASE_RET_TYPE(type, ctype) \ + case type: \ + DCHECK_EQ(1 << ElementSizeLog2Of(sig->GetReturn(0)), sizeof(ctype)); \ + *reinterpret_cast<ctype*>(arg_buffer) = ret_val.to<ctype>(); \ + break; + switch (sig->GetReturn(0)) { + CASE_RET_TYPE(kWasmI32, uint32_t) + CASE_RET_TYPE(kWasmI64, uint64_t) + CASE_RET_TYPE(kWasmF32, float) + CASE_RET_TYPE(kWasmF64, double) +#undef CASE_RET_TYPE + default: + UNREACHABLE(); + } + } + } +}; + +InterpreterHandle* GetOrCreateInterpreterHandle( + Isolate* isolate, Handle<WasmDebugInfo> debug_info) { + Handle<Object> handle(debug_info->get(WasmDebugInfo::kInterpreterHandle), + isolate); + if (handle->IsUndefined(isolate)) { + InterpreterHandle* cpp_handle = new InterpreterHandle(isolate, *debug_info); + handle = Managed<InterpreterHandle>::New(isolate, cpp_handle); + debug_info->set(WasmDebugInfo::kInterpreterHandle, *handle); } - return *all_tables; + + return Handle<Managed<InterpreterHandle>>::cast(handle)->get(); +} + +int GetNumFunctions(WasmInstanceObject* instance) { + size_t num_functions = + instance->compiled_module()->module()->functions.size(); + DCHECK_GE(kMaxInt, num_functions); + return static_cast<int>(num_functions); +} + +Handle<FixedArray> GetOrCreateInterpretedFunctions( + Isolate* isolate, Handle<WasmDebugInfo> debug_info) { + Handle<Object> obj(debug_info->get(WasmDebugInfo::kInterpretedFunctions), + isolate); + if (!obj->IsUndefined(isolate)) return Handle<FixedArray>::cast(obj); + + Handle<FixedArray> new_arr = isolate->factory()->NewFixedArray( + GetNumFunctions(debug_info->wasm_instance())); + debug_info->set(WasmDebugInfo::kInterpretedFunctions, *new_arr); + return new_arr; } -} // namespace -Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<JSObject> wasm) { - Isolate *isolate = wasm->GetIsolate(); - Factory *factory = isolate->factory(); - Handle<FixedArray> arr = - factory->NewFixedArray(kWasmDebugInfoNumEntries, TENURED); - arr->set(kWasmDebugInfoWasmObj, *wasm); - int hash = 0; - Handle<SeqOneByteString> wasm_bytes = GetWasmBytes(wasm); - { - DisallowHeapAllocation no_gc; - hash = StringHasher::HashSequentialString( - wasm_bytes->GetChars(), wasm_bytes->length(), kZeroHashSeed); +void RedirectCallsitesInCode(Code* code, Code* old_target, Code* new_target) { + DisallowHeapAllocation no_gc; + for (RelocIterator it(code, RelocInfo::kCodeTargetMask); !it.done(); + it.next()) { + DCHECK(RelocInfo::IsCodeTarget(it.rinfo()->rmode())); + Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); + if (target != old_target) continue; + it.rinfo()->set_target_address(new_target->instruction_start()); } - Handle<Object> hash_obj = factory->NewNumberFromInt(hash, TENURED); - arr->set(kWasmDebugInfoWasmBytesHash, *hash_obj); +} + +void RedirectCallsitesInInstance(Isolate* isolate, WasmInstanceObject* instance, + Code* old_target, Code* new_target) { + DisallowHeapAllocation no_gc; + // Redirect all calls in wasm functions. + FixedArray* code_table = instance->compiled_module()->ptr_to_code_table(); + for (int i = 0, e = GetNumFunctions(instance); i < e; ++i) { + RedirectCallsitesInCode(Code::cast(code_table->get(i)), old_target, + new_target); + } + + // Redirect all calls in exported functions. + FixedArray* weak_exported_functions = + instance->compiled_module()->ptr_to_weak_exported_functions(); + for (int i = 0, e = weak_exported_functions->length(); i != e; ++i) { + WeakCell* weak_function = WeakCell::cast(weak_exported_functions->get(i)); + if (weak_function->cleared()) continue; + Code* code = JSFunction::cast(weak_function->value())->code(); + RedirectCallsitesInCode(code, old_target, new_target); + } +} + +void EnsureRedirectToInterpreter(Isolate* isolate, + Handle<WasmDebugInfo> debug_info, + int func_index) { + Handle<FixedArray> interpreted_functions = + GetOrCreateInterpretedFunctions(isolate, debug_info); + if (!interpreted_functions->get(func_index)->IsUndefined(isolate)) return; + + Handle<WasmInstanceObject> instance(debug_info->wasm_instance(), isolate); + Handle<Code> new_code = compiler::CompileWasmInterpreterEntry( + isolate, func_index, + instance->compiled_module()->module()->functions[func_index].sig, + instance); + + Handle<FixedArray> code_table = instance->compiled_module()->code_table(); + Handle<Code> old_code(Code::cast(code_table->get(func_index)), isolate); + interpreted_functions->set(func_index, *new_code); + RedirectCallsitesInInstance(isolate, *instance, *old_code, *new_code); +} + +} // namespace + +Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<WasmInstanceObject> instance) { + Isolate* isolate = instance->GetIsolate(); + Factory* factory = isolate->factory(); + Handle<FixedArray> arr = factory->NewFixedArray(kFieldCount, TENURED); + arr->set(kInstance, *instance); return Handle<WasmDebugInfo>::cast(arr); } -bool WasmDebugInfo::IsDebugInfo(Object *object) { +bool WasmDebugInfo::IsDebugInfo(Object* object) { if (!object->IsFixedArray()) return false; - FixedArray *arr = FixedArray::cast(object); - return arr->length() == kWasmDebugInfoNumEntries && - IsWasmInstance(arr->get(kWasmDebugInfoWasmObj)) && - arr->get(kWasmDebugInfoWasmBytesHash)->IsNumber(); + FixedArray* arr = FixedArray::cast(object); + if (arr->length() != kFieldCount) return false; + if (!IsWasmInstance(arr->get(kInstance))) return false; + Isolate* isolate = arr->GetIsolate(); + if (!arr->get(kInterpreterHandle)->IsUndefined(isolate) && + !arr->get(kInterpreterHandle)->IsForeign()) + return false; + return true; } -WasmDebugInfo *WasmDebugInfo::cast(Object *object) { +WasmDebugInfo* WasmDebugInfo::cast(Object* object) { DCHECK(IsDebugInfo(object)); - return reinterpret_cast<WasmDebugInfo *>(object); + return reinterpret_cast<WasmDebugInfo*>(object); } -JSObject *WasmDebugInfo::wasm_instance() { - return JSObject::cast(get(kWasmDebugInfoWasmObj)); +WasmInstanceObject* WasmDebugInfo::wasm_instance() { + return WasmInstanceObject::cast(get(kInstance)); } -int WasmDebugInfo::GetAsmJsSourcePosition(Handle<WasmDebugInfo> debug_info, - int func_index, int byte_offset) { - Isolate *isolate = debug_info->GetIsolate(); - Handle<JSObject> instance(debug_info->wasm_instance(), isolate); - FixedArray *offset_tables = GetAsmJsOffsetTables(debug_info, isolate); - - WasmCompiledModule *compiled_module = wasm::GetCompiledModule(*instance); - int num_imported_functions = - compiled_module->module()->num_imported_functions; - DCHECK_LE(num_imported_functions, func_index); - func_index -= num_imported_functions; - DCHECK_LT(func_index, offset_tables->length()); - ByteArray *offset_table = ByteArray::cast(offset_tables->get(func_index)); - - // Binary search for the current byte offset. - int left = 0; // inclusive - int right = offset_table->length() / kIntSize / 2; // exclusive - DCHECK_LT(left, right); - while (right - left > 1) { - int mid = left + (right - left) / 2; - if (offset_table->get_int(2 * mid) <= byte_offset) { - left = mid; - } else { - right = mid; - } - } - // There should be an entry for each position that could show up on the stack - // trace: - DCHECK_EQ(byte_offset, offset_table->get_int(2 * left)); - return offset_table->get_int(2 * left + 1); +void WasmDebugInfo::SetBreakpoint(Handle<WasmDebugInfo> debug_info, + int func_index, int offset) { + Isolate* isolate = debug_info->GetIsolate(); + InterpreterHandle* handle = GetOrCreateInterpreterHandle(isolate, debug_info); + WasmInterpreter* interpreter = handle->interpreter(); + DCHECK_LE(0, func_index); + DCHECK_GT(handle->module()->functions.size(), func_index); + const WasmFunction* func = &handle->module()->functions[func_index]; + interpreter->SetBreakpoint(func, offset, true); + EnsureRedirectToInterpreter(isolate, debug_info, func_index); +} + +void WasmDebugInfo::RunInterpreter(Handle<WasmDebugInfo> debug_info, + int func_index, uint8_t* arg_buffer) { + DCHECK_LE(0, func_index); + InterpreterHandle* interp_handle = + GetOrCreateInterpreterHandle(debug_info->GetIsolate(), debug_info); + interp_handle->Execute(static_cast<uint32_t>(func_index), arg_buffer); } diff --git a/deps/v8/src/wasm/wasm-external-refs.cc b/deps/v8/src/wasm/wasm-external-refs.cc index 4c4c91b29c..e982cc7f99 100644 --- a/deps/v8/src/wasm/wasm-external-refs.cc +++ b/deps/v8/src/wasm/wasm-external-refs.cc @@ -208,6 +208,19 @@ void float64_pow_wrapper(double* param0, double* param1) { double y = ReadDoubleValue(param1); WriteDoubleValue(param0, Pow(x, y)); } + +static WasmTrapCallbackForTesting wasm_trap_callback_for_testing = nullptr; + +void set_trap_callback_for_testing(WasmTrapCallbackForTesting callback) { + wasm_trap_callback_for_testing = callback; +} + +void call_trap_callback_for_testing() { + if (wasm_trap_callback_for_testing) { + wasm_trap_callback_for_testing(); + } +} + } // namespace wasm } // namespace internal } // namespace v8 diff --git a/deps/v8/src/wasm/wasm-external-refs.h b/deps/v8/src/wasm/wasm-external-refs.h index d9539ce71a..04337b99ec 100644 --- a/deps/v8/src/wasm/wasm-external-refs.h +++ b/deps/v8/src/wasm/wasm-external-refs.h @@ -61,6 +61,12 @@ uint32_t word64_popcnt_wrapper(uint64_t* input); void float64_pow_wrapper(double* param0, double* param1); +typedef void (*WasmTrapCallbackForTesting)(); + +void set_trap_callback_for_testing(WasmTrapCallbackForTesting callback); + +void call_trap_callback_for_testing(); + } // namespace wasm } // namespace internal } // namespace v8 diff --git a/deps/v8/src/wasm/wasm-interpreter.cc b/deps/v8/src/wasm/wasm-interpreter.cc index 6e049ffd25..ac125caf7e 100644 --- a/deps/v8/src/wasm/wasm-interpreter.cc +++ b/deps/v8/src/wasm/wasm-interpreter.cc @@ -5,9 +5,10 @@ #include "src/wasm/wasm-interpreter.h" #include "src/utils.h" -#include "src/wasm/ast-decoder.h" #include "src/wasm/decoder.h" +#include "src/wasm/function-body-decoder.h" #include "src/wasm/wasm-external-refs.h" +#include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/zone/accounting-allocator.h" @@ -62,6 +63,7 @@ namespace wasm { V(I64GtS, int64_t, >) \ V(I64GeS, int64_t, >=) \ V(F32Add, float, +) \ + V(F32Sub, float, -) \ V(F32Eq, float, ==) \ V(F32Ne, float, !=) \ V(F32Lt, float, <) \ @@ -69,6 +71,7 @@ namespace wasm { V(F32Gt, float, >) \ V(F32Ge, float, >=) \ V(F64Add, double, +) \ + V(F64Sub, double, -) \ V(F64Eq, double, ==) \ V(F64Ne, double, !=) \ V(F64Lt, double, <) \ @@ -101,13 +104,11 @@ namespace wasm { V(I32Rol, int32_t) \ V(I64Ror, int64_t) \ V(I64Rol, int64_t) \ - V(F32Sub, float) \ V(F32Min, float) \ V(F32Max, float) \ V(F32CopySign, float) \ V(F64Min, double) \ V(F64Max, double) \ - V(F64Sub, double) \ V(F64CopySign, double) \ V(I32AsmjsDivS, int32_t) \ V(I32AsmjsDivU, uint32_t) \ @@ -158,8 +159,6 @@ namespace wasm { V(F64UConvertI64, uint64_t) \ V(F64ConvertF32, float) \ V(F64ReinterpretI64, int64_t) \ - V(I32ReinterpretF32, float) \ - V(I64ReinterpretF64, double) \ V(I32AsmjsSConvertF32, float) \ V(I32AsmjsUConvertF32, float) \ V(I32AsmjsSConvertF64, double) \ @@ -293,41 +292,6 @@ static inline uint64_t ExecuteI64Rol(uint64_t a, uint64_t b, TrapReason* trap) { return (a << shift) | (a >> (64 - shift)); } -static float quiet(float a) { - static const uint32_t kSignalingBit = 1 << 22; - uint32_t q = bit_cast<uint32_t>(std::numeric_limits<float>::quiet_NaN()); - if ((q & kSignalingBit) != 0) { - // On some machines, the signaling bit set indicates it's a quiet NaN. - return bit_cast<float>(bit_cast<uint32_t>(a) | kSignalingBit); - } else { - // On others, the signaling bit set indicates it's a signaling NaN. - return bit_cast<float>(bit_cast<uint32_t>(a) & ~kSignalingBit); - } -} - -static double quiet(double a) { - static const uint64_t kSignalingBit = 1ULL << 51; - uint64_t q = bit_cast<uint64_t>(std::numeric_limits<double>::quiet_NaN()); - if ((q & kSignalingBit) != 0) { - // On some machines, the signaling bit set indicates it's a quiet NaN. - return bit_cast<double>(bit_cast<uint64_t>(a) | kSignalingBit); - } else { - // On others, the signaling bit set indicates it's a signaling NaN. - return bit_cast<double>(bit_cast<uint64_t>(a) & ~kSignalingBit); - } -} - -static inline float ExecuteF32Sub(float a, float b, TrapReason* trap) { - float result = a - b; - // Some architectures (e.g. MIPS) need extra checking to preserve the payload - // of a NaN operand. - if (result - result != 0) { - if (std::isnan(a)) return quiet(a); - if (std::isnan(b)) return quiet(b); - } - return result; -} - static inline float ExecuteF32Min(float a, float b, TrapReason* trap) { return JSMin(a, b); } @@ -340,17 +304,6 @@ static inline float ExecuteF32CopySign(float a, float b, TrapReason* trap) { return copysignf(a, b); } -static inline double ExecuteF64Sub(double a, double b, TrapReason* trap) { - double result = a - b; - // Some architectures (e.g. MIPS) need extra checking to preserve the payload - // of a NaN operand. - if (result - result != 0) { - if (std::isnan(a)) return quiet(a); - if (std::isnan(b)) return quiet(b); - } - return result; -} - static inline double ExecuteF64Min(double a, double b, TrapReason* trap) { return JSMin(a, b); } @@ -651,19 +604,20 @@ static inline double ExecuteF64ReinterpretI64(int64_t a, TrapReason* trap) { return bit_cast<double>(a); } -static inline int32_t ExecuteI32ReinterpretF32(float a, TrapReason* trap) { - return bit_cast<int32_t>(a); +static inline int32_t ExecuteI32ReinterpretF32(WasmVal a) { + return a.to_unchecked<int32_t>(); } -static inline int64_t ExecuteI64ReinterpretF64(double a, TrapReason* trap) { - return bit_cast<int64_t>(a); +static inline int64_t ExecuteI64ReinterpretF64(WasmVal a) { + return a.to_unchecked<int64_t>(); } static inline int32_t ExecuteGrowMemory(uint32_t delta_pages, WasmInstance* instance) { // TODO(ahaas): Move memory allocation to wasm-module.cc for better // encapsulation. - if (delta_pages > wasm::WasmModule::kV8MaxPages) { + if (delta_pages > wasm::kV8MaxWasmMemoryPages || + delta_pages > instance->module->max_mem_pages) { return -1; } uint32_t old_size = instance->mem_size; @@ -679,8 +633,9 @@ static inline int32_t ExecuteGrowMemory(uint32_t delta_pages, } else { DCHECK_NOT_NULL(instance->mem_start); new_size = old_size + delta_pages * wasm::WasmModule::kPageSize; - if (new_size > - wasm::WasmModule::kV8MaxPages * wasm::WasmModule::kPageSize) { + if (new_size / wasm::WasmModule::kPageSize > wasm::kV8MaxWasmMemoryPages || + new_size / wasm::WasmModule::kPageSize > + instance->module->max_mem_pages) { return -1; } new_mem_start = static_cast<byte*>(realloc(instance->mem_start, new_size)); @@ -721,8 +676,8 @@ class ControlTransfers : public ZoneObject { public: ControlTransferMap map_; - ControlTransfers(Zone* zone, ModuleEnv* env, AstLocalDecls* locals, - const byte* start, const byte* end) + ControlTransfers(Zone* zone, BodyLocalDecls* locals, const byte* start, + const byte* end) : map_(zone) { // Represents a control flow label. struct CLabel : public ZoneObject { @@ -872,7 +827,7 @@ class ControlTransfers : public ZoneObject { // Code and metadata needed to execute a function. struct InterpreterCode { const WasmFunction* function; // wasm function - AstLocalDecls locals; // local declarations + BodyLocalDecls locals; // local declarations const byte* orig_start; // start of original code const byte* orig_end; // end of original code byte* start; // start of (maybe altered) code @@ -890,14 +845,13 @@ class CodeMap { const WasmModule* module_; ZoneVector<InterpreterCode> interpreter_code_; - CodeMap(const WasmModule* module, Zone* zone) + CodeMap(const WasmModule* module, const uint8_t* module_start, Zone* zone) : zone_(zone), module_(module), interpreter_code_(zone) { if (module == nullptr) return; for (size_t i = 0; i < module->functions.size(); ++i) { const WasmFunction* function = &module->functions[i]; - const byte* code_start = - module->module_start + function->code_start_offset; - const byte* code_end = module->module_start + function->code_end_offset; + const byte* code_start = module_start + function->code_start_offset; + const byte* code_end = module_start + function->code_end_offset; AddFunction(function, code_start, code_end); } } @@ -929,10 +883,9 @@ class CodeMap { InterpreterCode* Preprocess(InterpreterCode* code) { if (code->targets == nullptr && code->start) { // Compute the control targets map and the local declarations. - CHECK(DecodeLocalDecls(code->locals, code->start, code->end)); - ModuleEnv env = {module_, nullptr, kWasmOrigin}; + CHECK(DecodeLocalDecls(&code->locals, code->start, code->end)); code->targets = new (zone_) ControlTransfers( - zone_, &env, &code->locals, code->orig_start, code->orig_end); + zone_, &code->locals, code->orig_start, code->orig_end); } return code; } @@ -940,7 +893,7 @@ class CodeMap { int AddFunction(const WasmFunction* function, const byte* code_start, const byte* code_end) { InterpreterCode code = { - function, AstLocalDecls(zone_), code_start, + function, BodyLocalDecls(zone_), code_start, code_end, const_cast<byte*>(code_start), const_cast<byte*>(code_end), nullptr}; @@ -1072,7 +1025,7 @@ class ThreadImpl : public WasmInterpreter::Thread { // Limit of parameters. sp_t plimit() { return sp + code->function->sig->parameter_count(); } // Limit of locals. - sp_t llimit() { return plimit() + code->locals.total_local_count; } + sp_t llimit() { return plimit() + code->locals.type_list.size(); } }; struct Block { @@ -1121,28 +1074,28 @@ class ThreadImpl : public WasmInterpreter::Thread { } pc_t InitLocals(InterpreterCode* code) { - for (auto p : code->locals.local_types) { + for (auto p : code->locals.type_list) { WasmVal val; - switch (p.first) { - case kAstI32: + switch (p) { + case kWasmI32: val = WasmVal(static_cast<int32_t>(0)); break; - case kAstI64: + case kWasmI64: val = WasmVal(static_cast<int64_t>(0)); break; - case kAstF32: + case kWasmF32: val = WasmVal(static_cast<float>(0)); break; - case kAstF64: + case kWasmF64: val = WasmVal(static_cast<double>(0)); break; default: UNREACHABLE(); break; } - stack_.insert(stack_.end(), p.second, val); + stack_.push_back(val); } - return code->locals.decls_encoded_size; + return code->locals.encoded_size; } void CommitPc(pc_t pc) { @@ -1173,7 +1126,7 @@ class ThreadImpl : public WasmInterpreter::Thread { } bool DoReturn(InterpreterCode** code, pc_t* pc, pc_t* limit, size_t arity) { - DCHECK_GT(frames_.size(), 0u); + DCHECK_GT(frames_.size(), 0); // Pop all blocks for this frame. while (!blocks_.empty() && blocks_.back().fp == frames_.size()) { blocks_.pop_back(); @@ -1357,12 +1310,6 @@ class ThreadImpl : public WasmInterpreter::Thread { blocks_.pop_back(); break; } - case kExprI8Const: { - ImmI8Operand operand(&decoder, code->at(pc)); - Push(pc, WasmVal(operand.value)); - len = 1 + operand.length; - break; - } case kExprI32Const: { ImmI32Operand operand(&decoder, code->at(pc)); Push(pc, WasmVal(operand.value)); @@ -1450,15 +1397,15 @@ class ThreadImpl : public WasmInterpreter::Thread { GlobalIndexOperand operand(&decoder, code->at(pc)); const WasmGlobal* global = &module()->globals[operand.index]; byte* ptr = instance()->globals_start + global->offset; - LocalType type = global->type; + ValueType type = global->type; WasmVal val; - if (type == kAstI32) { + if (type == kWasmI32) { val = WasmVal(*reinterpret_cast<int32_t*>(ptr)); - } else if (type == kAstI64) { + } else if (type == kWasmI64) { val = WasmVal(*reinterpret_cast<int64_t*>(ptr)); - } else if (type == kAstF32) { + } else if (type == kWasmF32) { val = WasmVal(*reinterpret_cast<float*>(ptr)); - } else if (type == kAstF64) { + } else if (type == kWasmF64) { val = WasmVal(*reinterpret_cast<double*>(ptr)); } else { UNREACHABLE(); @@ -1471,15 +1418,15 @@ class ThreadImpl : public WasmInterpreter::Thread { GlobalIndexOperand operand(&decoder, code->at(pc)); const WasmGlobal* global = &module()->globals[operand.index]; byte* ptr = instance()->globals_start + global->offset; - LocalType type = global->type; + ValueType type = global->type; WasmVal val = Pop(); - if (type == kAstI32) { + if (type == kWasmI32) { *reinterpret_cast<int32_t*>(ptr) = val.to<int32_t>(); - } else if (type == kAstI64) { + } else if (type == kWasmI64) { *reinterpret_cast<int64_t*>(ptr) = val.to<int64_t>(); - } else if (type == kAstF32) { + } else if (type == kWasmF32) { *reinterpret_cast<float*>(ptr) = val.to<float>(); - } else if (type == kAstF64) { + } else if (type == kWasmF64) { *reinterpret_cast<double*>(ptr) = val.to<double>(); } else { UNREACHABLE(); @@ -1605,6 +1552,19 @@ class ThreadImpl : public WasmInterpreter::Thread { len = 1 + operand.length; break; } + // We need to treat kExprI32ReinterpretF32 and kExprI64ReinterpretF64 + // specially to guarantee that the quiet bit of a NaN is preserved on + // ia32 by the reinterpret casts. + case kExprI32ReinterpretF32: { + WasmVal result(ExecuteI32ReinterpretF32(Pop())); + Push(pc, result); + break; + } + case kExprI64ReinterpretF64: { + WasmVal result(ExecuteI64ReinterpretF64(Pop())); + Push(pc, result); + break; + } #define EXECUTE_SIMPLE_BINOP(name, ctype, op) \ case kExpr##name: { \ WasmVal rval = Pop(); \ @@ -1680,8 +1640,8 @@ class ThreadImpl : public WasmInterpreter::Thread { } WasmVal Pop() { - DCHECK_GT(stack_.size(), 0u); - DCHECK_GT(frames_.size(), 0u); + DCHECK_GT(stack_.size(), 0); + DCHECK_GT(frames_.size(), 0); DCHECK_GT(stack_.size(), frames_.back().llimit()); // can't pop into locals WasmVal val = stack_.back(); stack_.pop_back(); @@ -1689,8 +1649,8 @@ class ThreadImpl : public WasmInterpreter::Thread { } void PopN(int n) { - DCHECK_GE(stack_.size(), static_cast<size_t>(n)); - DCHECK_GT(frames_.size(), 0u); + DCHECK_GE(stack_.size(), n); + DCHECK_GT(frames_.size(), 0); size_t nsize = stack_.size() - n; DCHECK_GE(nsize, frames_.back().llimit()); // can't pop into locals stack_.resize(nsize); @@ -1698,13 +1658,13 @@ class ThreadImpl : public WasmInterpreter::Thread { WasmVal PopArity(size_t arity) { if (arity == 0) return WasmVal(); - CHECK_EQ(1u, arity); + CHECK_EQ(1, arity); return Pop(); } void Push(pc_t pc, WasmVal val) { // TODO(titzer): store PC as well? - if (val.type != kAstStmt) stack_.push_back(val); + if (val.type != kWasmStmt) stack_.push_back(val); } void TraceStack(const char* phase, pc_t pc) { @@ -1730,19 +1690,19 @@ class ThreadImpl : public WasmInterpreter::Thread { PrintF(" s%zu:", i); WasmVal val = stack_[i]; switch (val.type) { - case kAstI32: + case kWasmI32: PrintF("i32:%d", val.to<int32_t>()); break; - case kAstI64: + case kWasmI64: PrintF("i64:%" PRId64 "", val.to<int64_t>()); break; - case kAstF32: + case kWasmF32: PrintF("f32:%f", val.to<float>()); break; - case kAstF64: + case kWasmF64: PrintF("f64:%lf", val.to<double>()); break; - case kAstStmt: + case kWasmStmt: PrintF("void"); break; default: @@ -1760,14 +1720,19 @@ class ThreadImpl : public WasmInterpreter::Thread { class WasmInterpreterInternals : public ZoneObject { public: WasmInstance* instance_; + // Create a copy of the module bytes for the interpreter, since the passed + // pointer might be invalidated after constructing the interpreter. + const ZoneVector<uint8_t> module_bytes_; CodeMap codemap_; ZoneVector<ThreadImpl*> threads_; - WasmInterpreterInternals(Zone* zone, WasmInstance* instance) - : instance_(instance), - codemap_(instance_ ? instance_->module : nullptr, zone), + WasmInterpreterInternals(Zone* zone, const ModuleBytesEnv& env) + : instance_(env.instance), + module_bytes_(env.module_bytes.start(), env.module_bytes.end(), zone), + codemap_(env.instance ? env.instance->module : nullptr, + module_bytes_.data(), zone), threads_(zone) { - threads_.push_back(new ThreadImpl(zone, &codemap_, instance)); + threads_.push_back(new ThreadImpl(zone, &codemap_, env.instance)); } void Delete() { @@ -1780,10 +1745,10 @@ class WasmInterpreterInternals : public ZoneObject { //============================================================================ // Implementation of the public interface of the interpreter. //============================================================================ -WasmInterpreter::WasmInterpreter(WasmInstance* instance, +WasmInterpreter::WasmInterpreter(const ModuleBytesEnv& env, AccountingAllocator* allocator) : zone_(allocator, ZONE_NAME), - internals_(new (&zone_) WasmInterpreterInternals(&zone_, instance)) {} + internals_(new (&zone_) WasmInterpreterInternals(&zone_, env)) {} WasmInterpreter::~WasmInterpreter() { internals_->Delete(); } @@ -1797,7 +1762,7 @@ bool WasmInterpreter::SetBreakpoint(const WasmFunction* function, pc_t pc, if (!code) return false; size_t size = static_cast<size_t>(code->end - code->start); // Check bounds for {pc}. - if (pc < code->locals.decls_encoded_size || pc >= size) return false; + if (pc < code->locals.encoded_size || pc >= size) return false; // Make a copy of the code before enabling a breakpoint. if (enabled && code->orig_start == code->start) { code->start = reinterpret_cast<byte*>(zone_.New(size)); @@ -1818,7 +1783,7 @@ bool WasmInterpreter::GetBreakpoint(const WasmFunction* function, pc_t pc) { if (!code) return false; size_t size = static_cast<size_t>(code->end - code->start); // Check bounds for {pc}. - if (pc < code->locals.decls_encoded_size || pc >= size) return false; + if (pc < code->locals.encoded_size || pc >= size) return false; // Check if a breakpoint is present at that place in the code. return code->start[pc] == kInternalBreakpoint; } @@ -1841,14 +1806,14 @@ WasmVal WasmInterpreter::GetLocalVal(const WasmFrame* frame, int index) { CHECK_GE(index, 0); UNIMPLEMENTED(); WasmVal none; - none.type = kAstStmt; + none.type = kWasmStmt; return none; } WasmVal WasmInterpreter::GetExprVal(const WasmFrame* frame, int pc) { UNIMPLEMENTED(); WasmVal none; - none.type = kAstStmt; + none.type = kWasmStmt; return none; } @@ -1885,7 +1850,7 @@ bool WasmInterpreter::SetFunctionCodeForTesting(const WasmFunction* function, ControlTransferMap WasmInterpreter::ComputeControlTransfersForTesting( Zone* zone, const byte* start, const byte* end) { - ControlTransfers targets(zone, nullptr, nullptr, start, end); + ControlTransfers targets(zone, 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 360362b994..80e6c4ba79 100644 --- a/deps/v8/src/wasm/wasm-interpreter.h +++ b/deps/v8/src/wasm/wasm-interpreter.h @@ -17,8 +17,8 @@ namespace internal { namespace wasm { // forward declarations. +struct ModuleBytesEnv; struct WasmFunction; -struct WasmInstance; class WasmInterpreterInternals; typedef size_t pc_t; @@ -32,23 +32,23 @@ typedef ZoneMap<pc_t, pcdiff_t> ControlTransferMap; // Macro for defining union members. #define FOREACH_UNION_MEMBER(V) \ - V(i32, kAstI32, int32_t) \ - V(u32, kAstI32, uint32_t) \ - V(i64, kAstI64, int64_t) \ - V(u64, kAstI64, uint64_t) \ - V(f32, kAstF32, float) \ - V(f64, kAstF64, double) + V(i32, kWasmI32, int32_t) \ + V(u32, kWasmI32, uint32_t) \ + V(i64, kWasmI64, int64_t) \ + V(u64, kWasmI64, uint64_t) \ + V(f32, kWasmF32, float) \ + V(f64, kWasmF64, double) // Representation of values within the interpreter. struct WasmVal { - LocalType type; + ValueType type; union { #define DECLARE_FIELD(field, localtype, ctype) ctype field; FOREACH_UNION_MEMBER(DECLARE_FIELD) #undef DECLARE_FIELD } val; - WasmVal() : type(kAstStmt) {} + WasmVal() : type(kWasmStmt) {} #define DECLARE_CONSTRUCTOR(field, localtype, ctype) \ explicit WasmVal(ctype v) : type(localtype) { val.field = v; } @@ -56,13 +56,22 @@ struct WasmVal { #undef DECLARE_CONSTRUCTOR template <typename T> - T to() { + inline T to() { + UNREACHABLE(); + } + + template <typename T> + inline T to_unchecked() { UNREACHABLE(); } }; #define DECLARE_CAST(field, localtype, ctype) \ template <> \ + inline ctype WasmVal::to_unchecked() { \ + return val.field; \ + } \ + template <> \ inline ctype WasmVal::to() { \ CHECK_EQ(localtype, type); \ return val.field; \ @@ -70,11 +79,6 @@ struct WasmVal { FOREACH_UNION_MEMBER(DECLARE_CAST) #undef DECLARE_CAST -template <> -inline void WasmVal::to() { - CHECK_EQ(kAstStmt, type); -} - // Representation of frames within the interpreter. class WasmFrame { public: @@ -135,7 +139,7 @@ class V8_EXPORT_PRIVATE WasmInterpreter { bool GetBreakpoint(const WasmFunction* function, int pc); }; - WasmInterpreter(WasmInstance* instance, AccountingAllocator* allocator); + WasmInterpreter(const ModuleBytesEnv& env, AccountingAllocator* allocator); ~WasmInterpreter(); //========================================================================== diff --git a/deps/v8/src/wasm/wasm-js.cc b/deps/v8/src/wasm/wasm-js.cc index 0e030a28c4..b426d5bf3d 100644 --- a/deps/v8/src/wasm/wasm-js.cc +++ b/deps/v8/src/wasm/wasm-js.cc @@ -18,6 +18,7 @@ #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-js.h" +#include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects.h" #include "src/wasm/wasm-result.h" @@ -28,13 +29,12 @@ using v8::internal::wasm::ErrorThrower; namespace v8 { -enum WasmMemoryObjectData { - kWasmMemoryBuffer, - kWasmMemoryMaximum, - kWasmMemoryInstanceObject -}; - namespace { + +#define RANGE_ERROR_MSG \ + "Wasm compilation exceeds internal limits in this context for the provided " \ + "arguments" + i::Handle<i::String> v8_str(i::Isolate* isolate, const char* str) { return isolate->factory()->NewStringFromAsciiChecked(str); } @@ -48,6 +48,100 @@ struct RawBuffer { size_t size() { return static_cast<size_t>(end - start); } }; +bool IsCompilationAllowed(i::Isolate* isolate, ErrorThrower* thrower, + v8::Local<v8::Value> source, bool is_async) { + // Allow caller to do one final check on thrower state, rather than + // one at each step. No information is lost - failure reason is captured + // in the thrower state. + if (thrower->error()) return false; + + AllowWasmCompileCallback callback = isolate->allow_wasm_compile_callback(); + if (callback != nullptr && + !callback(reinterpret_cast<v8::Isolate*>(isolate), source, is_async)) { + thrower->RangeError(RANGE_ERROR_MSG); + return false; + } + return true; +} + +bool IsInstantiationAllowed(i::Isolate* isolate, ErrorThrower* thrower, + v8::Local<v8::Value> module_or_bytes, + i::MaybeHandle<i::JSReceiver> ffi, bool is_async) { + // Allow caller to do one final check on thrower state, rather than + // one at each step. No information is lost - failure reason is captured + // in the thrower state. + if (thrower->error()) return false; + v8::MaybeLocal<v8::Value> v8_ffi; + if (!ffi.is_null()) { + v8_ffi = v8::Local<v8::Value>::Cast(Utils::ToLocal(ffi.ToHandleChecked())); + } + AllowWasmInstantiateCallback callback = + isolate->allow_wasm_instantiate_callback(); + if (callback != nullptr && + !callback(reinterpret_cast<v8::Isolate*>(isolate), module_or_bytes, + v8_ffi, is_async)) { + thrower->RangeError(RANGE_ERROR_MSG); + return false; + } + return true; +} + +i::wasm::ModuleWireBytes GetFirstArgumentAsBytes( + const v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower* thrower) { + if (args.Length() < 1) { + thrower->TypeError("Argument 0 must be a buffer source"); + return i::wasm::ModuleWireBytes(nullptr, nullptr); + } + + const byte* start = nullptr; + size_t length = 0; + v8::Local<v8::Value> source = args[0]; + if (source->IsArrayBuffer()) { + // A raw array buffer was passed. + Local<ArrayBuffer> buffer = Local<ArrayBuffer>::Cast(source); + ArrayBuffer::Contents contents = buffer->GetContents(); + + start = reinterpret_cast<const byte*>(contents.Data()); + length = contents.ByteLength(); + } else if (source->IsTypedArray()) { + // A TypedArray was passed. + Local<TypedArray> array = Local<TypedArray>::Cast(source); + Local<ArrayBuffer> buffer = array->Buffer(); + + ArrayBuffer::Contents contents = buffer->GetContents(); + + start = + reinterpret_cast<const byte*>(contents.Data()) + array->ByteOffset(); + length = array->ByteLength(); + } else { + thrower->TypeError("Argument 0 must be a buffer source"); + } + DCHECK_IMPLIES(length, start != nullptr); + if (length == 0) { + thrower->CompileError("BufferSource argument is empty"); + } + if (length > i::wasm::kV8MaxWasmModuleSize) { + thrower->RangeError("buffer source exceeds maximum size of %zu (is %zu)", + i::wasm::kV8MaxWasmModuleSize, length); + } + if (thrower->error()) return i::wasm::ModuleWireBytes(nullptr, nullptr); + // TODO(titzer): use the handle as well? + return i::wasm::ModuleWireBytes(start, start + length); +} + +i::MaybeHandle<i::JSReceiver> GetSecondArgumentAsImports( + const v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower* thrower) { + if (args.Length() < 2) return {}; + if (args[1]->IsUndefined()) return {}; + + if (!args[1]->IsObject()) { + thrower->TypeError("Argument 1 must be an object"); + return {}; + } + Local<Object> obj = Local<Object>::Cast(args[1]); + return i::Handle<i::JSReceiver>::cast(v8::Utils::OpenHandle(*obj)); +} + RawBuffer GetRawBufferSource( v8::Local<v8::Value> source, ErrorThrower* thrower) { const byte* start = nullptr; @@ -61,9 +155,6 @@ RawBuffer GetRawBufferSource( start = reinterpret_cast<const byte*>(contents.Data()); end = start + contents.ByteLength(); - if (start == nullptr || end == start) { - thrower->CompileError("ArrayBuffer argument is empty"); - } } else if (source->IsTypedArray()) { // A TypedArray was passed. Local<TypedArray> array = Local<TypedArray>::Cast(source); @@ -75,13 +166,12 @@ RawBuffer GetRawBufferSource( reinterpret_cast<const byte*>(contents.Data()) + array->ByteOffset(); end = start + array->ByteLength(); - if (start == nullptr || end == start) { - thrower->TypeError("ArrayBuffer argument is empty"); - } } else { - thrower->TypeError("Argument 0 must be an ArrayBuffer or Uint8Array"); + thrower->TypeError("Argument 0 must be a buffer source"); + } + if (start == nullptr || end == start) { + thrower->CompileError("BufferSource argument is empty"); } - return {start, end}; } @@ -97,7 +187,7 @@ static i::MaybeHandle<i::WasmModuleObject> CreateModuleObject( DCHECK(source->IsArrayBuffer() || source->IsTypedArray()); return i::wasm::CreateModuleObjectFromBytes( i_isolate, buffer.start, buffer.end, thrower, i::wasm::kWasmOrigin, - i::Handle<i::Script>::null(), nullptr, nullptr); + i::Handle<i::Script>::null(), i::Vector<const byte>::empty()); } static bool ValidateModule(v8::Isolate* isolate, @@ -115,6 +205,17 @@ static bool ValidateModule(v8::Isolate* isolate, i::wasm::ModuleOrigin::kWasmOrigin); } +// TODO(wasm): move brand check to the respective types, and don't throw +// in it, rather, use a provided ErrorThrower, or let caller handle it. +static bool BrandCheck(Isolate* isolate, i::Handle<i::Object> value, + i::Handle<i::Symbol> sym) { + if (!value->IsJSObject()) return false; + 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; + return has_brand.ToChecked(); +} + static bool BrandCheck(Isolate* isolate, i::Handle<i::Object> value, i::Handle<i::Symbol> sym, const char* msg) { if (value->IsJSObject()) { @@ -130,27 +231,36 @@ static bool BrandCheck(Isolate* isolate, i::Handle<i::Object> value, void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::Isolate* isolate = args.GetIsolate(); + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); HandleScope scope(isolate); ErrorThrower thrower(reinterpret_cast<i::Isolate*>(isolate), "WebAssembly.compile()"); + Local<Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Promise::Resolver> resolver; + if (!v8::Promise::Resolver::New(context).ToLocal(&resolver)) return; + v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); + return_value.Set(resolver->GetPromise()); + if (args.Length() < 1) { thrower.TypeError("Argument 0 must be a buffer source"); + resolver->Reject(context, Utils::ToLocal(thrower.Reify())); + return; + } + auto bytes = GetFirstArgumentAsBytes(args, &thrower); + USE(bytes); + if (!IsCompilationAllowed(i_isolate, &thrower, args[0], true)) { + resolver->Reject(context, Utils::ToLocal(thrower.Reify())); return; } i::MaybeHandle<i::JSObject> module_obj = CreateModuleObject(isolate, args[0], &thrower); - Local<Context> context = isolate->GetCurrentContext(); - v8::Local<v8::Promise::Resolver> resolver; - if (!v8::Promise::Resolver::New(context).ToLocal(&resolver)) return; if (thrower.error()) { resolver->Reject(context, Utils::ToLocal(thrower.Reify())); } else { resolver->Resolve(context, Utils::ToLocal(module_obj.ToHandleChecked())); } - v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); - return_value.Set(resolver->GetPromise()); } void WebAssemblyValidate(const v8::FunctionCallbackInfo<v8::Value>& args) { @@ -168,12 +278,14 @@ void WebAssemblyValidate(const v8::FunctionCallbackInfo<v8::Value>& args) { if (ValidateModule(isolate, args[0], &thrower)) { return_value.Set(v8::True(isolate)); } else { + if (thrower.wasm_error()) thrower.Reify(); // Clear error. return_value.Set(v8::False(isolate)); } } void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::Isolate* isolate = args.GetIsolate(); + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); HandleScope scope(isolate); ErrorThrower thrower(reinterpret_cast<i::Isolate*>(isolate), "WebAssembly.Module()"); @@ -182,6 +294,10 @@ void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) { thrower.TypeError("Argument 0 must be a buffer source"); return; } + auto bytes = GetFirstArgumentAsBytes(args, &thrower); + USE(bytes); + if (!IsCompilationAllowed(i_isolate, &thrower, args[0], false)) return; + i::MaybeHandle<i::JSObject> module_obj = CreateModuleObject(isolate, args[0], &thrower); if (module_obj.is_null()) return; @@ -190,16 +306,49 @@ void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) { return_value.Set(Utils::ToLocal(module_obj.ToHandleChecked())); } -void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) { - HandleScope scope(args.GetIsolate()); - v8::Isolate* isolate = args.GetIsolate(); - i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); +MaybeLocal<Value> InstantiateModuleImpl( + i::Isolate* i_isolate, i::Handle<i::WasmModuleObject> i_module_obj, + const v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower* thrower) { + // It so happens that in both the WebAssembly.instantiate, as well as + // WebAssembly.Instance ctor, the positions of the ffi object and memory + // are the same. If that changes later, we refactor the consts into + // parameters. + static const int kFfiOffset = 1; - ErrorThrower thrower(i_isolate, "WebAssembly.Instance()"); + MaybeLocal<Value> nothing; + i::Handle<i::JSReceiver> ffi = i::Handle<i::JSObject>::null(); + // This is a first - level validation of the argument. If present, we only + // check its type. {Instantiate} will further check that if the module + // has imports, the argument must be present, as well as piecemeal + // import satisfaction. + if (args.Length() > kFfiOffset && !args[kFfiOffset]->IsUndefined()) { + if (!args[kFfiOffset]->IsObject()) { + thrower->TypeError("Argument %d must be an object", kFfiOffset); + return nothing; + } + Local<Object> obj = Local<Object>::Cast(args[kFfiOffset]); + ffi = i::Handle<i::JSReceiver>::cast(v8::Utils::OpenHandle(*obj)); + } + + i::MaybeHandle<i::JSObject> instance = + i::wasm::WasmModule::Instantiate(i_isolate, thrower, i_module_obj, ffi); + if (instance.is_null()) { + if (!thrower->error()) + thrower->RuntimeError("Could not instantiate module"); + return nothing; + } + DCHECK(!i_isolate->has_pending_exception()); + return Utils::ToLocal(instance.ToHandleChecked()); +} +namespace { +i::MaybeHandle<i::WasmModuleObject> GetFirstArgumentAsModule( + const v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower& thrower) { + v8::Isolate* isolate = args.GetIsolate(); + i::MaybeHandle<i::WasmModuleObject> nothing; if (args.Length() < 1) { thrower.TypeError("Argument 0 must be a WebAssembly.Module"); - return; + return nothing; } Local<Context> context = isolate->GetCurrentContext(); @@ -207,60 +356,210 @@ void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) { if (!BrandCheck(isolate, Utils::OpenHandle(*args[0]), i::Handle<i::Symbol>(i_context->wasm_module_sym()), "Argument 0 must be a WebAssembly.Module")) { - return; + return nothing; } - Local<Object> obj = Local<Object>::Cast(args[0]); - i::Handle<i::JSObject> i_obj = - i::Handle<i::JSObject>::cast(v8::Utils::OpenHandle(*obj)); + Local<Object> module_obj = Local<Object>::Cast(args[0]); + return i::Handle<i::WasmModuleObject>::cast( + v8::Utils::OpenHandle(*module_obj)); +} +} // namespace - 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]); - ffi = i::Handle<i::JSReceiver>::cast(v8::Utils::OpenHandle(*obj)); +void WebAssemblyModuleImports(const v8::FunctionCallbackInfo<v8::Value>& args) { + HandleScope scope(args.GetIsolate()); + v8::Isolate* isolate = args.GetIsolate(); + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + ErrorThrower thrower(i_isolate, "WebAssembly.Module.imports()"); + + auto maybe_module = GetFirstArgumentAsModule(args, thrower); + + if (!maybe_module.is_null()) { + auto imports = + i::wasm::GetImports(i_isolate, maybe_module.ToHandleChecked()); + args.GetReturnValue().Set(Utils::ToLocal(imports)); } +} - i::Handle<i::JSArrayBuffer> memory = i::Handle<i::JSArrayBuffer>::null(); - if (args.Length() > 2 && args[2]->IsObject()) { - Local<Object> obj = Local<Object>::Cast(args[2]); - i::Handle<i::Object> mem_obj = v8::Utils::OpenHandle(*obj); - if (i::WasmJs::IsWasmMemoryObject(i_isolate, mem_obj)) { - memory = i::Handle<i::JSArrayBuffer>( - i::Handle<i::WasmMemoryObject>::cast(mem_obj)->get_buffer(), - i_isolate); - } else { - thrower.TypeError("Argument 2 must be a WebAssembly.Memory"); +void WebAssemblyModuleExports(const v8::FunctionCallbackInfo<v8::Value>& args) { + HandleScope scope(args.GetIsolate()); + v8::Isolate* isolate = args.GetIsolate(); + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + + ErrorThrower thrower(i_isolate, "WebAssembly.Module.exports()"); + + auto maybe_module = GetFirstArgumentAsModule(args, thrower); + + if (!maybe_module.is_null()) { + auto exports = + i::wasm::GetExports(i_isolate, maybe_module.ToHandleChecked()); + args.GetReturnValue().Set(Utils::ToLocal(exports)); + } +} + +void WebAssemblyModuleCustomSections( + const v8::FunctionCallbackInfo<v8::Value>& args) { + HandleScope scope(args.GetIsolate()); + v8::Isolate* isolate = args.GetIsolate(); + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + + ErrorThrower thrower(i_isolate, "WebAssembly.Module.customSections()"); + + auto maybe_module = GetFirstArgumentAsModule(args, thrower); + + if (args.Length() < 2) { + thrower.TypeError("Argument 1 must be a string"); + return; + } + + i::Handle<i::Object> name = Utils::OpenHandle(*args[1]); + if (!name->IsString()) { + thrower.TypeError("Argument 1 must be a string"); + return; + } + + if (!maybe_module.is_null()) { + auto custom_sections = + i::wasm::GetCustomSections(i_isolate, maybe_module.ToHandleChecked(), + i::Handle<i::String>::cast(name), &thrower); + if (!thrower.error()) { + args.GetReturnValue().Set(Utils::ToLocal(custom_sections)); } } - i::MaybeHandle<i::JSObject> instance = - i::wasm::WasmModule::Instantiate(i_isolate, &thrower, i_obj, ffi, memory); - if (instance.is_null()) { - if (!thrower.error()) thrower.RuntimeError("Could not instantiate module"); +} + +void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) { + HandleScope scope(args.GetIsolate()); + v8::Isolate* isolate = args.GetIsolate(); + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + + ErrorThrower thrower(i_isolate, "WebAssembly.Instance()"); + + auto maybe_module = GetFirstArgumentAsModule(args, thrower); + if (thrower.error()) return; + auto maybe_imports = GetSecondArgumentAsImports(args, &thrower); + if (!IsInstantiationAllowed(i_isolate, &thrower, args[0], maybe_imports, + false)) { return; } - DCHECK(!i_isolate->has_pending_exception()); + DCHECK(!thrower.error()); + + if (!maybe_module.is_null()) { + MaybeLocal<Value> instance = InstantiateModuleImpl( + i_isolate, maybe_module.ToHandleChecked(), args, &thrower); + if (instance.IsEmpty()) { + DCHECK(thrower.error()); + return; + } + args.GetReturnValue().Set(instance.ToLocalChecked()); + } +} + +void WebAssemblyInstantiate(const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Isolate* isolate = args.GetIsolate(); + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + + HandleScope scope(isolate); + ErrorThrower thrower(i_isolate, "WebAssembly.instantiate()"); + + Local<Context> context = isolate->GetCurrentContext(); + i::Handle<i::Context> i_context = Utils::OpenHandle(*context); + + v8::Local<v8::Promise::Resolver> resolver; + if (!v8::Promise::Resolver::New(context).ToLocal(&resolver)) return; v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); - return_value.Set(Utils::ToLocal(instance.ToHandleChecked())); + return_value.Set(resolver->GetPromise()); + + if (args.Length() < 1) { + thrower.TypeError( + "Argument 0 must be provided and must be either a buffer source or a " + "WebAssembly.Module object"); + resolver->Reject(context, Utils::ToLocal(thrower.Reify())); + return; + } + + i::Handle<i::Object> first_arg = Utils::OpenHandle(*args[0]); + if (!first_arg->IsJSObject()) { + thrower.TypeError( + "Argument 0 must be a buffer source or a WebAssembly.Module object"); + resolver->Reject(context, Utils::ToLocal(thrower.Reify())); + return; + } + + bool want_pair = !BrandCheck( + isolate, first_arg, i::Handle<i::Symbol>(i_context->wasm_module_sym())); + auto maybe_imports = GetSecondArgumentAsImports(args, &thrower); + if (thrower.error()) { + resolver->Reject(context, Utils::ToLocal(thrower.Reify())); + return; + } + if (!IsInstantiationAllowed(i_isolate, &thrower, args[0], maybe_imports, + true)) { + resolver->Reject(context, Utils::ToLocal(thrower.Reify())); + return; + } + i::Handle<i::WasmModuleObject> module_obj; + if (want_pair) { + i::MaybeHandle<i::WasmModuleObject> maybe_module_obj = + CreateModuleObject(isolate, args[0], &thrower); + if (!maybe_module_obj.ToHandle(&module_obj)) { + DCHECK(thrower.error()); + resolver->Reject(context, Utils::ToLocal(thrower.Reify())); + return; + } + } else { + module_obj = i::Handle<i::WasmModuleObject>::cast(first_arg); + } + DCHECK(!module_obj.is_null()); + MaybeLocal<Value> instance = + InstantiateModuleImpl(i_isolate, module_obj, args, &thrower); + if (instance.IsEmpty()) { + DCHECK(thrower.error()); + resolver->Reject(context, Utils::ToLocal(thrower.Reify())); + } else { + DCHECK(!thrower.error()); + Local<Value> retval; + if (want_pair) { + i::Handle<i::JSFunction> object_function = i::Handle<i::JSFunction>( + i_isolate->native_context()->object_function(), i_isolate); + + i::Handle<i::JSObject> i_retval = + i_isolate->factory()->NewJSObject(object_function, i::TENURED); + i::Handle<i::String> module_property_name = + i_isolate->factory()->InternalizeUtf8String("module"); + i::Handle<i::String> instance_property_name = + i_isolate->factory()->InternalizeUtf8String("instance"); + i::JSObject::AddProperty(i_retval, module_property_name, module_obj, + i::NONE); + i::JSObject::AddProperty(i_retval, instance_property_name, + Utils::OpenHandle(*instance.ToLocalChecked()), + i::NONE); + retval = Utils::ToLocal(i_retval); + } else { + retval = instance.ToLocalChecked(); + } + DCHECK(!retval.IsEmpty()); + resolver->Resolve(context, retval); + } } 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) { + Local<String> property, int* result, + int64_t lower_bound, uint64_t 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)) { + if (number < lower_bound) { thrower->RangeError("Property value %" PRId64 - " is below the lower bound %d", + " is below the lower bound %" PRIx64, number, lower_bound); return false; } if (number > static_cast<int64_t>(upper_bound)) { thrower->RangeError("Property value %" PRId64 - " is above the upper bound %d", + " is above the upper bound %" PRIu64, number, upper_bound); return false; } @@ -270,8 +569,6 @@ bool GetIntegerProperty(v8::Isolate* isolate, ErrorThrower* thrower, return false; } -const int max_table_size = 1 << 26; - void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); @@ -299,28 +596,23 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) { } } // The descriptor's 'initial'. - int initial; + int initial = 0; if (!GetIntegerProperty(isolate, &thrower, context, descriptor, v8_str(isolate, "initial"), &initial, 0, - max_table_size)) { + i::wasm::kV8MaxWasmTableSize)) { return; } // The descriptor's 'maximum'. - int maximum = 0; + int maximum = -1; 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 (!has_maximum.IsNothing() && has_maximum.FromJust()) { if (!GetIntegerProperty(isolate, &thrower, context, descriptor, maximum_key, - &maximum, initial, max_table_size)) { + &maximum, initial, + i::wasm::kSpecMaxWasmTableSize)) { return; } - } else { - maximum = static_cast<int>(i::wasm::WasmModule::kV8MaxTableSize); } i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); @@ -335,7 +627,7 @@ 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()"); + "WebAssembly.Memory()"); if (args.Length() < 1 || !args[0]->IsObject()) { thrower.TypeError("Argument 0 must be a memory descriptor"); return; @@ -343,35 +635,35 @@ void WebAssemblyMemory(const v8::FunctionCallbackInfo<v8::Value>& args) { Local<Context> context = isolate->GetCurrentContext(); Local<v8::Object> descriptor = args[0]->ToObject(context).ToLocalChecked(); // The descriptor's 'initial'. - int initial; + int initial = 0; if (!GetIntegerProperty(isolate, &thrower, context, descriptor, - v8_str(isolate, "initial"), &initial, 0, 65536)) { + v8_str(isolate, "initial"), &initial, 0, + i::wasm::kV8MaxWasmMemoryPages)) { return; } // The descriptor's 'maximum'. - int maximum = 0; + int maximum = -1; 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 (!has_maximum.IsNothing() && has_maximum.FromJust()) { if (!GetIntegerProperty(isolate, &thrower, context, descriptor, maximum_key, - &maximum, initial, 65536)) { + &maximum, initial, + i::wasm::kSpecMaxWasmMemoryPages)) { 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::WasmMemoryObject::New( - i_isolate, buffer, has_maximum.FromJust() ? maximum : -1); + i::Handle<i::JSArrayBuffer> buffer = + i::wasm::NewArrayBuffer(i_isolate, size, i::FLAG_wasm_guard_pages); + if (buffer.is_null()) { + thrower.RangeError("could not allocate memory"); + return; + } + i::Handle<i::JSObject> memory_obj = + i::WasmMemoryObject::New(i_isolate, buffer, maximum); args.GetReturnValue().Set(Utils::ToLocal(memory_obj)); } @@ -404,7 +696,7 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) { i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); auto receiver = i::Handle<i::WasmTableObject>::cast(Utils::OpenHandle(*args.This())); - i::Handle<i::FixedArray> old_array(receiver->get_functions(), i_isolate); + i::Handle<i::FixedArray> old_array(receiver->functions(), i_isolate); int old_size = old_array->length(); int64_t new_size64 = 0; if (args.Length() > 0 && !args[0]->IntegerValue(context).To(&new_size64)) { @@ -412,14 +704,23 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) { } new_size64 += old_size; - if (new_size64 < old_size || new_size64 > receiver->maximum_length()) { + int64_t max_size64 = receiver->maximum_length(); + if (max_size64 < 0 || + max_size64 > static_cast<int64_t>(i::wasm::kV8MaxWasmTableSize)) { + max_size64 = i::wasm::kV8MaxWasmTableSize; + } + + if (new_size64 < old_size || new_size64 > max_size64) { v8::Local<v8::Value> e = v8::Exception::RangeError( v8_str(isolate, new_size64 < old_size ? "trying to shrink table" : "maximum table size exceeded")); isolate->ThrowException(e); return; } + int new_size = static_cast<int>(new_size64); + i::WasmTableObject::Grow(i_isolate, receiver, + static_cast<uint32_t>(new_size - old_size)); if (new_size != old_size) { i::Handle<i::FixedArray> new_array = @@ -430,7 +731,9 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) { receiver->set_functions(*new_array); } - // TODO(titzer): update relevant instances. + // TODO(gdeepti): use weak links for instances + v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); + return_value.Set(old_size); } void WebAssemblyTableGet(const v8::FunctionCallbackInfo<v8::Value>& args) { @@ -446,7 +749,7 @@ void WebAssemblyTableGet(const v8::FunctionCallbackInfo<v8::Value>& args) { i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); auto receiver = i::Handle<i::WasmTableObject>::cast(Utils::OpenHandle(*args.This())); - i::Handle<i::FixedArray> array(receiver->get_functions(), i_isolate); + i::Handle<i::FixedArray> array(receiver->functions(), i_isolate); int i = 0; if (args.Length() > 0 && !args[0]->Int32Value(context).To(&i)) return; v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); @@ -490,7 +793,7 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) { auto receiver = i::Handle<i::WasmTableObject>::cast(Utils::OpenHandle(*args.This())); - i::Handle<i::FixedArray> array(receiver->get_functions(), i_isolate); + i::Handle<i::FixedArray> array(receiver->functions(), i_isolate); int i; if (!args[0]->Int32Value(context).To(&i)) return; if (i < 0 || i >= array->length()) { @@ -500,7 +803,7 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) { return; } - i::Handle<i::FixedArray> dispatch_tables(receiver->get_dispatch_tables(), + i::Handle<i::FixedArray> dispatch_tables(receiver->dispatch_tables(), i_isolate); if (value->IsNull(i_isolate)) { i::wasm::UpdateDispatchTables(i_isolate, dispatch_tables, i, @@ -522,40 +825,40 @@ void WebAssemblyMemoryGrow(const v8::FunctionCallbackInfo<v8::Value>& args) { "Receiver is not a WebAssembly.Memory")) { return; } - if (args.Length() < 1) { + int64_t delta_size = 0; + if (args.Length() < 1 || !args[0]->IntegerValue(context).To(&delta_size)) { v8::Local<v8::Value> e = v8::Exception::TypeError( v8_str(isolate, "Argument 0 required, must be numeric value of pages")); isolate->ThrowException(e); return; } - - uint32_t delta = args[0]->Uint32Value(context).FromJust(); - 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> instance_object( - receiver->GetInternalField(kWasmMemoryInstanceObject), i_isolate); - i::Handle<i::JSObject> instance( - i::Handle<i::JSObject>::cast(instance_object)); - - // TODO(gdeepti) Implement growing memory when shared by different - // instances. - int32_t ret = internal::wasm::GrowInstanceMemory(i_isolate, instance, delta); - if (ret == -1) { - v8::Local<v8::Value> e = v8::Exception::Error( - v8_str(isolate, "Unable to grow instance memory.")); + i::Handle<i::WasmMemoryObject> receiver = + i::Handle<i::WasmMemoryObject>::cast(Utils::OpenHandle(*args.This())); + int64_t max_size64 = receiver->maximum_pages(); + if (max_size64 < 0 || + max_size64 > static_cast<int64_t>(i::wasm::kV8MaxWasmTableSize)) { + max_size64 = i::wasm::kV8MaxWasmMemoryPages; + } + i::Handle<i::JSArrayBuffer> old_buffer(receiver->buffer()); + uint32_t old_size = + old_buffer->byte_length()->Number() / i::wasm::kSpecMaxWasmMemoryPages; + int64_t new_size64 = old_size + delta_size; + if (delta_size < 0 || max_size64 < new_size64 || new_size64 < old_size) { + v8::Local<v8::Value> e = v8::Exception::RangeError(v8_str( + isolate, new_size64 < old_size ? "trying to shrink memory" + : "maximum memory size exceeded")); isolate->ThrowException(e); return; } - i::MaybeHandle<i::JSArrayBuffer> buffer = - internal::wasm::GetInstanceMemory(i_isolate, instance); - if (buffer.is_null()) { - v8::Local<v8::Value> e = v8::Exception::Error( - v8_str(isolate, "WebAssembly.Memory buffer object not set.")); + i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); + int32_t ret = i::wasm::GrowWebAssemblyMemory( + i_isolate, receiver, static_cast<uint32_t>(delta_size)); + if (ret == -1) { + v8::Local<v8::Value> e = v8::Exception::RangeError( + v8_str(isolate, "Unable to grow instance memory.")); isolate->ThrowException(e); return; } - receiver->SetInternalField(kWasmMemoryBuffer, *buffer.ToHandleChecked()); v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); return_value.Set(ret); } @@ -571,10 +874,9 @@ void WebAssemblyMemoryGetBuffer( 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(kWasmMemoryBuffer), - i_isolate); + i::Handle<i::WasmMemoryObject> receiver = + i::Handle<i::WasmMemoryObject>::cast(Utils::OpenHandle(*args.This())); + i::Handle<i::Object> buffer(receiver->buffer(), i_isolate); DCHECK(buffer->IsJSArrayBuffer()); v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); return_value.Set(Utils::ToLocal(buffer)); @@ -586,20 +888,23 @@ void WebAssemblyMemoryGetBuffer( static i::Handle<i::FunctionTemplateInfo> NewTemplate(i::Isolate* i_isolate, FunctionCallback func) { Isolate* isolate = reinterpret_cast<Isolate*>(i_isolate); - Local<FunctionTemplate> local = FunctionTemplate::New(isolate, func); - return v8::Utils::OpenHandle(*local); + Local<FunctionTemplate> templ = FunctionTemplate::New(isolate, func); + templ->ReadOnlyPrototype(); + return v8::Utils::OpenHandle(*templ); } namespace internal { Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object, - const char* str, FunctionCallback func) { + const char* str, FunctionCallback func, + int length = 0) { Handle<String> name = v8_str(isolate, str); Handle<FunctionTemplateInfo> temp = NewTemplate(isolate, func); Handle<JSFunction> function = ApiNatives::InstantiateFunction(temp).ToHandleChecked(); - PropertyAttributes attributes = - static_cast<PropertyAttributes>(DONT_DELETE | READ_ONLY); + JSFunction::SetName(function, name, isolate->factory()->empty_string()); + function->shared()->set_length(length); + PropertyAttributes attributes = static_cast<PropertyAttributes>(DONT_ENUM); JSObject::AddProperty(object, name, function, attributes); return function; } @@ -611,27 +916,46 @@ Handle<JSFunction> InstallGetter(Isolate* isolate, Handle<JSObject> object, Handle<JSFunction> function = ApiNatives::InstantiateFunction(temp).ToHandleChecked(); v8::PropertyAttribute attributes = - static_cast<v8::PropertyAttribute>(v8::DontDelete | v8::ReadOnly); + static_cast<v8::PropertyAttribute>(v8::DontEnum); 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::Install(Isolate* isolate) { + Handle<JSGlobalObject> global = isolate->global_object(); + Handle<Context> context(global->native_context(), isolate); + // TODO(titzer): once FLAG_expose_wasm is gone, this should become a DCHECK. + if (context->get(Context::WASM_FUNCTION_MAP_INDEX)->IsMap()) return; + + // Install Maps. + + // TODO(titzer): Also make one for strict mode functions? + Handle<Map> prev_map = Handle<Map>(context->sloppy_function_map(), isolate); + + InstanceType instance_type = prev_map->instance_type(); + int internal_fields = JSObject::GetInternalFieldCount(*prev_map); + CHECK_EQ(0, internal_fields); + int pre_allocated = + prev_map->GetInObjectProperties() - prev_map->unused_property_fields(); + int instance_size = 0; + int in_object_properties = 0; + int wasm_internal_fields = internal_fields + 1 // module instance object + + 1 // function arity + + 1; // function signature + JSFunction::CalculateInstanceSizeHelper(instance_type, wasm_internal_fields, + 0, &instance_size, + &in_object_properties); + + int unused_property_fields = in_object_properties - pre_allocated; + Handle<Map> map = Map::CopyInitialMap( + prev_map, instance_size, in_object_properties, unused_property_fields); + + context->set_wasm_function_map(*map); + + // Install symbols. -void WasmJs::InstallWasmConstructors(Isolate* isolate, - Handle<JSGlobalObject> global, - Handle<Context> context) { Factory* factory = isolate->factory(); // Create private symbols. Handle<Symbol> module_sym = factory->NewPrivateSymbol(); @@ -646,7 +970,9 @@ void WasmJs::InstallWasmConstructors(Isolate* isolate, Handle<Symbol> memory_sym = factory->NewPrivateSymbol(); context->set_wasm_memory_sym(*memory_sym); - // Bind the WebAssembly object. + // Install the JS API. + + // Setup WebAssembly Handle<String> name = v8_str(isolate, "WebAssembly"); Handle<JSFunction> cons = factory->NewFunction(name); JSFunction::SetInstancePrototype( @@ -655,118 +981,103 @@ void WasmJs::InstallWasmConstructors(Isolate* isolate, Handle<JSObject> webassembly = factory->NewJSObject(cons, TENURED); PropertyAttributes attributes = static_cast<PropertyAttributes>(DONT_ENUM); JSObject::AddProperty(global, name, webassembly, attributes); - - // Setup compile - InstallFunc(isolate, webassembly, "compile", WebAssemblyCompile); - - // Setup compile - InstallFunc(isolate, webassembly, "validate", WebAssemblyValidate); + PropertyAttributes ro_attributes = + static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY); + JSObject::AddProperty(webassembly, factory->to_string_tag_symbol(), + v8_str(isolate, "WebAssembly"), ro_attributes); + InstallFunc(isolate, webassembly, "compile", WebAssemblyCompile, 1); + InstallFunc(isolate, webassembly, "validate", WebAssemblyValidate, 1); + InstallFunc(isolate, webassembly, "instantiate", WebAssemblyInstantiate, 1); // Setup Module Handle<JSFunction> module_constructor = - InstallFunc(isolate, webassembly, "Module", WebAssemblyModule); + InstallFunc(isolate, webassembly, "Module", WebAssemblyModule, 1); 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::Handle<i::Map> module_map = isolate->factory()->NewMap( + i::JS_API_OBJECT_TYPE, i::JSObject::kHeaderSize + WasmModuleObject::kFieldCount * i::kPointerSize); - JSFunction::SetInitialMap(module_constructor, map, module_proto); + JSFunction::SetInitialMap(module_constructor, module_map, module_proto); + InstallFunc(isolate, module_constructor, "imports", WebAssemblyModuleImports, + 1); + InstallFunc(isolate, module_constructor, "exports", WebAssemblyModuleExports, + 1); + InstallFunc(isolate, module_constructor, "customSections", + WebAssemblyModuleCustomSections, 2); JSObject::AddProperty(module_proto, isolate->factory()->constructor_string(), module_constructor, DONT_ENUM); + JSObject::AddProperty(module_proto, factory->to_string_tag_symbol(), + v8_str(isolate, "WebAssembly.Module"), ro_attributes); // Setup Instance Handle<JSFunction> instance_constructor = - InstallFunc(isolate, webassembly, "Instance", WebAssemblyInstance); + InstallFunc(isolate, webassembly, "Instance", WebAssemblyInstance, 1); context->set_wasm_instance_constructor(*instance_constructor); + Handle<JSObject> instance_proto = + factory->NewJSObject(instance_constructor, TENURED); + i::Handle<i::Map> instance_map = isolate->factory()->NewMap( + i::JS_API_OBJECT_TYPE, i::JSObject::kHeaderSize + + WasmInstanceObject::kFieldCount * i::kPointerSize); + JSFunction::SetInitialMap(instance_constructor, instance_map, instance_proto); + JSObject::AddProperty(instance_proto, + isolate->factory()->constructor_string(), + instance_constructor, DONT_ENUM); + JSObject::AddProperty(instance_proto, factory->to_string_tag_symbol(), + v8_str(isolate, "WebAssembly.Instance"), ro_attributes); // Setup Table Handle<JSFunction> table_constructor = - InstallFunc(isolate, webassembly, "Table", WebAssemblyTable); + InstallFunc(isolate, webassembly, "Table", WebAssemblyTable, 1); 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 + + i::Handle<i::Map> table_map = isolate->factory()->NewMap( + i::JS_API_OBJECT_TYPE, i::JSObject::kHeaderSize + WasmTableObject::kFieldCount * i::kPointerSize); - JSFunction::SetInitialMap(table_constructor, map, table_proto); + JSFunction::SetInitialMap(table_constructor, table_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); + InstallFunc(isolate, table_proto, "grow", WebAssemblyTableGrow, 1); + InstallFunc(isolate, table_proto, "get", WebAssemblyTableGet, 1); + InstallFunc(isolate, table_proto, "set", WebAssemblyTableSet, 2); + JSObject::AddProperty(table_proto, factory->to_string_tag_symbol(), + v8_str(isolate, "WebAssembly.Table"), ro_attributes); // Setup Memory Handle<JSFunction> memory_constructor = - InstallFunc(isolate, webassembly, "Memory", WebAssemblyMemory); + InstallFunc(isolate, webassembly, "Memory", WebAssemblyMemory, 1); 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 + + i::Handle<i::Map> memory_map = isolate->factory()->NewMap( + i::JS_API_OBJECT_TYPE, i::JSObject::kHeaderSize + WasmMemoryObject::kFieldCount * i::kPointerSize); - JSFunction::SetInitialMap(memory_constructor, map, memory_proto); + JSFunction::SetInitialMap(memory_constructor, memory_map, memory_proto); JSObject::AddProperty(memory_proto, isolate->factory()->constructor_string(), memory_constructor, DONT_ENUM); - InstallFunc(isolate, memory_proto, "grow", WebAssemblyMemoryGrow); + InstallFunc(isolate, memory_proto, "grow", WebAssemblyMemoryGrow, 1); InstallGetter(isolate, memory_proto, "buffer", WebAssemblyMemoryGetBuffer); + JSObject::AddProperty(memory_proto, factory->to_string_tag_symbol(), + v8_str(isolate, "WebAssembly.Memory"), ro_attributes); // Setup errors - attributes = static_cast<PropertyAttributes>(DONT_DELETE | READ_ONLY); + attributes = static_cast<PropertyAttributes>(DONT_ENUM); Handle<JSFunction> compile_error( isolate->native_context()->wasm_compile_error_function()); JSObject::AddProperty(webassembly, isolate->factory()->CompileError_string(), compile_error, attributes); + Handle<JSFunction> link_error( + isolate->native_context()->wasm_link_error_function()); + JSObject::AddProperty(webassembly, isolate->factory()->LinkError_string(), + link_error, attributes); Handle<JSFunction> runtime_error( isolate->native_context()->wasm_runtime_error_function()); JSObject::AddProperty(webassembly, isolate->factory()->RuntimeError_string(), runtime_error, attributes); } -void WasmJs::Install(Isolate* isolate, Handle<JSGlobalObject> global) { - if (!FLAG_expose_wasm && !FLAG_validate_asm) { - return; - } - - // Setup wasm function map. - Handle<Context> context(global->native_context(), isolate); - InstallWasmMapsIfNeeded(isolate, context); - - if (FLAG_expose_wasm) { - InstallWasmConstructors(isolate, global, 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? - Handle<Map> prev_map = Handle<Map>(context->sloppy_function_map(), isolate); - - InstanceType instance_type = prev_map->instance_type(); - int internal_fields = JSObject::GetInternalFieldCount(*prev_map); - CHECK_EQ(0, internal_fields); - int pre_allocated = - prev_map->GetInObjectProperties() - prev_map->unused_property_fields(); - int instance_size = 0; - int in_object_properties = 0; - int wasm_internal_fields = internal_fields + 1 // module instance object - + 1 // function arity - + 1; // function signature - JSFunction::CalculateInstanceSizeHelper(instance_type, wasm_internal_fields, - 0, &instance_size, - &in_object_properties); - - int unused_property_fields = in_object_properties - pre_allocated; - Handle<Map> map = Map::CopyInitialMap( - prev_map, instance_size, in_object_properties, unused_property_fields); - - context->set_wasm_function_map(*map); - } -} - static bool HasBrand(i::Handle<i::Object> value, i::Handle<i::Symbol> symbol) { if (value->IsJSObject()) { i::Handle<i::JSObject> object = i::Handle<i::JSObject>::cast(value); diff --git a/deps/v8/src/wasm/wasm-js.h b/deps/v8/src/wasm/wasm-js.h index f5b9596ee2..05d5ea3061 100644 --- a/deps/v8/src/wasm/wasm-js.h +++ b/deps/v8/src/wasm/wasm-js.h @@ -13,16 +13,7 @@ namespace internal { // Exposes a WASM API to JavaScript through the V8 API. class WasmJs { public: - static void Install(Isolate* isolate, Handle<JSGlobalObject> global_object); - - 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); + V8_EXPORT_PRIVATE static void Install(Isolate* isolate); // WebAssembly.Table. static bool IsWasmTableObject(Isolate* isolate, Handle<Object> value); diff --git a/deps/v8/src/wasm/wasm-limits.h b/deps/v8/src/wasm/wasm-limits.h new file mode 100644 index 0000000000..4c7455adc5 --- /dev/null +++ b/deps/v8/src/wasm/wasm-limits.h @@ -0,0 +1,45 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_WASM_WASM_LIMITS_H_ +#define V8_WASM_WASM_LIMITS_H_ + +namespace v8 { +namespace internal { +namespace wasm { + +// The following limits are imposed by V8 on WebAssembly modules. +// The limits are agreed upon with other engines for consistency. +const size_t kV8MaxWasmTypes = 1000000; +const size_t kV8MaxWasmFunctions = 1000000; +const size_t kV8MaxWasmImports = 100000; +const size_t kV8MaxWasmExports = 100000; +const size_t kV8MaxWasmGlobals = 1000000; +const size_t kV8MaxWasmDataSegments = 100000; +const size_t kV8MaxWasmMemoryPages = 16384; // = 1 GiB +const size_t kV8MaxWasmStringSize = 100000; +const size_t kV8MaxWasmModuleSize = 1024 * 1024 * 1024; // = 1 GiB +const size_t kV8MaxWasmFunctionSize = 128 * 1024; +const size_t kV8MaxWasmFunctionLocals = 50000; +const size_t kV8MaxWasmFunctionParams = 1000; +const size_t kV8MaxWasmFunctionMultiReturns = 1000; +const size_t kV8MaxWasmFunctionReturns = 1; +const size_t kV8MaxWasmTableSize = 10000000; +const size_t kV8MaxWasmTableEntries = 10000000; +const size_t kV8MaxWasmTables = 1; +const size_t kV8MaxWasmMemories = 1; + +const size_t kSpecMaxWasmMemoryPages = 65536; +const size_t kSpecMaxWasmTableSize = 0xFFFFFFFFu; + +const uint64_t kWasmMaxHeapOffset = + static_cast<uint64_t>( + std::numeric_limits<uint32_t>::max()) // maximum base value + + std::numeric_limits<uint32_t>::max(); // maximum index value + +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_WASM_WASM_LIMITS_H_ diff --git a/deps/v8/src/wasm/wasm-macro-gen.h b/deps/v8/src/wasm/wasm-macro-gen.h index ce2f843e71..1ec9ee80ff 100644 --- a/deps/v8/src/wasm/wasm-macro-gen.h +++ b/deps/v8/src/wasm/wasm-macro-gen.h @@ -59,6 +59,7 @@ // Control. //------------------------------------------------------------------------------ #define WASM_NOP kExprNop +#define WASM_END kExprEnd #define ARITY_0 0 #define ARITY_1 1 @@ -71,13 +72,13 @@ #define WASM_BLOCK(...) kExprBlock, kLocalVoid, __VA_ARGS__, kExprEnd #define WASM_BLOCK_T(t, ...) \ - kExprBlock, static_cast<byte>(WasmOpcodes::LocalTypeCodeFor(t)), \ + kExprBlock, static_cast<byte>(WasmOpcodes::ValueTypeCodeFor(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__, \ + static_cast<byte>(WasmOpcodes::ValueTypeCodeFor(t1)), \ + static_cast<byte>(WasmOpcodes::ValueTypeCodeFor(t2)), __VA_ARGS__, \ kExprEnd #define WASM_BLOCK_I(...) kExprBlock, kLocalI32, __VA_ARGS__, kExprEnd @@ -99,13 +100,13 @@ 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, \ + cond, kExprIf, static_cast<byte>(WasmOpcodes::ValueTypeCodeFor(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, \ + static_cast<byte>(WasmOpcodes::ValueTypeCodeFor(t1)), \ + static_cast<byte>(WasmOpcodes::ValueTypeCodeFor(t2)), tstmt, kExprElse, \ fstmt, kExprEnd #define WASM_IF_ELSE_I(cond, tstmt, fstmt) \ @@ -140,9 +141,8 @@ // Misc expressions. //------------------------------------------------------------------------------ #define WASM_ID(...) __VA_ARGS__ -#define WASM_ZERO kExprI8Const, 0 -#define WASM_ONE kExprI8Const, 1 -#define WASM_I8(val) kExprI8Const, static_cast<byte>(val) +#define WASM_ZERO kExprI32Const, 0 +#define WASM_ONE kExprI32Const, 1 #define I32V_MIN(length) -(1 << (6 + (7 * ((length) - 1)))) #define I32V_MAX(length) ((1 << (6 + (7 * ((length) - 1)))) - 1) @@ -195,7 +195,7 @@ class LocalDeclEncoder { pos = WriteUint32v(buffer, pos, static_cast<uint32_t>(local_decls.size())); for (size_t i = 0; i < local_decls.size(); ++i) { pos = WriteUint32v(buffer, pos, local_decls[i].first); - buffer[pos++] = WasmOpcodes::LocalTypeCodeFor(local_decls[i].second); + buffer[pos++] = WasmOpcodes::ValueTypeCodeFor(local_decls[i].second); } DCHECK_EQ(Size(), pos); return pos; @@ -203,7 +203,7 @@ class LocalDeclEncoder { // Add locals declarations to this helper. Return the index of the newly added // local(s), with an optional adjustment for the parameters. - uint32_t AddLocals(uint32_t count, LocalType type) { + uint32_t AddLocals(uint32_t count, ValueType type) { uint32_t result = static_cast<uint32_t>(total + (sig ? sig->parameter_count() : 0)); total += count; @@ -211,7 +211,7 @@ class LocalDeclEncoder { count += local_decls.back().first; local_decls.pop_back(); } - local_decls.push_back(std::pair<uint32_t, LocalType>(count, type)); + local_decls.push_back(std::pair<uint32_t, ValueType>(count, type)); return result; } @@ -227,7 +227,7 @@ class LocalDeclEncoder { private: FunctionSig* sig; - ZoneVector<std::pair<uint32_t, LocalType>> local_decls; + ZoneVector<std::pair<uint32_t, ValueType>> local_decls; size_t total; size_t SizeofUint32v(uint32_t val) const { @@ -447,19 +447,22 @@ class LocalDeclEncoder { #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, \ +#define WASM_INC_LOCAL(index) \ + kExprGetLocal, static_cast<byte>(index), kExprI32Const, 1, kExprI32Add, \ kExprTeeLocal, static_cast<byte>(index) #define WASM_INC_LOCAL_BYV(index, count) \ - kExprGetLocal, static_cast<byte>(index), kExprI8Const, \ + kExprGetLocal, static_cast<byte>(index), kExprI32Const, \ static_cast<byte>(count), kExprI32Add, kExprTeeLocal, \ static_cast<byte>(index) #define WASM_INC_LOCAL_BY(index, count) \ - kExprGetLocal, static_cast<byte>(index), kExprI8Const, \ + kExprGetLocal, static_cast<byte>(index), kExprI32Const, \ static_cast<byte>(count), kExprI32Add, kExprSetLocal, \ static_cast<byte>(index) #define WASM_UNOP(opcode, x) x, static_cast<byte>(opcode) #define WASM_BINOP(opcode, x, y) x, y, static_cast<byte>(opcode) +#define WASM_SIMD_UNOP(opcode, x) x, kSimdPrefix, static_cast<byte>(opcode) +#define WASM_SIMD_BINOP(opcode, x, y) \ + x, y, kSimdPrefix, static_cast<byte>(opcode) //------------------------------------------------------------------------------ // Int32 operations @@ -621,14 +624,31 @@ class LocalDeclEncoder { //------------------------------------------------------------------------------ // Simd Operations. //------------------------------------------------------------------------------ -#define WASM_SIMD_I32x4_SPLAT(x) x, kSimdPrefix, kExprI32x4Splat & 0xff -#define WASM_SIMD_I32x4_EXTRACT_LANE(lane, x) \ - x, kSimdPrefix, kExprI32x4ExtractLane & 0xff, static_cast<byte>(lane) -#define WASM_SIMD_I32x4_ADD(x, y) x, y, kSimdPrefix, kExprI32x4Add & 0xff #define WASM_SIMD_F32x4_SPLAT(x) x, kSimdPrefix, kExprF32x4Splat & 0xff #define WASM_SIMD_F32x4_EXTRACT_LANE(lane, x) \ x, kSimdPrefix, kExprF32x4ExtractLane & 0xff, static_cast<byte>(lane) +#define WASM_SIMD_F32x4_REPLACE_LANE(lane, x, y) \ + x, y, kSimdPrefix, kExprF32x4ReplaceLane & 0xff, static_cast<byte>(lane) +#define WASM_SIMD_F32x4_FROM_I32x4(x) \ + x, kSimdPrefix, kExprF32x4FromInt32x4 & 0xff +#define WASM_SIMD_F32x4_FROM_U32x4(x) \ + x, kSimdPrefix, kExprF32x4FromUint32x4 & 0xff #define WASM_SIMD_F32x4_ADD(x, y) x, y, kSimdPrefix, kExprF32x4Add & 0xff +#define WASM_SIMD_F32x4_SUB(x, y) x, y, kSimdPrefix, kExprF32x4Sub & 0xff + +#define WASM_SIMD_I32x4_SPLAT(x) x, kSimdPrefix, kExprI32x4Splat & 0xff +#define WASM_SIMD_I32x4_EXTRACT_LANE(lane, x) \ + x, kSimdPrefix, kExprI32x4ExtractLane & 0xff, static_cast<byte>(lane) +#define WASM_SIMD_I32x4_REPLACE_LANE(lane, x, y) \ + x, y, kSimdPrefix, kExprI32x4ReplaceLane & 0xff, static_cast<byte>(lane) +#define WASM_SIMD_I32x4_FROM_F32x4(x) \ + x, kSimdPrefix, kExprI32x4FromFloat32x4 & 0xff +#define WASM_SIMD_U32x4_FROM_F32x4(x) \ + x, kSimdPrefix, kExprUi32x4FromFloat32x4 & 0xff +#define WASM_SIMD_S32x4_SELECT(x, y, z) \ + x, y, z, kSimdPrefix, kExprS32x4Select & 0xff +#define WASM_SIMD_I32x4_ADD(x, y) x, y, kSimdPrefix, kExprI32x4Add & 0xff +#define WASM_SIMD_I32x4_SUB(x, y) x, y, kSimdPrefix, kExprI32x4Sub & 0xff #define SIG_ENTRY_v_v kWasmFunctionTypeForm, 0, 0 #define SIZEOF_SIG_ENTRY_v_v 3 diff --git a/deps/v8/src/wasm/wasm-module-builder.cc b/deps/v8/src/wasm/wasm-module-builder.cc index 290e98ecf8..cd83d46d3e 100644 --- a/deps/v8/src/wasm/wasm-module-builder.cc +++ b/deps/v8/src/wasm/wasm-module-builder.cc @@ -8,7 +8,7 @@ #include "src/v8.h" #include "src/zone/zone-containers.h" -#include "src/wasm/ast-decoder.h" +#include "src/wasm/function-body-decoder.h" #include "src/wasm/leb-helper.h" #include "src/wasm/wasm-macro-gen.h" #include "src/wasm/wasm-module-builder.h" @@ -50,11 +50,10 @@ WasmFunctionBuilder::WasmFunctionBuilder(WasmModuleBuilder* builder) : builder_(builder), locals_(builder->zone()), signature_index_(0), - exported_(0), func_index_(static_cast<uint32_t>(builder->functions_.size())), body_(builder->zone()), name_(builder->zone()), - exported_name_(builder->zone()), + exported_names_(builder->zone()), i32_temps_(builder->zone()), i64_temps_(builder->zone()), f32_temps_(builder->zone()), @@ -77,7 +76,7 @@ void WasmFunctionBuilder::SetSignature(FunctionSig* sig) { signature_index_ = builder_->AddSignature(sig); } -uint32_t WasmFunctionBuilder::AddLocal(LocalType type) { +uint32_t WasmFunctionBuilder::AddLocal(ValueType type) { DCHECK(locals_.has_sig()); return locals_.AddLocals(1, type); } @@ -123,10 +122,10 @@ void WasmFunctionBuilder::EmitWithVarInt(WasmOpcode opcode, } void WasmFunctionBuilder::EmitI32Const(int32_t value) { - // TODO(titzer): variable-length signed and unsigned i32 constants. - if (-128 <= value && value <= 127) { - EmitWithU8(kExprI8Const, static_cast<byte>(value)); + if (-64 <= value && value <= 63) { + EmitWithU8(kExprI32Const, static_cast<byte>(value & 0x7F)); } else { + // TODO(titzer): variable-length signed and unsigned i32 constants. byte code[] = {WASM_I32V_5(value)}; EmitCode(code, sizeof(code)); } @@ -141,12 +140,9 @@ void WasmFunctionBuilder::EmitDirectCallIndex(uint32_t index) { EmitCode(code, sizeof(code)); } -void WasmFunctionBuilder::Export() { exported_ = true; } - void WasmFunctionBuilder::ExportAs(Vector<const char> name) { - exported_ = true; - exported_name_.resize(name.length()); - memcpy(exported_name_.data(), name.start(), name.length()); + exported_names_.push_back(ZoneVector<char>( + name.start(), name.start() + name.length(), builder_->zone())); } void WasmFunctionBuilder::SetName(Vector<const char> name) { @@ -154,8 +150,9 @@ void WasmFunctionBuilder::SetName(Vector<const char> name) { memcpy(name_.data(), name.start(), name.length()); } -void WasmFunctionBuilder::AddAsmWasmOffset(int asm_position) { - // We only want to emit one mapping per byte offset: +void WasmFunctionBuilder::AddAsmWasmOffset(int call_position, + int to_number_position) { + // We only want to emit one mapping per byte offset. DCHECK(asm_offsets_.size() == 0 || body_.size() > last_asm_byte_offset_); DCHECK_LE(body_.size(), kMaxUInt32); @@ -163,22 +160,31 @@ void WasmFunctionBuilder::AddAsmWasmOffset(int asm_position) { asm_offsets_.write_u32v(byte_offset - last_asm_byte_offset_); last_asm_byte_offset_ = byte_offset; - DCHECK_GE(asm_position, 0); - asm_offsets_.write_i32v(asm_position - last_asm_source_position_); - last_asm_source_position_ = asm_position; + DCHECK_GE(call_position, 0); + asm_offsets_.write_i32v(call_position - last_asm_source_position_); + + DCHECK_GE(to_number_position, 0); + asm_offsets_.write_i32v(to_number_position - call_position); + last_asm_source_position_ = to_number_position; +} + +void WasmFunctionBuilder::SetAsmFunctionStartPosition(int position) { + DCHECK_EQ(0, asm_func_start_source_position_); + DCHECK_LE(0, position); + // Must be called before emitting any asm.js source position. + DCHECK_EQ(0, asm_offsets_.size()); + asm_func_start_source_position_ = position; + last_asm_source_position_ = position; } void WasmFunctionBuilder::WriteSignature(ZoneBuffer& buffer) const { buffer.write_u32v(signature_index_); } -void WasmFunctionBuilder::WriteExport(ZoneBuffer& buffer) const { - if (exported_) { - const ZoneVector<char>* exported_name = - exported_name_.size() == 0 ? &name_ : &exported_name_; - buffer.write_size(exported_name->size()); - buffer.write(reinterpret_cast<const byte*>(exported_name->data()), - exported_name->size()); +void WasmFunctionBuilder::WriteExports(ZoneBuffer& buffer) const { + for (auto name : exported_names_) { + buffer.write_size(name.size()); + buffer.write(reinterpret_cast<const byte*>(name.data()), name.size()); buffer.write_u8(kExternalFunction); buffer.write_u32v(func_index_ + static_cast<uint32_t>(builder_->imports_.size())); @@ -204,14 +210,19 @@ void WasmFunctionBuilder::WriteBody(ZoneBuffer& buffer) const { } void WasmFunctionBuilder::WriteAsmWasmOffsetTable(ZoneBuffer& buffer) const { - if (asm_offsets_.size() == 0) { + if (asm_func_start_source_position_ == 0 && asm_offsets_.size() == 0) { buffer.write_size(0); return; } - buffer.write_size(asm_offsets_.size() + kInt32Size); + size_t locals_enc_size = LEBHelper::sizeof_u32v(locals_.Size()); + size_t func_start_size = + LEBHelper::sizeof_u32v(asm_func_start_source_position_); + buffer.write_size(asm_offsets_.size() + locals_enc_size + func_start_size); // Offset of the recorded byte offsets. DCHECK_GE(kMaxUInt32, locals_.Size()); - buffer.write_u32(static_cast<uint32_t>(locals_.Size())); + buffer.write_u32v(static_cast<uint32_t>(locals_.Size())); + // Start position of the function. + buffer.write_u32v(asm_func_start_source_position_); buffer.write(asm_offsets_.begin(), asm_offsets_.size()); } @@ -271,8 +282,15 @@ uint32_t WasmModuleBuilder::AddSignature(FunctionSig* sig) { } } -void WasmModuleBuilder::AddIndirectFunction(uint32_t index) { - indirect_functions_.push_back(index); +uint32_t WasmModuleBuilder::AllocateIndirectFunctions(uint32_t count) { + uint32_t ret = static_cast<uint32_t>(indirect_functions_.size()); + indirect_functions_.resize(indirect_functions_.size() + count); + return ret; +} + +void WasmModuleBuilder::SetIndirectFunction(uint32_t indirect, + uint32_t direct) { + indirect_functions_[indirect] = direct; } uint32_t WasmModuleBuilder::AddImport(const char* name, int name_length, @@ -285,7 +303,7 @@ void WasmModuleBuilder::MarkStartFunction(WasmFunctionBuilder* function) { start_function_index_ = function->func_index(); } -uint32_t WasmModuleBuilder::AddGlobal(LocalType type, bool exported, +uint32_t WasmModuleBuilder::AddGlobal(ValueType type, bool exported, bool mutability, const WasmInitExpr& init) { globals_.push_back({type, exported, mutability, init}); @@ -309,11 +327,11 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { buffer.write_u8(kWasmFunctionTypeForm); buffer.write_size(sig->parameter_count()); for (size_t j = 0; j < sig->parameter_count(); j++) { - buffer.write_u8(WasmOpcodes::LocalTypeCodeFor(sig->GetParam(j))); + buffer.write_u8(WasmOpcodes::ValueTypeCodeFor(sig->GetParam(j))); } buffer.write_size(sig->return_count()); for (size_t j = 0; j < sig->return_count(); j++) { - buffer.write_u8(WasmOpcodes::LocalTypeCodeFor(sig->GetReturn(j))); + buffer.write_u8(WasmOpcodes::ValueTypeCodeFor(sig->GetReturn(j))); } } FixupSection(buffer, start); @@ -324,10 +342,10 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { size_t start = EmitSection(kImportSectionCode, buffer); buffer.write_size(imports_.size()); for (auto import : imports_) { - buffer.write_u32v(import.name_length); // module name length - buffer.write(reinterpret_cast<const byte*>(import.name), // module name + buffer.write_u32v(0); // module name length + buffer.write_u32v(import.name_length); // field name length + buffer.write(reinterpret_cast<const byte*>(import.name), // field name import.name_length); - buffer.write_u32v(0); // field name length buffer.write_u8(kExternalFunction); buffer.write_u32v(import.sig_index); } @@ -341,7 +359,7 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { buffer.write_size(functions_.size()); for (auto function : functions_) { function->WriteSignature(buffer); - if (function->exported()) exports++; + exports += function->exported_names_.size(); if (function->name_.size() > 0) has_names = true; } FixupSection(buffer, start); @@ -374,29 +392,29 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { buffer.write_size(globals_.size()); for (auto global : globals_) { - buffer.write_u8(WasmOpcodes::LocalTypeCodeFor(global.type)); + buffer.write_u8(WasmOpcodes::ValueTypeCodeFor(global.type)); buffer.write_u8(global.mutability ? 1 : 0); switch (global.init.kind) { case WasmInitExpr::kI32Const: { - DCHECK_EQ(kAstI32, global.type); + DCHECK_EQ(kWasmI32, global.type); const byte code[] = {WASM_I32V_5(global.init.val.i32_const)}; buffer.write(code, sizeof(code)); break; } case WasmInitExpr::kI64Const: { - DCHECK_EQ(kAstI64, global.type); + DCHECK_EQ(kWasmI64, global.type); const byte code[] = {WASM_I64V_10(global.init.val.i64_const)}; buffer.write(code, sizeof(code)); break; } case WasmInitExpr::kF32Const: { - DCHECK_EQ(kAstF32, global.type); + DCHECK_EQ(kWasmF32, global.type); const byte code[] = {WASM_F32(global.init.val.f32_const)}; buffer.write(code, sizeof(code)); break; } case WasmInitExpr::kF64Const: { - DCHECK_EQ(kAstF64, global.type); + DCHECK_EQ(kWasmF64, global.type); const byte code[] = {WASM_F64(global.init.val.f64_const)}; buffer.write(code, sizeof(code)); break; @@ -410,22 +428,22 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { default: { // No initializer, emit a default value. switch (global.type) { - case kAstI32: { + case kWasmI32: { const byte code[] = {WASM_I32V_1(0)}; buffer.write(code, sizeof(code)); break; } - case kAstI64: { + case kWasmI64: { const byte code[] = {WASM_I64V_1(0)}; buffer.write(code, sizeof(code)); break; } - case kAstF32: { + case kWasmF32: { const byte code[] = {WASM_F32(0.0)}; buffer.write(code, sizeof(code)); break; } - case kAstF64: { + case kWasmF64: { const byte code[] = {WASM_F64(0.0)}; buffer.write(code, sizeof(code)); break; @@ -444,7 +462,7 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { if (exports > 0) { size_t start = EmitSection(kExportSectionCode, buffer); buffer.write_u32v(exports); - for (auto function : functions_) function->WriteExport(buffer); + for (auto function : functions_) function->WriteExports(buffer); FixupSection(buffer, start); } @@ -517,10 +535,8 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const { } 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(reinterpret_cast<const byte*>(function->name_.data()), + function->name_.size()); buffer.write_u8(0); } FixupSection(buffer, start); @@ -534,6 +550,8 @@ void WasmModuleBuilder::WriteAsmJsOffsetTable(ZoneBuffer& buffer) const { for (auto function : functions_) { function->WriteAsmWasmOffsetTable(buffer); } + // Append a 0 to indicate that this is an encoded table. + buffer.write_u8(0); } } // namespace wasm } // namespace internal diff --git a/deps/v8/src/wasm/wasm-module-builder.h b/deps/v8/src/wasm/wasm-module-builder.h index d35313ef47..3258f78d50 100644 --- a/deps/v8/src/wasm/wasm-module-builder.h +++ b/deps/v8/src/wasm/wasm-module-builder.h @@ -120,7 +120,7 @@ class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject { public: // Building methods. void SetSignature(FunctionSig* sig); - uint32_t AddLocal(LocalType type); + uint32_t AddLocal(ValueType type); void EmitVarInt(uint32_t val); void EmitCode(const byte* code, uint32_t code_size); void Emit(WasmOpcode opcode); @@ -132,17 +132,16 @@ class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject { void EmitWithU8U8(WasmOpcode opcode, const byte imm1, const byte imm2); void EmitWithVarInt(WasmOpcode opcode, uint32_t immediate); void EmitDirectCallIndex(uint32_t index); - void Export(); void ExportAs(Vector<const char> name); void SetName(Vector<const char> name); - void AddAsmWasmOffset(int asm_position); + void AddAsmWasmOffset(int call_position, int to_number_position); + void SetAsmFunctionStartPosition(int position); void WriteSignature(ZoneBuffer& buffer) const; - void WriteExport(ZoneBuffer& buffer) const; + void WriteExports(ZoneBuffer& buffer) const; void WriteBody(ZoneBuffer& buffer) const; void WriteAsmWasmOffsetTable(ZoneBuffer& buffer) const; - bool exported() { return exported_; } uint32_t func_index() { return func_index_; } FunctionSig* signature(); @@ -159,11 +158,10 @@ class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject { WasmModuleBuilder* builder_; LocalDeclEncoder locals_; uint32_t signature_index_; - bool exported_; uint32_t func_index_; ZoneVector<uint8_t> body_; ZoneVector<char> name_; - ZoneVector<char> exported_name_; + ZoneVector<ZoneVector<char>> exported_names_; ZoneVector<uint32_t> i32_temps_; ZoneVector<uint32_t> i64_temps_; ZoneVector<uint32_t> f32_temps_; @@ -174,22 +172,23 @@ class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject { ZoneBuffer asm_offsets_; uint32_t last_asm_byte_offset_ = 0; uint32_t last_asm_source_position_ = 0; + uint32_t asm_func_start_source_position_ = 0; }; class WasmTemporary { public: - WasmTemporary(WasmFunctionBuilder* builder, LocalType type) { + WasmTemporary(WasmFunctionBuilder* builder, ValueType type) { switch (type) { - case kAstI32: + case kWasmI32: temporary_ = &builder->i32_temps_; break; - case kAstI64: + case kWasmI64: temporary_ = &builder->i64_temps_; break; - case kAstF32: + case kWasmF32: temporary_ = &builder->f32_temps_; break; - case kAstF64: + case kWasmF64: temporary_ = &builder->f64_temps_; break; default: @@ -226,11 +225,12 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { imports_[index].name_length = name_length; } WasmFunctionBuilder* AddFunction(FunctionSig* sig = nullptr); - uint32_t AddGlobal(LocalType type, bool exported, bool mutability = true, + uint32_t AddGlobal(ValueType type, bool exported, bool mutability = true, const WasmInitExpr& init = WasmInitExpr()); void AddDataSegment(const byte* data, uint32_t size, uint32_t dest); uint32_t AddSignature(FunctionSig* sig); - void AddIndirectFunction(uint32_t index); + uint32_t AllocateIndirectFunctions(uint32_t count); + void SetIndirectFunction(uint32_t indirect, uint32_t direct); void MarkStartFunction(WasmFunctionBuilder* builder); // Writing methods. @@ -256,7 +256,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { }; struct WasmGlobal { - LocalType type; + ValueType type; bool exported; bool mutability; WasmInitExpr init; diff --git a/deps/v8/src/wasm/wasm-module.cc b/deps/v8/src/wasm/wasm-module.cc index 79b99fe04d..60dda925fa 100644 --- a/deps/v8/src/wasm/wasm-module.cc +++ b/deps/v8/src/wasm/wasm-module.cc @@ -4,25 +4,26 @@ #include <memory> +#include "src/assembler-inl.h" +#include "src/base/adapters.h" #include "src/base/atomic-utils.h" #include "src/code-stubs.h" - -#include "src/macro-assembler.h" +#include "src/compiler/wasm-compiler.h" +#include "src/debug/interface-types.h" #include "src/objects.h" #include "src/property-descriptor.h" #include "src/simulator.h" #include "src/snapshot/snapshot.h" #include "src/v8.h" -#include "src/wasm/ast-decoder.h" +#include "src/wasm/function-body-decoder.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-js.h" +#include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects.h" #include "src/wasm/wasm-result.h" -#include "src/compiler/wasm-compiler.h" - using namespace v8::internal; using namespace v8::internal::wasm; namespace base = v8::base; @@ -40,26 +41,11 @@ namespace base = v8::base; namespace { static const int kInvalidSigIndex = -1; -static const int kPlaceholderMarker = 1000000000; byte* raw_buffer_ptr(MaybeHandle<JSArrayBuffer> buffer, int offset) { return static_cast<byte*>(buffer.ToHandleChecked()->backing_store()) + offset; } -MaybeHandle<String> ExtractStringFromModuleBytes( - Isolate* isolate, Handle<WasmCompiledModule> compiled_module, - uint32_t offset, uint32_t size) { - // TODO(wasm): cache strings from modules if it's a performance win. - Handle<SeqOneByteString> module_bytes = compiled_module->module_bytes(); - DCHECK_GE(static_cast<size_t>(module_bytes->length()), offset); - DCHECK_GE(static_cast<size_t>(module_bytes->length() - offset), size); - Address raw = module_bytes->GetCharsAddress() + offset; - if (!unibrow::Utf8::Validate(reinterpret_cast<const byte*>(raw), size)) - return {}; // UTF8 decoding error for name. - return isolate->factory()->NewStringFromUtf8SubString( - module_bytes, static_cast<int>(offset), static_cast<int>(size)); -} - void ReplaceReferenceInCode(Handle<Code> code, Handle<Object> old_ref, Handle<Object> new_ref) { for (RelocIterator it(*code, 1 << RelocInfo::EMBEDDED_OBJECT); !it.done(); @@ -70,34 +56,74 @@ void ReplaceReferenceInCode(Handle<Code> code, Handle<Object> old_ref, } } -Handle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size) { - if (size > (WasmModule::kV8MaxPages * WasmModule::kPageSize)) { - // TODO(titzer): lift restriction on maximum memory allocated here. - return Handle<JSArrayBuffer>::null(); - } - void* memory = isolate->array_buffer_allocator()->Allocate(size); - if (memory == nullptr) { - return Handle<JSArrayBuffer>::null(); - } +static void MemoryFinalizer(const v8::WeakCallbackInfo<void>& data) { + DisallowHeapAllocation no_gc; + JSArrayBuffer** p = reinterpret_cast<JSArrayBuffer**>(data.GetParameter()); + JSArrayBuffer* buffer = *p; -#if DEBUG - // Double check the API allocator actually zero-initialized the memory. - const byte* bytes = reinterpret_cast<const byte*>(memory); - for (size_t i = 0; i < size; ++i) { - DCHECK_EQ(0, bytes[i]); - } + void* memory = buffer->backing_store(); + base::OS::Free(memory, + RoundUp(kWasmMaxHeapOffset, base::OS::CommitPageSize())); + + data.GetIsolate()->AdjustAmountOfExternalAllocatedMemory( + -buffer->byte_length()->Number()); + + GlobalHandles::Destroy(reinterpret_cast<Object**>(p)); +} + +#if V8_TARGET_ARCH_64_BIT +const bool kGuardRegionsSupported = true; +#else +const bool kGuardRegionsSupported = false; #endif - Handle<JSArrayBuffer> buffer = isolate->factory()->NewJSArrayBuffer(); - JSArrayBuffer::Setup(buffer, isolate, false, memory, static_cast<int>(size)); - buffer->set_is_neuterable(false); - return buffer; +bool EnableGuardRegions() { + return FLAG_wasm_guard_pages && kGuardRegionsSupported; +} + +void* TryAllocateBackingStore(Isolate* isolate, size_t size, + bool enable_guard_regions, bool& is_external) { + is_external = false; + // TODO(eholk): Right now enable_guard_regions has no effect on 32-bit + // systems. It may be safer to fail instead, given that other code might do + // things that would be unsafe if they expected guard pages where there + // weren't any. + if (enable_guard_regions && kGuardRegionsSupported) { + // TODO(eholk): On Windows we want to make sure we don't commit the guard + // pages yet. + + // We always allocate the largest possible offset into the heap, so the + // addressable memory after the guard page can be made inaccessible. + const size_t alloc_size = + RoundUp(kWasmMaxHeapOffset, base::OS::CommitPageSize()); + DCHECK_EQ(0, size % base::OS::CommitPageSize()); + + // AllocateGuarded makes the whole region inaccessible by default. + void* memory = base::OS::AllocateGuarded(alloc_size); + if (memory == nullptr) { + return nullptr; + } + + // Make the part we care about accessible. + base::OS::Unprotect(memory, size); + + reinterpret_cast<v8::Isolate*>(isolate) + ->AdjustAmountOfExternalAllocatedMemory(size); + + is_external = true; + return memory; + } else { + void* memory = isolate->array_buffer_allocator()->Allocate(size); + return memory; + } } void RelocateMemoryReferencesInCode(Handle<FixedArray> code_table, + uint32_t num_imported_functions, Address old_start, Address start, uint32_t prev_size, uint32_t new_size) { - for (int i = 0; i < code_table->length(); ++i) { + for (int i = static_cast<int>(num_imported_functions); + i < code_table->length(); ++i) { DCHECK(code_table->get(i)->IsCode()); Handle<Code> code = Handle<Code>(Code::cast(code_table->get(i))); AllowDeferredHandleDereference embedding_raw_address; @@ -123,52 +149,25 @@ void RelocateGlobals(Handle<FixedArray> code_table, Address old_start, } } -Handle<Code> CreatePlaceholder(Factory* factory, uint32_t index, - Code::Kind kind) { - // Create a placeholder code object and encode the corresponding index in - // the {constant_pool_offset} field of the code object. - // TODO(titzer): instead of placeholders, use a reloc_info mode. - static byte buffer[] = {0, 0, 0, 0}; // fake instructions. - static CodeDesc desc = { - buffer, arraysize(buffer), arraysize(buffer), 0, 0, nullptr, 0, nullptr}; - Handle<Code> code = factory->NewCode(desc, Code::KindField::encode(kind), - Handle<Object>::null()); - code->set_constant_pool_offset(static_cast<int>(index) + kPlaceholderMarker); - return code; -} - -bool LinkFunction(Handle<Code> unlinked, - std::vector<Handle<Code>>& code_table) { - bool modified = false; - 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->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; - } +void RelocateTableSizeReferences(Handle<FixedArray> code_table, + uint32_t old_size, uint32_t new_size) { + for (int i = 0; i < code_table->length(); ++i) { + DCHECK(code_table->get(i)->IsCode()); + Handle<Code> code = Handle<Code>(Code::cast(code_table->get(i))); + AllowDeferredHandleDereference embedding_raw_address; + int mask = 1 << RelocInfo::WASM_FUNCTION_TABLE_SIZE_REFERENCE; + for (RelocIterator it(*code, mask); !it.done(); it.next()) { + it.rinfo()->update_wasm_function_table_size_reference(old_size, new_size); } } - return modified; +} + +Handle<Code> CreatePlaceholder(Factory* factory, Code::Kind kind) { + byte buffer[] = {0, 0, 0, 0}; // fake instructions. + CodeDesc desc = { + buffer, arraysize(buffer), arraysize(buffer), 0, 0, nullptr, 0, nullptr}; + return factory->NewCode(desc, Code::KindField::encode(kind), + Handle<Object>::null()); } void FlushICache(Isolate* isolate, Handle<FixedArray> code_table) { @@ -257,7 +256,7 @@ Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = - static_cast<Address>(instance->get_globals_buffer()->backing_store()); + static_cast<Address>(instance->globals_buffer()->backing_store()); } return old_address; } @@ -265,7 +264,7 @@ Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, void InitializeParallelCompilation( Isolate* isolate, const std::vector<WasmFunction>& functions, std::vector<compiler::WasmCompilationUnit*>& compilation_units, - ModuleEnv& module_env, ErrorThrower* thrower) { + ModuleBytesEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = @@ -329,9 +328,10 @@ void FinishCompilationUnits( } } -void CompileInParallel(Isolate* isolate, const WasmModule* module, +void CompileInParallel(Isolate* isolate, ModuleBytesEnv* module_env, std::vector<Handle<Code>>& functions, - ErrorThrower* thrower, ModuleEnv* module_env) { + ErrorThrower* thrower) { + const WasmModule* module = module_env->module; // Data structures for the parallel compilation. std::vector<compiler::WasmCompilationUnit*> compilation_units( module->functions.size()); @@ -393,22 +393,23 @@ void CompileInParallel(Isolate* isolate, const WasmModule* module, FinishCompilationUnits(executed_units, functions, result_mutex); } -void CompileSequentially(Isolate* isolate, const WasmModule* module, +void CompileSequentially(Isolate* isolate, ModuleBytesEnv* module_env, std::vector<Handle<Code>>& functions, - ErrorThrower* thrower, ModuleEnv* module_env) { + ErrorThrower* thrower) { DCHECK(!thrower->error()); + const WasmModule* module = module_env->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. - WasmName str = module->GetName(func.name_offset, func.name_length); Handle<Code> code = Handle<Code>::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { + WasmName str = module_env->GetName(&func); thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; @@ -418,36 +419,120 @@ void CompileSequentially(Isolate* isolate, const WasmModule* module, } } -void PatchDirectCalls(Handle<FixedArray> old_functions, - Handle<FixedArray> new_functions, int start) { - DCHECK_EQ(new_functions->length(), old_functions->length()); +int ExtractDirectCallIndex(wasm::Decoder& decoder, const byte* pc) { + DCHECK_EQ(static_cast<int>(kExprCallFunction), static_cast<int>(*pc)); + decoder.Reset(pc + 1, pc + 6); + uint32_t call_idx = decoder.consume_u32v("call index"); + DCHECK(decoder.ok()); + DCHECK_GE(kMaxInt, call_idx); + return static_cast<int>(call_idx); +} + +int AdvanceSourcePositionTableIterator(SourcePositionTableIterator& iterator, + size_t offset_l) { + DCHECK_GE(kMaxInt, offset_l); + int offset = static_cast<int>(offset_l); + DCHECK(!iterator.done()); + int byte_pos; + do { + byte_pos = iterator.source_position().ScriptOffset(); + iterator.Advance(); + } while (!iterator.done() && iterator.code_offset() <= offset); + return byte_pos; +} +void PatchContext(RelocIterator& it, Context* context) { + Object* old = it.rinfo()->target_object(); + // The only context we use is the native context. + DCHECK_IMPLIES(old->IsContext(), old->IsNativeContext()); + if (!old->IsNativeContext()) return; + it.rinfo()->set_target_object(context, UPDATE_WRITE_BARRIER, + SKIP_ICACHE_FLUSH); +} + +void PatchDirectCallsAndContext(Handle<FixedArray> new_functions, + Handle<WasmCompiledModule> compiled_module, + WasmModule* module, int start) { 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)); + SeqOneByteString* module_bytes = compiled_module->module_bytes(); + std::vector<WasmFunction>* wasm_functions = + &compiled_module->module()->functions; + DCHECK_EQ(wasm_functions->size() + + compiled_module->module()->num_exported_functions, + new_functions->length()); + DCHECK_EQ(start, compiled_module->module()->num_imported_functions); + Context* context = compiled_module->ptr_to_native_context(); + int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET) | + RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); + + // Allocate decoder outside of the loop and reuse it to decode all function + // indexes. + wasm::Decoder decoder(nullptr, nullptr); + int num_wasm_functions = static_cast<int>(wasm_functions->size()); + int func_index = start; + // Patch all wasm functions. + for (; func_index < num_wasm_functions; ++func_index) { + Code* wasm_function = Code::cast(new_functions->get(func_index)); + DCHECK(wasm_function->kind() == Code::WASM_FUNCTION); + // Iterate simultaneously over the relocation information and the source + // position table. For each call in the reloc info, move the source position + // iterator forward to that position to find the byte offset of the + // respective call. Then extract the call index from the module wire bytes + // to find the new compiled function. + SourcePositionTableIterator source_pos_iterator( + wasm_function->source_position_table()); + const byte* func_bytes = + module_bytes->GetChars() + + compiled_module->module()->functions[func_index].code_start_offset; 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); - } + if (RelocInfo::IsEmbeddedObject(it.rinfo()->rmode())) { + PatchContext(it, context); + continue; } + DCHECK(RelocInfo::IsCodeTarget(it.rinfo()->rmode())); + Code::Kind kind = + Code::GetCodeFromTargetAddress(it.rinfo()->target_address())->kind(); + if (kind != Code::WASM_FUNCTION && kind != Code::WASM_TO_JS_FUNCTION) + continue; + size_t offset = it.rinfo()->pc() - wasm_function->instruction_start(); + int byte_pos = + AdvanceSourcePositionTableIterator(source_pos_iterator, offset); + int called_func_index = + ExtractDirectCallIndex(decoder, func_bytes + byte_pos); + Code* new_code = Code::cast(new_functions->get(called_func_index)); + it.rinfo()->set_target_address(new_code->instruction_start(), + UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } + // Patch all exported functions. + for (auto exp : module->export_table) { + if (exp.kind != kExternalFunction) continue; + Code* export_wrapper = Code::cast(new_functions->get(func_index)); + DCHECK_EQ(Code::JS_TO_WASM_FUNCTION, export_wrapper->kind()); + // There must be exactly one call to WASM_FUNCTION or WASM_TO_JS_FUNCTION. + int num_wasm_calls = 0; + for (RelocIterator it(export_wrapper, mode_mask); !it.done(); it.next()) { + if (RelocInfo::IsEmbeddedObject(it.rinfo()->rmode())) { + PatchContext(it, context); + continue; + } + DCHECK(RelocInfo::IsCodeTarget(it.rinfo()->rmode())); + Code::Kind kind = + Code::GetCodeFromTargetAddress(it.rinfo()->target_address())->kind(); + if (kind != Code::WASM_FUNCTION && kind != Code::WASM_TO_JS_FUNCTION) + continue; + ++num_wasm_calls; + Code* new_code = Code::cast(new_functions->get(exp.index)); + DCHECK(new_code->kind() == Code::WASM_FUNCTION || + new_code->kind() == Code::WASM_TO_JS_FUNCTION); + it.rinfo()->set_target_address(new_code->instruction_start(), + UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); + } + DCHECK_EQ(1, num_wasm_calls); + func_index++; + } + DCHECK_EQ(new_functions->length(), func_index); } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, @@ -456,7 +541,7 @@ static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); - Object* mem_start = compiled_module->ptr_to_memory(); + Object* mem_start = compiled_module->maybe_ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); @@ -486,7 +571,8 @@ static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, if (fct_obj != nullptr && fct_obj != undefined && (old_mem_size > 0 || globals_start != nullptr || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); - for (int i = 0; i < functions->length(); ++i) { + for (int i = compiled_module->num_imported_functions(); + i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { @@ -518,12 +604,58 @@ static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, compiled_module->reset_memory(); } +static void MemoryInstanceFinalizer(Isolate* isolate, + WasmInstanceObject* instance) { + DisallowHeapAllocation no_gc; + // If the memory object is destroyed, nothing needs to be done here. + if (!instance->has_memory_object()) return; + Handle<WasmInstanceWrapper> instance_wrapper = + handle(instance->instance_wrapper()); + DCHECK(WasmInstanceWrapper::IsWasmInstanceWrapper(*instance_wrapper)); + DCHECK(instance_wrapper->has_instance()); + bool has_prev = instance_wrapper->has_previous(); + bool has_next = instance_wrapper->has_next(); + Handle<WasmMemoryObject> memory_object(instance->memory_object()); + + if (!has_prev && !has_next) { + memory_object->ResetInstancesLink(isolate); + return; + } else { + Handle<WasmInstanceWrapper> next_wrapper, prev_wrapper; + if (!has_prev) { + Handle<WasmInstanceWrapper> next_wrapper = + instance_wrapper->next_wrapper(); + next_wrapper->reset_previous_wrapper(); + // As this is the first link in the memory object, destroying + // without updating memory object would corrupt the instance chain in + // the memory object. + memory_object->set_instances_link(*next_wrapper); + } else if (!has_next) { + instance_wrapper->previous_wrapper()->reset_next_wrapper(); + } else { + DCHECK(has_next && has_prev); + Handle<WasmInstanceWrapper> prev_wrapper = + instance_wrapper->previous_wrapper(); + Handle<WasmInstanceWrapper> next_wrapper = + instance_wrapper->next_wrapper(); + prev_wrapper->set_next_wrapper(*next_wrapper); + next_wrapper->set_previous_wrapper(*prev_wrapper); + } + // Reset to avoid dangling pointers + instance_wrapper->reset(); + } +} + static void InstanceFinalizer(const v8::WeakCallbackInfo<void>& data) { + DisallowHeapAllocation no_gc; JSObject** p = reinterpret_cast<JSObject**>(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast<WasmInstanceObject*>(*p); - WasmCompiledModule* compiled_module = owner->get_compiled_module(); - TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast<Isolate*>(data.GetIsolate()); + // If a link to shared memory instances exists, update the list of memory + // instances before the instance is destroyed. + if (owner->has_instance_wrapper()) MemoryInstanceFinalizer(isolate, owner); + WasmCompiledModule* compiled_module = owner->compiled_module(); + TRACE("Finalizing %d {\n", compiled_module->instance_id()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); @@ -540,8 +672,8 @@ static void InstanceFinalizer(const v8::WeakCallbackInfo<void>& data) { 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(); + WeakCell* next = compiled_module->maybe_ptr_to_weak_next_instance(); + WeakCell* prev = compiled_module->maybe_ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { @@ -597,8 +729,81 @@ std::pair<int, int> GetFunctionOffsetAndLength( static_cast<int>(func.code_end_offset - func.code_start_offset)}; } +Handle<Script> CreateWasmScript(Isolate* isolate, + const ModuleWireBytes& wire_bytes) { + Handle<Script> script = + isolate->factory()->NewScript(isolate->factory()->empty_string()); + script->set_type(Script::TYPE_WASM); + + int hash = StringHasher::HashSequentialString( + reinterpret_cast<const char*>(wire_bytes.module_bytes.start()), + wire_bytes.module_bytes.length(), kZeroHashSeed); + + const int kBufferSize = 50; + char buffer[kBufferSize]; + int url_chars = SNPrintF(ArrayVector(buffer), "wasm://wasm/%08x", hash); + DCHECK(url_chars >= 0 && url_chars < kBufferSize); + MaybeHandle<String> url_str = isolate->factory()->NewStringFromOneByte( + Vector<const uint8_t>(reinterpret_cast<uint8_t*>(buffer), url_chars), + TENURED); + script->set_source_url(*url_str.ToHandleChecked()); + + int name_chars = SNPrintF(ArrayVector(buffer), "wasm-%08x", hash); + DCHECK(name_chars >= 0 && name_chars < kBufferSize); + MaybeHandle<String> name_str = isolate->factory()->NewStringFromOneByte( + Vector<const uint8_t>(reinterpret_cast<uint8_t*>(buffer), name_chars), + TENURED); + script->set_name(*name_str.ToHandleChecked()); + + return script; +} } // namespace +Handle<JSArrayBuffer> wasm::NewArrayBuffer(Isolate* isolate, size_t size, + bool enable_guard_regions) { + if (size > (kV8MaxWasmMemoryPages * WasmModule::kPageSize)) { + // TODO(titzer): lift restriction on maximum memory allocated here. + return Handle<JSArrayBuffer>::null(); + } + + enable_guard_regions = enable_guard_regions && kGuardRegionsSupported; + + bool is_external; // Set by TryAllocateBackingStore + void* memory = + TryAllocateBackingStore(isolate, size, enable_guard_regions, is_external); + + if (memory == nullptr) { + return Handle<JSArrayBuffer>::null(); + } + +#if DEBUG + // Double check the API allocator actually zero-initialized the memory. + const byte* bytes = reinterpret_cast<const byte*>(memory); + for (size_t i = 0; i < size; ++i) { + DCHECK_EQ(0, bytes[i]); + } +#endif + + Handle<JSArrayBuffer> buffer = isolate->factory()->NewJSArrayBuffer(); + JSArrayBuffer::Setup(buffer, isolate, is_external, memory, + static_cast<int>(size)); + buffer->set_is_neuterable(false); + buffer->set_has_guard_region(enable_guard_regions); + + if (is_external) { + // We mark the buffer as external if we allocated it here with guard + // pages. That means we need to arrange for it to be freed. + + // TODO(eholk): Finalizers may not run when the main thread is shutting + // down, which means we may leak memory here. + Handle<Object> global_handle = isolate->global_handles()->Create(*buffer); + GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), + &MemoryFinalizer, v8::WeakCallbackType::kFinalizer); + } + + return buffer; +} + const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: @@ -650,15 +855,12 @@ std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { return os; } -std::ostream& wasm::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; +std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& name) { + os << "#" << name.function_->func_index; + if (name.function_->name_offset > 0) { + if (name.name_.start()) { + os << ":"; + os.write(name.name_.start(), name.name_.length()); } } else { os << "?"; @@ -666,16 +868,17 @@ std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { return os; } -Object* wasm::GetOwningWasmInstance(Code* code) { +WasmInstanceObject* wasm::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); + DCHECK_EQ(2, deopt_data->length()); Object* weak_link = deopt_data->get(0); - if (!weak_link->IsWeakCell()) return nullptr; + DCHECK(weak_link->IsWeakCell()); WeakCell* cell = WeakCell::cast(weak_link); - return cell->value(); + if (!cell->value()) return nullptr; + return WasmInstanceObject::cast(cell->value()); } int wasm::GetFunctionCodeOffset(Handle<WasmCompiledModule> compiled_module, @@ -683,69 +886,41 @@ int wasm::GetFunctionCodeOffset(Handle<WasmCompiledModule> compiled_module, return GetFunctionOffsetAndLength(compiled_module, func_index).first; } -bool wasm::GetPositionInfo(Handle<WasmCompiledModule> compiled_module, - uint32_t position, Script::PositionInfo* info) { - std::vector<WasmFunction>& functions = compiled_module->module()->functions; - - // Binary search for a function containing the given position. - int left = 0; // inclusive - int right = static_cast<int>(functions.size()); // exclusive - if (right == 0) return false; - while (right - left > 1) { - int mid = left + (right - left) / 2; - if (functions[mid].code_start_offset <= position) { - left = mid; - } else { - right = mid; - } - } - // If the found entry does not contains the given position, return false. - WasmFunction& func = functions[left]; - if (position < func.code_start_offset || position >= func.code_end_offset) { - return false; - } - - info->line = left; - info->column = position - func.code_start_offset; - info->line_start = func.code_start_offset; - info->line_end = func.code_end_offset; - return true; -} - -WasmModule::WasmModule(Zone* owned, const byte* module_start) - : owned_zone(owned), - module_start(module_start), - pending_tasks(new base::Semaphore(0)) {} +WasmModule::WasmModule(Zone* owned) + : owned_zone(owned), pending_tasks(new base::Semaphore(0)) {} MaybeHandle<WasmCompiledModule> WasmModule::CompileFunctions( Isolate* isolate, Handle<WasmModuleWrapper> module_wrapper, - ErrorThrower* thrower) const { + ErrorThrower* thrower, const ModuleWireBytes& wire_bytes, + Handle<Script> asm_js_script, + Vector<const byte> asm_js_offset_table_bytes) const { Factory* factory = isolate->factory(); MaybeHandle<WasmCompiledModule> nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); - temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; + temp_instance.mem_size = WasmModule::kPageSize * min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. - int function_table_count = static_cast<int>(this->function_tables.size()); + int function_table_count = static_cast<int>(function_tables.size()); Handle<FixedArray> function_tables = - factory->NewFixedArray(function_table_count); + factory->NewFixedArray(function_table_count, TENURED); + Handle<FixedArray> signature_tables = + factory->NewFixedArray(function_table_count, TENURED); for (int i = 0; i < function_table_count; ++i) { - temp_instance.function_tables[i] = factory->NewFixedArray(0); + temp_instance.function_tables[i] = factory->NewFixedArray(1, TENURED); + temp_instance.signature_tables[i] = factory->NewFixedArray(1, TENURED); function_tables->set(i, *temp_instance.function_tables[i]); + signature_tables->set(i, *temp_instance.signature_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); - ModuleEnv module_env; - module_env.module = this; - module_env.instance = &temp_instance; - module_env.origin = origin; + ModuleBytesEnv module_env(this, &temp_instance, wire_bytes); // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. @@ -755,12 +930,11 @@ MaybeHandle<WasmCompiledModule> WasmModule::CompileFunctions( factory->NewFixedArray(static_cast<int>(code_table_size), TENURED); // Initialize the code table with placeholders. + Handle<Code> code_placeholder = + CreatePlaceholder(factory, Code::WASM_FUNCTION); 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; + code_table->set(static_cast<int>(i), *code_placeholder); + temp_instance.function_code[i] = code_placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( @@ -772,14 +946,14 @@ MaybeHandle<WasmCompiledModule> WasmModule::CompileFunctions( 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); + CompileInParallel(isolate, &module_env, results, thrower); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { - CompileSequentially(isolate, this, temp_instance.function_code, thrower, - &module_env); + CompileSequentially(isolate, &module_env, temp_instance.function_code, + thrower); } if (thrower->error()) return nothing; @@ -788,61 +962,74 @@ MaybeHandle<WasmCompiledModule> WasmModule::CompileFunctions( i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast<int>(i), code); + RecordStats(isolate, 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 heap objects for script, module bytes and asm.js offset table to be + // stored in the shared module data. + Handle<Script> script; + Handle<ByteArray> asm_js_offset_table; + if (asm_js_script.is_null()) { + script = CreateWasmScript(isolate, wire_bytes); + } else { + script = asm_js_script; + asm_js_offset_table = + isolate->factory()->NewByteArray(asm_js_offset_table_bytes.length()); + asm_js_offset_table->copy_in(0, asm_js_offset_table_bytes.start(), + asm_js_offset_table_bytes.length()); + } + // TODO(wasm): only save the sections necessary to deserialize a + // {WasmModule}. E.g. function bodies could be omitted. + Handle<String> module_bytes = + factory->NewStringFromOneByte(wire_bytes.module_bytes, TENURED) + .ToHandleChecked(); + DCHECK(module_bytes->IsSeqOneByteString()); + + // Create the shared module data. + // TODO(clemensh): For the same module (same bytes / same hash), we should + // only have one WasmSharedModuleData. Otherwise, we might only set + // breakpoints on a (potentially empty) subset of the instances. + + Handle<WasmSharedModuleData> shared = WasmSharedModuleData::New( + isolate, module_wrapper, Handle<SeqOneByteString>::cast(module_bytes), + script, asm_js_offset_table); // 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<WasmCompiledModule> ret = - WasmCompiledModule::New(isolate, module_wrapper); + Handle<WasmCompiledModule> ret = WasmCompiledModule::New(isolate, shared); + ret->set_num_imported_functions(num_imported_functions); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); + ret->set_signature_tables(signature_tables); ret->set_empty_function_tables(function_tables); } + // If we created a wasm script, finish it now and make it public to the + // debugger. + if (asm_js_script.is_null()) { + script->set_wasm_compiled_module(*ret); + isolate->debug()->OnAfterCompile(script); + } + // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle<Code> wasm_code = code_table->GetValueChecked<Code>(isolate, exp.index); - Handle<Code> wrapper_code = compiler::CompileJSToWasmWrapper( - isolate, &module_env, wasm_code, exp.index); + Handle<Code> wrapper_code = + compiler::CompileJSToWasmWrapper(isolate, this, wasm_code, exp.index); int export_index = static_cast<int>(functions.size() + func_index); code_table->set(export_index, *wrapper_code); + RecordStats(isolate, *wrapper_code); func_index++; } - { - // TODO(wasm): only save the sections necessary to deserialize a - // {WasmModule}. E.g. function bodies could be omitted. - size_t module_bytes_len = module_end - module_start; - DCHECK_LE(module_bytes_len, static_cast<size_t>(kMaxInt)); - Vector<const uint8_t> module_bytes_vec(module_start, - static_cast<int>(module_bytes_len)); - Handle<String> module_bytes_string = - factory->NewStringFromOneByte(module_bytes_vec, TENURED) - .ToHandleChecked(); - DCHECK(module_bytes_string->IsSeqOneByteString()); - ret->set_module_bytes(Handle<SeqOneByteString>::cast(module_bytes_string)); - } - return ret; } @@ -876,7 +1063,7 @@ static Handle<Code> UnwrapImportWrapper(Handle<Object> target) { code = handle(target); } } - DCHECK(found == 1); + DCHECK_EQ(1, found); return code; } @@ -884,8 +1071,8 @@ static Handle<Code> CompileImportWrapper(Isolate* isolate, int index, FunctionSig* sig, Handle<JSReceiver> target, Handle<String> module_name, - MaybeHandle<String> import_name) { - Handle<Code> code; + MaybeHandle<String> import_name, + ModuleOrigin origin) { WasmFunction* other_func = GetWasmFunctionForImportWrapper(isolate, target); if (other_func) { if (sig->Equals(other_func->sig)) { @@ -898,7 +1085,7 @@ static Handle<Code> CompileImportWrapper(Isolate* isolate, int index, } else { // Signature mismatch. Compile a new wrapper for the new signature. return compiler::CompileWasmToJSWrapper(isolate, target, sig, index, - module_name, import_name); + module_name, import_name, origin); } } @@ -906,11 +1093,13 @@ static void UpdateDispatchTablesInternal(Isolate* isolate, Handle<FixedArray> dispatch_tables, int index, WasmFunction* function, Handle<Code> code) { - DCHECK_EQ(0, dispatch_tables->length() % 3); - for (int i = 0; i < dispatch_tables->length(); i += 3) { + DCHECK_EQ(0, dispatch_tables->length() % 4); + for (int i = 0; i < dispatch_tables->length(); i += 4) { int table_index = Smi::cast(dispatch_tables->get(i + 1))->value(); - Handle<FixedArray> dispatch_table( + Handle<FixedArray> function_table( FixedArray::cast(dispatch_tables->get(i + 2)), isolate); + Handle<FixedArray> signature_table( + FixedArray::cast(dispatch_tables->get(i + 3)), isolate); if (function) { // TODO(titzer): the signature might need to be copied to avoid // a dangling pointer in the signature map. @@ -919,12 +1108,12 @@ static void UpdateDispatchTablesInternal(Isolate* isolate, int sig_index = static_cast<int>( instance->module()->function_tables[table_index].map.FindOrInsert( function->sig)); - dispatch_table->set(index, Smi::FromInt(sig_index)); - dispatch_table->set(index + (dispatch_table->length() / 2), *code); + signature_table->set(index, Smi::FromInt(sig_index)); + function_table->set(index, *code); } else { Code* code = nullptr; - dispatch_table->set(index, Smi::FromInt(-1)); - dispatch_table->set(index + (dispatch_table->length() / 2), code); + signature_table->set(index, Smi::FromInt(-1)); + function_table->set(index, code); } } } @@ -949,17 +1138,27 @@ void wasm::UpdateDispatchTables(Isolate* isolate, class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, - Handle<JSObject> module_object, Handle<JSReceiver> ffi, - Handle<JSArrayBuffer> memory) + Handle<WasmModuleObject> module_object, + Handle<JSReceiver> ffi, Handle<JSArrayBuffer> memory) : isolate_(isolate), + module_(module_object->compiled_module()->module()), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. - MaybeHandle<JSObject> Build() { - MaybeHandle<JSObject> nothing; + MaybeHandle<WasmInstanceObject> Build() { + MaybeHandle<WasmInstanceObject> nothing; + + // Check that an imports argument was provided, if the module requires it. + // No point in continuing otherwise. + if (!module_->import_table.empty() && ffi_.is_null()) { + thrower_->TypeError( + "Imports argument must be present and must be an object"); + return nothing; + } + HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); @@ -982,8 +1181,7 @@ class WasmInstanceBuilder { Handle<WasmCompiledModule> original; { DisallowHeapAllocation no_gc; - original = handle( - WasmCompiledModule::cast(module_object_->GetInternalField(0))); + original = handle(module_object_->compiled_module()); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); @@ -1032,10 +1230,8 @@ class WasmInstanceBuilder { compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); + compiled_module_->set_native_context(isolate_->native_context()); } - module_ = reinterpret_cast<WasmModuleWrapper*>( - *compiled_module_->module_wrapper()) - ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. @@ -1049,8 +1245,9 @@ class WasmInstanceBuilder { MaybeHandle<JSArrayBuffer> old_globals; uint32_t globals_size = module_->globals_size; if (globals_size > 0) { + const bool enable_guard_regions = false; Handle<JSArrayBuffer> global_buffer = - NewArrayBuffer(isolate_, globals_size); + NewArrayBuffer(isolate_, globals_size, enable_guard_regions); globals_ = global_buffer; if (globals_.is_null()) { thrower_->RangeError("Out of memory: wasm globals"); @@ -1072,9 +1269,9 @@ class WasmInstanceBuilder { static_cast<int>(module_->function_tables.size()); table_instances_.reserve(module_->function_tables.size()); for (int index = 0; index < function_table_count; ++index) { - table_instances_.push_back({Handle<WasmTableObject>::null(), - Handle<FixedArray>::null(), - Handle<FixedArray>::null()}); + table_instances_.push_back( + {Handle<WasmTableObject>::null(), Handle<FixedArray>::null(), + Handle<FixedArray>::null(), Handle<FixedArray>::null()}); } //-------------------------------------------------------------------------- @@ -1089,6 +1286,11 @@ class WasmInstanceBuilder { InitGlobals(); //-------------------------------------------------------------------------- + // Set up the indirect function tables for the new instance. + //-------------------------------------------------------------------------- + if (function_table_count > 0) InitializeTables(code_table, instance); + + //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle<JSArrayBuffer> old_memory; @@ -1099,17 +1301,51 @@ class WasmInstanceBuilder { if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); + + DCHECK_IMPLIES(EnableGuardRegions(), module_->origin == kAsmJsOrigin || + memory_->has_guard_region()); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } + //-------------------------------------------------------------------------- + // Check that indirect function table segments are within bounds. + //-------------------------------------------------------------------------- + for (WasmTableInit& table_init : module_->table_inits) { + DCHECK(table_init.table_index < table_instances_.size()); + uint32_t base = EvalUint32InitExpr(table_init.offset); + uint32_t table_size = + table_instances_[table_init.table_index].function_table->length(); + if (!in_bounds(base, static_cast<uint32_t>(table_init.entries.size()), + table_size)) { + thrower_->LinkError("table initializer is out of bounds"); + return nothing; + } + } + + //-------------------------------------------------------------------------- + // Check that memory segments are within bounds. + //-------------------------------------------------------------------------- + for (WasmDataSegment& seg : module_->data_segments) { + uint32_t base = EvalUint32InitExpr(seg.dest_addr); + uint32_t mem_size = memory_.is_null() + ? 0 : static_cast<uint32_t>(memory_->byte_length()->Number()); + if (!in_bounds(base, seg.source_size, mem_size)) { + thrower_->LinkError("data segment is out of bounds"); + return nothing; + } + } + + //-------------------------------------------------------------------------- + // Initialize memory. + //-------------------------------------------------------------------------- if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast<Address>(memory_->backing_store()); uint32_t mem_size = static_cast<uint32_t>(memory_->byte_length()->Number()); - LoadDataSegments(mem_start, mem_size); + if (!LoadDataSegments(mem_start, mem_size)) return nothing; uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = @@ -1117,11 +1353,10 @@ class WasmInstanceBuilder { ? static_cast<Address>( compiled_module_->memory()->backing_store()) : nullptr; - RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, - old_mem_size, mem_size); + RelocateMemoryReferencesInCode( + code_table, module_->num_imported_functions, old_mem_start, mem_start, + old_mem_size, mem_size); compiled_module_->set_memory(memory_); - } else { - LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- @@ -1144,21 +1379,61 @@ class WasmInstanceBuilder { //-------------------------------------------------------------------------- // Set up the exports object for the new instance. //-------------------------------------------------------------------------- - ProcessExports(code_table, instance); + ProcessExports(code_table, instance, compiled_module_); //-------------------------------------------------------------------------- - // Set up the indirect function tables for the new instance. + // Add instance to Memory object //-------------------------------------------------------------------------- - if (function_table_count > 0) InitializeTables(code_table, instance); - - 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); + DCHECK(wasm::IsWasmInstance(*instance)); + if (instance->has_memory_object()) { + instance->memory_object()->AddInstance(isolate_, instance); } + //-------------------------------------------------------------------------- + // Initialize the indirect function tables. + //-------------------------------------------------------------------------- + if (function_table_count > 0) LoadTableSegments(code_table, instance); + + // Patch new call sites and the context. + PatchDirectCallsAndContext(code_table, compiled_module_, module_, + num_imported_functions); + FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- + // Unpack and notify signal handler of protected instructions. + //-------------------------------------------------------------------------- + { + for (int i = 0; i < code_table->length(); ++i) { + Handle<Code> code = code_table->GetValueChecked<Code>(isolate_, i); + + if (code->kind() != Code::WASM_FUNCTION) { + continue; + } + + FixedArray* protected_instructions = code->protected_instructions(); + DCHECK(protected_instructions != nullptr); + Zone zone(isolate_->allocator(), "Wasm Module"); + ZoneVector<trap_handler::ProtectedInstructionData> unpacked(&zone); + for (int i = 0; i < protected_instructions->length(); + i += Code::kTrapDataSize) { + trap_handler::ProtectedInstructionData data; + data.instr_offset = + protected_instructions + ->GetValueChecked<Smi>(isolate_, i + Code::kTrapCodeOffset) + ->value(); + data.landing_offset = + protected_instructions + ->GetValueChecked<Smi>(isolate_, i + Code::kTrapLandingOffset) + ->value(); + unpacked.emplace_back(data); + } + // TODO(eholk): Register the protected instruction information once the + // trap handler is in place. + } + } + + //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { @@ -1174,7 +1449,7 @@ class WasmInstanceBuilder { // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. - original = handle(owner.ToHandleChecked()->get_compiled_module()); + original = handle(owner.ToHandleChecked()->compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. @@ -1194,30 +1469,20 @@ class WasmInstanceBuilder { v8::WeakCallbackType::kFinalizer); } } - - DCHECK(wasm::IsWasmInstance(*instance)); - if (instance->has_memory_object()) { - instance->get_memory_object()->AddInstance(*instance); - } - //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); - ModuleEnv module_env; - module_env.module = module_; - module_env.instance = nullptr; - module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle<Code> startup_code = code_table->GetValueChecked<Code>(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle<Code> wrapper_code = compiler::CompileJSToWasmWrapper( - isolate_, &module_env, startup_code, start_index); + isolate_, module_, startup_code, start_index); Handle<WasmExportedFunction> startup_fct = WasmExportedFunction::New( - isolate_, instance, factory->InternalizeUtf8String("start"), - wrapper_code, static_cast<int>(sig->parameter_count()), start_index); + isolate_, instance, MaybeHandle<String>(), start_index, + static_cast<int>(sig->parameter_count()), wrapper_code); RecordStats(isolate_, *startup_code); // Call the JS function. Handle<Object> undefined = factory->undefined_value(); @@ -1237,7 +1502,7 @@ class WasmInstanceBuilder { DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); - TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); + TRACE_CHAIN(module_object_->compiled_module()); return instance; } @@ -1246,13 +1511,14 @@ class WasmInstanceBuilder { struct TableInstance { Handle<WasmTableObject> table_object; // WebAssembly.Table instance Handle<FixedArray> js_wrappers; // JSFunctions exported - Handle<FixedArray> dispatch_table; // internal (code, sig) pairs + Handle<FixedArray> function_table; // internal code array + Handle<FixedArray> signature_table; // internal sig array }; Isolate* isolate_; - WasmModule* module_; + WasmModule* const module_; ErrorThrower* thrower_; - Handle<JSObject> module_object_; + Handle<WasmModuleObject> module_object_; Handle<JSReceiver> ffi_; Handle<JSArrayBuffer> memory_; Handle<JSArrayBuffer> globals_; @@ -1260,58 +1526,49 @@ class WasmInstanceBuilder { std::vector<TableInstance> table_instances_; std::vector<Handle<JSFunction>> js_wrappers_; - // Helper routine to print out errors with imports (FFI). - MaybeHandle<JSFunction> ReportFFIError(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_->TypeError( - "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_->TypeError("Import #%d module=\"%.*s\" error: %s", index, - module_name->length(), module_name->ToCString().get(), - error); - } - thrower_->TypeError("Import "); - return MaybeHandle<JSFunction>(); + // Helper routines to print out errors with imports. + void ReportLinkError(const char* error, uint32_t index, + Handle<String> module_name, Handle<String> import_name) { + thrower_->LinkError( + "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, + module_name->length(), module_name->ToCString().get(), + import_name->length(), import_name->ToCString().get(), error); + } + + MaybeHandle<Object> ReportLinkError(const char* error, uint32_t index, + Handle<String> module_name) { + thrower_->LinkError("Import #%d module=\"%.*s\" error: %s", index, + module_name->length(), module_name->ToCString().get(), + error); + return MaybeHandle<Object>(); } // Look up an import value in the {ffi_} object. MaybeHandle<Object> LookupImport(uint32_t index, Handle<String> module_name, - MaybeHandle<String> import_name) { - if (ffi_.is_null()) { - return ReportFFIError("FFI is not an object", index, module_name, - import_name); - } + Handle<String> import_name) { + // We pre-validated in the js-api layer that the ffi object is present, and + // a JSObject, if the module has imports. + DCHECK(!ffi_.is_null()); // Look up the module first. - MaybeHandle<Object> result = Object::GetProperty(ffi_, module_name); + MaybeHandle<Object> result = + Object::GetPropertyOrElement(ffi_, module_name); if (result.is_null()) { - return ReportFFIError("module not found", index, module_name, - import_name); + return ReportLinkError("module not found", index, module_name); } Handle<Object> module = result.ToHandleChecked(); - if (!import_name.is_null()) { - // Look up the value in the module. - if (!module->IsJSReceiver()) { - return ReportFFIError("module is not an object or function", index, - module_name, import_name); - } + // Look up the value in the module. + if (!module->IsJSReceiver()) { + return ReportLinkError("module is not an object or function", index, + module_name); + } - result = Object::GetProperty(module, import_name.ToHandleChecked()); - if (result.is_null()) { - return ReportFFIError("import not found", index, module_name, - import_name); - } - } else { - // No function specified. Use the "default export". - result = module; + result = Object::GetPropertyOrElement(module, import_name); + if (result.is_null()) { + ReportLinkError("import not found", index, module_name, import_name); + return MaybeHandle<JSFunction>(); } return result; @@ -1331,26 +1588,27 @@ class WasmInstanceBuilder { } } + bool in_bounds(uint32_t offset, uint32_t size, uint32_t upper) { + return offset + size <= upper && offset + size >= offset; + } + // Load data segments into the memory. - void LoadDataSegments(Address mem_addr, size_t mem_size) { - Handle<SeqOneByteString> module_bytes = compiled_module_->module_bytes(); + bool LoadDataSegments(Address mem_addr, size_t mem_size) { + Handle<SeqOneByteString> module_bytes(compiled_module_->module_bytes(), + isolate_); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); - if (dest_offset >= mem_size || source_size >= mem_size || - dest_offset > (mem_size - source_size)) { - thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 - ") does not fit into memory (size = %" PRIuS ")", - dest_offset, source_size, mem_size); - return; - } + DCHECK(in_bounds(dest_offset, source_size, + static_cast<uint32_t>(mem_size))); byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast<const byte*>( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } + return true; } void WriteGlobalValue(WasmGlobal& global, Handle<Object> value) { @@ -1365,17 +1623,17 @@ class WasmInstanceBuilder { TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { - case kAstI32: + case kWasmI32: *GetRawGlobalPtr<int32_t>(global) = static_cast<int32_t>(num); break; - case kAstI64: + case kWasmI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; - case kAstF32: + case kWasmF32: *GetRawGlobalPtr<float>(global) = static_cast<float>(num); break; - case kAstF64: + case kWasmF64: *GetRawGlobalPtr<double>(global) = static_cast<double>(num); break; default: @@ -1393,39 +1651,43 @@ class WasmInstanceBuilder { for (int index = 0; index < static_cast<int>(module_->import_table.size()); ++index) { WasmImport& import = module_->import_table[index]; - Handle<String> module_name = - ExtractStringFromModuleBytes(isolate_, compiled_module_, - import.module_name_offset, - import.module_name_length) - .ToHandleChecked(); - Handle<String> function_name = Handle<String>::null(); - if (import.field_name_length > 0) { - function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, - import.field_name_offset, - import.field_name_length) - .ToHandleChecked(); - } + + Handle<String> module_name; + MaybeHandle<String> maybe_module_name = + WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate_, compiled_module_, import.module_name_offset, + import.module_name_length); + if (!maybe_module_name.ToHandle(&module_name)) return -1; + + Handle<String> import_name; + MaybeHandle<String> maybe_import_name = + WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate_, compiled_module_, import.field_name_offset, + import.field_name_length); + if (!maybe_import_name.ToHandle(&import_name)) return -1; MaybeHandle<Object> result = - LookupImport(index, module_name, function_name); + LookupImport(index, module_name, import_name); if (thrower_->error()) return -1; + Handle<Object> value = result.ToHandleChecked(); switch (import.kind) { case kExternalFunction: { // Function imports must be callable. - Handle<Object> function = result.ToHandleChecked(); - if (!function->IsCallable()) { - ReportFFIError("function import requires a callable", index, - module_name, function_name); + if (!value->IsCallable()) { + ReportLinkError("function import requires a callable", index, + module_name, import_name); return -1; } Handle<Code> import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, - Handle<JSReceiver>::cast(function), module_name, function_name); + Handle<JSReceiver>::cast(value), module_name, import_name, + module_->origin); if (import_wrapper.is_null()) { - ReportFFIError("imported function does not match the expected type", - index, module_name, function_name); + ReportLinkError( + "imported function does not match the expected type", index, + module_name, import_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); @@ -1434,10 +1696,9 @@ class WasmInstanceBuilder { break; } case kExternalTable: { - Handle<Object> value = result.ToHandleChecked(); if (!WasmJs::IsWasmTableObject(isolate_, value)) { - ReportFFIError("table import requires a WebAssembly.Table", index, - module_name, function_name); + ReportLinkError("table import requires a WebAssembly.Table", index, + module_name, import_name); return -1; } WasmIndirectFunctionTable& table = @@ -1445,23 +1706,43 @@ class WasmInstanceBuilder { TableInstance& table_instance = table_instances_[num_imported_tables]; table_instance.table_object = Handle<WasmTableObject>::cast(value); table_instance.js_wrappers = Handle<FixedArray>( - table_instance.table_object->get_functions(), isolate_); - - // TODO(titzer): import table size must match exactly for now. - int table_size = table_instance.js_wrappers->length(); - if (table_size != static_cast<int>(table.min_size)) { - thrower_->TypeError( - "table import %d is wrong size (%d), expected %u", index, - table_size, table.min_size); + table_instance.table_object->functions(), isolate_); + + int imported_cur_size = table_instance.js_wrappers->length(); + if (imported_cur_size < static_cast<int>(table.min_size)) { + thrower_->LinkError( + "table import %d is smaller than minimum %d, got %u", index, + table.min_size, imported_cur_size); return -1; } - // Allocate a new dispatch table. - table_instance.dispatch_table = - isolate_->factory()->NewFixedArray(table_size * 2); - for (int i = 0; i < table_size * 2; ++i) { - table_instance.dispatch_table->set(i, - Smi::FromInt(kInvalidSigIndex)); + if (table.has_max) { + int64_t imported_max_size = + table_instance.table_object->maximum_length(); + if (imported_max_size < 0) { + thrower_->LinkError( + "table import %d has no maximum length, expected %d", index, + table.max_size); + return -1; + } + if (imported_max_size > table.max_size) { + thrower_->LinkError( + "table import %d has maximum larger than maximum %d, " + "got %" PRIx64, + index, table.max_size, imported_max_size); + return -1; + } + } + + // Allocate a new dispatch table and signature table. + int table_size = imported_cur_size; + table_instance.function_table = + isolate_->factory()->NewFixedArray(table_size); + table_instance.signature_table = + isolate_->factory()->NewFixedArray(table_size); + for (int i = 0; i < table_size; ++i) { + table_instance.signature_table->set(i, + Smi::FromInt(kInvalidSigIndex)); } // Initialize the dispatch table with the (foreign) JS functions // that are already in the table. @@ -1471,43 +1752,70 @@ class WasmInstanceBuilder { WasmFunction* function = GetWasmFunctionForImportWrapper(isolate_, val); if (function == nullptr) { - thrower_->TypeError("table import %d[%d] is not a WASM function", + thrower_->LinkError("table import %d[%d] is not a WASM function", index, i); return -1; } int sig_index = table.map.FindOrInsert(function->sig); - table_instance.dispatch_table->set(i, Smi::FromInt(sig_index)); - table_instance.dispatch_table->set(i + table_size, - *UnwrapImportWrapper(val)); + table_instance.signature_table->set(i, Smi::FromInt(sig_index)); + table_instance.function_table->set(i, *UnwrapImportWrapper(val)); } num_imported_tables++; break; } case kExternalMemory: { - Handle<Object> object = result.ToHandleChecked(); - if (!WasmJs::IsWasmMemoryObject(isolate_, object)) { - ReportFFIError("memory import must be a WebAssembly.Memory object", - index, module_name, function_name); + // Validation should have failed if more than one memory object was + // provided. + DCHECK(!instance->has_memory_object()); + if (!WasmJs::IsWasmMemoryObject(isolate_, value)) { + ReportLinkError("memory import must be a WebAssembly.Memory object", + index, module_name, import_name); return -1; } - auto memory = Handle<WasmMemoryObject>::cast(object); + auto memory = Handle<WasmMemoryObject>::cast(value); + DCHECK(WasmJs::IsWasmMemoryObject(isolate_, memory)); instance->set_memory_object(*memory); - memory_ = Handle<JSArrayBuffer>(memory->get_buffer(), isolate_); + memory_ = Handle<JSArrayBuffer>(memory->buffer(), isolate_); + uint32_t imported_cur_pages = static_cast<uint32_t>( + memory_->byte_length()->Number() / WasmModule::kPageSize); + if (imported_cur_pages < module_->min_mem_pages) { + thrower_->LinkError( + "memory import %d is smaller than maximum %u, got %u", index, + module_->min_mem_pages, imported_cur_pages); + } + int32_t imported_max_pages = memory->maximum_pages(); + if (module_->has_max_mem) { + if (imported_max_pages < 0) { + thrower_->LinkError( + "memory import %d has no maximum limit, expected at most %u", + index, imported_max_pages); + return -1; + } + if (static_cast<uint32_t>(imported_max_pages) > + module_->max_mem_pages) { + thrower_->LinkError( + "memory import %d has larger maximum than maximum %u, got %d", + index, module_->max_mem_pages, imported_max_pages); + return -1; + } + } break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. - Handle<Object> object = result.ToHandleChecked(); - MaybeHandle<Object> number = Object::ToNumber(object); - if (number.is_null()) { - ReportFFIError("global import could not be converted to number", - index, module_name, function_name); + if (module_->globals[import.index].type == kWasmI64) { + ReportLinkError("global import cannot have type i64", index, + module_name, import_name); + return -1; + } + if (!value->IsNumber()) { + ReportLinkError("global import must be a number", index, + module_name, import_name); return -1; } - Handle<Object> val = number.ToHandleChecked(); - WriteGlobalValue(module_->globals[import.index], val); + WriteGlobalValue(module_->globals[import.index], value); break; } default: @@ -1546,7 +1854,7 @@ class WasmInstanceBuilder { module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); - size_t size = (global.type == kAstI64 || global.type == kAstF64) + size_t size = (global.type == kWasmI64 || global.type == kWasmF64) ? sizeof(double) : sizeof(int32_t); memcpy(raw_buffer_ptr(globals_, new_offset), @@ -1565,12 +1873,13 @@ class WasmInstanceBuilder { // Allocate memory for a module instance as a new JSArrayBuffer. Handle<JSArrayBuffer> AllocateMemory(uint32_t min_mem_pages) { - if (min_mem_pages > WasmModule::kV8MaxPages) { + if (min_mem_pages > kV8MaxWasmMemoryPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle<JSArrayBuffer>::null(); } - Handle<JSArrayBuffer> mem_buffer = - NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); + const bool enable_guard_regions = EnableGuardRegions(); + Handle<JSArrayBuffer> mem_buffer = NewArrayBuffer( + isolate_, min_mem_pages * WasmModule::kPageSize, enable_guard_regions); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); @@ -1578,31 +1887,30 @@ class WasmInstanceBuilder { return mem_buffer; } - // Process the exports, creating wrappers for functions, tables, memories, - // and globals. - void ProcessExports(Handle<FixedArray> code_table, - Handle<WasmInstanceObject> instance) { - bool needs_wrappers = module_->num_exported_functions > 0; + bool NeedsWrappers() { + if (module_->num_exported_functions > 0) return true; for (auto table_instance : table_instances_) { - if (!table_instance.js_wrappers.is_null()) { - needs_wrappers = true; - break; - } + if (!table_instance.js_wrappers.is_null()) return true; } for (auto table : module_->function_tables) { - if (table.exported) { - needs_wrappers = true; - break; - } + if (table.exported) return true; } - if (needs_wrappers) { + return false; + } + + // Process the exports, creating wrappers for functions, tables, memories, + // and globals. + void ProcessExports(Handle<FixedArray> code_table, + Handle<WasmInstanceObject> instance, + Handle<WasmCompiledModule> compiled_module) { + if (NeedsWrappers()) { // Fill the table to cache the exported JSFunction wrappers. js_wrappers_.insert(js_wrappers_.begin(), module_->functions.size(), Handle<JSFunction>::null()); } Handle<JSObject> exports_object = instance; - if (module_->export_table.size() > 0 && module_->origin == kWasmOrigin) { + if (module_->origin == kWasmOrigin) { // Create the "exports" object. Handle<JSFunction> object_function = Handle<JSFunction>( isolate_->native_context()->object_function(), isolate_); @@ -1610,38 +1918,68 @@ class WasmInstanceBuilder { isolate_->factory()->NewJSObject(object_function, TENURED); Handle<String> exports_name = isolate_->factory()->InternalizeUtf8String("exports"); - JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); + JSObject::AddProperty(instance, exports_name, exports_object, NONE); } PropertyDescriptor desc; desc.set_writable(false); + desc.set_enumerable(true); - // Process each export in the export table. + // Count up export indexes. int export_index = 0; for (auto exp : module_->export_table) { + if (exp.kind == kExternalFunction) { + ++export_index; + } + } + + // Store weak references to all exported functions. + Handle<FixedArray> weak_exported_functions; + if (compiled_module->has_weak_exported_functions()) { + weak_exported_functions = compiled_module->weak_exported_functions(); + } else { + weak_exported_functions = + isolate_->factory()->NewFixedArray(export_index); + compiled_module->set_weak_exported_functions(weak_exported_functions); + } + DCHECK_EQ(export_index, weak_exported_functions->length()); + + // Process each export in the export table (go in reverse so asm.js + // can skip duplicates). + for (auto exp : base::Reversed(module_->export_table)) { Handle<String> name = - ExtractStringFromModuleBytes(isolate_, compiled_module_, - exp.name_offset, exp.name_length) + WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = - static_cast<int>(module_->functions.size() + export_index); + static_cast<int>(module_->functions.size() + --export_index); Handle<JSFunction> js_function = js_wrappers_[exp.index]; if (js_function.is_null()) { // Wrap the exported code as a JSFunction. Handle<Code> export_code = code_table->GetValueChecked<Code>(isolate_, func_index); + MaybeHandle<String> func_name; + if (module_->origin == kAsmJsOrigin) { + // For modules arising from asm.js, honor the names section. + func_name = WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate_, compiled_module_, function.name_offset, + function.name_length) + .ToHandleChecked(); + } js_function = WasmExportedFunction::New( - isolate_, instance, name, export_code, - static_cast<int>(function.sig->parameter_count()), - function.func_index); + isolate_, instance, func_name, function.func_index, + static_cast<int>(function.sig->parameter_count()), export_code); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); - export_index++; + Handle<WeakCell> weak_export = + isolate_->factory()->NewWeakCell(js_function); + DCHECK_GT(weak_exported_functions->length(), export_index); + weak_exported_functions->set(export_index, *weak_export); break; } case kExternalTable: { @@ -1651,7 +1989,7 @@ class WasmInstanceBuilder { module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = - table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; + table.has_max ? table.max_size : kV8MaxWasmTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } @@ -1663,15 +2001,16 @@ class WasmInstanceBuilder { Handle<WasmMemoryObject> memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. - Handle<JSArrayBuffer> buffer(instance->get_memory_buffer(), - isolate_); + Handle<JSArrayBuffer> buffer(instance->memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { - memory_object = Handle<WasmMemoryObject>( - instance->get_memory_object(), isolate_); + memory_object = + Handle<WasmMemoryObject>(instance->memory_object(), isolate_); + DCHECK(WasmJs::IsWasmMemoryObject(isolate_, memory_object)); + memory_object->ResetInstancesLink(isolate_); } desc.set_value(memory_object); @@ -1682,15 +2021,19 @@ class WasmInstanceBuilder { WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { - case kAstI32: + case kWasmI32: num = *GetRawGlobalPtr<int32_t>(global); break; - case kAstF32: + case kWasmF32: num = *GetRawGlobalPtr<float>(global); break; - case kAstF64: + case kWasmF64: num = *GetRawGlobalPtr<double>(global); break; + case kWasmI64: + thrower_->LinkError( + "export of globals of type I64 is not allowed."); + break; default: UNREACHABLE(); } @@ -1702,42 +2045,99 @@ class WasmInstanceBuilder { break; } + // Skip duplicates for asm.js. + if (module_->origin == kAsmJsOrigin) { + v8::Maybe<bool> status = + JSReceiver::HasOwnProperty(exports_object, name); + if (status.FromMaybe(false)) { + continue; + } + } v8::Maybe<bool> status = JSReceiver::DefineOwnProperty( isolate_, exports_object, name, &desc, Object::THROW_ON_ERROR); if (!status.IsJust()) { - thrower_->TypeError("export of %.*s failed.", name->length(), + thrower_->LinkError("export of %.*s failed.", name->length(), name->ToCString().get()); return; } } + + if (module_->origin == kWasmOrigin) { + v8::Maybe<bool> success = JSReceiver::SetIntegrityLevel( + exports_object, FROZEN, Object::DONT_THROW); + DCHECK(success.FromMaybe(false)); + USE(success); + } } void InitializeTables(Handle<FixedArray> code_table, Handle<WasmInstanceObject> instance) { - Handle<FixedArray> old_function_tables = - compiled_module_->function_tables(); int function_table_count = static_cast<int>(module_->function_tables.size()); Handle<FixedArray> new_function_tables = isolate_->factory()->NewFixedArray(function_table_count); + Handle<FixedArray> new_signature_tables = + isolate_->factory()->NewFixedArray(function_table_count); for (int index = 0; index < function_table_count; ++index) { WasmIndirectFunctionTable& table = module_->function_tables[index]; TableInstance& table_instance = table_instances_[index]; int table_size = static_cast<int>(table.min_size); - if (table_instance.dispatch_table.is_null()) { + if (table_instance.function_table.is_null()) { // Create a new dispatch table if necessary. - table_instance.dispatch_table = - isolate_->factory()->NewFixedArray(table_size * 2); + table_instance.function_table = + isolate_->factory()->NewFixedArray(table_size); + table_instance.signature_table = + isolate_->factory()->NewFixedArray(table_size); for (int i = 0; i < table_size; ++i) { // Fill the table with invalid signature indexes so that // uninitialized entries will always fail the signature check. - table_instance.dispatch_table->set(i, Smi::FromInt(kInvalidSigIndex)); + table_instance.signature_table->set(i, + Smi::FromInt(kInvalidSigIndex)); + } + } else { + // Table is imported, patch table bounds check + DCHECK(table_size <= table_instance.function_table->length()); + if (table_size < table_instance.function_table->length()) { + RelocateTableSizeReferences(code_table, table_size, + table_instance.function_table->length()); } } new_function_tables->set(static_cast<int>(index), - *table_instance.dispatch_table); + *table_instance.function_table); + new_signature_tables->set(static_cast<int>(index), + *table_instance.signature_table); + } + + // Patch all code that has references to the old indirect tables. + Handle<FixedArray> old_function_tables = + compiled_module_->function_tables(); + Handle<FixedArray> old_signature_tables = + compiled_module_->signature_tables(); + for (int i = 0; i < code_table->length(); ++i) { + if (!code_table->get(i)->IsCode()) continue; + Handle<Code> code(Code::cast(code_table->get(i)), isolate_); + for (int j = 0; j < function_table_count; ++j) { + ReplaceReferenceInCode( + code, Handle<Object>(old_function_tables->get(j), isolate_), + Handle<Object>(new_function_tables->get(j), isolate_)); + ReplaceReferenceInCode( + code, Handle<Object>(old_signature_tables->get(j), isolate_), + Handle<Object>(new_signature_tables->get(j), isolate_)); + } + } + compiled_module_->set_function_tables(new_function_tables); + compiled_module_->set_signature_tables(new_signature_tables); + } + + void LoadTableSegments(Handle<FixedArray> code_table, + Handle<WasmInstanceObject> instance) { + int function_table_count = + static_cast<int>(module_->function_tables.size()); + for (int index = 0; index < function_table_count; ++index) { + WasmIndirectFunctionTable& table = module_->function_tables[index]; + TableInstance& table_instance = table_instances_[index]; Handle<FixedArray> all_dispatch_tables; if (!table_instance.table_object.is_null()) { @@ -1745,28 +2145,24 @@ class WasmInstanceBuilder { all_dispatch_tables = WasmTableObject::AddDispatchTable( isolate_, table_instance.table_object, Handle<WasmInstanceObject>::null(), index, - Handle<FixedArray>::null()); + Handle<FixedArray>::null(), Handle<FixedArray>::null()); } // TODO(titzer): this does redundant work if there are multiple tables, // since initializations are not sorted by table index. for (auto table_init : module_->table_inits) { uint32_t base = EvalUint32InitExpr(table_init.offset); - if (base > static_cast<uint32_t>(table_size) || - (base + table_init.entries.size() > - static_cast<uint32_t>(table_size))) { - thrower_->CompileError("table initializer is out of bounds"); - continue; - } + DCHECK(in_bounds(base, static_cast<uint32_t>(table_init.entries.size()), + table_instance.function_table->length())); for (int i = 0; i < static_cast<int>(table_init.entries.size()); ++i) { uint32_t func_index = table_init.entries[i]; WasmFunction* function = &module_->functions[func_index]; int table_index = static_cast<int>(i + base); int32_t sig_index = table.map.Find(function->sig); DCHECK_GE(sig_index, 0); - table_instance.dispatch_table->set(table_index, - Smi::FromInt(sig_index)); - table_instance.dispatch_table->set(table_index + table_size, + table_instance.signature_table->set(table_index, + Smi::FromInt(sig_index)); + table_instance.function_table->set(table_index, code_table->get(func_index)); if (!all_dispatch_tables.is_null()) { @@ -1783,19 +2179,22 @@ class WasmInstanceBuilder { temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; - ModuleEnv module_env; - module_env.module = module_; - module_env.instance = &temp_instance; - module_env.origin = module_->origin; - Handle<Code> wrapper_code = compiler::CompileJSToWasmWrapper( - isolate_, &module_env, wasm_code, func_index); + isolate_, module_, wasm_code, func_index); + MaybeHandle<String> func_name; + if (module_->origin == kAsmJsOrigin) { + // For modules arising from asm.js, honor the names section. + func_name = + WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate_, compiled_module_, function->name_offset, + function->name_length) + .ToHandleChecked(); + } Handle<WasmExportedFunction> js_function = WasmExportedFunction::New( - isolate_, instance, isolate_->factory()->empty_string(), - wrapper_code, + isolate_, instance, func_name, func_index, static_cast<int>(function->sig->parameter_count()), - func_index); + wrapper_code); js_wrappers_[func_index] = js_function; } table_instance.js_wrappers->set(table_index, @@ -1814,115 +2213,57 @@ class WasmInstanceBuilder { // Add the new dispatch table to the WebAssembly.Table object. all_dispatch_tables = WasmTableObject::AddDispatchTable( isolate_, table_instance.table_object, instance, index, - table_instance.dispatch_table); + table_instance.function_table, table_instance.signature_table); } } - // Patch all code that has references to the old indirect tables. - for (int i = 0; i < code_table->length(); ++i) { - if (!code_table->get(i)->IsCode()) continue; - Handle<Code> code(Code::cast(code_table->get(i)), isolate_); - for (int j = 0; j < function_table_count; ++j) { - ReplaceReferenceInCode( - code, Handle<Object>(old_function_tables->get(j), isolate_), - Handle<Object>(new_function_tables->get(j), isolate_)); - } - } - compiled_module_->set_function_tables(new_function_tables); } }; // Instantiates a WASM module, creating a WebAssembly.Instance from a // WebAssembly.Module. -MaybeHandle<JSObject> WasmModule::Instantiate(Isolate* isolate, - ErrorThrower* thrower, - Handle<JSObject> wasm_module, - Handle<JSReceiver> ffi, - Handle<JSArrayBuffer> memory) { +MaybeHandle<WasmInstanceObject> WasmModule::Instantiate( + Isolate* isolate, ErrorThrower* thrower, + Handle<WasmModuleObject> wasm_module, Handle<JSReceiver> ffi, + Handle<JSArrayBuffer> memory) { WasmInstanceBuilder builder(isolate, thrower, wasm_module, ffi, memory); return builder.Build(); } -Handle<String> wasm::GetWasmFunctionName(Isolate* isolate, - Handle<Object> instance_or_undef, - uint32_t func_index) { - if (!instance_or_undef->IsUndefined(isolate)) { - Handle<WasmCompiledModule> compiled_module( - Handle<WasmInstanceObject>::cast(instance_or_undef) - ->get_compiled_module()); - MaybeHandle<String> maybe_name = - WasmCompiledModule::GetFunctionName(compiled_module, func_index); - if (!maybe_name.is_null()) return maybe_name.ToHandleChecked(); - } - return isolate->factory()->NewStringFromStaticChars("<WASM UNNAMED>"); -} - bool wasm::IsWasmInstance(Object* object) { return WasmInstanceObject::IsWasmInstanceObject(object); } -WasmCompiledModule* wasm::GetCompiledModule(Object* object) { - return WasmInstanceObject::cast(object)->get_compiled_module(); -} - -bool wasm::WasmIsAsmJs(Object* instance, Isolate* isolate) { - if (instance->IsUndefined(isolate)) return false; - DCHECK(IsWasmInstance(instance)); - WasmCompiledModule* compiled_module = - GetCompiledModule(JSObject::cast(instance)); - DCHECK_EQ(compiled_module->has_asm_js_offset_tables(), - compiled_module->script()->type() == Script::TYPE_NORMAL); - return compiled_module->has_asm_js_offset_tables(); -} - Handle<Script> wasm::GetScript(Handle<JSObject> instance) { - DCHECK(IsWasmInstance(*instance)); - WasmCompiledModule* compiled_module = GetCompiledModule(*instance); - DCHECK(compiled_module->has_script()); - return compiled_module->script(); -} - -int wasm::GetAsmWasmSourcePosition(Handle<JSObject> instance, int func_index, - int byte_offset) { - return WasmDebugInfo::GetAsmJsSourcePosition(GetDebugInfo(instance), - func_index, byte_offset); -} - -Handle<SeqOneByteString> wasm::GetWasmBytes(Handle<JSObject> object) { - return Handle<WasmInstanceObject>::cast(object) - ->get_compiled_module() - ->module_bytes(); -} - -Handle<WasmDebugInfo> wasm::GetDebugInfo(Handle<JSObject> object) { - auto instance = Handle<WasmInstanceObject>::cast(object); - if (instance->has_debug_info()) { - Handle<WasmDebugInfo> info(instance->get_debug_info(), - instance->GetIsolate()); - return info; - } - Handle<WasmDebugInfo> new_info = WasmDebugInfo::New(instance); - instance->set_debug_info(*new_info); - return new_info; + WasmCompiledModule* compiled_module = + WasmInstanceObject::cast(*instance)->compiled_module(); + return handle(compiled_module->script()); } -int wasm::GetNumberOfFunctions(Handle<JSObject> object) { - return static_cast<int>( - Handle<WasmInstanceObject>::cast(object)->module()->functions.size()); +bool wasm::IsWasmCodegenAllowed(Isolate* isolate, Handle<Context> context) { + return isolate->allow_code_gen_callback() == nullptr || + isolate->allow_code_gen_callback()(v8::Utils::ToLocal(context)); } // TODO(clemensh): origin can be inferred from asm_js_script; remove it. MaybeHandle<WasmModuleObject> wasm::CreateModuleObjectFromBytes( Isolate* isolate, const byte* start, const byte* end, ErrorThrower* thrower, ModuleOrigin origin, Handle<Script> asm_js_script, - const byte* asm_js_offset_tables_start, - const byte* asm_js_offset_tables_end) { + Vector<const byte> asm_js_offset_table_bytes) { MaybeHandle<WasmModuleObject> nothing; + + if (origin != kAsmJsOrigin && + !IsWasmCodegenAllowed(isolate, isolate->native_context())) { + thrower->CompileError("Wasm code generation disallowed in this context"); + return nothing; + } + ModuleResult result = DecodeWasmModule(isolate, start, end, false, origin); if (result.failed()) { if (result.val) delete result.val; thrower->CompileFailed("Wasm decoding failed", result); return nothing; } + // The {module_wrapper} will take ownership of the {WasmModule} object, // and it will be destroyed when the GC reclaims the wrapper object. Handle<WasmModuleWrapper> module_wrapper = @@ -1930,61 +2271,15 @@ MaybeHandle<WasmModuleObject> wasm::CreateModuleObjectFromBytes( // Compile the functions of the module, producing a compiled module. MaybeHandle<WasmCompiledModule> maybe_compiled_module = - result.val->CompileFunctions(isolate, module_wrapper, thrower); + result.val->CompileFunctions(isolate, module_wrapper, thrower, + ModuleWireBytes(start, end), asm_js_script, + asm_js_offset_table_bytes); if (maybe_compiled_module.is_null()) return nothing; Handle<WasmCompiledModule> compiled_module = maybe_compiled_module.ToHandleChecked(); - DCHECK_EQ(origin == kAsmJsOrigin, !asm_js_script.is_null()); - DCHECK(!compiled_module->has_script()); - DCHECK(!compiled_module->has_asm_js_offset_tables()); - if (origin == kAsmJsOrigin) { - // Set script for the asm.js source, and the offset table mapping wasm byte - // offsets to source positions. - compiled_module->set_script(asm_js_script); - size_t offset_tables_len = - asm_js_offset_tables_end - asm_js_offset_tables_start; - DCHECK_GE(static_cast<size_t>(kMaxInt), offset_tables_len); - Handle<ByteArray> offset_tables = - isolate->factory()->NewByteArray(static_cast<int>(offset_tables_len)); - memcpy(offset_tables->GetDataStartAddress(), asm_js_offset_tables_start, - offset_tables_len); - compiled_module->set_asm_js_offset_tables(offset_tables); - } else { - // Create a new Script object representing this wasm module, store it in the - // compiled wasm module, and register it at the debugger. - Handle<Script> script = - isolate->factory()->NewScript(isolate->factory()->empty_string()); - script->set_type(Script::TYPE_WASM); - - DCHECK_GE(kMaxInt, end - start); - int hash = StringHasher::HashSequentialString( - reinterpret_cast<const char*>(start), static_cast<int>(end - start), - kZeroHashSeed); - - const int kBufferSize = 50; - char buffer[kBufferSize]; - int url_chars = SNPrintF(ArrayVector(buffer), "wasm://wasm/%08x", hash); - DCHECK(url_chars >= 0 && url_chars < kBufferSize); - MaybeHandle<String> url_str = isolate->factory()->NewStringFromOneByte( - Vector<const uint8_t>(reinterpret_cast<uint8_t*>(buffer), url_chars), - TENURED); - script->set_source_url(*url_str.ToHandleChecked()); - - int name_chars = SNPrintF(ArrayVector(buffer), "wasm-%08x", hash); - DCHECK(name_chars >= 0 && name_chars < kBufferSize); - MaybeHandle<String> name_str = isolate->factory()->NewStringFromOneByte( - Vector<const uint8_t>(reinterpret_cast<uint8_t*>(buffer), name_chars), - TENURED); - script->set_name(*name_str.ToHandleChecked()); - - script->set_wasm_compiled_module(*compiled_module); - compiled_module->set_script(script); - isolate->debug()->OnAfterCompile(script); - } - return WasmModuleObject::New(isolate, compiled_module); } @@ -2000,24 +2295,25 @@ bool wasm::ValidateModuleBytes(Isolate* isolate, const byte* start, return result.ok(); } -MaybeHandle<JSArrayBuffer> wasm::GetInstanceMemory(Isolate* isolate, - Handle<JSObject> object) { +MaybeHandle<JSArrayBuffer> wasm::GetInstanceMemory( + Isolate* isolate, Handle<WasmInstanceObject> object) { auto instance = Handle<WasmInstanceObject>::cast(object); if (instance->has_memory_buffer()) { - return Handle<JSArrayBuffer>(instance->get_memory_buffer(), isolate); + return Handle<JSArrayBuffer>(instance->memory_buffer(), isolate); } return MaybeHandle<JSArrayBuffer>(); } -void SetInstanceMemory(Handle<JSObject> object, JSArrayBuffer* buffer) { +void SetInstanceMemory(Handle<WasmInstanceObject> instance, + JSArrayBuffer* buffer) { DisallowHeapAllocation no_gc; - auto instance = Handle<WasmInstanceObject>::cast(object); instance->set_memory_buffer(buffer); - instance->get_compiled_module()->set_ptr_to_memory(buffer); + instance->compiled_module()->set_ptr_to_memory(buffer); } int32_t wasm::GetInstanceMemorySize(Isolate* isolate, - Handle<JSObject> instance) { + Handle<WasmInstanceObject> instance) { + DCHECK(IsWasmInstance(*instance)); MaybeHandle<JSArrayBuffer> maybe_mem_buffer = GetInstanceMemory(isolate, instance); Handle<JSArrayBuffer> buffer; @@ -2028,92 +2324,222 @@ int32_t wasm::GetInstanceMemorySize(Isolate* isolate, } } -uint32_t GetMaxInstanceMemorySize(Isolate* isolate, - Handle<WasmInstanceObject> instance) { +uint32_t GetMaxInstanceMemoryPages(Isolate* isolate, + Handle<WasmInstanceObject> instance) { if (instance->has_memory_object()) { - Handle<WasmMemoryObject> memory_object(instance->get_memory_object(), - isolate); - - int maximum = memory_object->maximum_pages(); - if (maximum > 0) return static_cast<uint32_t>(maximum); + Handle<WasmMemoryObject> memory_object(instance->memory_object(), isolate); + if (memory_object->has_maximum_pages()) { + uint32_t maximum = static_cast<uint32_t>(memory_object->maximum_pages()); + if (maximum < kV8MaxWasmMemoryPages) return maximum; + } } - uint32_t compiled_max_pages = - instance->get_compiled_module()->max_mem_pages(); + uint32_t compiled_max_pages = instance->compiled_module()->max_mem_pages(); isolate->counters()->wasm_max_mem_pages_count()->AddSample( compiled_max_pages); if (compiled_max_pages != 0) return compiled_max_pages; - return WasmModule::kV8MaxPages; + return kV8MaxWasmMemoryPages; } -int32_t wasm::GrowInstanceMemory(Isolate* isolate, Handle<JSObject> object, - uint32_t pages) { - if (!IsWasmInstance(*object)) return -1; - auto instance = Handle<WasmInstanceObject>::cast(object); - if (pages == 0) return GetInstanceMemorySize(isolate, instance); - uint32_t max_pages = GetMaxInstanceMemorySize(isolate, instance); - - Address old_mem_start = nullptr; - uint32_t old_size = 0, new_size = 0; - - MaybeHandle<JSArrayBuffer> maybe_mem_buffer = - GetInstanceMemory(isolate, instance); +Handle<JSArrayBuffer> GrowMemoryBuffer(Isolate* isolate, + MaybeHandle<JSArrayBuffer> buffer, + uint32_t pages, uint32_t max_pages) { Handle<JSArrayBuffer> old_buffer; - if (!maybe_mem_buffer.ToHandle(&old_buffer) || - old_buffer->backing_store() == nullptr) { - // If module object does not have linear memory associated with it, - // Allocate new array buffer of given size. - new_size = pages * WasmModule::kPageSize; - if (max_pages < pages) return -1; - } else { + Address old_mem_start = nullptr; + uint32_t old_size = 0; + if (buffer.ToHandle(&old_buffer) && old_buffer->backing_store() != nullptr) { 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(old_size + pages * WasmModule::kPageSize <= - std::numeric_limits<uint32_t>::max()); - new_size = old_size + pages * WasmModule::kPageSize; + old_size = old_buffer->byte_length()->Number(); } - + DCHECK(old_size + pages * WasmModule::kPageSize <= + std::numeric_limits<uint32_t>::max()); + uint32_t new_size = old_size + pages * WasmModule::kPageSize; if (new_size <= old_size || max_pages * WasmModule::kPageSize < new_size || - WasmModule::kV8MaxPages * WasmModule::kPageSize < new_size) { - return -1; - } - 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); - } - SetInstanceMemory(instance, *buffer); - Handle<FixedArray> code_table = instance->get_compiled_module()->code_table(); - RelocateMemoryReferencesInCode(code_table, old_mem_start, new_mem_start, - old_size, new_size); - if (instance->has_memory_object()) { - instance->get_memory_object()->set_buffer(*buffer); + kV8MaxWasmMemoryPages * WasmModule::kPageSize < new_size) { + return Handle<JSArrayBuffer>::null(); } + Handle<JSArrayBuffer> new_buffer; + if (!old_buffer.is_null() && old_buffer->has_guard_region()) { + // We don't move the backing store, we simply change the protection to make + // more of it accessible. + base::OS::Unprotect(old_buffer->backing_store(), new_size); + reinterpret_cast<v8::Isolate*>(isolate) + ->AdjustAmountOfExternalAllocatedMemory(pages * WasmModule::kPageSize); + Handle<Object> new_size_object = + isolate->factory()->NewNumberFromSize(new_size); + old_buffer->set_byte_length(*new_size_object); + new_buffer = old_buffer; + } else { + const bool enable_guard_regions = false; + new_buffer = NewArrayBuffer(isolate, new_size, enable_guard_regions); + if (new_buffer.is_null()) return new_buffer; + Address new_mem_start = static_cast<Address>(new_buffer->backing_store()); + if (old_size != 0) { + memcpy(new_mem_start, old_mem_start, old_size); + } + } + return new_buffer; +} + +void UncheckedUpdateInstanceMemory(Isolate* isolate, + Handle<WasmInstanceObject> instance, + Address old_mem_start, uint32_t old_size) { + DCHECK(instance->has_memory_buffer()); + Handle<JSArrayBuffer> new_buffer(instance->memory_buffer()); + uint32_t new_size = new_buffer->byte_length()->Number(); + DCHECK(new_size <= std::numeric_limits<uint32_t>::max()); + Address new_mem_start = static_cast<Address>(new_buffer->backing_store()); + DCHECK_NOT_NULL(new_mem_start); + Handle<FixedArray> code_table = instance->compiled_module()->code_table(); + RelocateMemoryReferencesInCode( + code_table, instance->compiled_module()->module()->num_imported_functions, + old_mem_start, new_mem_start, old_size, new_size); +} + +int32_t wasm::GrowWebAssemblyMemory(Isolate* isolate, + Handle<WasmMemoryObject> receiver, + uint32_t pages) { + DCHECK(WasmJs::IsWasmMemoryObject(isolate, receiver)); + Handle<WasmMemoryObject> memory_object = + handle(WasmMemoryObject::cast(*receiver)); + MaybeHandle<JSArrayBuffer> memory_buffer = handle(memory_object->buffer()); + Handle<JSArrayBuffer> old_buffer; + uint32_t old_size = 0; + Address old_mem_start = nullptr; + if (memory_buffer.ToHandle(&old_buffer) && + old_buffer->backing_store() != nullptr) { + old_size = old_buffer->byte_length()->Number(); + old_mem_start = static_cast<Address>(old_buffer->backing_store()); + } + // Return current size if grow by 0 + if (pages == 0) { + DCHECK(old_size % WasmModule::kPageSize == 0); + return (old_size / WasmModule::kPageSize); + } + Handle<JSArrayBuffer> new_buffer; + if (!memory_object->has_instances_link()) { + // Memory object does not have an instance associated with it, just grow + uint32_t max_pages; + if (memory_object->has_maximum_pages()) { + max_pages = static_cast<uint32_t>(memory_object->maximum_pages()); + if (kV8MaxWasmMemoryPages < max_pages) return -1; + } else { + max_pages = kV8MaxWasmMemoryPages; + } + new_buffer = GrowMemoryBuffer(isolate, memory_buffer, pages, max_pages); + if (new_buffer.is_null()) return -1; + } else { + Handle<WasmInstanceWrapper> instance_wrapper( + memory_object->instances_link()); + DCHECK(WasmInstanceWrapper::IsWasmInstanceWrapper(*instance_wrapper)); + DCHECK(instance_wrapper->has_instance()); + Handle<WasmInstanceObject> instance = instance_wrapper->instance_object(); + DCHECK(IsWasmInstance(*instance)); + uint32_t max_pages = GetMaxInstanceMemoryPages(isolate, instance); + + // Grow memory object buffer and update instances associated with it. + new_buffer = GrowMemoryBuffer(isolate, memory_buffer, pages, max_pages); + if (new_buffer.is_null()) return -1; + DCHECK(!instance_wrapper->has_previous()); + SetInstanceMemory(instance, *new_buffer); + UncheckedUpdateInstanceMemory(isolate, instance, old_mem_start, old_size); + while (instance_wrapper->has_next()) { + instance_wrapper = instance_wrapper->next_wrapper(); + DCHECK(WasmInstanceWrapper::IsWasmInstanceWrapper(*instance_wrapper)); + Handle<WasmInstanceObject> instance = instance_wrapper->instance_object(); + DCHECK(IsWasmInstance(*instance)); + SetInstanceMemory(instance, *new_buffer); + UncheckedUpdateInstanceMemory(isolate, instance, old_mem_start, old_size); + } + } + memory_object->set_buffer(*new_buffer); DCHECK(old_size % WasmModule::kPageSize == 0); return (old_size / WasmModule::kPageSize); } +int32_t wasm::GrowMemory(Isolate* isolate, Handle<WasmInstanceObject> instance, + uint32_t pages) { + if (!IsWasmInstance(*instance)) return -1; + if (pages == 0) return GetInstanceMemorySize(isolate, instance); + Handle<WasmInstanceObject> instance_obj(WasmInstanceObject::cast(*instance)); + if (!instance_obj->has_memory_object()) { + // No other instances to grow, grow just the one. + MaybeHandle<JSArrayBuffer> instance_buffer = + GetInstanceMemory(isolate, instance); + Handle<JSArrayBuffer> old_buffer; + uint32_t old_size = 0; + Address old_mem_start = nullptr; + if (instance_buffer.ToHandle(&old_buffer) && + old_buffer->backing_store() != nullptr) { + old_size = old_buffer->byte_length()->Number(); + old_mem_start = static_cast<Address>(old_buffer->backing_store()); + } + uint32_t max_pages = GetMaxInstanceMemoryPages(isolate, instance_obj); + Handle<JSArrayBuffer> buffer = + GrowMemoryBuffer(isolate, instance_buffer, pages, max_pages); + if (buffer.is_null()) return -1; + SetInstanceMemory(instance, *buffer); + UncheckedUpdateInstanceMemory(isolate, instance, old_mem_start, old_size); + DCHECK(old_size % WasmModule::kPageSize == 0); + return (old_size / WasmModule::kPageSize); + } else { + return GrowWebAssemblyMemory(isolate, handle(instance_obj->memory_object()), + pages); + } +} + +void wasm::GrowDispatchTables(Isolate* isolate, + Handle<FixedArray> dispatch_tables, + uint32_t old_size, uint32_t count) { + DCHECK_EQ(0, dispatch_tables->length() % 4); + for (int i = 0; i < dispatch_tables->length(); i += 4) { + Handle<FixedArray> old_function_table( + FixedArray::cast(dispatch_tables->get(i + 2))); + Handle<FixedArray> old_signature_table( + FixedArray::cast(dispatch_tables->get(i + 3))); + Handle<FixedArray> new_function_table = + isolate->factory()->CopyFixedArrayAndGrow(old_function_table, count); + Handle<FixedArray> new_signature_table = + isolate->factory()->CopyFixedArrayAndGrow(old_signature_table, count); + + // Get code table for the instance + Handle<WasmInstanceObject> instance( + WasmInstanceObject::cast(dispatch_tables->get(i))); + Handle<FixedArray> code_table(instance->compiled_module()->code_table()); + + // Relocate size references + RelocateTableSizeReferences(code_table, old_size, old_size + count); + + // Replace references of old tables with new tables. + for (int j = 0; j < code_table->length(); ++j) { + if (!code_table->get(j)->IsCode()) continue; + Handle<Code> code = Handle<Code>(Code::cast(code_table->get(j))); + ReplaceReferenceInCode(code, old_function_table, new_function_table); + ReplaceReferenceInCode(code, old_signature_table, new_signature_table); + } + + // Update dispatch tables with new function/signature tables + dispatch_tables->set(i + 2, *new_function_table); + dispatch_tables->set(i + 3, *new_signature_table); + } +} + void testing::ValidateInstancesChain(Isolate* isolate, - Handle<JSObject> wasm_module, + Handle<WasmModuleObject> module_obj, int instance_count) { CHECK_GE(instance_count, 0); DisallowHeapAllocation no_gc; - WasmCompiledModule* compiled_module = - WasmCompiledModule::cast(wasm_module->GetInternalField(0)); + WasmCompiledModule* compiled_module = module_obj->compiled_module(); CHECK_EQ(JSObject::cast(compiled_module->ptr_to_weak_wasm_module()->value()), - *wasm_module); + *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_wasm_module()->value(), - *wasm_module); + CHECK_EQ(current_instance->ptr_to_weak_wasm_module()->value(), *module_obj); CHECK(IsWasmInstance( current_instance->ptr_to_weak_owning_instance()->value())); prev = current_instance; @@ -2126,63 +2552,222 @@ void testing::ValidateInstancesChain(Isolate* isolate, } void testing::ValidateModuleState(Isolate* isolate, - Handle<JSObject> wasm_module) { + Handle<WasmModuleObject> module_obj) { DisallowHeapAllocation no_gc; - WasmCompiledModule* compiled_module = - WasmCompiledModule::cast(wasm_module->GetInternalField(0)); + WasmCompiledModule* compiled_module = module_obj->compiled_module(); CHECK(compiled_module->has_weak_wasm_module()); - CHECK_EQ(compiled_module->ptr_to_weak_wasm_module()->value(), *wasm_module); + CHECK_EQ(compiled_module->ptr_to_weak_wasm_module()->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 testing::ValidateOrphanedInstance(Isolate* isolate, - Handle<JSObject> object) { + Handle<WasmInstanceObject> instance) { DisallowHeapAllocation no_gc; - WasmInstanceObject* instance = WasmInstanceObject::cast(*object); - WasmCompiledModule* compiled_module = instance->get_compiled_module(); + WasmCompiledModule* compiled_module = instance->compiled_module(); CHECK(compiled_module->has_weak_wasm_module()); CHECK(compiled_module->ptr_to_weak_wasm_module()->cleared()); } -void WasmCompiledModule::RecreateModuleWrapper(Isolate* isolate, - Handle<FixedArray> array) { - Handle<WasmCompiledModule> compiled_module( - reinterpret_cast<WasmCompiledModule*>(*array), isolate); +Handle<JSArray> wasm::GetImports(Isolate* isolate, + Handle<WasmModuleObject> module_object) { + Handle<WasmCompiledModule> compiled_module(module_object->compiled_module(), + isolate); + Factory* factory = isolate->factory(); - WasmModule* module = nullptr; + Handle<String> module_string = factory->InternalizeUtf8String("module"); + Handle<String> name_string = factory->InternalizeUtf8String("name"); + Handle<String> kind_string = factory->InternalizeUtf8String("kind"); + + Handle<String> function_string = factory->InternalizeUtf8String("function"); + Handle<String> table_string = factory->InternalizeUtf8String("table"); + Handle<String> memory_string = factory->InternalizeUtf8String("memory"); + Handle<String> global_string = factory->InternalizeUtf8String("global"); + + // Create the result array. + WasmModule* module = compiled_module->module(); + int num_imports = static_cast<int>(module->import_table.size()); + Handle<JSArray> array_object = factory->NewJSArray(FAST_ELEMENTS, 0, 0); + Handle<FixedArray> storage = factory->NewFixedArray(num_imports); + JSArray::SetContent(array_object, storage); + array_object->set_length(Smi::FromInt(num_imports)); + + Handle<JSFunction> object_function = + Handle<JSFunction>(isolate->native_context()->object_function(), isolate); + + // Populate the result array. + for (int index = 0; index < num_imports; ++index) { + WasmImport& import = module->import_table[index]; + + Handle<JSObject> entry = factory->NewJSObject(object_function); + + Handle<String> import_kind; + switch (import.kind) { + case kExternalFunction: + import_kind = function_string; + break; + case kExternalTable: + import_kind = table_string; + break; + case kExternalMemory: + import_kind = memory_string; + break; + case kExternalGlobal: + import_kind = global_string; + break; + default: + UNREACHABLE(); + } + + MaybeHandle<String> import_module = + WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate, compiled_module, import.module_name_offset, + import.module_name_length); + + MaybeHandle<String> import_name = + WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate, compiled_module, import.field_name_offset, + import.field_name_length); + + JSObject::AddProperty(entry, module_string, import_module.ToHandleChecked(), + NONE); + JSObject::AddProperty(entry, name_string, import_name.ToHandleChecked(), + NONE); + JSObject::AddProperty(entry, kind_string, import_kind, NONE); + + storage->set(index, *entry); + } + + return array_object; +} + +Handle<JSArray> wasm::GetExports(Isolate* isolate, + Handle<WasmModuleObject> module_object) { + Handle<WasmCompiledModule> compiled_module(module_object->compiled_module(), + isolate); + Factory* factory = isolate->factory(); + + Handle<String> name_string = factory->InternalizeUtf8String("name"); + Handle<String> kind_string = factory->InternalizeUtf8String("kind"); + + Handle<String> function_string = factory->InternalizeUtf8String("function"); + Handle<String> table_string = factory->InternalizeUtf8String("table"); + Handle<String> memory_string = factory->InternalizeUtf8String("memory"); + Handle<String> global_string = factory->InternalizeUtf8String("global"); + + // Create the result array. + WasmModule* module = compiled_module->module(); + int num_exports = static_cast<int>(module->export_table.size()); + Handle<JSArray> array_object = factory->NewJSArray(FAST_ELEMENTS, 0, 0); + Handle<FixedArray> storage = factory->NewFixedArray(num_exports); + JSArray::SetContent(array_object, storage); + array_object->set_length(Smi::FromInt(num_exports)); + + Handle<JSFunction> object_function = + Handle<JSFunction>(isolate->native_context()->object_function(), isolate); + + // Populate the result array. + for (int index = 0; index < num_exports; ++index) { + WasmExport& exp = module->export_table[index]; + + Handle<String> export_kind; + switch (exp.kind) { + case kExternalFunction: + export_kind = function_string; + break; + case kExternalTable: + export_kind = table_string; + break; + case kExternalMemory: + export_kind = memory_string; + break; + case kExternalGlobal: + export_kind = global_string; + break; + default: + UNREACHABLE(); + } + + Handle<JSObject> entry = factory->NewJSObject(object_function); + + MaybeHandle<String> export_name = + WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate, compiled_module, exp.name_offset, exp.name_length); + + JSObject::AddProperty(entry, name_string, export_name.ToHandleChecked(), + NONE); + JSObject::AddProperty(entry, kind_string, export_kind, NONE); + + storage->set(index, *entry); + } + + return array_object; +} + +Handle<JSArray> wasm::GetCustomSections(Isolate* isolate, + Handle<WasmModuleObject> module_object, + Handle<String> name, + ErrorThrower* thrower) { + Handle<WasmCompiledModule> compiled_module(module_object->compiled_module(), + isolate); + Factory* factory = isolate->factory(); + + std::vector<CustomSectionOffset> custom_sections; { - Handle<SeqOneByteString> module_bytes = compiled_module->module_bytes(); - // We parse the module again directly from the module bytes, so - // the underlying storage must not be moved meanwhile. - DisallowHeapAllocation no_allocation; + DisallowHeapAllocation no_gc; // for raw access to string bytes. + Handle<SeqOneByteString> module_bytes(compiled_module->module_bytes(), + isolate); const byte* start = reinterpret_cast<const byte*>(module_bytes->GetCharsAddress()); const byte* end = start + module_bytes->length(); - // TODO(titzer): remember the module origin in the compiled_module - // For now, we assume serialized modules did not originate from asm.js. - ModuleResult result = - DecodeWasmModule(isolate, start, end, false, kWasmOrigin); - CHECK(result.ok()); - CHECK_NOT_NULL(result.val); - module = const_cast<WasmModule*>(result.val); + custom_sections = DecodeCustomSections(start, end); + } + + std::vector<Handle<Object>> matching_sections; + + // Gather matching sections. + for (auto section : custom_sections) { + MaybeHandle<String> section_name = + WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate, compiled_module, section.name_offset, section.name_length); + + if (!name->Equals(*section_name.ToHandleChecked())) continue; + + // Make a copy of the payload data in the section. + bool is_external; // Set by TryAllocateBackingStore + void* memory = TryAllocateBackingStore(isolate, section.payload_length, + false, is_external); + + Handle<Object> section_data = factory->undefined_value(); + if (memory) { + Handle<JSArrayBuffer> buffer = isolate->factory()->NewJSArrayBuffer(); + JSArrayBuffer::Setup(buffer, isolate, is_external, memory, + static_cast<int>(section.payload_length)); + DisallowHeapAllocation no_gc; // for raw access to string bytes. + Handle<SeqOneByteString> module_bytes(compiled_module->module_bytes(), + isolate); + const byte* start = + reinterpret_cast<const byte*>(module_bytes->GetCharsAddress()); + memcpy(memory, start + section.payload_offset, section.payload_length); + section_data = buffer; + } else { + thrower->RangeError("out of memory allocating custom section data"); + return Handle<JSArray>(); + } + + matching_sections.push_back(section_data); } - Handle<WasmModuleWrapper> module_wrapper = - WasmModuleWrapper::New(isolate, module); + int num_custom_sections = static_cast<int>(matching_sections.size()); + Handle<JSArray> array_object = factory->NewJSArray(FAST_ELEMENTS, 0, 0); + Handle<FixedArray> storage = factory->NewFixedArray(num_custom_sections); + JSArray::SetContent(array_object, storage); + array_object->set_length(Smi::FromInt(num_custom_sections)); - compiled_module->set_module_wrapper(module_wrapper); - DCHECK(WasmCompiledModule::IsWasmCompiledModule(*compiled_module)); -} + for (int i = 0; i < num_custom_sections; i++) { + storage->set(i, *matching_sections[i]); + } -MaybeHandle<String> WasmCompiledModule::GetFunctionName( - Handle<WasmCompiledModule> compiled_module, uint32_t func_index) { - DCHECK_LT(func_index, compiled_module->module()->functions.size()); - WasmFunction& function = compiled_module->module()->functions[func_index]; - Isolate* isolate = compiled_module->GetIsolate(); - MaybeHandle<String> string = ExtractStringFromModuleBytes( - isolate, compiled_module, function.name_offset, function.name_length); - if (!string.is_null()) return string.ToHandleChecked(); - return {}; + return array_object; } diff --git a/deps/v8/src/wasm/wasm-module.h b/deps/v8/src/wasm/wasm-module.h index 2ad46e21b6..2f368a7391 100644 --- a/deps/v8/src/wasm/wasm-module.h +++ b/deps/v8/src/wasm/wasm-module.h @@ -8,6 +8,7 @@ #include <memory> #include "src/api.h" +#include "src/debug/debug-interface.h" #include "src/globals.h" #include "src/handles.h" #include "src/parsing/preparse-data.h" @@ -22,6 +23,8 @@ namespace internal { class WasmCompiledModule; class WasmDebugInfo; class WasmModuleObject; +class WasmInstanceObject; +class WasmMemoryObject; namespace compiler { class CallDescriptor; @@ -31,11 +34,8 @@ class WasmCompilationUnit; namespace wasm { class ErrorThrower; -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 = 0x0d; +const uint32_t kWasmVersion = 0x01; const uint8_t kWasmFunctionTypeForm = 0x60; const uint8_t kWasmAnyFunctionTypeForm = 0x70; @@ -63,7 +63,6 @@ inline bool IsValidSectionCode(uint8_t byte) { const char* SectionName(WasmSectionCode code); // 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; @@ -118,7 +117,7 @@ struct WasmFunction { // Static representation of a wasm global variable. struct WasmGlobal { - LocalType type; // type of the global. + ValueType type; // type of the global. bool mutability; // {true} if mutable. WasmInitExpr init; // the initialization expression of the global. uint32_t offset; // offset into global memory. @@ -170,21 +169,18 @@ struct WasmExport { uint32_t index; // index into the respective space. }; -enum ModuleOrigin { kWasmOrigin, kAsmJsOrigin }; +enum ModuleOrigin : uint8_t { kWasmOrigin, kAsmJsOrigin }; +struct ModuleWireBytes; // Static representation of a module. struct V8_EXPORT_PRIVATE WasmModule { static const uint32_t kPageSize = 0x10000; // Page size, 64kb. static const uint32_t kMinMemPages = 1; // Minimum memory size = 64kb - static const size_t kV8MaxPages = 16384; // Maximum memory size = 1gb - static const size_t kSpecMaxPages = 65536; // Maximum according to the spec - static const size_t kV8MaxTableSize = 16 * 1024 * 1024; Zone* owned_zone; - const byte* module_start = nullptr; // starting address for the module bytes - const byte* module_end = nullptr; // end address for the module bytes uint32_t min_mem_pages = 0; // minimum size of the memory in 64k pages uint32_t max_mem_pages = 0; // maximum size of the memory in 64k pages + bool has_max_mem = false; // try if a maximum memory size exists bool has_memory = false; // true if the memory was defined or imported bool mem_export = false; // true if the memory is exported // TODO(wasm): reconcile start function index being an int with @@ -214,56 +210,23 @@ struct V8_EXPORT_PRIVATE WasmModule { // switch to libc-2.21 or higher. std::unique_ptr<base::Semaphore> pending_tasks; - WasmModule() : WasmModule(nullptr, nullptr) {} - WasmModule(Zone* owned_zone, const byte* module_start); + WasmModule() : WasmModule(nullptr) {} + WasmModule(Zone* owned_zone); ~WasmModule() { if (owned_zone) delete owned_zone; } - // Get a string stored in the module bytes representing a name. - WasmName GetName(uint32_t offset, uint32_t length) const { - if (length == 0) return {"<?>", 3}; // no name. - CHECK(BoundsCheck(offset, offset + length)); - DCHECK_GE(static_cast<int>(length), 0); - return {reinterpret_cast<const char*>(module_start + offset), - static_cast<int>(length)}; - } - - // Get a string stored in the module bytes representing a function name. - WasmName GetName(WasmFunction* function) const { - return GetName(function->name_offset, function->name_length); - } - - // Get a string stored in the module bytes representing a name. - WasmName GetNameOrNull(uint32_t offset, uint32_t length) const { - if (offset == 0 && length == 0) return {NULL, 0}; // no name. - CHECK(BoundsCheck(offset, offset + length)); - DCHECK_GE(static_cast<int>(length), 0); - return {reinterpret_cast<const char*>(module_start + offset), - static_cast<int>(length)}; - } - - // Get a string stored in the module bytes representing a function name. - WasmName GetNameOrNull(const WasmFunction* function) const { - return GetNameOrNull(function->name_offset, function->name_length); - } - - // Checks the given offset range is contained within the module bytes. - bool BoundsCheck(uint32_t start, uint32_t end) const { - size_t size = module_end - module_start; - return start <= size && end <= size; - } - // Creates a new instantiation of the module in the given isolate. - static MaybeHandle<JSObject> Instantiate(Isolate* isolate, - ErrorThrower* thrower, - Handle<JSObject> wasm_module, - Handle<JSReceiver> ffi, - Handle<JSArrayBuffer> memory); + static MaybeHandle<WasmInstanceObject> Instantiate( + Isolate* isolate, ErrorThrower* thrower, + Handle<WasmModuleObject> wasm_module, Handle<JSReceiver> ffi, + Handle<JSArrayBuffer> memory = Handle<JSArrayBuffer>::null()); MaybeHandle<WasmCompiledModule> CompileFunctions( Isolate* isolate, Handle<Managed<WasmModule>> module_wrapper, - ErrorThrower* thrower) const; + ErrorThrower* thrower, const ModuleWireBytes& wire_bytes, + Handle<Script> asm_js_script, + Vector<const byte> asm_js_offset_table_bytes) const; }; typedef Managed<WasmModule> WasmModuleWrapper; @@ -272,11 +235,10 @@ typedef Managed<WasmModule> WasmModuleWrapper; struct WasmInstance { const WasmModule* module; // static representation of the module. // -- Heap allocated -------------------------------------------------------- - Handle<JSObject> js_object; // JavaScript module object. Handle<Context> context; // JavaScript native context. - Handle<JSArrayBuffer> mem_buffer; // Handle to array buffer of memory. - Handle<JSArrayBuffer> globals_buffer; // Handle to array buffer of globals. std::vector<Handle<FixedArray>> function_tables; // indirect function tables. + std::vector<Handle<FixedArray>> + signature_tables; // indirect signature tables. std::vector<Handle<Code>> function_code; // code objects for each function. // -- raw memory ------------------------------------------------------------ byte* mem_start = nullptr; // start of linear memory. @@ -287,15 +249,67 @@ struct WasmInstance { explicit WasmInstance(const WasmModule* m) : module(m), function_tables(m->function_tables.size()), + signature_tables(m->function_tables.size()), function_code(m->functions.size()) {} }; +// Interface to the storage (wire bytes) of a wasm module. +// It is illegal for anyone receiving a ModuleWireBytes to store pointers based +// on module_bytes, as this storage is only guaranteed to be alive as long as +// this struct is alive. +struct V8_EXPORT_PRIVATE ModuleWireBytes { + ModuleWireBytes(Vector<const byte> module_bytes) + : module_bytes(module_bytes) {} + ModuleWireBytes(const byte* start, const byte* end) + : module_bytes(start, static_cast<int>(end - start)) { + DCHECK_GE(kMaxInt, end - start); + } + + const Vector<const byte> module_bytes; + + // Get a string stored in the module bytes representing a name. + WasmName GetName(uint32_t offset, uint32_t length) const { + if (length == 0) return {"<?>", 3}; // no name. + CHECK(BoundsCheck(offset, length)); + DCHECK_GE(length, 0); + return Vector<const char>::cast( + module_bytes.SubVector(offset, offset + length)); + } + + // Get a string stored in the module bytes representing a function name. + WasmName GetName(const WasmFunction* function) const { + return GetName(function->name_offset, function->name_length); + } + + // Get a string stored in the module bytes representing a name. + WasmName GetNameOrNull(uint32_t offset, uint32_t length) const { + if (offset == 0 && length == 0) return {NULL, 0}; // no name. + CHECK(BoundsCheck(offset, length)); + DCHECK_GE(length, 0); + return Vector<const char>::cast( + module_bytes.SubVector(offset, offset + length)); + } + + // Get a string stored in the module bytes representing a function name. + WasmName GetNameOrNull(const WasmFunction* function) const { + return GetNameOrNull(function->name_offset, function->name_length); + } + + // Checks the given offset range is contained within the module bytes. + bool BoundsCheck(uint32_t offset, uint32_t length) const { + uint32_t size = static_cast<uint32_t>(module_bytes.length()); + return offset <= size && length <= size - offset; + } +}; + // Interface provided to the decoder/graph builder which contains only // minimal information about the globals, functions, and function tables. struct V8_EXPORT_PRIVATE ModuleEnv { + ModuleEnv(const WasmModule* module, WasmInstance* instance) + : module(module), instance(instance) {} + const WasmModule* module; WasmInstance* instance; - ModuleOrigin origin; bool IsValidGlobal(uint32_t index) const { return module && index < module->globals.size(); @@ -309,7 +323,7 @@ struct V8_EXPORT_PRIVATE ModuleEnv { bool IsValidTable(uint32_t index) const { return module && index < module->function_tables.size(); } - LocalType GetGlobalType(uint32_t index) { + ValueType GetGlobalType(uint32_t index) { DCHECK(IsValidGlobal(index)); return module->globals[index].type; } @@ -326,7 +340,7 @@ struct V8_EXPORT_PRIVATE ModuleEnv { return &module->function_tables[index]; } - bool asm_js() { return origin == kAsmJsOrigin; } + bool asm_js() { return module->origin == kAsmJsOrigin; } Handle<Code> GetFunctionCode(uint32_t index) { DCHECK_NOT_NULL(instance); @@ -341,42 +355,33 @@ struct V8_EXPORT_PRIVATE ModuleEnv { Zone* zone, compiler::CallDescriptor* descriptor); }; +// A ModuleEnv together with ModuleWireBytes. +struct ModuleBytesEnv : public ModuleEnv, public ModuleWireBytes { + ModuleBytesEnv(const WasmModule* module, WasmInstance* instance, + Vector<const byte> module_bytes) + : ModuleEnv(module, instance), ModuleWireBytes(module_bytes) {} + ModuleBytesEnv(const WasmModule* module, WasmInstance* instance, + const ModuleWireBytes& wire_bytes) + : ModuleEnv(module, instance), ModuleWireBytes(wire_bytes) {} +}; + // A helper for printing out the names of functions. struct WasmFunctionName { + WasmFunctionName(const WasmFunction* function, ModuleBytesEnv* module_env) + : function_(function), name_(module_env->GetNameOrNull(function)) {} + const WasmFunction* function_; - const WasmModule* module_; - WasmFunctionName(const WasmFunction* function, const ModuleEnv* menv) - : function_(function), module_(menv ? menv->module : nullptr) {} + WasmName name_; }; std::ostream& operator<<(std::ostream& os, const WasmModule& module); std::ostream& operator<<(std::ostream& os, const WasmFunction& function); std::ostream& operator<<(std::ostream& os, const WasmFunctionName& name); -// Extract a function name from the given wasm instance. -// Returns "<WASM UNNAMED>" if no instance is passed, the function is unnamed or -// the name is not a valid UTF-8 string. -// TODO(5620): Refactor once we always get a wasm instance. -Handle<String> GetWasmFunctionName(Isolate* isolate, Handle<Object> instance, - uint32_t func_index); - -// Return the binary source bytes of a wasm module. -Handle<SeqOneByteString> GetWasmBytes(Handle<JSObject> wasm); - // Get the debug info associated with the given wasm object. // If no debug info exists yet, it is created automatically. Handle<WasmDebugInfo> GetDebugInfo(Handle<JSObject> wasm); -// Return the number of functions in the given wasm object. -int GetNumberOfFunctions(Handle<JSObject> wasm); - -// Create and export JSFunction -Handle<JSFunction> WrapExportCodeAsJSFunction(Isolate* isolate, - Handle<Code> export_code, - Handle<String> name, - FunctionSig* sig, int func_index, - Handle<JSObject> instance); - // Check whether the given object represents a WebAssembly.Instance instance. // This checks the number and type of internal fields, so it's not 100 percent // secure. If it turns out that we need more complete checks, we could add a @@ -384,26 +389,26 @@ Handle<JSFunction> WrapExportCodeAsJSFunction(Isolate* isolate, // else. bool IsWasmInstance(Object* instance); -// Return the compiled module object for this WASM instance. -WasmCompiledModule* GetCompiledModule(Object* wasm_instance); - -// Check whether the wasm module was generated from asm.js code. -bool WasmIsAsmJs(Object* instance, Isolate* isolate); - // Get the script of the wasm module. If the origin of the module is asm.js, the // returned Script will be a JavaScript Script of Script::TYPE_NORMAL, otherwise // it's of type TYPE_WASM. Handle<Script> GetScript(Handle<JSObject> instance); -// Get the asm.js source position for the given byte offset in the given -// function. -int GetAsmWasmSourcePosition(Handle<JSObject> instance, int func_index, - int byte_offset); - V8_EXPORT_PRIVATE MaybeHandle<WasmModuleObject> CreateModuleObjectFromBytes( Isolate* isolate, const byte* start, const byte* end, ErrorThrower* thrower, ModuleOrigin origin, Handle<Script> asm_js_script, - const byte* asm_offset_tables_start, const byte* asm_offset_tables_end); + Vector<const byte> asm_offset_table); + +V8_EXPORT_PRIVATE bool IsWasmCodegenAllowed(Isolate* isolate, + Handle<Context> context); + +V8_EXPORT_PRIVATE Handle<JSArray> GetImports(Isolate* isolate, + Handle<WasmModuleObject> module); +V8_EXPORT_PRIVATE Handle<JSArray> GetExports(Isolate* isolate, + Handle<WasmModuleObject> module); +V8_EXPORT_PRIVATE Handle<JSArray> GetCustomSections( + Isolate* isolate, Handle<WasmModuleObject> module, Handle<String> name, + ErrorThrower* thrower); V8_EXPORT_PRIVATE bool ValidateModuleBytes(Isolate* isolate, const byte* start, const byte* end, @@ -414,33 +419,44 @@ V8_EXPORT_PRIVATE bool ValidateModuleBytes(Isolate* isolate, const byte* start, int GetFunctionCodeOffset(Handle<WasmCompiledModule> compiled_module, int func_index); -// Translate from byte offset in the module to function number and byte offset -// within that function, encoded as line and column in the position info. -bool GetPositionInfo(Handle<WasmCompiledModule> compiled_module, - uint32_t position, Script::PositionInfo* info); - // 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); +WasmInstanceObject* GetOwningWasmInstance(Code* code); -MaybeHandle<JSArrayBuffer> GetInstanceMemory(Isolate* isolate, - Handle<JSObject> instance); +MaybeHandle<JSArrayBuffer> GetInstanceMemory( + Isolate* isolate, Handle<WasmInstanceObject> instance); -int32_t GetInstanceMemorySize(Isolate* isolate, Handle<JSObject> instance); +int32_t GetInstanceMemorySize(Isolate* isolate, + Handle<WasmInstanceObject> instance); -int32_t GrowInstanceMemory(Isolate* isolate, Handle<JSObject> instance, - uint32_t pages); +int32_t GrowInstanceMemory(Isolate* isolate, + Handle<WasmInstanceObject> instance, uint32_t pages); + +Handle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size, + bool enable_guard_regions); + +int32_t GrowWebAssemblyMemory(Isolate* isolate, + Handle<WasmMemoryObject> receiver, + uint32_t pages); + +int32_t GrowMemory(Isolate* isolate, Handle<WasmInstanceObject> instance, + uint32_t pages); void UpdateDispatchTables(Isolate* isolate, Handle<FixedArray> dispatch_tables, int index, Handle<JSFunction> js_function); +void GrowDispatchTables(Isolate* isolate, Handle<FixedArray> dispatch_tables, + uint32_t old_size, uint32_t count); + namespace testing { -void ValidateInstancesChain(Isolate* isolate, Handle<JSObject> wasm_module, +void ValidateInstancesChain(Isolate* isolate, + Handle<WasmModuleObject> module_obj, int instance_count); -void ValidateModuleState(Isolate* isolate, Handle<JSObject> wasm_module); -void ValidateOrphanedInstance(Isolate* isolate, Handle<JSObject> instance); +void ValidateModuleState(Isolate* isolate, Handle<WasmModuleObject> module_obj); +void ValidateOrphanedInstance(Isolate* isolate, + Handle<WasmInstanceObject> instance); } // namespace testing } // namespace wasm diff --git a/deps/v8/src/wasm/wasm-objects.cc b/deps/v8/src/wasm/wasm-objects.cc index 68f66d246d..3f694c579f 100644 --- a/deps/v8/src/wasm/wasm-objects.cc +++ b/deps/v8/src/wasm/wasm-objects.cc @@ -3,7 +3,12 @@ // found in the LICENSE file. #include "src/wasm/wasm-objects.h" +#include "src/utils.h" + +#include "src/debug/debug-interface.h" +#include "src/wasm/module-decoder.h" #include "src/wasm/wasm-module.h" +#include "src/wasm/wasm-text.h" #define TRACE(...) \ do { \ @@ -18,29 +23,38 @@ using namespace v8::internal; using namespace v8::internal::wasm; -#define DEFINE_ACCESSORS(Container, name, field, type) \ - type* Container::get_##name() { \ - return type::cast(GetInternalField(field)); \ - } \ - void Container::set_##name(type* value) { \ - return SetInternalField(field, value); \ - } +#define DEFINE_GETTER0(getter, Container, name, field, type) \ + type* Container::name() { return type::cast(getter(field)); } -#define DEFINE_OPTIONAL_ACCESSORS(Container, name, field, type) \ - bool Container::has_##name() { \ - return !GetInternalField(field)->IsUndefined(GetIsolate()); \ - } \ - type* Container::get_##name() { \ - return type::cast(GetInternalField(field)); \ - } \ - void Container::set_##name(type* value) { \ - return SetInternalField(field, value); \ - } +#define DEFINE_ACCESSORS0(getter, setter, Container, name, field, type) \ + DEFINE_GETTER0(getter, Container, name, field, type) \ + void Container::set_##name(type* value) { return setter(field, value); } -#define DEFINE_GETTER(Container, name, field, type) \ - type* Container::get_##name() { return type::cast(GetInternalField(field)); } +#define DEFINE_OPTIONAL_ACCESSORS0(getter, setter, Container, name, field, \ + type) \ + DEFINE_ACCESSORS0(getter, setter, Container, name, field, type) \ + bool Container::has_##name() { \ + return !getter(field)->IsUndefined(GetIsolate()); \ + } -static uint32_t SafeUint32(Object* value) { +#define DEFINE_OBJ_GETTER(Container, name, field, type) \ + DEFINE_GETTER0(GetInternalField, Container, name, field, type) +#define DEFINE_OBJ_ACCESSORS(Container, name, field, type) \ + DEFINE_ACCESSORS0(GetInternalField, SetInternalField, Container, name, \ + field, type) +#define DEFINE_OPTIONAL_OBJ_ACCESSORS(Container, name, field, type) \ + DEFINE_OPTIONAL_ACCESSORS0(GetInternalField, SetInternalField, Container, \ + name, field, type) +#define DEFINE_ARR_GETTER(Container, name, field, type) \ + DEFINE_GETTER0(get, Container, name, field, type) +#define DEFINE_ARR_ACCESSORS(Container, name, field, type) \ + DEFINE_ACCESSORS0(get, set, Container, name, field, type) +#define DEFINE_OPTIONAL_ARR_ACCESSORS(Container, name, field, type) \ + DEFINE_OPTIONAL_ACCESSORS0(get, set, Container, name, field, type) + +namespace { + +uint32_t SafeUint32(Object* value) { if (value->IsSmi()) { int32_t val = Smi::cast(value)->value(); CHECK_GE(val, 0); @@ -49,21 +63,23 @@ static uint32_t SafeUint32(Object* value) { DCHECK(value->IsHeapNumber()); HeapNumber* num = HeapNumber::cast(value); CHECK_GE(num->value(), 0.0); - CHECK_LE(num->value(), static_cast<double>(kMaxUInt32)); + CHECK_LE(num->value(), kMaxUInt32); return static_cast<uint32_t>(num->value()); } -static int32_t SafeInt32(Object* value) { +int32_t SafeInt32(Object* value) { if (value->IsSmi()) { return Smi::cast(value)->value(); } DCHECK(value->IsHeapNumber()); HeapNumber* num = HeapNumber::cast(value); - CHECK_GE(num->value(), static_cast<double>(Smi::kMinValue)); - CHECK_LE(num->value(), static_cast<double>(Smi::kMaxValue)); + CHECK_GE(num->value(), Smi::kMinValue); + CHECK_LE(num->value(), Smi::kMaxValue); return static_cast<int32_t>(num->value()); } +} // namespace + Handle<WasmModuleObject> WasmModuleObject::New( Isolate* isolate, Handle<WasmCompiledModule> compiled_module) { ModuleOrigin origin = compiled_module->module()->origin; @@ -97,8 +113,16 @@ WasmModuleObject* WasmModuleObject::cast(Object* object) { return reinterpret_cast<WasmModuleObject*>(object); } +bool WasmModuleObject::IsWasmModuleObject(Object* object) { + return object->IsJSObject() && + JSObject::cast(object)->GetInternalFieldCount() == kFieldCount; +} + +DEFINE_OBJ_GETTER(WasmModuleObject, compiled_module, kCompiledModule, + WasmCompiledModule) + Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, uint32_t initial, - uint32_t maximum, + int64_t maximum, Handle<FixedArray>* js_functions) { Handle<JSFunction> table_ctor( isolate->native_context()->wasm_table_constructor()); @@ -109,8 +133,8 @@ Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, uint32_t initial, (*js_functions)->set(i, null); } table_obj->SetInternalField(kFunctions, *(*js_functions)); - table_obj->SetInternalField(kMaximum, - static_cast<Object*>(Smi::FromInt(maximum))); + Handle<Object> max = isolate->factory()->NewNumber(maximum); + table_obj->SetInternalField(kMaximum, *max); Handle<FixedArray> dispatch_tables = isolate->factory()->NewFixedArray(0); table_obj->SetInternalField(kDispatchTables, *dispatch_tables); @@ -119,27 +143,28 @@ Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, uint32_t initial, return Handle<WasmTableObject>::cast(table_obj); } -DEFINE_GETTER(WasmTableObject, dispatch_tables, kDispatchTables, FixedArray) +DEFINE_OBJ_GETTER(WasmTableObject, dispatch_tables, kDispatchTables, FixedArray) Handle<FixedArray> WasmTableObject::AddDispatchTable( Isolate* isolate, Handle<WasmTableObject> table_obj, Handle<WasmInstanceObject> instance, int table_index, - Handle<FixedArray> dispatch_table) { + Handle<FixedArray> function_table, Handle<FixedArray> signature_table) { Handle<FixedArray> dispatch_tables( FixedArray::cast(table_obj->GetInternalField(kDispatchTables)), isolate); - DCHECK_EQ(0, dispatch_tables->length() % 3); + DCHECK_EQ(0, dispatch_tables->length() % 4); if (instance.is_null()) return dispatch_tables; // TODO(titzer): use weak cells here to avoid leaking instances. // Grow the dispatch table and add a new triple at the end. Handle<FixedArray> new_dispatch_tables = - isolate->factory()->CopyFixedArrayAndGrow(dispatch_tables, 3); + isolate->factory()->CopyFixedArrayAndGrow(dispatch_tables, 4); new_dispatch_tables->set(dispatch_tables->length() + 0, *instance); new_dispatch_tables->set(dispatch_tables->length() + 1, Smi::FromInt(table_index)); - new_dispatch_tables->set(dispatch_tables->length() + 2, *dispatch_table); + new_dispatch_tables->set(dispatch_tables->length() + 2, *function_table); + new_dispatch_tables->set(dispatch_tables->length() + 3, *signature_table); table_obj->SetInternalField(WasmTableObject::kDispatchTables, *new_dispatch_tables); @@ -147,12 +172,16 @@ Handle<FixedArray> WasmTableObject::AddDispatchTable( return new_dispatch_tables; } -DEFINE_ACCESSORS(WasmTableObject, functions, kFunctions, FixedArray) +DEFINE_OBJ_ACCESSORS(WasmTableObject, functions, kFunctions, FixedArray) + +uint32_t WasmTableObject::current_length() { return functions()->length(); } -uint32_t WasmTableObject::current_length() { return get_functions()->length(); } +bool WasmTableObject::has_maximum_length() { + return GetInternalField(kMaximum)->Number() >= 0; +} -uint32_t WasmTableObject::maximum_length() { - return SafeUint32(GetInternalField(kMaximum)); +int64_t WasmTableObject::maximum_length() { + return static_cast<int64_t>(GetInternalField(kMaximum)->Number()); } WasmTableObject* WasmTableObject::cast(Object* object) { @@ -161,28 +190,42 @@ WasmTableObject* WasmTableObject::cast(Object* object) { return reinterpret_cast<WasmTableObject*>(object); } +void WasmTableObject::Grow(Isolate* isolate, Handle<WasmTableObject> table, + uint32_t count) { + Handle<FixedArray> dispatch_tables(table->dispatch_tables()); + wasm::GrowDispatchTables(isolate, dispatch_tables, + table->functions()->length(), count); +} + Handle<WasmMemoryObject> WasmMemoryObject::New(Isolate* isolate, Handle<JSArrayBuffer> buffer, - int maximum) { + int32_t maximum) { Handle<JSFunction> memory_ctor( isolate->native_context()->wasm_memory_constructor()); - Handle<JSObject> memory_obj = isolate->factory()->NewJSObject(memory_ctor); + Handle<JSObject> memory_obj = + isolate->factory()->NewJSObject(memory_ctor, TENURED); memory_obj->SetInternalField(kArrayBuffer, *buffer); - memory_obj->SetInternalField(kMaximum, - static_cast<Object*>(Smi::FromInt(maximum))); + Handle<Object> max = isolate->factory()->NewNumber(maximum); + memory_obj->SetInternalField(kMaximum, *max); Handle<Symbol> memory_sym(isolate->native_context()->wasm_memory_sym()); Object::SetProperty(memory_obj, memory_sym, memory_obj, STRICT).Check(); return Handle<WasmMemoryObject>::cast(memory_obj); } -DEFINE_ACCESSORS(WasmMemoryObject, buffer, kArrayBuffer, JSArrayBuffer) +DEFINE_OBJ_ACCESSORS(WasmMemoryObject, buffer, kArrayBuffer, JSArrayBuffer) +DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmMemoryObject, instances_link, kInstancesLink, + WasmInstanceWrapper) uint32_t WasmMemoryObject::current_pages() { - return SafeUint32(get_buffer()->byte_length()) / wasm::WasmModule::kPageSize; + return SafeUint32(buffer()->byte_length()) / wasm::WasmModule::kPageSize; +} + +bool WasmMemoryObject::has_maximum_pages() { + return GetInternalField(kMaximum)->Number() >= 0; } int32_t WasmMemoryObject::maximum_pages() { - return SafeInt32(GetInternalField(kMaximum)); + return static_cast<int32_t>(GetInternalField(kMaximum)->Number()); } WasmMemoryObject* WasmMemoryObject::cast(Object* object) { @@ -191,31 +234,50 @@ WasmMemoryObject* WasmMemoryObject::cast(Object* object) { return reinterpret_cast<WasmMemoryObject*>(object); } -void WasmMemoryObject::AddInstance(WasmInstanceObject* instance) { - // TODO(gdeepti): This should be a weak list of instance objects - // for instances that share memory. - SetInternalField(kInstance, instance); +void WasmMemoryObject::AddInstance(Isolate* isolate, + Handle<WasmInstanceObject> instance) { + Handle<WasmInstanceWrapper> instance_wrapper = + handle(instance->instance_wrapper()); + if (has_instances_link()) { + Handle<WasmInstanceWrapper> current_wrapper(instances_link()); + DCHECK(WasmInstanceWrapper::IsWasmInstanceWrapper(*current_wrapper)); + DCHECK(!current_wrapper->has_previous()); + instance_wrapper->set_next_wrapper(*current_wrapper); + current_wrapper->set_previous_wrapper(*instance_wrapper); + } + set_instances_link(*instance_wrapper); +} + +void WasmMemoryObject::ResetInstancesLink(Isolate* isolate) { + Handle<Object> undefined = isolate->factory()->undefined_value(); + SetInternalField(kInstancesLink, *undefined); } -DEFINE_ACCESSORS(WasmInstanceObject, compiled_module, kCompiledModule, - WasmCompiledModule) -DEFINE_OPTIONAL_ACCESSORS(WasmInstanceObject, globals_buffer, - kGlobalsArrayBuffer, JSArrayBuffer) -DEFINE_OPTIONAL_ACCESSORS(WasmInstanceObject, memory_buffer, kMemoryArrayBuffer, - JSArrayBuffer) -DEFINE_OPTIONAL_ACCESSORS(WasmInstanceObject, memory_object, kMemoryObject, - WasmMemoryObject) -DEFINE_OPTIONAL_ACCESSORS(WasmInstanceObject, debug_info, kDebugInfo, - WasmDebugInfo) +DEFINE_OBJ_ACCESSORS(WasmInstanceObject, compiled_module, kCompiledModule, + WasmCompiledModule) +DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, globals_buffer, + kGlobalsArrayBuffer, JSArrayBuffer) +DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, memory_buffer, + kMemoryArrayBuffer, JSArrayBuffer) +DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, memory_object, kMemoryObject, + WasmMemoryObject) +DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, debug_info, kDebugInfo, + WasmDebugInfo) +DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, instance_wrapper, + kWasmMemInstanceWrapper, WasmInstanceWrapper) WasmModuleObject* WasmInstanceObject::module_object() { - return WasmModuleObject::cast(*get_compiled_module()->wasm_module()); + return *compiled_module()->wasm_module(); } -WasmModule* WasmInstanceObject::module() { - return reinterpret_cast<WasmModuleWrapper*>( - *get_compiled_module()->module_wrapper()) - ->get(); +WasmModule* WasmInstanceObject::module() { return compiled_module()->module(); } + +Handle<WasmDebugInfo> WasmInstanceObject::GetOrCreateDebugInfo( + Handle<WasmInstanceObject> instance) { + if (instance->has_debug_info()) return handle(instance->debug_info()); + Handle<WasmDebugInfo> new_info = WasmDebugInfo::New(instance); + instance->set_debug_info(*new_info); + return new_info; } WasmInstanceObject* WasmInstanceObject::cast(Object* object) { @@ -224,7 +286,6 @@ WasmInstanceObject* WasmInstanceObject::cast(Object* object) { } bool WasmInstanceObject::IsWasmInstanceObject(Object* object) { - if (!object->IsObject()) return false; if (!object->IsJSObject()) return false; JSObject* obj = JSObject::cast(object); @@ -246,15 +307,21 @@ bool WasmInstanceObject::IsWasmInstanceObject(Object* object) { Handle<WasmInstanceObject> WasmInstanceObject::New( Isolate* isolate, Handle<WasmCompiledModule> compiled_module) { - Handle<Map> map = isolate->factory()->NewMap( - JS_OBJECT_TYPE, JSObject::kHeaderSize + kFieldCount * kPointerSize); + Handle<JSFunction> instance_cons( + isolate->native_context()->wasm_instance_constructor()); + Handle<JSObject> instance_object = + isolate->factory()->NewJSObject(instance_cons, TENURED); + Handle<Symbol> instance_sym(isolate->native_context()->wasm_instance_sym()); + Object::SetProperty(instance_object, instance_sym, instance_object, STRICT) + .Check(); Handle<WasmInstanceObject> instance( - reinterpret_cast<WasmInstanceObject*>( - *isolate->factory()->NewJSObjectFromMap(map, TENURED)), - isolate); + reinterpret_cast<WasmInstanceObject*>(*instance_object), isolate); instance->SetInternalField(kCompiledModule, *compiled_module); instance->SetInternalField(kMemoryObject, isolate->heap()->undefined_value()); + Handle<WasmInstanceWrapper> instance_wrapper = + WasmInstanceWrapper::New(isolate, instance); + instance->SetInternalField(kWasmMemInstanceWrapper, *instance_wrapper); return instance; } @@ -275,8 +342,20 @@ WasmExportedFunction* WasmExportedFunction::cast(Object* object) { } Handle<WasmExportedFunction> WasmExportedFunction::New( - Isolate* isolate, Handle<WasmInstanceObject> instance, Handle<String> name, - Handle<Code> export_wrapper, int arity, int func_index) { + Isolate* isolate, Handle<WasmInstanceObject> instance, + MaybeHandle<String> maybe_name, int func_index, int arity, + Handle<Code> export_wrapper) { + Handle<String> name; + if (maybe_name.is_null()) { + EmbeddedVector<char, 16> buffer; + int length = SNPrintF(buffer, "%d", func_index); + name = isolate->factory() + ->NewStringFromAscii( + Vector<const char>::cast(buffer.SubVector(0, length))) + .ToHandleChecked(); + } else { + name = maybe_name.ToHandleChecked(); + } DCHECK_EQ(Code::JS_TO_WASM_FUNCTION, export_wrapper->kind()); Handle<SharedFunctionInfo> shared = isolate->factory()->NewSharedFunctionInfo(name, export_wrapper, false); @@ -291,22 +370,109 @@ Handle<WasmExportedFunction> WasmExportedFunction::New( return Handle<WasmExportedFunction>::cast(function); } +bool WasmSharedModuleData::IsWasmSharedModuleData(Object* object) { + if (!object->IsFixedArray()) return false; + FixedArray* arr = FixedArray::cast(object); + if (arr->length() != kFieldCount) return false; + Isolate* isolate = arr->GetIsolate(); + if (!arr->get(kModuleWrapper)->IsForeign()) return false; + if (!arr->get(kModuleBytes)->IsUndefined(isolate) && + !arr->get(kModuleBytes)->IsSeqOneByteString()) + return false; + if (!arr->get(kScript)->IsScript()) return false; + if (!arr->get(kAsmJsOffsetTable)->IsUndefined(isolate) && + !arr->get(kAsmJsOffsetTable)->IsByteArray()) + return false; + return true; +} + +WasmSharedModuleData* WasmSharedModuleData::cast(Object* object) { + DCHECK(IsWasmSharedModuleData(object)); + return reinterpret_cast<WasmSharedModuleData*>(object); +} + +wasm::WasmModule* WasmSharedModuleData::module() { + return reinterpret_cast<WasmModuleWrapper*>(get(kModuleWrapper))->get(); +} + +DEFINE_OPTIONAL_ARR_ACCESSORS(WasmSharedModuleData, module_bytes, kModuleBytes, + SeqOneByteString); +DEFINE_ARR_GETTER(WasmSharedModuleData, script, kScript, Script); +DEFINE_OPTIONAL_ARR_ACCESSORS(WasmSharedModuleData, asm_js_offset_table, + kAsmJsOffsetTable, ByteArray); + +Handle<WasmSharedModuleData> WasmSharedModuleData::New( + Isolate* isolate, Handle<Foreign> module_wrapper, + Handle<SeqOneByteString> module_bytes, Handle<Script> script, + Handle<ByteArray> asm_js_offset_table) { + Handle<FixedArray> arr = + isolate->factory()->NewFixedArray(kFieldCount, TENURED); + + arr->set(kModuleWrapper, *module_wrapper); + if (!module_bytes.is_null()) { + arr->set(kModuleBytes, *module_bytes); + } + if (!script.is_null()) { + arr->set(kScript, *script); + } + if (!asm_js_offset_table.is_null()) { + arr->set(kAsmJsOffsetTable, *asm_js_offset_table); + } + + DCHECK(WasmSharedModuleData::IsWasmSharedModuleData(*arr)); + return Handle<WasmSharedModuleData>::cast(arr); +} + +bool WasmSharedModuleData::is_asm_js() { + bool asm_js = module()->origin == wasm::ModuleOrigin::kAsmJsOrigin; + DCHECK_EQ(asm_js, script()->type() == Script::TYPE_NORMAL); + DCHECK_EQ(asm_js, has_asm_js_offset_table()); + return asm_js; +} + +void WasmSharedModuleData::RecreateModuleWrapper( + Isolate* isolate, Handle<WasmSharedModuleData> shared) { + DCHECK(shared->get(kModuleWrapper)->IsUndefined(isolate)); + + WasmModule* module = nullptr; + { + // We parse the module again directly from the module bytes, so + // the underlying storage must not be moved meanwhile. + DisallowHeapAllocation no_allocation; + SeqOneByteString* module_bytes = shared->module_bytes(); + const byte* start = + reinterpret_cast<const byte*>(module_bytes->GetCharsAddress()); + const byte* end = start + module_bytes->length(); + // TODO(titzer): remember the module origin in the compiled_module + // For now, we assume serialized modules did not originate from asm.js. + ModuleResult result = + DecodeWasmModule(isolate, start, end, false, kWasmOrigin); + CHECK(result.ok()); + CHECK_NOT_NULL(result.val); + module = const_cast<WasmModule*>(result.val); + } + + Handle<WasmModuleWrapper> module_wrapper = + WasmModuleWrapper::New(isolate, module); + + shared->set(kModuleWrapper, *module_wrapper); + DCHECK(WasmSharedModuleData::IsWasmSharedModuleData(*shared)); +} + Handle<WasmCompiledModule> WasmCompiledModule::New( - Isolate* isolate, Handle<WasmModuleWrapper> module_wrapper) { + Isolate* isolate, Handle<WasmSharedModuleData> shared) { Handle<FixedArray> ret = isolate->factory()->NewFixedArray(PropertyIndices::Count, TENURED); - // WasmCompiledModule::cast would fail since module bytes are not set yet. + // WasmCompiledModule::cast would fail since fields are not set yet. Handle<WasmCompiledModule> compiled_module( reinterpret_cast<WasmCompiledModule*>(*ret), isolate); compiled_module->InitId(); - compiled_module->set_module_wrapper(module_wrapper); + compiled_module->set_num_imported_functions(0); + compiled_module->set_shared(shared); + compiled_module->set_native_context(isolate->native_context()); return compiled_module; } -wasm::WasmModule* WasmCompiledModule::module() const { - return reinterpret_cast<WasmModuleWrapper*>(*module_wrapper())->get(); -} - void WasmCompiledModule::InitId() { #if DEBUG static uint32_t instance_id_counter = 0; @@ -315,19 +481,39 @@ void WasmCompiledModule::InitId() { #endif } +MaybeHandle<String> WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + Isolate* isolate, Handle<WasmCompiledModule> compiled_module, + uint32_t offset, uint32_t size) { + // TODO(wasm): cache strings from modules if it's a performance win. + Handle<SeqOneByteString> module_bytes(compiled_module->module_bytes(), + isolate); + DCHECK_GE(module_bytes->length(), offset); + DCHECK_GE(module_bytes->length() - offset, size); + Address raw = module_bytes->GetCharsAddress() + offset; + if (!unibrow::Utf8::Validate(reinterpret_cast<const byte*>(raw), size)) + return {}; // UTF8 decoding error for name. + DCHECK_GE(kMaxInt, offset); + DCHECK_GE(kMaxInt, size); + return isolate->factory()->NewStringFromUtf8SubString( + module_bytes, static_cast<int>(offset), static_cast<int>(size)); +} + bool WasmCompiledModule::IsWasmCompiledModule(Object* obj) { if (!obj->IsFixedArray()) return false; FixedArray* arr = FixedArray::cast(obj); if (arr->length() != PropertyIndices::Count) return false; Isolate* isolate = arr->GetIsolate(); -#define WCM_CHECK_SMALL_NUMBER(TYPE, NAME) \ - if (!arr->get(kID_##NAME)->IsSmi()) return false; -#define WCM_CHECK_OBJECT_OR_WEAK(TYPE, NAME) \ - if (!arr->get(kID_##NAME)->IsUndefined(isolate) && \ - !arr->get(kID_##NAME)->Is##TYPE()) \ - return false; -#define WCM_CHECK_OBJECT(TYPE, NAME) WCM_CHECK_OBJECT_OR_WEAK(TYPE, NAME) -#define WCM_CHECK_WEAK_LINK(TYPE, NAME) WCM_CHECK_OBJECT_OR_WEAK(WeakCell, NAME) +#define WCM_CHECK_TYPE(NAME, TYPE_CHECK) \ + do { \ + Object* obj = arr->get(kID_##NAME); \ + if (!(TYPE_CHECK)) return false; \ + } while (false); +#define WCM_CHECK_OBJECT(TYPE, NAME) \ + WCM_CHECK_TYPE(NAME, obj->IsUndefined(isolate) || obj->Is##TYPE()) +#define WCM_CHECK_WASM_OBJECT(TYPE, NAME) \ + WCM_CHECK_TYPE(NAME, TYPE::Is##TYPE(obj)) +#define WCM_CHECK_WEAK_LINK(TYPE, NAME) WCM_CHECK_OBJECT(WeakCell, NAME) +#define WCM_CHECK_SMALL_NUMBER(TYPE, NAME) WCM_CHECK_TYPE(NAME, obj->IsSmi()) #define WCM_CHECK(KIND, TYPE, NAME) WCM_CHECK_##KIND(TYPE, NAME) WCM_PROPERTY_TABLE(WCM_CHECK) #undef WCM_CHECK @@ -341,7 +527,7 @@ void WasmCompiledModule::PrintInstancesChain() { 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; + if (!current->has_weak_next_instance()) break; CHECK(!current->ptr_to_weak_next_instance()->cleared()); current = WasmCompiledModule::cast(current->ptr_to_weak_next_instance()->value()); @@ -350,6 +536,19 @@ void WasmCompiledModule::PrintInstancesChain() { #endif } +void WasmCompiledModule::RecreateModuleWrapper( + Isolate* isolate, Handle<WasmCompiledModule> compiled_module) { + // This method must only be called immediately after deserialization. + // At this point, no module wrapper exists, so the shared module data is + // incomplete. + Handle<WasmSharedModuleData> shared( + static_cast<WasmSharedModuleData*>(compiled_module->get(kID_shared)), + isolate); + DCHECK(!WasmSharedModuleData::IsWasmSharedModuleData(*shared)); + WasmSharedModuleData::RecreateModuleWrapper(isolate, shared); + DCHECK(WasmSharedModuleData::IsWasmSharedModuleData(*shared)); +} + uint32_t WasmCompiledModule::mem_size() const { return has_memory() ? memory()->byte_length()->Number() : default_mem_size(); } @@ -357,3 +556,310 @@ uint32_t WasmCompiledModule::mem_size() const { uint32_t WasmCompiledModule::default_mem_size() const { return min_mem_pages() * WasmModule::kPageSize; } + +MaybeHandle<String> WasmCompiledModule::GetFunctionNameOrNull( + Isolate* isolate, Handle<WasmCompiledModule> compiled_module, + uint32_t func_index) { + DCHECK_LT(func_index, compiled_module->module()->functions.size()); + WasmFunction& function = compiled_module->module()->functions[func_index]; + return WasmCompiledModule::ExtractUtf8StringFromModuleBytes( + isolate, compiled_module, function.name_offset, function.name_length); +} + +Handle<String> WasmCompiledModule::GetFunctionName( + Isolate* isolate, Handle<WasmCompiledModule> compiled_module, + uint32_t func_index) { + MaybeHandle<String> name = + GetFunctionNameOrNull(isolate, compiled_module, func_index); + if (!name.is_null()) return name.ToHandleChecked(); + return isolate->factory()->NewStringFromStaticChars("<WASM UNNAMED>"); +} + +Vector<const uint8_t> WasmCompiledModule::GetRawFunctionName( + uint32_t func_index) { + DCHECK_GT(module()->functions.size(), func_index); + WasmFunction& function = module()->functions[func_index]; + SeqOneByteString* bytes = module_bytes(); + DCHECK_GE(bytes->length(), function.name_offset); + DCHECK_GE(bytes->length() - function.name_offset, function.name_length); + return Vector<const uint8_t>(bytes->GetCharsAddress() + function.name_offset, + function.name_length); +} + +int WasmCompiledModule::GetFunctionOffset(uint32_t func_index) { + std::vector<WasmFunction>& functions = module()->functions; + if (static_cast<uint32_t>(func_index) >= functions.size()) return -1; + DCHECK_GE(kMaxInt, functions[func_index].code_start_offset); + return static_cast<int>(functions[func_index].code_start_offset); +} + +int WasmCompiledModule::GetContainingFunction(uint32_t byte_offset) { + std::vector<WasmFunction>& functions = module()->functions; + + // Binary search for a function containing the given position. + int left = 0; // inclusive + int right = static_cast<int>(functions.size()); // exclusive + if (right == 0) return false; + while (right - left > 1) { + int mid = left + (right - left) / 2; + if (functions[mid].code_start_offset <= byte_offset) { + left = mid; + } else { + right = mid; + } + } + // If the found function does not contains the given position, return -1. + WasmFunction& func = functions[left]; + if (byte_offset < func.code_start_offset || + byte_offset >= func.code_end_offset) { + return -1; + } + + return left; +} + +bool WasmCompiledModule::GetPositionInfo(uint32_t position, + Script::PositionInfo* info) { + int func_index = GetContainingFunction(position); + if (func_index < 0) return false; + + WasmFunction& function = module()->functions[func_index]; + + info->line = func_index; + info->column = position - function.code_start_offset; + info->line_start = function.code_start_offset; + info->line_end = function.code_end_offset; + return true; +} + +namespace { + +enum AsmJsOffsetTableEntryLayout { + kOTEByteOffset, + kOTECallPosition, + kOTENumberConvPosition, + kOTESize +}; + +Handle<ByteArray> GetDecodedAsmJsOffsetTable( + Handle<WasmCompiledModule> compiled_module, Isolate* isolate) { + DCHECK(compiled_module->is_asm_js()); + Handle<ByteArray> offset_table( + compiled_module->shared()->asm_js_offset_table(), isolate); + + // The last byte in the asm_js_offset_tables ByteArray tells whether it is + // still encoded (0) or decoded (1). + enum AsmJsTableType : int { Encoded = 0, Decoded = 1 }; + int table_type = offset_table->get(offset_table->length() - 1); + DCHECK(table_type == Encoded || table_type == Decoded); + if (table_type == Decoded) return offset_table; + + AsmJsOffsetsResult asm_offsets; + { + DisallowHeapAllocation no_gc; + const byte* bytes_start = offset_table->GetDataStartAddress(); + const byte* bytes_end = bytes_start + offset_table->length() - 1; + asm_offsets = wasm::DecodeAsmJsOffsets(bytes_start, bytes_end); + } + // Wasm bytes must be valid and must contain asm.js offset table. + DCHECK(asm_offsets.ok()); + DCHECK_GE(kMaxInt, asm_offsets.val.size()); + int num_functions = static_cast<int>(asm_offsets.val.size()); + int num_imported_functions = + static_cast<int>(compiled_module->module()->num_imported_functions); + DCHECK_EQ(compiled_module->module()->functions.size(), + static_cast<size_t>(num_functions) + num_imported_functions); + int num_entries = 0; + for (int func = 0; func < num_functions; ++func) { + size_t new_size = asm_offsets.val[func].size(); + DCHECK_LE(new_size, static_cast<size_t>(kMaxInt) - num_entries); + num_entries += static_cast<int>(new_size); + } + // One byte to encode that this is a decoded table. + DCHECK_GE(kMaxInt, + 1 + static_cast<uint64_t>(num_entries) * kOTESize * kIntSize); + int total_size = 1 + num_entries * kOTESize * kIntSize; + Handle<ByteArray> decoded_table = + isolate->factory()->NewByteArray(total_size, TENURED); + decoded_table->set(total_size - 1, AsmJsTableType::Decoded); + compiled_module->shared()->set_asm_js_offset_table(*decoded_table); + + int idx = 0; + std::vector<WasmFunction>& wasm_funs = compiled_module->module()->functions; + for (int func = 0; func < num_functions; ++func) { + std::vector<AsmJsOffsetEntry>& func_asm_offsets = asm_offsets.val[func]; + if (func_asm_offsets.empty()) continue; + int func_offset = + wasm_funs[num_imported_functions + func].code_start_offset; + for (AsmJsOffsetEntry& e : func_asm_offsets) { + // Byte offsets must be strictly monotonously increasing: + DCHECK_IMPLIES(idx > 0, func_offset + e.byte_offset > + decoded_table->get_int(idx - kOTESize)); + decoded_table->set_int(idx + kOTEByteOffset, func_offset + e.byte_offset); + decoded_table->set_int(idx + kOTECallPosition, e.source_position_call); + decoded_table->set_int(idx + kOTENumberConvPosition, + e.source_position_number_conversion); + idx += kOTESize; + } + } + DCHECK_EQ(total_size, idx * kIntSize + 1); + return decoded_table; +} + +} // namespace + +int WasmCompiledModule::GetAsmJsSourcePosition( + Handle<WasmCompiledModule> compiled_module, uint32_t func_index, + uint32_t byte_offset, bool is_at_number_conversion) { + Isolate* isolate = compiled_module->GetIsolate(); + Handle<ByteArray> offset_table = + GetDecodedAsmJsOffsetTable(compiled_module, isolate); + + DCHECK_LT(func_index, compiled_module->module()->functions.size()); + uint32_t func_code_offset = + compiled_module->module()->functions[func_index].code_start_offset; + uint32_t total_offset = func_code_offset + byte_offset; + + // Binary search for the total byte offset. + int left = 0; // inclusive + int right = offset_table->length() / kIntSize / kOTESize; // exclusive + DCHECK_LT(left, right); + while (right - left > 1) { + int mid = left + (right - left) / 2; + int mid_entry = offset_table->get_int(kOTESize * mid); + DCHECK_GE(kMaxInt, mid_entry); + if (static_cast<uint32_t>(mid_entry) <= total_offset) { + left = mid; + } else { + right = mid; + } + } + // There should be an entry for each position that could show up on the stack + // trace: + DCHECK_EQ(total_offset, offset_table->get_int(kOTESize * left)); + int idx = is_at_number_conversion ? kOTENumberConvPosition : kOTECallPosition; + return offset_table->get_int(kOTESize * left + idx); +} + +v8::debug::WasmDisassembly WasmCompiledModule::DisassembleFunction( + int func_index) { + DisallowHeapAllocation no_gc; + + if (func_index < 0 || + static_cast<uint32_t>(func_index) >= module()->functions.size()) + return {}; + + SeqOneByteString* module_bytes_str = module_bytes(); + Vector<const byte> module_bytes(module_bytes_str->GetChars(), + module_bytes_str->length()); + + std::ostringstream disassembly_os; + v8::debug::WasmDisassembly::OffsetTable offset_table; + + PrintWasmText(module(), module_bytes, static_cast<uint32_t>(func_index), + disassembly_os, &offset_table); + + return {disassembly_os.str(), std::move(offset_table)}; +} + +bool WasmCompiledModule::GetPossibleBreakpoints( + const v8::debug::Location& start, const v8::debug::Location& end, + std::vector<v8::debug::Location>* locations) { + DisallowHeapAllocation no_gc; + + std::vector<WasmFunction>& functions = module()->functions; + if (start.GetLineNumber() < 0 || start.GetColumnNumber() < 0 || + (!end.IsEmpty() && + (end.GetLineNumber() < 0 || end.GetColumnNumber() < 0))) + return false; + + // start_func_index, start_offset and end_func_index is inclusive. + // end_offset is exclusive. + // start_offset and end_offset are module-relative byte offsets. + uint32_t start_func_index = start.GetLineNumber(); + if (start_func_index >= functions.size()) return false; + int start_func_len = functions[start_func_index].code_end_offset - + functions[start_func_index].code_start_offset; + if (start.GetColumnNumber() > start_func_len) return false; + uint32_t start_offset = + functions[start_func_index].code_start_offset + start.GetColumnNumber(); + uint32_t end_func_index; + uint32_t end_offset; + if (end.IsEmpty()) { + // Default: everything till the end of the Script. + end_func_index = static_cast<uint32_t>(functions.size() - 1); + end_offset = functions[end_func_index].code_end_offset; + } else { + // If end is specified: Use it and check for valid input. + end_func_index = static_cast<uint32_t>(end.GetLineNumber()); + + // Special case: Stop before the start of the next function. Change to: Stop + // at the end of the function before, such that we don't disassemble the + // next function also. + if (end.GetColumnNumber() == 0 && end_func_index > 0) { + --end_func_index; + end_offset = functions[end_func_index].code_end_offset; + } else { + if (end_func_index >= functions.size()) return false; + end_offset = + functions[end_func_index].code_start_offset + end.GetColumnNumber(); + if (end_offset > functions[end_func_index].code_end_offset) return false; + } + } + + AccountingAllocator alloc; + Zone tmp(&alloc, ZONE_NAME); + const byte* module_start = module_bytes()->GetChars(); + + for (uint32_t func_idx = start_func_index; func_idx <= end_func_index; + ++func_idx) { + WasmFunction& func = functions[func_idx]; + if (func.code_start_offset == func.code_end_offset) continue; + + BodyLocalDecls locals(&tmp); + BytecodeIterator iterator(module_start + func.code_start_offset, + module_start + func.code_end_offset, &locals); + DCHECK_LT(0u, locals.encoded_size); + for (uint32_t offset : iterator.offsets()) { + uint32_t total_offset = func.code_start_offset + offset; + if (total_offset >= end_offset) { + DCHECK_EQ(end_func_index, func_idx); + break; + } + if (total_offset < start_offset) continue; + locations->push_back(v8::debug::Location(func_idx, offset)); + } + } + return true; +} + +Handle<WasmInstanceWrapper> WasmInstanceWrapper::New( + Isolate* isolate, Handle<WasmInstanceObject> instance) { + Handle<FixedArray> array = + isolate->factory()->NewFixedArray(kWrapperPropertyCount, TENURED); + Handle<WasmInstanceWrapper> instance_wrapper( + reinterpret_cast<WasmInstanceWrapper*>(*array), isolate); + instance_wrapper->set_instance_object(instance, isolate); + return instance_wrapper; +} + +bool WasmInstanceWrapper::IsWasmInstanceWrapper(Object* obj) { + if (!obj->IsFixedArray()) return false; + Handle<FixedArray> array = handle(FixedArray::cast(obj)); + if (array->length() != kWrapperPropertyCount) return false; + if (!array->get(kWrapperInstanceObject)->IsWeakCell()) return false; + Isolate* isolate = array->GetIsolate(); + if (!array->get(kNextInstanceWrapper)->IsUndefined(isolate) && + !array->get(kNextInstanceWrapper)->IsFixedArray()) + return false; + if (!array->get(kPreviousInstanceWrapper)->IsUndefined(isolate) && + !array->get(kPreviousInstanceWrapper)->IsFixedArray()) + return false; + return true; +} + +void WasmInstanceWrapper::set_instance_object(Handle<JSObject> instance, + Isolate* isolate) { + Handle<WeakCell> cell = isolate->factory()->NewWeakCell(instance); + set(kWrapperInstanceObject, *cell); +} diff --git a/deps/v8/src/wasm/wasm-objects.h b/deps/v8/src/wasm/wasm-objects.h index f74661f652..c478fe0419 100644 --- a/deps/v8/src/wasm/wasm-objects.h +++ b/deps/v8/src/wasm/wasm-objects.h @@ -5,8 +5,11 @@ #ifndef V8_WASM_OBJECTS_H_ #define V8_WASM_OBJECTS_H_ +#include "src/debug/interface-types.h" #include "src/objects-inl.h" +#include "src/trap-handler/trap-handler.h" #include "src/wasm/managed.h" +#include "src/wasm/wasm-limits.h" namespace v8 { namespace internal { @@ -17,19 +20,21 @@ struct WasmModule; class WasmCompiledModule; class WasmDebugInfo; class WasmInstanceObject; +class WasmInstanceWrapper; #define DECLARE_CASTS(name) \ static bool Is##name(Object* object); \ static name* cast(Object* object) +#define DECLARE_GETTER(name, type) type* name() + #define DECLARE_ACCESSORS(name, type) \ - type* get_##name(); \ - void set_##name(type* value) + void set_##name(type* value); \ + DECLARE_GETTER(name, type) #define DECLARE_OPTIONAL_ACCESSORS(name, type) \ bool has_##name(); \ - type* get_##name(); \ - void set_##name(type* value) + DECLARE_ACCESSORS(name, type) // Representation of a WebAssembly.Module JavaScript-level object. class WasmModuleObject : public JSObject { @@ -40,13 +45,6 @@ class WasmModuleObject : public JSObject { DECLARE_CASTS(WasmModuleObject); WasmCompiledModule* compiled_module(); - wasm::WasmModule* module(); - int num_functions(); - bool is_asm_js(); - int GetAsmWasmSourcePosition(int func_index, int byte_offset); - WasmDebugInfo* debug_info(); - void set_debug_info(WasmDebugInfo* debug_info); - MaybeHandle<String> GetFunctionName(Isolate* isolate, int func_index); static Handle<WasmModuleObject> New( Isolate* isolate, Handle<WasmCompiledModule> compiled_module); @@ -61,38 +59,44 @@ class WasmTableObject : public JSObject { DECLARE_CASTS(WasmTableObject); DECLARE_ACCESSORS(functions, FixedArray); - FixedArray* get_dispatch_tables(); + FixedArray* dispatch_tables(); uint32_t current_length(); - uint32_t maximum_length(); + bool has_maximum_length(); + int64_t maximum_length(); // Returns < 0 if no maximum. static Handle<WasmTableObject> New(Isolate* isolate, uint32_t initial, - uint32_t maximum, + int64_t maximum, Handle<FixedArray>* js_functions); - static bool Grow(Handle<WasmTableObject> table, uint32_t count); + static void Grow(Isolate* isolate, Handle<WasmTableObject> table, + uint32_t count); static Handle<FixedArray> AddDispatchTable( Isolate* isolate, Handle<WasmTableObject> table, Handle<WasmInstanceObject> instance, int table_index, - Handle<FixedArray> dispatch_table); + Handle<FixedArray> function_table, Handle<FixedArray> signature_table); }; // Representation of a WebAssembly.Memory JavaScript-level object. class WasmMemoryObject : public JSObject { public: // TODO(titzer): add the brand as an internal field instead of a property. - enum Fields : uint8_t { kArrayBuffer, kMaximum, kInstance, kFieldCount }; + enum Fields : uint8_t { kArrayBuffer, kMaximum, kInstancesLink, kFieldCount }; DECLARE_CASTS(WasmMemoryObject); DECLARE_ACCESSORS(buffer, JSArrayBuffer); + DECLARE_OPTIONAL_ACCESSORS(instances_link, WasmInstanceWrapper); - void AddInstance(WasmInstanceObject* object); + void AddInstance(Isolate* isolate, Handle<WasmInstanceObject> object); + void ResetInstancesLink(Isolate* isolate); uint32_t current_pages(); - int32_t maximum_pages(); // returns < 0 if there is no maximum + bool has_maximum_pages(); + int32_t maximum_pages(); // Returns < 0 if there is no maximum. static Handle<WasmMemoryObject> New(Isolate* isolate, Handle<JSArrayBuffer> buffer, - int maximum); + int32_t maximum); - static bool Grow(Handle<WasmMemoryObject> memory, uint32_t count); + static bool Grow(Isolate* isolate, Handle<WasmMemoryObject> memory, + uint32_t count); }; // Representation of a WebAssembly.Instance JavaScript-level object. @@ -105,6 +109,7 @@ class WasmInstanceObject : public JSObject { kMemoryArrayBuffer, kGlobalsArrayBuffer, kDebugInfo, + kWasmMemInstanceWrapper, kFieldCount }; @@ -115,10 +120,16 @@ class WasmInstanceObject : public JSObject { DECLARE_OPTIONAL_ACCESSORS(memory_buffer, JSArrayBuffer); DECLARE_OPTIONAL_ACCESSORS(memory_object, WasmMemoryObject); DECLARE_OPTIONAL_ACCESSORS(debug_info, WasmDebugInfo); + DECLARE_OPTIONAL_ACCESSORS(instance_wrapper, WasmInstanceWrapper); WasmModuleObject* module_object(); wasm::WasmModule* module(); + // Get the debug info associated with the given wasm object. + // If no debug info exists yet, it is created automatically. + static Handle<WasmDebugInfo> GetOrCreateDebugInfo( + Handle<WasmInstanceObject> instance); + static Handle<WasmInstanceObject> New( Isolate* isolate, Handle<WasmCompiledModule> compiled_module); }; @@ -135,9 +146,39 @@ class WasmExportedFunction : public JSFunction { static Handle<WasmExportedFunction> New(Isolate* isolate, Handle<WasmInstanceObject> instance, - Handle<String> name, - Handle<Code> export_wrapper, - int arity, int func_index); + MaybeHandle<String> maybe_name, + int func_index, int arity, + Handle<Code> export_wrapper); +}; + +// Information shared by all WasmCompiledModule objects for the same module. +class WasmSharedModuleData : public FixedArray { + enum Fields { + kModuleWrapper, + kModuleBytes, + kScript, + kAsmJsOffsetTable, + kFieldCount + }; + + public: + DECLARE_CASTS(WasmSharedModuleData); + + DECLARE_GETTER(module, wasm::WasmModule); + DECLARE_OPTIONAL_ACCESSORS(module_bytes, SeqOneByteString); + DECLARE_GETTER(script, Script); + DECLARE_OPTIONAL_ACCESSORS(asm_js_offset_table, ByteArray); + + static Handle<WasmSharedModuleData> New( + Isolate* isolate, Handle<Foreign> module_wrapper, + Handle<SeqOneByteString> module_bytes, Handle<Script> script, + Handle<ByteArray> asm_js_offset_table); + + // Check whether this module was generated from asm.js source. + bool is_asm_js(); + + // Recreate the ModuleWrapper from the module bytes after deserialization. + static void RecreateModuleWrapper(Isolate*, Handle<WasmSharedModuleData>); }; class WasmCompiledModule : public FixedArray { @@ -149,7 +190,7 @@ class WasmCompiledModule : public FixedArray { return reinterpret_cast<WasmCompiledModule*>(fixed_array); } -#define WCM_OBJECT_OR_WEAK(TYPE, NAME, ID) \ +#define WCM_OBJECT_OR_WEAK(TYPE, NAME, ID, TYPE_CHECK) \ Handle<TYPE> NAME() const { return handle(ptr_to_##NAME()); } \ \ MaybeHandle<TYPE> maybe_##NAME() const { \ @@ -157,9 +198,15 @@ class WasmCompiledModule : public FixedArray { return MaybeHandle<TYPE>(); \ } \ \ + TYPE* maybe_ptr_to_##NAME() const { \ + Object* obj = get(ID); \ + if (!(TYPE_CHECK)) return nullptr; \ + return TYPE::cast(obj); \ + } \ + \ TYPE* ptr_to_##NAME() const { \ Object* obj = get(ID); \ - if (!obj->Is##TYPE()) return nullptr; \ + DCHECK(TYPE_CHECK); \ return TYPE::cast(obj); \ } \ \ @@ -167,11 +214,18 @@ class WasmCompiledModule : public FixedArray { \ void set_ptr_to_##NAME(TYPE* value) { set(ID, value); } \ \ - bool has_##NAME() const { return get(ID)->Is##TYPE(); } \ + bool has_##NAME() const { \ + Object* obj = get(ID); \ + return TYPE_CHECK; \ + } \ \ void reset_##NAME() { set_undefined(ID); } -#define WCM_OBJECT(TYPE, NAME) WCM_OBJECT_OR_WEAK(TYPE, NAME, kID_##NAME) +#define WCM_OBJECT(TYPE, NAME) \ + WCM_OBJECT_OR_WEAK(TYPE, NAME, kID_##NAME, obj->Is##TYPE()) + +#define WCM_WASM_OBJECT(TYPE, NAME) \ + WCM_OBJECT_OR_WEAK(TYPE, NAME, kID_##NAME, TYPE::Is##TYPE(obj)) #define WCM_SMALL_NUMBER(TYPE, NAME) \ TYPE NAME() const { \ @@ -179,30 +233,29 @@ class WasmCompiledModule : public FixedArray { } \ void set_##NAME(TYPE value) { set(kID_##NAME, Smi::FromInt(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 WCM_WEAK_LINK(TYPE, NAME) \ + WCM_OBJECT_OR_WEAK(WeakCell, weak_##NAME, kID_##NAME, obj->IsWeakCell()); \ + \ + Handle<TYPE> NAME() const { \ + return handle(TYPE::cast(weak_##NAME()->value())); \ } -#define CORE_WCM_PROPERTY_TABLE(MACRO) \ - MACRO(OBJECT, FixedArray, code_table) \ - MACRO(OBJECT, Foreign, module_wrapper) \ - /* For debugging: */ \ - MACRO(OBJECT, SeqOneByteString, module_bytes) \ - MACRO(OBJECT, Script, script) \ - MACRO(OBJECT, ByteArray, asm_js_offset_tables) \ - /* End of debugging stuff */ \ - MACRO(OBJECT, FixedArray, function_tables) \ - MACRO(OBJECT, FixedArray, empty_function_tables) \ - MACRO(OBJECT, JSArrayBuffer, memory) \ - MACRO(SMALL_NUMBER, uint32_t, min_mem_pages) \ - MACRO(SMALL_NUMBER, uint32_t, max_mem_pages) \ - MACRO(WEAK_LINK, WasmCompiledModule, next_instance) \ - MACRO(WEAK_LINK, WasmCompiledModule, prev_instance) \ - MACRO(WEAK_LINK, JSObject, owning_instance) \ - MACRO(WEAK_LINK, JSObject, wasm_module) +#define CORE_WCM_PROPERTY_TABLE(MACRO) \ + MACRO(WASM_OBJECT, WasmSharedModuleData, shared) \ + MACRO(OBJECT, Context, native_context) \ + MACRO(SMALL_NUMBER, uint32_t, num_imported_functions) \ + MACRO(OBJECT, FixedArray, code_table) \ + MACRO(OBJECT, FixedArray, weak_exported_functions) \ + MACRO(OBJECT, FixedArray, function_tables) \ + MACRO(OBJECT, FixedArray, signature_tables) \ + MACRO(OBJECT, FixedArray, empty_function_tables) \ + MACRO(OBJECT, JSArrayBuffer, memory) \ + MACRO(SMALL_NUMBER, uint32_t, min_mem_pages) \ + MACRO(SMALL_NUMBER, uint32_t, max_mem_pages) \ + MACRO(WEAK_LINK, WasmCompiledModule, next_instance) \ + MACRO(WEAK_LINK, WasmCompiledModule, prev_instance) \ + MACRO(WEAK_LINK, JSObject, owning_instance) \ + MACRO(WEAK_LINK, WasmModuleObject, wasm_module) #if DEBUG #define DEBUG_ONLY_TABLE(MACRO) MACRO(SMALL_NUMBER, uint32_t, instance_id) @@ -223,8 +276,8 @@ class WasmCompiledModule : public FixedArray { }; public: - static Handle<WasmCompiledModule> New( - Isolate* isolate, Handle<Managed<wasm::WasmModule>> module_wrapper); + static Handle<WasmCompiledModule> New(Isolate* isolate, + Handle<WasmSharedModuleData> shared); static Handle<WasmCompiledModule> Clone(Isolate* isolate, Handle<WasmCompiledModule> module) { @@ -234,30 +287,93 @@ class WasmCompiledModule : public FixedArray { ret->reset_weak_owning_instance(); ret->reset_weak_next_instance(); ret->reset_weak_prev_instance(); + ret->reset_weak_exported_functions(); return ret; } uint32_t mem_size() const; uint32_t default_mem_size() const; - wasm::WasmModule* module() const; - #define DECLARATION(KIND, TYPE, NAME) WCM_##KIND(TYPE, NAME) WCM_PROPERTY_TABLE(DECLARATION) #undef DECLARATION +// Allow to call method on WasmSharedModuleData also on this object. +#define FORWARD_SHARED(type, name) \ + type name() { return shared()->name(); } + FORWARD_SHARED(SeqOneByteString*, module_bytes) + FORWARD_SHARED(wasm::WasmModule*, module) + FORWARD_SHARED(Script*, script) + FORWARD_SHARED(bool, is_asm_js) +#undef FORWARD_SHARED + static bool IsWasmCompiledModule(Object* obj); void PrintInstancesChain(); + // Recreate the ModuleWrapper from the module bytes after deserialization. static void RecreateModuleWrapper(Isolate* isolate, - Handle<FixedArray> compiled_module); + Handle<WasmCompiledModule> compiled_module); - // Extract a function name from the given wasm instance. + // Get the function name of the function identified by the given index. // Returns a null handle if the function is unnamed or the name is not a valid // UTF-8 string. - static MaybeHandle<String> GetFunctionName( - Handle<WasmCompiledModule> compiled_module, uint32_t func_index); + static MaybeHandle<String> GetFunctionNameOrNull( + Isolate* isolate, Handle<WasmCompiledModule> compiled_module, + uint32_t func_index); + + // Get the function name of the function identified by the given index. + // Returns "<WASM UNNAMED>" if the function is unnamed or the name is not a + // valid UTF-8 string. + static Handle<String> GetFunctionName( + Isolate* isolate, Handle<WasmCompiledModule> compiled_module, + uint32_t func_index); + + // Get the raw bytes of the function name of the function identified by the + // given index. + // Meant to be used for debugging or frame printing. + // Does not allocate, hence gc-safe. + Vector<const uint8_t> GetRawFunctionName(uint32_t func_index); + + // Return the byte offset of the function identified by the given index. + // The offset will be relative to the start of the module bytes. + // Returns -1 if the function index is invalid. + int GetFunctionOffset(uint32_t func_index); + + // Returns the function containing the given byte offset. + // Returns -1 if the byte offset is not contained in any function of this + // module. + int GetContainingFunction(uint32_t byte_offset); + + // Translate from byte offset in the module to function number and byte offset + // within that function, encoded as line and column in the position info. + // Returns true if the position is valid inside this module, false otherwise. + bool GetPositionInfo(uint32_t position, Script::PositionInfo* info); + + // Get the asm.js source position from a byte offset. + // Must only be called if the associated wasm object was created from asm.js. + static int GetAsmJsSourcePosition(Handle<WasmCompiledModule> compiled_module, + uint32_t func_index, uint32_t byte_offset, + bool is_at_number_conversion); + + // Compute the disassembly of a wasm function. + // Returns the disassembly string and a list of <byte_offset, line, column> + // entries, mapping wasm byte offsets to line and column in the disassembly. + // The list is guaranteed to be ordered by the byte_offset. + // Returns an empty string and empty vector if the function index is invalid. + debug::WasmDisassembly DisassembleFunction(int func_index); + + // Extract a portion of the wire bytes as UTF-8 string. + // Returns a null handle if the respective bytes do not form a valid UTF-8 + // string. + static MaybeHandle<String> ExtractUtf8StringFromModuleBytes( + Isolate* isolate, Handle<WasmCompiledModule> compiled_module, + uint32_t offset, uint32_t size); + + // Get a list of all possible breakpoints within a given range of this module. + bool GetPossibleBreakpoints(const debug::Location& start, + const debug::Location& end, + std::vector<debug::Location>* locations); private: void InitId(); @@ -267,36 +383,79 @@ class WasmCompiledModule : public FixedArray { class WasmDebugInfo : public FixedArray { public: - enum class Fields { kFieldCount }; - - static Handle<WasmDebugInfo> New(Handle<JSObject> wasm); + enum Fields { + kInstance, + kInterpreterHandle, + kInterpretedFunctions, + kFieldCount + }; - static bool IsDebugInfo(Object* object); - static WasmDebugInfo* cast(Object* object); + static Handle<WasmDebugInfo> New(Handle<WasmInstanceObject>); - JSObject* wasm_instance(); + static bool IsDebugInfo(Object*); + static WasmDebugInfo* cast(Object*); - bool SetBreakPoint(int byte_offset); + static void SetBreakpoint(Handle<WasmDebugInfo>, int func_index, int offset); - // Get the Script for the specified function. - static Script* GetFunctionScript(Handle<WasmDebugInfo> debug_info, - int func_index); + static void RunInterpreter(Handle<WasmDebugInfo>, int func_index, + uint8_t* arg_buffer); - // Disassemble the specified function from this module. - static Handle<String> DisassembleFunction(Handle<WasmDebugInfo> debug_info, - int func_index); + DECLARE_GETTER(wasm_instance, WasmInstanceObject); +}; - // Get the offset table for the specified function, mapping from byte offsets - // to position in the disassembly. - // Returns an array with three entries per instruction: byte offset, line and - // column. - static Handle<FixedArray> GetFunctionOffsetTable( - Handle<WasmDebugInfo> debug_info, int func_index); +class WasmInstanceWrapper : public FixedArray { + public: + static Handle<WasmInstanceWrapper> New(Isolate* isolate, + Handle<WasmInstanceObject> instance); + static WasmInstanceWrapper* cast(Object* fixed_array) { + SLOW_DCHECK(IsWasmInstanceWrapper(fixed_array)); + return reinterpret_cast<WasmInstanceWrapper*>(fixed_array); + } + static bool IsWasmInstanceWrapper(Object* obj); + bool has_instance() { return get(kWrapperInstanceObject)->IsWeakCell(); } + Handle<WasmInstanceObject> instance_object() { + Object* obj = get(kWrapperInstanceObject); + DCHECK(obj->IsWeakCell()); + WeakCell* cell = WeakCell::cast(obj); + DCHECK(cell->value()->IsJSObject()); + return handle(WasmInstanceObject::cast(cell->value())); + } + bool has_next() { return IsWasmInstanceWrapper(get(kNextInstanceWrapper)); } + bool has_previous() { + return IsWasmInstanceWrapper(get(kPreviousInstanceWrapper)); + } + void set_instance_object(Handle<JSObject> instance, Isolate* isolate); + void set_next_wrapper(Object* obj) { + DCHECK(IsWasmInstanceWrapper(obj)); + set(kNextInstanceWrapper, obj); + } + void set_previous_wrapper(Object* obj) { + DCHECK(IsWasmInstanceWrapper(obj)); + set(kPreviousInstanceWrapper, obj); + } + Handle<WasmInstanceWrapper> next_wrapper() { + Object* obj = get(kNextInstanceWrapper); + DCHECK(IsWasmInstanceWrapper(obj)); + return handle(WasmInstanceWrapper::cast(obj)); + } + Handle<WasmInstanceWrapper> previous_wrapper() { + Object* obj = get(kPreviousInstanceWrapper); + DCHECK(IsWasmInstanceWrapper(obj)); + return handle(WasmInstanceWrapper::cast(obj)); + } + void reset_next_wrapper() { set_undefined(kNextInstanceWrapper); } + void reset_previous_wrapper() { set_undefined(kPreviousInstanceWrapper); } + void reset() { + for (int kID = 0; kID < kWrapperPropertyCount; kID++) set_undefined(kID); + } - // Get the asm.js source position from a byte offset. - // Must only be called if the associated wasm object was created from asm.js. - static int GetAsmJsSourcePosition(Handle<WasmDebugInfo> debug_info, - int func_index, int byte_offset); + private: + enum { + kWrapperInstanceObject, + kNextInstanceWrapper, + kPreviousInstanceWrapper, + kWrapperPropertyCount + }; }; #undef DECLARE_ACCESSORS diff --git a/deps/v8/src/wasm/wasm-opcodes.cc b/deps/v8/src/wasm/wasm-opcodes.cc index 8f81b81a50..2a00a73cbd 100644 --- a/deps/v8/src/wasm/wasm-opcodes.cc +++ b/deps/v8/src/wasm/wasm-opcodes.cc @@ -4,13 +4,14 @@ #include "src/wasm/wasm-opcodes.h" #include "src/messages.h" +#include "src/runtime/runtime.h" #include "src/signature.h" namespace v8 { namespace internal { namespace wasm { -typedef Signature<LocalType> FunctionSig; +typedef Signature<ValueType> FunctionSig; const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) { switch (opcode) { @@ -69,7 +70,7 @@ enum WasmOpcodeSig { FOREACH_SIGNATURE(DECLARE_SIG_ENUM) }; // TODO(titzer): not static-initializer safe. Wrap in LazyInstance. #define DECLARE_SIG(name, ...) \ - static LocalType kTypes_##name[] = {__VA_ARGS__}; \ + static ValueType kTypes_##name[] = {__VA_ARGS__}; \ static const FunctionSig kSig_##name( \ 1, static_cast<int>(arraysize(kTypes_##name)) - 1, kTypes_##name); diff --git a/deps/v8/src/wasm/wasm-opcodes.h b/deps/v8/src/wasm/wasm-opcodes.h index ec22579bd7..6c231ac69b 100644 --- a/deps/v8/src/wasm/wasm-opcodes.h +++ b/deps/v8/src/wasm/wasm-opcodes.h @@ -7,6 +7,7 @@ #include "src/globals.h" #include "src/machine-type.h" +#include "src/runtime/runtime.h" #include "src/signature.h" namespace v8 { @@ -14,7 +15,7 @@ namespace internal { namespace wasm { // Binary encoding of local types. -enum LocalTypeCode { +enum ValueTypeCode { kLocalVoid = 0x40, kLocalI32 = 0x7f, kLocalI64 = 0x7e, @@ -26,19 +27,18 @@ enum LocalTypeCode { // Type code for multi-value block types. static const uint8_t kMultivalBlock = 0x41; -// We reuse the internal machine type to represent WebAssembly AST types. +// We reuse the internal machine type to represent WebAssembly types. // A typedef improves readability without adding a whole new type system. -typedef MachineRepresentation LocalType; -const LocalType kAstStmt = MachineRepresentation::kNone; -const LocalType kAstI32 = MachineRepresentation::kWord32; -const LocalType kAstI64 = MachineRepresentation::kWord64; -const LocalType kAstF32 = MachineRepresentation::kFloat32; -const LocalType kAstF64 = MachineRepresentation::kFloat64; -const LocalType kAstS128 = MachineRepresentation::kSimd128; -// We use kTagged here because kNone is already used by kAstStmt. -const LocalType kAstEnd = MachineRepresentation::kTagged; - -typedef Signature<LocalType> FunctionSig; +typedef MachineRepresentation ValueType; +const ValueType kWasmStmt = MachineRepresentation::kNone; +const ValueType kWasmI32 = MachineRepresentation::kWord32; +const ValueType kWasmI64 = MachineRepresentation::kWord64; +const ValueType kWasmF32 = MachineRepresentation::kFloat32; +const ValueType kWasmF64 = MachineRepresentation::kFloat64; +const ValueType kWasmS128 = MachineRepresentation::kSimd128; +const ValueType kWasmVar = MachineRepresentation::kTagged; + +typedef Signature<ValueType> FunctionSig; std::ostream& operator<<(std::ostream& os, const FunctionSig& function); typedef Vector<const char> WasmName; @@ -77,8 +77,7 @@ const WasmCodePosition kNoCodePosition = -1; V(I32Const, 0x41, _) \ V(I64Const, 0x42, _) \ V(F32Const, 0x43, _) \ - V(F64Const, 0x44, _) \ - V(I8Const, 0xcb, _ /* TODO(titzer): V8 specific, remove */) + V(F64Const, 0x44, _) // Load memory expressions. #define FOREACH_LOAD_MEM_OPCODE(V) \ @@ -276,7 +275,6 @@ const WasmCodePosition kNoCodePosition = -1; #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) \ @@ -296,13 +294,9 @@ const WasmCodePosition kNoCodePosition = -1; 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) \ @@ -330,7 +324,6 @@ const WasmCodePosition kNoCodePosition = -1; 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) \ @@ -360,7 +353,6 @@ const WasmCodePosition kNoCodePosition = -1; 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) \ @@ -392,13 +384,20 @@ const WasmCodePosition kNoCodePosition = -1; V(S128And, 0xe576, s_ss) \ V(S128Ior, 0xe577, s_ss) \ V(S128Xor, 0xe578, s_ss) \ - V(S128Not, 0xe579, s_s) + V(S128Not, 0xe579, s_s) \ + V(S32x4Select, 0xe580, s_sss) \ + V(S32x4Swizzle, 0xe581, s_s) \ + V(S32x4Shuffle, 0xe582, s_ss) #define FOREACH_SIMD_1_OPERAND_OPCODE(V) \ V(F32x4ExtractLane, 0xe501, _) \ + V(F32x4ReplaceLane, 0xe502, _) \ V(I32x4ExtractLane, 0xe51c, _) \ + V(I32x4ReplaceLane, 0xe51d, _) \ V(I16x8ExtractLane, 0xe539, _) \ - V(I8x16ExtractLane, 0xe558, _) + V(I16x8ReplaceLane, 0xe53a, _) \ + V(I8x16ExtractLane, 0xe558, _) \ + V(I8x16ReplaceLane, 0xe559, _) #define FOREACH_ATOMIC_OPCODE(V) \ V(I32AtomicAdd8S, 0xe601, i_ii) \ @@ -451,45 +450,43 @@ const WasmCodePosition kNoCodePosition = -1; FOREACH_ATOMIC_OPCODE(V) // All signatures. -#define FOREACH_SIGNATURE(V) \ - FOREACH_SIMD_SIGNATURE(V) \ - V(i_ii, kAstI32, kAstI32, kAstI32) \ - V(i_i, kAstI32, kAstI32) \ - V(i_v, kAstI32) \ - V(i_ff, kAstI32, kAstF32, kAstF32) \ - V(i_f, kAstI32, kAstF32) \ - V(i_dd, kAstI32, kAstF64, kAstF64) \ - V(i_d, kAstI32, kAstF64) \ - V(i_l, kAstI32, kAstI64) \ - V(l_ll, kAstI64, kAstI64, kAstI64) \ - V(i_ll, kAstI32, kAstI64, kAstI64) \ - V(l_l, kAstI64, kAstI64) \ - V(l_i, kAstI64, kAstI32) \ - V(l_f, kAstI64, kAstF32) \ - V(l_d, kAstI64, kAstF64) \ - V(f_ff, kAstF32, kAstF32, kAstF32) \ - V(f_f, kAstF32, kAstF32) \ - V(f_d, kAstF32, kAstF64) \ - V(f_i, kAstF32, kAstI32) \ - V(f_l, kAstF32, kAstI64) \ - V(d_dd, kAstF64, kAstF64, kAstF64) \ - V(d_d, kAstF64, kAstF64) \ - V(d_f, kAstF64, kAstF32) \ - V(d_i, kAstF64, kAstI32) \ - V(d_l, kAstF64, kAstI64) \ - V(d_id, kAstF64, kAstI32, kAstF64) \ - V(f_if, kAstF32, kAstI32, kAstF32) \ - V(l_il, kAstI64, kAstI32, kAstI64) - -#define FOREACH_SIMD_SIGNATURE(V) \ - V(s_s, kAstS128, kAstS128) \ - V(s_f, kAstS128, kAstF32) \ - 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(s_sii, kAstS128, kAstS128, kAstI32, kAstI32) \ - V(s_si, kAstS128, kAstS128, kAstI32) +#define FOREACH_SIGNATURE(V) \ + FOREACH_SIMD_SIGNATURE(V) \ + V(i_ii, kWasmI32, kWasmI32, kWasmI32) \ + V(i_i, kWasmI32, kWasmI32) \ + V(i_v, kWasmI32) \ + V(i_ff, kWasmI32, kWasmF32, kWasmF32) \ + V(i_f, kWasmI32, kWasmF32) \ + V(i_dd, kWasmI32, kWasmF64, kWasmF64) \ + V(i_d, kWasmI32, kWasmF64) \ + V(i_l, kWasmI32, kWasmI64) \ + V(l_ll, kWasmI64, kWasmI64, kWasmI64) \ + V(i_ll, kWasmI32, kWasmI64, kWasmI64) \ + V(l_l, kWasmI64, kWasmI64) \ + V(l_i, kWasmI64, kWasmI32) \ + V(l_f, kWasmI64, kWasmF32) \ + V(l_d, kWasmI64, kWasmF64) \ + V(f_ff, kWasmF32, kWasmF32, kWasmF32) \ + V(f_f, kWasmF32, kWasmF32) \ + V(f_d, kWasmF32, kWasmF64) \ + V(f_i, kWasmF32, kWasmI32) \ + V(f_l, kWasmF32, kWasmI64) \ + V(d_dd, kWasmF64, kWasmF64, kWasmF64) \ + V(d_d, kWasmF64, kWasmF64) \ + V(d_f, kWasmF64, kWasmF32) \ + V(d_i, kWasmF64, kWasmI32) \ + V(d_l, kWasmF64, kWasmI64) \ + V(d_id, kWasmF64, kWasmI32, kWasmF64) \ + V(f_if, kWasmF32, kWasmI32, kWasmF32) \ + V(l_il, kWasmI64, kWasmI32, kWasmI64) + +#define FOREACH_SIMD_SIGNATURE(V) \ + V(s_s, kWasmS128, kWasmS128) \ + V(s_f, kWasmS128, kWasmF32) \ + V(s_ss, kWasmS128, kWasmS128, kWasmS128) \ + V(s_sss, kWasmS128, kWasmS128, kWasmS128, kWasmS128) \ + V(s_i, kWasmS128, kWasmI32) \ + V(s_si, kWasmS128, kWasmS128, kWasmI32) #define FOREACH_PREFIX(V) \ V(Simd, 0xe5) \ @@ -514,8 +511,7 @@ enum WasmOpcode { V(TrapRemByZero) \ V(TrapFloatUnrepresentable) \ V(TrapFuncInvalid) \ - V(TrapFuncSigMismatch) \ - V(TrapInvalidIndex) + V(TrapFuncSigMismatch) enum TrapReason { #define DECLARE_ENUM(name) k##name, @@ -541,21 +537,21 @@ class V8_EXPORT_PRIVATE WasmOpcodes { return 1 << ElementSizeLog2Of(type.representation()); } - static byte MemSize(LocalType type) { return 1 << ElementSizeLog2Of(type); } + static byte MemSize(ValueType type) { return 1 << ElementSizeLog2Of(type); } - static LocalTypeCode LocalTypeCodeFor(LocalType type) { + static ValueTypeCode ValueTypeCodeFor(ValueType type) { switch (type) { - case kAstI32: + case kWasmI32: return kLocalI32; - case kAstI64: + case kWasmI64: return kLocalI64; - case kAstF32: + case kWasmF32: return kLocalF32; - case kAstF64: + case kWasmF64: return kLocalF64; - case kAstS128: + case kWasmS128: return kLocalS128; - case kAstStmt: + case kWasmStmt: return kLocalVoid; default: UNREACHABLE(); @@ -563,19 +559,19 @@ class V8_EXPORT_PRIVATE WasmOpcodes { } } - static MachineType MachineTypeFor(LocalType type) { + static MachineType MachineTypeFor(ValueType type) { switch (type) { - case kAstI32: + case kWasmI32: return MachineType::Int32(); - case kAstI64: + case kWasmI64: return MachineType::Int64(); - case kAstF32: + case kWasmF32: return MachineType::Float32(); - case kAstF64: + case kWasmF64: return MachineType::Float64(); - case kAstS128: + case kWasmS128: return MachineType::Simd128(); - case kAstStmt: + case kWasmStmt: return MachineType::None(); default: UNREACHABLE(); @@ -583,32 +579,32 @@ class V8_EXPORT_PRIVATE WasmOpcodes { } } - static LocalType LocalTypeFor(MachineType type) { + static ValueType ValueTypeFor(MachineType type) { if (type == MachineType::Int8()) { - return kAstI32; + return kWasmI32; } else if (type == MachineType::Uint8()) { - return kAstI32; + return kWasmI32; } else if (type == MachineType::Int16()) { - return kAstI32; + return kWasmI32; } else if (type == MachineType::Uint16()) { - return kAstI32; + return kWasmI32; } else if (type == MachineType::Int32()) { - return kAstI32; + return kWasmI32; } else if (type == MachineType::Uint32()) { - return kAstI32; + return kWasmI32; } else if (type == MachineType::Int64()) { - return kAstI64; + return kWasmI64; } else if (type == MachineType::Uint64()) { - return kAstI64; + return kWasmI64; } else if (type == MachineType::Float32()) { - return kAstF32; + return kWasmF32; } else if (type == MachineType::Float64()) { - return kAstF64; + return kWasmF64; } else if (type == MachineType::Simd128()) { - return kAstS128; + return kWasmS128; } else { UNREACHABLE(); - return kAstI32; + return kWasmI32; } } @@ -639,44 +635,43 @@ class V8_EXPORT_PRIVATE WasmOpcodes { } } - static char ShortNameOf(LocalType type) { + static char ShortNameOf(ValueType type) { switch (type) { - case kAstI32: + case kWasmI32: return 'i'; - case kAstI64: + case kWasmI64: return 'l'; - case kAstF32: + case kWasmF32: return 'f'; - case kAstF64: + case kWasmF64: return 'd'; - case kAstS128: + case kWasmS128: return 's'; - case kAstStmt: + case kWasmStmt: return 'v'; - case kAstEnd: - return 'x'; + case kWasmVar: + return '*'; default: - UNREACHABLE(); return '?'; } } - static const char* TypeName(LocalType type) { + static const char* TypeName(ValueType type) { switch (type) { - case kAstI32: + case kWasmI32: return "i32"; - case kAstI64: + case kWasmI64: return "i64"; - case kAstF32: + case kWasmF32: return "f32"; - case kAstF64: + case kWasmF64: return "f64"; - case kAstS128: + case kWasmS128: return "s128"; - case kAstStmt: + case kWasmStmt: return "<stmt>"; - case kAstEnd: - return "<end>"; + case kWasmVar: + return "<var>"; default: return "<unknown>"; } diff --git a/deps/v8/src/wasm/wasm-result.cc b/deps/v8/src/wasm/wasm-result.cc index 6d535e3f57..e22f9ad442 100644 --- a/deps/v8/src/wasm/wasm-result.cc +++ b/deps/v8/src/wasm/wasm-result.cc @@ -64,14 +64,25 @@ void ErrorThrower::RangeError(const char* format, ...) { void ErrorThrower::CompileError(const char* format, ...) { if (error()) return; + wasm_error_ = true; va_list arguments; va_start(arguments, format); Format(isolate_->wasm_compile_error_function(), format, arguments); va_end(arguments); } +void ErrorThrower::LinkError(const char* format, ...) { + if (error()) return; + wasm_error_ = true; + va_list arguments; + va_start(arguments, format); + Format(isolate_->wasm_link_error_function(), format, arguments); + va_end(arguments); +} + void ErrorThrower::RuntimeError(const char* format, ...) { if (error()) return; + wasm_error_ = true; va_list arguments; va_start(arguments, format); Format(isolate_->wasm_runtime_error_function(), format, arguments); diff --git a/deps/v8/src/wasm/wasm-result.h b/deps/v8/src/wasm/wasm-result.h index 53c6b8dcf9..004ac22d33 100644 --- a/deps/v8/src/wasm/wasm-result.h +++ b/deps/v8/src/wasm/wasm-result.h @@ -95,6 +95,7 @@ class V8_EXPORT_PRIVATE ErrorThrower { PRINTF_FORMAT(2, 3) void TypeError(const char* fmt, ...); PRINTF_FORMAT(2, 3) void RangeError(const char* fmt, ...); PRINTF_FORMAT(2, 3) void CompileError(const char* fmt, ...); + PRINTF_FORMAT(2, 3) void LinkError(const char* fmt, ...); PRINTF_FORMAT(2, 3) void RuntimeError(const char* fmt, ...); template <typename T> @@ -111,6 +112,7 @@ class V8_EXPORT_PRIVATE ErrorThrower { } bool error() const { return !exception_.is_null(); } + bool wasm_error() { return wasm_error_; } private: void Format(i::Handle<i::JSFunction> constructor, const char* fmt, va_list); @@ -118,6 +120,7 @@ class V8_EXPORT_PRIVATE ErrorThrower { i::Isolate* isolate_; const char* context_; i::Handle<i::Object> exception_; + bool wasm_error_ = false; }; } // namespace wasm } // namespace internal diff --git a/deps/v8/src/wasm/wasm-text.cc b/deps/v8/src/wasm/wasm-text.cc new file mode 100644 index 0000000000..1878095b09 --- /dev/null +++ b/deps/v8/src/wasm/wasm-text.cc @@ -0,0 +1,312 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/wasm/wasm-text.h" + +#include "src/debug/interface-types.h" +#include "src/ostreams.h" +#include "src/vector.h" +#include "src/wasm/function-body-decoder.h" +#include "src/wasm/wasm-module.h" +#include "src/wasm/wasm-opcodes.h" +#include "src/zone/zone.h" + +using namespace v8; +using namespace v8::internal; +using namespace v8::internal::wasm; + +namespace { +const char *GetOpName(WasmOpcode opcode) { +#define CASE_OP(name, str) \ + case kExpr##name: \ + return str; +#define CASE_I32_OP(name, str) CASE_OP(I32##name, "i32." str) +#define CASE_I64_OP(name, str) CASE_OP(I64##name, "i64." str) +#define CASE_F32_OP(name, str) CASE_OP(F32##name, "f32." str) +#define CASE_F64_OP(name, str) CASE_OP(F64##name, "f64." str) +#define CASE_INT_OP(name, str) CASE_I32_OP(name, str) CASE_I64_OP(name, str) +#define CASE_FLOAT_OP(name, str) CASE_F32_OP(name, str) CASE_F64_OP(name, str) +#define CASE_ALL_OP(name, str) CASE_FLOAT_OP(name, str) CASE_INT_OP(name, str) +#define CASE_SIGN_OP(TYPE, name, str) \ + CASE_##TYPE##_OP(name##S, str "_s") CASE_##TYPE##_OP(name##U, str "_u") +#define CASE_ALL_SIGN_OP(name, str) \ + CASE_FLOAT_OP(name, str) CASE_SIGN_OP(INT, name, str) +#define CASE_CONVERT_OP(name, RES, SRC, src_suffix, str) \ + CASE_##RES##_OP(U##name##SRC, str "_u/" src_suffix) \ + CASE_##RES##_OP(S##name##SRC, str "_s/" src_suffix) + + switch (opcode) { + CASE_INT_OP(Eqz, "eqz") + CASE_ALL_OP(Eq, "eq") + CASE_ALL_OP(Ne, "ne") + CASE_ALL_OP(Add, "add") + CASE_ALL_OP(Sub, "sub") + CASE_ALL_OP(Mul, "mul") + CASE_ALL_SIGN_OP(Lt, "lt") + CASE_ALL_SIGN_OP(Gt, "gt") + CASE_ALL_SIGN_OP(Le, "le") + CASE_ALL_SIGN_OP(Ge, "ge") + CASE_INT_OP(Clz, "clz") + CASE_INT_OP(Ctz, "ctz") + CASE_INT_OP(Popcnt, "popcnt") + CASE_ALL_SIGN_OP(Div, "div") + CASE_SIGN_OP(INT, Rem, "rem") + CASE_INT_OP(And, "and") + CASE_INT_OP(Ior, "or") + CASE_INT_OP(Xor, "xor") + CASE_INT_OP(Shl, "shl") + CASE_SIGN_OP(INT, Shr, "shr") + CASE_INT_OP(Rol, "rol") + CASE_INT_OP(Ror, "ror") + CASE_FLOAT_OP(Abs, "abs") + CASE_FLOAT_OP(Neg, "neg") + CASE_FLOAT_OP(Ceil, "ceil") + CASE_FLOAT_OP(Floor, "floor") + CASE_FLOAT_OP(Trunc, "trunc") + CASE_FLOAT_OP(NearestInt, "nearest") + CASE_FLOAT_OP(Sqrt, "sqrt") + CASE_FLOAT_OP(Min, "min") + CASE_FLOAT_OP(Max, "max") + CASE_FLOAT_OP(CopySign, "copysign") + CASE_I32_OP(ConvertI64, "wrap/i64") + CASE_CONVERT_OP(Convert, INT, F32, "f32", "trunc") + CASE_CONVERT_OP(Convert, INT, F64, "f64", "trunc") + CASE_CONVERT_OP(Convert, I64, I32, "i32", "extend") + CASE_CONVERT_OP(Convert, F32, I32, "i32", "convert") + CASE_CONVERT_OP(Convert, F32, I64, "i64", "convert") + CASE_F32_OP(ConvertF64, "demote/f64") + CASE_CONVERT_OP(Convert, F64, I32, "i32", "convert") + CASE_CONVERT_OP(Convert, F64, I64, "i64", "convert") + CASE_F64_OP(ConvertF32, "promote/f32") + CASE_I32_OP(ReinterpretF32, "reinterpret/f32") + CASE_I64_OP(ReinterpretF64, "reinterpret/f64") + CASE_F32_OP(ReinterpretI32, "reinterpret/i32") + CASE_F64_OP(ReinterpretI64, "reinterpret/i64") + CASE_OP(Unreachable, "unreachable") + CASE_OP(Nop, "nop") + CASE_OP(Return, "return") + CASE_OP(MemorySize, "current_memory") + CASE_OP(GrowMemory, "grow_memory") + CASE_OP(Loop, "loop") + CASE_OP(If, "if") + CASE_OP(Block, "block") + CASE_OP(Try, "try") + CASE_OP(Throw, "throw") + CASE_OP(Catch, "catch") + CASE_OP(Drop, "drop") + CASE_OP(Select, "select") + CASE_ALL_OP(LoadMem, "load") + CASE_SIGN_OP(INT, LoadMem8, "load8") + CASE_SIGN_OP(INT, LoadMem16, "load16") + CASE_SIGN_OP(I64, LoadMem32, "load32") + CASE_ALL_OP(StoreMem, "store") + CASE_INT_OP(StoreMem8, "store8") + CASE_INT_OP(StoreMem16, "store16") + CASE_I64_OP(StoreMem32, "store32") + CASE_OP(SetLocal, "set_local") + CASE_OP(GetLocal, "get_local") + CASE_OP(TeeLocal, "tee_local") + CASE_OP(GetGlobal, "get_global") + CASE_OP(SetGlobal, "set_global") + CASE_OP(Br, "br") + CASE_OP(BrIf, "br_if") + default: + UNREACHABLE(); + return ""; + } +} + +bool IsValidFunctionName(const Vector<const char> &name) { + if (name.is_empty()) return false; + const char *special_chars = "_.+-*/\\^~=<>!?@#$%&|:'`"; + for (char c : name) { + bool valid_char = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || strchr(special_chars, c); + if (!valid_char) return false; + } + return true; +} + +} // namespace + +void wasm::PrintWasmText(const WasmModule *module, + const ModuleWireBytes &wire_bytes, uint32_t func_index, + std::ostream &os, + debug::WasmDisassembly::OffsetTable *offset_table) { + DCHECK_NOT_NULL(module); + DCHECK_GT(module->functions.size(), func_index); + const WasmFunction *fun = &module->functions[func_index]; + + AccountingAllocator allocator; + Zone zone(&allocator, ZONE_NAME); + int line_nr = 0; + int control_depth = 1; + + // Print the function signature. + os << "func"; + WasmName fun_name = wire_bytes.GetNameOrNull(fun); + if (IsValidFunctionName(fun_name)) { + os << " $"; + os.write(fun_name.start(), fun_name.length()); + } + size_t param_count = fun->sig->parameter_count(); + if (param_count) { + os << " (param"; + for (size_t i = 0; i < param_count; ++i) + os << ' ' << WasmOpcodes::TypeName(fun->sig->GetParam(i)); + os << ')'; + } + size_t return_count = fun->sig->return_count(); + if (return_count) { + os << " (result"; + for (size_t i = 0; i < return_count; ++i) + os << ' ' << WasmOpcodes::TypeName(fun->sig->GetReturn(i)); + os << ')'; + } + os << "\n"; + ++line_nr; + + // Print the local declarations. + BodyLocalDecls decls(&zone); + Vector<const byte> func_bytes = wire_bytes.module_bytes.SubVector( + fun->code_start_offset, fun->code_end_offset); + BytecodeIterator i(func_bytes.begin(), func_bytes.end(), &decls); + DCHECK_LT(func_bytes.begin(), i.pc()); + if (!decls.type_list.empty()) { + os << "(local"; + for (const ValueType &v : decls.type_list) { + os << ' ' << WasmOpcodes::TypeName(v); + } + os << ")\n"; + ++line_nr; + } + + for (; i.has_next(); i.next()) { + WasmOpcode opcode = i.current(); + if (opcode == kExprElse || opcode == kExprEnd) --control_depth; + + DCHECK_LE(0, control_depth); + const int kMaxIndentation = 64; + int indentation = std::min(kMaxIndentation, 2 * control_depth); + if (offset_table) { + offset_table->push_back(debug::WasmDisassemblyOffsetTableEntry( + i.pc_offset(), line_nr, indentation)); + } + + // 64 whitespaces + const char padding[kMaxIndentation + 1] = + " "; + os.write(padding, indentation); + + switch (opcode) { + case kExprLoop: + case kExprIf: + case kExprBlock: + case kExprTry: { + BlockTypeOperand operand(&i, i.pc()); + os << GetOpName(opcode); + for (unsigned i = 0; i < operand.arity; i++) { + os << " " << WasmOpcodes::TypeName(operand.read_entry(i)); + } + control_depth++; + break; + } + case kExprBr: + case kExprBrIf: { + BreakDepthOperand operand(&i, i.pc()); + os << GetOpName(opcode) << ' ' << operand.depth; + break; + } + case kExprElse: + os << "else"; + control_depth++; + break; + case kExprEnd: + os << "end"; + break; + case kExprBrTable: { + BranchTableOperand operand(&i, i.pc()); + BranchTableIterator iterator(&i, operand); + os << "br_table"; + while (iterator.has_next()) os << ' ' << iterator.next(); + break; + } + case kExprCallIndirect: { + CallIndirectOperand operand(&i, i.pc()); + DCHECK_EQ(0, operand.table_index); + os << "call_indirect " << operand.index; + break; + } + case kExprCallFunction: { + CallFunctionOperand operand(&i, i.pc()); + os << "call " << operand.index; + break; + } + case kExprGetLocal: + case kExprSetLocal: + case kExprTeeLocal: + case kExprCatch: { + LocalIndexOperand operand(&i, i.pc()); + os << GetOpName(opcode) << ' ' << operand.index; + break; + } + case kExprGetGlobal: + case kExprSetGlobal: { + GlobalIndexOperand operand(&i, i.pc()); + os << GetOpName(opcode) << ' ' << operand.index; + break; + } +#define CASE_CONST(type, str, cast_type) \ + case kExpr##type##Const: { \ + Imm##type##Operand operand(&i, i.pc()); \ + os << #str ".const " << static_cast<cast_type>(operand.value); \ + break; \ + } + CASE_CONST(I32, i32, int32_t) + CASE_CONST(I64, i64, int64_t) + CASE_CONST(F32, f32, float) + CASE_CONST(F64, f64, double) + +#define CASE_OPCODE(opcode, _, __) case kExpr##opcode: + FOREACH_LOAD_MEM_OPCODE(CASE_OPCODE) + FOREACH_STORE_MEM_OPCODE(CASE_OPCODE) { + MemoryAccessOperand operand(&i, i.pc(), kMaxUInt32); + os << GetOpName(opcode) << " offset=" << operand.offset + << " align=" << (1ULL << operand.alignment); + break; + } + + FOREACH_SIMPLE_OPCODE(CASE_OPCODE) + case kExprUnreachable: + case kExprNop: + case kExprReturn: + case kExprMemorySize: + case kExprGrowMemory: + case kExprDrop: + case kExprSelect: + case kExprThrow: + os << GetOpName(opcode); + break; + + // This group is just printed by their internal opcode name, as they + // should never be shown to end-users. + FOREACH_ASMJS_COMPAT_OPCODE(CASE_OPCODE) + // TODO(wasm): Add correct printing for SIMD and atomic opcodes once + // they are publicly available. + FOREACH_SIMD_0_OPERAND_OPCODE(CASE_OPCODE) + FOREACH_SIMD_1_OPERAND_OPCODE(CASE_OPCODE) + FOREACH_ATOMIC_OPCODE(CASE_OPCODE) + os << WasmOpcodes::OpcodeName(opcode); + break; + + default: + UNREACHABLE(); + break; + } + os << '\n'; + ++line_nr; + } + DCHECK_EQ(0, control_depth); + DCHECK(i.ok()); +} diff --git a/deps/v8/src/wasm/wasm-text.h b/deps/v8/src/wasm/wasm-text.h new file mode 100644 index 0000000000..1608ea9a2d --- /dev/null +++ b/deps/v8/src/wasm/wasm-text.h @@ -0,0 +1,38 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_WASM_S_EXPR_H_ +#define V8_WASM_S_EXPR_H_ + +#include <cstdint> +#include <ostream> +#include <tuple> +#include <vector> + +namespace v8 { + +namespace debug { +struct WasmDisassemblyOffsetTableEntry; +} // namespace debug + +namespace internal { +namespace wasm { + +// Forward declaration. +struct WasmModule; +struct ModuleWireBytes; + +// Generate disassembly according to official text format. +// Output disassembly to the given output stream, and optionally return an +// offset table of <byte offset, line, column> via the given pointer. +void PrintWasmText( + const WasmModule *module, const ModuleWireBytes &wire_bytes, + uint32_t func_index, std::ostream &os, + std::vector<debug::WasmDisassemblyOffsetTableEntry> *offset_table); + +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_WASM_S_EXPR_H_ |