| // Copyright 2015 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. |
| |
| #if !V8_ENABLE_WEBASSEMBLY |
| #error This header should only be included if WebAssembly is enabled. |
| #endif // !V8_ENABLE_WEBASSEMBLY |
| |
| #ifndef V8_WASM_WASM_MODULE_BUILDER_H_ |
| #define V8_WASM_WASM_MODULE_BUILDER_H_ |
| |
| #include "src/base/memory.h" |
| #include "src/base/platform/wrappers.h" |
| #include "src/base/vector.h" |
| #include "src/codegen/signature.h" |
| #include "src/wasm/leb-helper.h" |
| #include "src/wasm/local-decl-encoder.h" |
| #include "src/wasm/value-type.h" |
| #include "src/wasm/wasm-module.h" |
| #include "src/wasm/wasm-opcodes.h" |
| #include "src/wasm/wasm-result.h" |
| #include "src/zone/zone-containers.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace wasm { |
| |
| class ZoneBuffer : public ZoneObject { |
| public: |
| // This struct is just a type tag for Zone::NewArray<T>(size_t) call. |
| struct Buffer {}; |
| |
| static constexpr size_t kInitialSize = 1024; |
| explicit ZoneBuffer(Zone* zone, size_t initial = kInitialSize) |
| : zone_(zone), buffer_(zone->AllocateArray<uint8_t, Buffer>(initial)) { |
| pos_ = buffer_; |
| end_ = buffer_ + initial; |
| } |
| |
| void write_u8(uint8_t x) { |
| EnsureSpace(1); |
| *(pos_++) = x; |
| } |
| |
| void write_u16(uint16_t x) { |
| EnsureSpace(2); |
| base::WriteLittleEndianValue<uint16_t>(reinterpret_cast<Address>(pos_), x); |
| pos_ += 2; |
| } |
| |
| void write_u32(uint32_t x) { |
| EnsureSpace(4); |
| base::WriteLittleEndianValue<uint32_t>(reinterpret_cast<Address>(pos_), x); |
| pos_ += 4; |
| } |
| |
| void write_u64(uint64_t x) { |
| EnsureSpace(8); |
| base::WriteLittleEndianValue<uint64_t>(reinterpret_cast<Address>(pos_), x); |
| pos_ += 8; |
| } |
| |
| void write_u32v(uint32_t val) { |
| EnsureSpace(kMaxVarInt32Size); |
| LEBHelper::write_u32v(&pos_, val); |
| } |
| |
| void write_i32v(int32_t val) { |
| EnsureSpace(kMaxVarInt32Size); |
| LEBHelper::write_i32v(&pos_, val); |
| } |
| |
| void write_u64v(uint64_t val) { |
| EnsureSpace(kMaxVarInt64Size); |
| LEBHelper::write_u64v(&pos_, val); |
| } |
| |
| void write_i64v(int64_t val) { |
| EnsureSpace(kMaxVarInt64Size); |
| LEBHelper::write_i64v(&pos_, val); |
| } |
| |
| void write_size(size_t val) { |
| EnsureSpace(kMaxVarInt32Size); |
| DCHECK_EQ(val, static_cast<uint32_t>(val)); |
| LEBHelper::write_u32v(&pos_, static_cast<uint32_t>(val)); |
| } |
| |
| void write_f32(float val) { write_u32(base::bit_cast<uint32_t>(val)); } |
| |
| void write_f64(double val) { write_u64(base::bit_cast<uint64_t>(val)); } |
| |
| void write(const uint8_t* data, size_t size) { |
| if (size == 0) return; |
| EnsureSpace(size); |
| memcpy(pos_, data, size); |
| pos_ += size; |
| } |
| |
| void write_string(base::Vector<const char> name) { |
| write_size(name.length()); |
| write(reinterpret_cast<const uint8_t*>(name.begin()), name.length()); |
| } |
| |
| size_t reserve_u32v() { |
| size_t off = offset(); |
| EnsureSpace(kMaxVarInt32Size); |
| pos_ += kMaxVarInt32Size; |
| return off; |
| } |
| |
| // Patch a (padded) u32v at the given offset to be the given value. |
| void patch_u32v(size_t offset, uint32_t val) { |
| uint8_t* ptr = buffer_ + offset; |
| for (size_t pos = 0; pos != kPaddedVarInt32Size; ++pos) { |
| uint32_t next = val >> 7; |
| uint8_t out = static_cast<uint8_t>(val & 0x7f); |
| if (pos != kPaddedVarInt32Size - 1) { |
| *(ptr++) = 0x80 | out; |
| val = next; |
| } else { |
| *(ptr++) = out; |
| } |
| } |
| } |
| |
| void patch_u8(size_t offset, uint8_t val) { |
| DCHECK_GE(size(), offset); |
| buffer_[offset] = val; |
| } |
| |
| size_t offset() const { return static_cast<size_t>(pos_ - buffer_); } |
| size_t size() const { return static_cast<size_t>(pos_ - buffer_); } |
| uint8_t* data() const { return buffer_; } |
| uint8_t* begin() const { return buffer_; } |
| uint8_t* end() const { return pos_; } |
| |
| void EnsureSpace(size_t size) { |
| if ((pos_ + size) > end_) { |
| size_t new_size = size + (end_ - buffer_) * 2; |
| uint8_t* new_buffer = zone_->AllocateArray<uint8_t, Buffer>(new_size); |
| memcpy(new_buffer, buffer_, (pos_ - buffer_)); |
| pos_ = new_buffer + (pos_ - buffer_); |
| buffer_ = new_buffer; |
| end_ = new_buffer + new_size; |
| } |
| DCHECK(pos_ + size <= end_); |
| } |
| |
| void Truncate(size_t size) { |
| DCHECK_GE(offset(), size); |
| pos_ = buffer_ + size; |
| } |
| |
| uint8_t** pos_ptr() { return &pos_; } |
| |
| private: |
| Zone* zone_; |
| uint8_t* buffer_; |
| uint8_t* pos_; |
| uint8_t* end_; |
| }; |
| |
| class WasmModuleBuilder; |
| |
| class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject { |
| public: |
| // Building methods. |
| void SetSignature(const FunctionSig* sig); |
| void SetSignature(uint32_t sig_index); |
| uint32_t AddLocal(ValueType type); |
| void EmitByte(uint8_t b); |
| void EmitI32V(int32_t val); |
| void EmitU32V(uint32_t val); |
| void EmitU64V(uint64_t val); |
| void EmitCode(const uint8_t* code, uint32_t code_size); |
| void Emit(WasmOpcode opcode); |
| void EmitWithPrefix(WasmOpcode opcode); |
| void EmitGetLocal(uint32_t index); |
| void EmitSetLocal(uint32_t index); |
| void EmitTeeLocal(uint32_t index); |
| void EmitI32Const(int32_t val); |
| void EmitI64Const(int64_t val); |
| void EmitF32Const(float val); |
| void EmitF64Const(double val); |
| void EmitS128Const(Simd128 val); |
| void EmitWithU8(WasmOpcode opcode, const uint8_t immediate); |
| void EmitWithU8U8(WasmOpcode opcode, const uint8_t imm1, const uint8_t imm2); |
| void EmitWithI32V(WasmOpcode opcode, int32_t immediate); |
| void EmitWithU32V(WasmOpcode opcode, uint32_t immediate); |
| void EmitValueType(ValueType type); |
| void EmitDirectCallIndex(uint32_t index); |
| void EmitFromInitializerExpression(const WasmInitExpr& init_expr); |
| void SetName(base::Vector<const char> name); |
| void AddAsmWasmOffset(size_t call_position, size_t to_number_position); |
| void SetAsmFunctionStartPosition(size_t function_position); |
| void SetCompilationHint(WasmCompilationHintStrategy strategy, |
| WasmCompilationHintTier baseline, |
| WasmCompilationHintTier top_tier); |
| |
| size_t GetPosition() const { return body_.size(); } |
| void FixupByte(size_t position, uint8_t value) { |
| body_.patch_u8(position, value); |
| } |
| void DeleteCodeAfter(size_t position); |
| |
| void WriteSignature(ZoneBuffer* buffer) const; |
| void WriteBody(ZoneBuffer* buffer) const; |
| void WriteAsmWasmOffsetTable(ZoneBuffer* buffer) const; |
| |
| WasmModuleBuilder* builder() const { return builder_; } |
| uint32_t func_index() { return func_index_; } |
| uint32_t sig_index() { return signature_index_; } |
| inline const FunctionSig* signature(); |
| |
| private: |
| explicit WasmFunctionBuilder(WasmModuleBuilder* builder); |
| friend class WasmModuleBuilder; |
| friend Zone; |
| |
| struct DirectCallIndex { |
| size_t offset; |
| uint32_t direct_index; |
| }; |
| |
| WasmModuleBuilder* builder_; |
| LocalDeclEncoder locals_; |
| uint32_t signature_index_; |
| uint32_t func_index_; |
| ZoneBuffer body_; |
| base::Vector<const char> name_; |
| ZoneVector<uint32_t> i32_temps_; |
| ZoneVector<uint32_t> i64_temps_; |
| ZoneVector<uint32_t> f32_temps_; |
| ZoneVector<uint32_t> f64_temps_; |
| ZoneVector<DirectCallIndex> direct_calls_; |
| |
| // Delta-encoded mapping from wasm bytes to asm.js source positions. |
| 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; |
| uint8_t hint_ = kNoCompilationHint; |
| }; |
| |
| class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { |
| public: |
| explicit WasmModuleBuilder(Zone* zone); |
| WasmModuleBuilder(const WasmModuleBuilder&) = delete; |
| WasmModuleBuilder& operator=(const WasmModuleBuilder&) = delete; |
| |
| // Static representation of wasm element segment (table initializer). This is |
| // different than the version in wasm-module.h. |
| class WasmElemSegment { |
| public: |
| // asm.js gives function indices starting with the first non-imported |
| // function. |
| enum FunctionIndexingMode { |
| kRelativeToImports, |
| kRelativeToDeclaredFunctions |
| }; |
| enum Status { |
| kStatusActive, // copied automatically during instantiation. |
| kStatusPassive, // copied explicitly after instantiation. |
| kStatusDeclarative // purely declarative and never copied. |
| }; |
| struct Entry { |
| enum Kind { kGlobalGetEntry, kRefFuncEntry, kRefNullEntry } kind; |
| uint32_t index; |
| Entry(Kind kind, uint32_t index) : kind(kind), index(index) {} |
| Entry() : kind(kRefNullEntry), index(0) {} |
| }; |
| |
| // Construct an active segment. |
| WasmElemSegment(Zone* zone, ValueType type, uint32_t table_index, |
| WasmInitExpr offset) |
| : type(type), |
| table_index(table_index), |
| offset(offset), |
| entries(zone), |
| status(kStatusActive) { |
| DCHECK(IsValidOffsetKind(offset.kind())); |
| } |
| |
| // Construct a passive or declarative segment, which has no table |
| // index or offset. |
| WasmElemSegment(Zone* zone, ValueType type, bool declarative, |
| WasmInitExpr offset) |
| : type(type), |
| table_index(0), |
| offset(offset), |
| entries(zone), |
| status(declarative ? kStatusDeclarative : kStatusPassive) { |
| DCHECK(IsValidOffsetKind(offset.kind())); |
| } |
| |
| MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(WasmElemSegment); |
| |
| ValueType type; |
| uint32_t table_index; |
| WasmInitExpr offset; |
| FunctionIndexingMode indexing_mode = kRelativeToImports; |
| ZoneVector<Entry> entries; |
| Status status; |
| |
| private: |
| // This ensures no {WasmInitExpr} with subexpressions is used, which would |
| // cause a memory leak because those are stored in an std::vector. Such |
| // offset would also be mistyped. |
| bool IsValidOffsetKind(WasmInitExpr::Operator kind) { |
| return kind == WasmInitExpr::kI32Const || |
| kind == WasmInitExpr::kGlobalGet || |
| kind == WasmInitExpr::kRefNullConst; |
| } |
| }; |
| |
| // Building methods. |
| uint32_t AddImport(base::Vector<const char> name, const FunctionSig* sig, |
| base::Vector<const char> module = {}); |
| WasmFunctionBuilder* AddFunction(const FunctionSig* sig = nullptr); |
| WasmFunctionBuilder* AddFunction(uint32_t sig_index); |
| uint32_t AddGlobal(ValueType type, bool mutability, WasmInitExpr init); |
| uint32_t AddGlobalImport(base::Vector<const char> name, ValueType type, |
| bool mutability, |
| base::Vector<const char> module = {}); |
| void AddDataSegment(const uint8_t* data, uint32_t size, uint32_t dest); |
| void AddPassiveDataSegment(const uint8_t* data, uint32_t size); |
| // Add an element segment to this {WasmModuleBuilder}. {segment}'s enties |
| // have to be initialized. |
| uint32_t AddElementSegment(WasmElemSegment segment); |
| // Helper method to create an active segment with one function. Assumes that |
| // table segment at {table_index} is typed as funcref. |
| void SetIndirectFunction(uint32_t table_index, uint32_t index_in_table, |
| uint32_t direct_function_index, |
| WasmElemSegment::FunctionIndexingMode indexing_mode); |
| // Increase the starting size of the table at {table_index} by {count}. Also |
| // increases the maximum table size if needed. Returns the former starting |
| // size, or the maximum uint32_t value if the maximum table size has been |
| // exceeded. |
| uint32_t IncreaseTableMinSize(uint32_t table_index, uint32_t count); |
| // Adds the signature to the module if it does not already exist. |
| uint32_t AddSignature(const FunctionSig* sig, bool is_final, |
| uint32_t supertype = kNoSuperType); |
| // Does not deduplicate function signatures. |
| uint32_t ForceAddSignature(const FunctionSig* sig, bool is_final, |
| uint32_t supertype = kNoSuperType); |
| uint32_t AddTag(const FunctionSig* type); |
| uint32_t AddStructType(StructType* type, bool is_final, |
| uint32_t supertype = kNoSuperType); |
| uint32_t AddArrayType(ArrayType* type, bool is_final, |
| uint32_t supertype = kNoSuperType); |
| uint32_t AddTable(ValueType type, uint32_t min_size); |
| uint32_t AddTable(ValueType type, uint32_t min_size, uint32_t max_size); |
| uint32_t AddTable(ValueType type, uint32_t min_size, uint32_t max_size, |
| WasmInitExpr init); |
| uint32_t AddMemory(uint32_t min_size); |
| uint32_t AddMemory(uint32_t min_size, uint32_t max_size); |
| uint32_t AddMemory64(uintptr_t min_size, uintptr_t max_size); |
| void MarkStartFunction(WasmFunctionBuilder* builder); |
| void AddExport(base::Vector<const char> name, ImportExportKindCode kind, |
| uint32_t index); |
| void AddExport(base::Vector<const char> name, WasmFunctionBuilder* builder) { |
| AddExport(name, kExternalFunction, builder->func_index()); |
| } |
| uint32_t AddExportedGlobal(ValueType type, bool mutability, WasmInitExpr init, |
| base::Vector<const char> name); |
| void ExportImportedFunction(base::Vector<const char> name, int import_index); |
| |
| void StartRecursiveTypeGroup() { |
| DCHECK_EQ(current_recursive_group_start_, -1); |
| current_recursive_group_start_ = static_cast<int>(types_.size()); |
| } |
| |
| void EndRecursiveTypeGroup() { |
| // Make sure we are in a recursive group. |
| DCHECK_NE(current_recursive_group_start_, -1); |
| // Make sure the current recursive group has at least one element. |
| DCHECK_GT(static_cast<int>(types_.size()), current_recursive_group_start_); |
| recursive_groups_.emplace( |
| current_recursive_group_start_, |
| static_cast<uint32_t>(types_.size()) - current_recursive_group_start_); |
| current_recursive_group_start_ = -1; |
| } |
| |
| void AddRecursiveTypeGroup(uint32_t start, uint32_t size) { |
| recursive_groups_.emplace(start, size); |
| } |
| |
| // Writing methods. |
| void WriteTo(ZoneBuffer* buffer) const; |
| void WriteAsmJsOffsetTable(ZoneBuffer* buffer) const; |
| |
| Zone* zone() { return zone_; } |
| |
| ValueType GetTableType(uint32_t index) { return tables_[index].type; } |
| |
| bool IsSignature(uint32_t index) { |
| return types_[index].kind == TypeDefinition::kFunction; |
| } |
| |
| const FunctionSig* GetSignature(uint32_t index) { |
| DCHECK(types_[index].kind == TypeDefinition::kFunction); |
| return types_[index].function_sig; |
| } |
| |
| bool IsStructType(uint32_t index) { |
| return types_[index].kind == TypeDefinition::kStruct; |
| } |
| const StructType* GetStructType(uint32_t index) { |
| return types_[index].struct_type; |
| } |
| |
| bool IsArrayType(uint32_t index) { |
| return types_[index].kind == TypeDefinition::kArray; |
| } |
| const ArrayType* GetArrayType(uint32_t index) { |
| return types_[index].array_type; |
| } |
| |
| uint32_t GetSuperType(uint32_t index) { return types_[index].supertype; } |
| |
| WasmFunctionBuilder* GetFunction(uint32_t index) { return functions_[index]; } |
| int NumTags() { return static_cast<int>(tags_.size()); } |
| |
| int NumTypes() { return static_cast<int>(types_.size()); } |
| |
| int NumTables() { return static_cast<int>(tables_.size()); } |
| |
| int NumMemories() { return static_cast<int>(memories_.size()); } |
| |
| int NumGlobals() { return static_cast<int>(globals_.size()); } |
| |
| int NumImportedFunctions() { |
| return static_cast<int>(function_imports_.size()); |
| } |
| int NumDeclaredFunctions() { return static_cast<int>(functions_.size()); } |
| |
| int NumDataSegments() { return static_cast<int>(data_segments_.size()); } |
| |
| bool IsMemory64(uint32_t index) { return memories_[index].is_memory64; } |
| |
| const FunctionSig* GetTagType(int index) { |
| return types_[tags_[index]].function_sig; |
| } |
| |
| ValueType GetGlobalType(uint32_t index) const { return globals_[index].type; } |
| |
| bool IsMutableGlobal(uint32_t index) const { |
| return globals_[index].mutability; |
| } |
| |
| private: |
| struct WasmFunctionImport { |
| base::Vector<const char> module; |
| base::Vector<const char> name; |
| uint32_t sig_index; |
| }; |
| |
| struct WasmGlobalImport { |
| base::Vector<const char> module; |
| base::Vector<const char> name; |
| // TODO(manoskouk): Extend to full value type. |
| ValueTypeCode type_code; |
| bool mutability; |
| }; |
| |
| struct WasmExport { |
| base::Vector<const char> name; |
| ImportExportKindCode kind; |
| int index; // Can be negative for re-exported imports. |
| }; |
| |
| struct WasmGlobal { |
| ValueType type; |
| bool mutability; |
| WasmInitExpr init; |
| }; |
| |
| struct WasmTable { |
| ValueType type; |
| uint32_t min_size; |
| uint32_t max_size; |
| bool has_maximum; |
| base::Optional<WasmInitExpr> init; |
| }; |
| |
| struct WasmMemory { |
| uintptr_t min_size; |
| uintptr_t max_size; |
| bool has_max_size; |
| bool is_shared; |
| bool is_memory64; |
| }; |
| |
| struct WasmDataSegment { |
| ZoneVector<uint8_t> data; |
| uint32_t dest; |
| bool is_active = true; |
| }; |
| |
| friend class WasmFunctionBuilder; |
| Zone* zone_; |
| ZoneVector<TypeDefinition> types_; |
| ZoneVector<WasmFunctionImport> function_imports_; |
| ZoneVector<WasmGlobalImport> global_imports_; |
| ZoneVector<WasmExport> exports_; |
| ZoneVector<WasmFunctionBuilder*> functions_; |
| ZoneVector<WasmTable> tables_; |
| ZoneVector<WasmMemory> memories_; |
| ZoneVector<WasmDataSegment> data_segments_; |
| ZoneVector<WasmElemSegment> element_segments_; |
| ZoneVector<WasmGlobal> globals_; |
| ZoneVector<int> tags_; |
| ZoneUnorderedMap<FunctionSig, uint32_t> signature_map_; |
| int current_recursive_group_start_; |
| // first index -> size |
| ZoneUnorderedMap<uint32_t, uint32_t> recursive_groups_; |
| int start_function_index_; |
| #if DEBUG |
| // Once AddExportedImport is called, no more imports can be added. |
| bool adding_imports_allowed_ = true; |
| #endif |
| }; |
| |
| const FunctionSig* WasmFunctionBuilder::signature() { |
| return builder_->types_[signature_index_].function_sig; |
| } |
| |
| } // namespace wasm |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_WASM_WASM_MODULE_BUILDER_H_ |