blob: 9c54f17b9c37cbc20de2e493decb067b20253d62 [file] [log] [blame]
// 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_