| // 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. |
| |
| #ifndef V8_WASM_WASM_MODULE_H_ |
| #define V8_WASM_WASM_MODULE_H_ |
| |
| #include <memory> |
| |
| #include "src/base/optional.h" |
| #include "src/common/globals.h" |
| #include "src/handles/handles.h" |
| #include "src/utils/vector.h" |
| #include "src/wasm/signature-map.h" |
| #include "src/wasm/struct-types.h" |
| #include "src/wasm/wasm-constants.h" |
| #include "src/wasm/wasm-opcodes.h" |
| |
| namespace v8 { |
| |
| namespace internal { |
| |
| class WasmModuleObject; |
| |
| namespace wasm { |
| |
| using WasmName = Vector<const char>; |
| |
| struct AsmJsOffsets; |
| class ErrorThrower; |
| |
| // Reference to a string in the wire bytes. |
| class WireBytesRef { |
| public: |
| WireBytesRef() : WireBytesRef(0, 0) {} |
| WireBytesRef(uint32_t offset, uint32_t length) |
| : offset_(offset), length_(length) { |
| DCHECK_IMPLIES(offset_ == 0, length_ == 0); |
| DCHECK_LE(offset_, offset_ + length_); // no uint32_t overflow. |
| } |
| |
| uint32_t offset() const { return offset_; } |
| uint32_t length() const { return length_; } |
| uint32_t end_offset() const { return offset_ + length_; } |
| bool is_empty() const { return length_ == 0; } |
| bool is_set() const { return offset_ != 0; } |
| |
| private: |
| uint32_t offset_; |
| uint32_t length_; |
| }; |
| |
| // Static representation of a wasm function. |
| struct WasmFunction { |
| const FunctionSig* sig; // signature of the function. |
| uint32_t func_index; // index into the function table. |
| uint32_t sig_index; // index into the signature table. |
| WireBytesRef code; // code of this function. |
| bool imported; |
| bool exported; |
| bool declared; |
| }; |
| |
| // Static representation of a wasm global variable. |
| struct WasmGlobal { |
| ValueType type; // type of the global. |
| bool mutability; // {true} if mutable. |
| WasmInitExpr init; // the initialization expression of the global. |
| union { |
| uint32_t index; // index of imported mutable global. |
| uint32_t offset; // offset into global memory (if not imported & mutable). |
| }; |
| bool imported; // true if imported. |
| bool exported; // true if exported. |
| }; |
| |
| // Note: An exception signature only uses the params portion of a |
| // function signature. |
| using WasmExceptionSig = FunctionSig; |
| |
| // Static representation of a wasm exception type. |
| struct WasmException { |
| explicit WasmException(const WasmExceptionSig* sig) : sig(sig) {} |
| const FunctionSig* ToFunctionSig() const { return sig; } |
| |
| const WasmExceptionSig* sig; // type signature of the exception. |
| }; |
| |
| // Static representation of a wasm data segment. |
| struct WasmDataSegment { |
| // Construct an active segment. |
| explicit WasmDataSegment(WasmInitExpr dest_addr) |
| : dest_addr(std::move(dest_addr)), active(true) {} |
| |
| // Construct a passive segment, which has no dest_addr. |
| WasmDataSegment() : active(false) {} |
| |
| WasmInitExpr dest_addr; // destination memory address of the data. |
| WireBytesRef source; // start offset in the module bytes. |
| bool active = true; // true if copied automatically during instantiation. |
| }; |
| |
| // Static representation of wasm element segment (table initializer). |
| struct WasmElemSegment { |
| MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(WasmElemSegment); |
| |
| // Construct an active segment. |
| WasmElemSegment(uint32_t table_index, WasmInitExpr offset) |
| : type(kWasmFuncRef), |
| table_index(table_index), |
| offset(std::move(offset)), |
| status(kStatusActive) {} |
| |
| // Construct a passive or declarative segment, which has no table index or |
| // offset. |
| explicit WasmElemSegment(bool declarative) |
| : type(kWasmFuncRef), |
| table_index(0), |
| status(declarative ? kStatusDeclarative : kStatusPassive) {} |
| |
| // Used in the {entries} vector to represent a `ref.null` entry in a passive |
| // segment. |
| V8_EXPORT_PRIVATE static const uint32_t kNullIndex = ~0u; |
| |
| ValueType type; |
| uint32_t table_index; |
| WasmInitExpr offset; |
| std::vector<uint32_t> entries; |
| enum Status { |
| kStatusActive, // copied automatically during instantiation. |
| kStatusPassive, // copied explicitly after instantiation. |
| kStatusDeclarative // purely declarative and never copied. |
| } status; |
| }; |
| |
| // Static representation of a wasm import. |
| struct WasmImport { |
| WireBytesRef module_name; // module name. |
| WireBytesRef field_name; // import name. |
| ImportExportKindCode kind; // kind of the import. |
| uint32_t index; // index into the respective space. |
| }; |
| |
| // Static representation of a wasm export. |
| struct WasmExport { |
| WireBytesRef name; // exported name. |
| ImportExportKindCode kind; // kind of the export. |
| uint32_t index; // index into the respective space. |
| }; |
| |
| enum class WasmCompilationHintStrategy : uint8_t { |
| kDefault = 0, |
| kLazy = 1, |
| kEager = 2, |
| kLazyBaselineEagerTopTier = 3, |
| }; |
| |
| enum class WasmCompilationHintTier : uint8_t { |
| kDefault = 0, |
| kBaseline = 1, |
| kOptimized = 2, |
| }; |
| |
| // Static representation of a wasm compilation hint |
| struct WasmCompilationHint { |
| WasmCompilationHintStrategy strategy; |
| WasmCompilationHintTier baseline_tier; |
| WasmCompilationHintTier top_tier; |
| }; |
| |
| enum ModuleOrigin : uint8_t { |
| kWasmOrigin, |
| kAsmJsSloppyOrigin, |
| kAsmJsStrictOrigin |
| }; |
| |
| #define SELECT_WASM_COUNTER(counters, origin, prefix, suffix) \ |
| ((origin) == kWasmOrigin ? (counters)->prefix##_wasm_##suffix() \ |
| : (counters)->prefix##_asm_##suffix()) |
| |
| struct ModuleWireBytes; |
| |
| class V8_EXPORT_PRIVATE LazilyGeneratedNames { |
| public: |
| WireBytesRef LookupFunctionName(const ModuleWireBytes& wire_bytes, |
| uint32_t function_index, |
| Vector<const WasmExport> export_table) const; |
| |
| // For memory and global. |
| std::pair<WireBytesRef, WireBytesRef> LookupNameFromImportsAndExports( |
| ImportExportKindCode kind, uint32_t index, |
| const Vector<const WasmImport> import_table, |
| const Vector<const WasmExport> export_table) const; |
| |
| void AddForTesting(int function_index, WireBytesRef name); |
| |
| private: |
| // {function_names_}, {global_names_}, {memory_names_} and {table_names_} are |
| // populated lazily after decoding, and therefore need a mutex to protect |
| // concurrent modifications from multiple {WasmModuleObject}. |
| mutable base::Mutex mutex_; |
| mutable std::unique_ptr<std::unordered_map<uint32_t, WireBytesRef>> |
| function_names_; |
| mutable std::unique_ptr< |
| std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>> |
| global_names_; |
| mutable std::unique_ptr< |
| std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>> |
| memory_names_; |
| mutable std::unique_ptr< |
| std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>> |
| table_names_; |
| }; |
| |
| class V8_EXPORT_PRIVATE AsmJsOffsetInformation { |
| public: |
| explicit AsmJsOffsetInformation(Vector<const byte> encoded_offsets); |
| |
| // Destructor defined in wasm-module.cc, where the definition of |
| // {AsmJsOffsets} is available. |
| ~AsmJsOffsetInformation(); |
| |
| int GetSourcePosition(int func_index, int byte_offset, |
| bool is_at_number_conversion); |
| |
| std::pair<int, int> GetFunctionOffsets(int func_index); |
| |
| private: |
| void EnsureDecodedOffsets(); |
| |
| // The offset information table is decoded lazily, hence needs to be |
| // protected against concurrent accesses. |
| // Exactly one of the two fields below will be set at a time. |
| mutable base::Mutex mutex_; |
| |
| // Holds the encoded offset table bytes. |
| OwnedVector<const uint8_t> encoded_offsets_; |
| |
| // Holds the decoded offset table. |
| std::unique_ptr<AsmJsOffsets> decoded_offsets_; |
| }; |
| |
| struct TypeDefinition { |
| explicit TypeDefinition(const FunctionSig* sig) : function_sig(sig) {} |
| explicit TypeDefinition(const StructType* type) : struct_type(type) {} |
| explicit TypeDefinition(const ArrayType* type) : array_type(type) {} |
| union { |
| const FunctionSig* function_sig; |
| const StructType* struct_type; |
| const ArrayType* array_type; |
| }; |
| }; |
| |
| struct V8_EXPORT_PRIVATE WasmDebugSymbols { |
| enum class Type { None, SourceMap, EmbeddedDWARF, ExternalDWARF }; |
| Type type = Type::None; |
| WireBytesRef external_url; |
| }; |
| |
| struct WasmTable; |
| |
| // Static representation of a module. |
| struct V8_EXPORT_PRIVATE WasmModule { |
| std::unique_ptr<Zone> signature_zone; |
| uint32_t initial_pages = 0; // initial size of the memory in 64k pages |
| uint32_t maximum_pages = 0; // maximum size of the memory in 64k pages |
| bool has_shared_memory = false; // true if memory is a SharedArrayBuffer |
| bool has_maximum_pages = false; // true if there is a maximum memory size |
| bool is_memory64 = false; // true if the memory is 64 bit |
| bool has_memory = false; // true if the memory was defined or imported |
| bool mem_export = false; // true if the memory is exported |
| int start_function_index = -1; // start function, >= 0 if any |
| |
| std::vector<WasmGlobal> globals; |
| // Size of the buffer required for all globals that are not imported and |
| // mutable. |
| uint32_t untagged_globals_buffer_size = 0; |
| uint32_t tagged_globals_buffer_size = 0; |
| uint32_t num_imported_mutable_globals = 0; |
| uint32_t num_imported_functions = 0; |
| uint32_t num_imported_tables = 0; |
| uint32_t num_declared_functions = 0; // excluding imported |
| uint32_t num_exported_functions = 0; |
| uint32_t num_declared_data_segments = 0; // From the DataCount section. |
| WireBytesRef code = {0, 0}; |
| WireBytesRef name = {0, 0}; |
| std::vector<TypeDefinition> types; // by type index |
| std::vector<uint8_t> type_kinds; // by type index |
| // Map from each type index to the index of its corresponding canonical type. |
| // Note: right now, only functions are canonicalized, and arrays and structs |
| // map to themselves. |
| std::vector<uint32_t> canonicalized_type_ids; |
| |
| bool has_type(uint32_t index) const { return index < types.size(); } |
| |
| void add_signature(const FunctionSig* sig) { |
| types.push_back(TypeDefinition(sig)); |
| type_kinds.push_back(kWasmFunctionTypeCode); |
| uint32_t canonical_id = sig ? signature_map.FindOrInsert(*sig) : 0; |
| canonicalized_type_ids.push_back(canonical_id); |
| } |
| bool has_signature(uint32_t index) const { |
| return index < types.size() && type_kinds[index] == kWasmFunctionTypeCode; |
| } |
| const FunctionSig* signature(uint32_t index) const { |
| DCHECK(has_signature(index)); |
| return types[index].function_sig; |
| } |
| |
| void add_struct_type(const StructType* type) { |
| types.push_back(TypeDefinition(type)); |
| type_kinds.push_back(kWasmStructTypeCode); |
| // No canonicalization for structs. |
| canonicalized_type_ids.push_back(0); |
| } |
| bool has_struct(uint32_t index) const { |
| return index < types.size() && type_kinds[index] == kWasmStructTypeCode; |
| } |
| const StructType* struct_type(uint32_t index) const { |
| DCHECK(has_struct(index)); |
| return types[index].struct_type; |
| } |
| |
| void add_array_type(const ArrayType* type) { |
| types.push_back(TypeDefinition(type)); |
| type_kinds.push_back(kWasmArrayTypeCode); |
| // No canonicalization for arrays. |
| canonicalized_type_ids.push_back(0); |
| } |
| bool has_array(uint32_t index) const { |
| return index < types.size() && type_kinds[index] == kWasmArrayTypeCode; |
| } |
| const ArrayType* array_type(uint32_t index) const { |
| DCHECK(has_array(index)); |
| return types[index].array_type; |
| } |
| |
| std::vector<WasmFunction> functions; |
| std::vector<WasmDataSegment> data_segments; |
| std::vector<WasmTable> tables; |
| std::vector<WasmImport> import_table; |
| std::vector<WasmExport> export_table; |
| std::vector<WasmException> exceptions; |
| std::vector<WasmElemSegment> elem_segments; |
| std::vector<WasmCompilationHint> compilation_hints; |
| SignatureMap signature_map; // canonicalizing map for signature indexes. |
| |
| ModuleOrigin origin = kWasmOrigin; // origin of the module |
| LazilyGeneratedNames lazily_generated_names; |
| WasmDebugSymbols debug_symbols; |
| |
| // Asm.js source position information. Only available for modules compiled |
| // from asm.js. |
| std::unique_ptr<AsmJsOffsetInformation> asm_js_offset_information; |
| |
| explicit WasmModule(std::unique_ptr<Zone> signature_zone = nullptr); |
| WasmModule(const WasmModule&) = delete; |
| WasmModule& operator=(const WasmModule&) = delete; |
| }; |
| |
| // Static representation of a wasm indirect call table. |
| struct WasmTable { |
| MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(WasmTable); |
| |
| // 'module' can be nullptr |
| // TODO(9495): Update this function as more table types are supported, or |
| // remove it completely when all reference types are allowed. |
| static bool IsValidTableType(ValueType type, const WasmModule* module) { |
| if (!type.is_nullable()) return false; |
| HeapType heap_type = type.heap_type(); |
| return heap_type == HeapType::kFunc || heap_type == HeapType::kExtern || |
| heap_type == HeapType::kExn || |
| (module != nullptr && heap_type.is_index() && |
| module->has_signature(heap_type.ref_index())); |
| } |
| |
| ValueType type = kWasmStmt; // table type. |
| uint32_t initial_size = 0; // initial table size. |
| uint32_t maximum_size = 0; // maximum table size. |
| bool has_maximum_size = false; // true if there is a maximum size. |
| bool imported = false; // true if imported. |
| bool exported = false; // true if exported. |
| }; |
| |
| inline bool is_asmjs_module(const WasmModule* module) { |
| return module->origin != kWasmOrigin; |
| } |
| |
| size_t EstimateStoredSize(const WasmModule* module); |
| |
| // Returns the number of possible export wrappers for a given module. |
| V8_EXPORT_PRIVATE int MaxNumExportWrappers(const WasmModule* module); |
| |
| // Returns the wrapper index for a function in {module} with signature {sig} |
| // and origin defined by {is_import}. |
| int GetExportWrapperIndex(const WasmModule* module, const FunctionSig* sig, |
| bool is_import); |
| |
| // 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 GetWasmFunctionOffset(const WasmModule* module, 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 GetContainingWasmFunction(const WasmModule* module, uint32_t byte_offset); |
| |
| // Returns the function containing the given byte offset. |
| // Will return preceding function if the byte offset is not |
| // contained within a function. |
| int GetNearestWasmFunction(const WasmModule* module, uint32_t byte_offset); |
| |
| // 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 { |
| explicit 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); |
| } |
| |
| // Get a string stored in the module bytes representing a name. |
| WasmName GetNameOrNull(WireBytesRef ref) const; |
| |
| // Get a string stored in the module bytes representing a function name. |
| WasmName GetNameOrNull(const WasmFunction* function, |
| const WasmModule* module) const; |
| |
| // Checks the given reference is contained within the module bytes. |
| bool BoundsCheck(WireBytesRef ref) const { |
| uint32_t size = static_cast<uint32_t>(module_bytes_.length()); |
| return ref.offset() <= size && ref.length() <= size - ref.offset(); |
| } |
| |
| Vector<const byte> GetFunctionBytes(const WasmFunction* function) const { |
| return module_bytes_.SubVector(function->code.offset(), |
| function->code.end_offset()); |
| } |
| |
| Vector<const byte> module_bytes() const { return module_bytes_; } |
| const byte* start() const { return module_bytes_.begin(); } |
| const byte* end() const { return module_bytes_.end(); } |
| size_t length() const { return module_bytes_.length(); } |
| |
| private: |
| Vector<const byte> module_bytes_; |
| }; |
| |
| // A helper for printing out the names of functions. |
| struct WasmFunctionName { |
| WasmFunctionName(const WasmFunction* function, WasmName name) |
| : function_(function), name_(name) {} |
| |
| const WasmFunction* function_; |
| const WasmName name_; |
| }; |
| |
| std::ostream& operator<<(std::ostream& os, const WasmFunctionName& name); |
| |
| V8_EXPORT_PRIVATE bool IsWasmCodegenAllowed(Isolate* isolate, |
| Handle<Context> context); |
| |
| Handle<JSObject> GetTypeForFunction(Isolate* isolate, const FunctionSig* sig); |
| Handle<JSObject> GetTypeForGlobal(Isolate* isolate, bool is_mutable, |
| ValueType type); |
| Handle<JSObject> GetTypeForMemory(Isolate* isolate, uint32_t min_size, |
| base::Optional<uint32_t> max_size); |
| Handle<JSObject> GetTypeForTable(Isolate* isolate, ValueType type, |
| uint32_t min_size, |
| base::Optional<uint32_t> max_size); |
| Handle<JSArray> GetImports(Isolate* isolate, Handle<WasmModuleObject> module); |
| Handle<JSArray> GetExports(Isolate* isolate, Handle<WasmModuleObject> module); |
| Handle<JSArray> GetCustomSections(Isolate* isolate, |
| Handle<WasmModuleObject> module, |
| Handle<String> name, ErrorThrower* thrower); |
| |
| // Get the source position from a given function index and byte offset, |
| // for either asm.js or pure Wasm modules. |
| int GetSourcePosition(const WasmModule*, uint32_t func_index, |
| uint32_t byte_offset, bool is_at_number_conversion); |
| |
| // Translate function index to the index relative to the first declared (i.e. |
| // non-imported) function. |
| inline int declared_function_index(const WasmModule* module, int func_index) { |
| DCHECK_LE(module->num_imported_functions, func_index); |
| int declared_idx = func_index - module->num_imported_functions; |
| DCHECK_GT(module->num_declared_functions, declared_idx); |
| return declared_idx; |
| } |
| |
| // TruncatedUserString makes it easy to output names up to a certain length, and |
| // output a truncation followed by '...' if they exceed a limit. |
| // Use like this: |
| // TruncatedUserString<> name (pc, len); |
| // printf("... %.*s ...", name.length(), name.start()) |
| template <int kMaxLen = 50> |
| class TruncatedUserString { |
| static_assert(kMaxLen >= 4, "minimum length is 4 (length of '...' plus one)"); |
| |
| public: |
| template <typename T> |
| explicit TruncatedUserString(Vector<T> name) |
| : TruncatedUserString(name.begin(), name.length()) {} |
| |
| TruncatedUserString(const byte* start, size_t len) |
| : TruncatedUserString(reinterpret_cast<const char*>(start), len) {} |
| |
| TruncatedUserString(const char* start, size_t len) |
| : start_(start), length_(std::min(kMaxLen, static_cast<int>(len))) { |
| if (len > static_cast<size_t>(kMaxLen)) { |
| memcpy(buffer_, start, kMaxLen - 3); |
| memset(buffer_ + kMaxLen - 3, '.', 3); |
| start_ = buffer_; |
| } |
| } |
| |
| const char* start() const { return start_; } |
| |
| int length() const { return length_; } |
| |
| private: |
| const char* start_; |
| const int length_; |
| char buffer_[kMaxLen]; |
| }; |
| |
| // Print the signature into the given {buffer}, using {delimiter} as separator |
| // between parameter types and return types. If {buffer} is non-empty, it will |
| // be null-terminated, even if the signature is cut off. Returns the number of |
| // characters written, excluding the terminating null-byte. |
| size_t PrintSignature(Vector<char> buffer, const wasm::FunctionSig*, |
| char delimiter = ':'); |
| |
| } // namespace wasm |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_WASM_WASM_MODULE_H_ |