blob: a4b0139ea43758e08568bba1024490d0c267edb5 [file] [log] [blame]
// Copyright 2019 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/module-instantiate.h"
#include "src/asmjs/asm-js.h"
#include "src/logging/counters.h"
#include "src/numbers/conversions-inl.h"
#include "src/objects/property-descriptor.h"
#include "src/tracing/trace-event.h"
#include "src/utils/utils.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/wasm-external-refs.h"
#include "src/wasm/wasm-import-wrapper-cache.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#define TRACE(...) \
do { \
if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \
} while (false)
namespace v8 {
namespace internal {
namespace wasm {
using base::ReadLittleEndianValue;
using base::WriteLittleEndianValue;
namespace {
byte* raw_buffer_ptr(MaybeHandle<JSArrayBuffer> buffer, int offset) {
return static_cast<byte*>(buffer.ToHandleChecked()->backing_store()) + offset;
}
uint32_t EvalUint32InitExpr(Handle<WasmInstanceObject> instance,
const WasmInitExpr& expr) {
switch (expr.kind) {
case WasmInitExpr::kI32Const:
return expr.val.i32_const;
case WasmInitExpr::kGlobalIndex: {
uint32_t offset =
instance->module()->globals[expr.val.global_index].offset;
auto raw_addr = reinterpret_cast<Address>(
instance->untagged_globals_buffer().backing_store()) +
offset;
return ReadLittleEndianValue<uint32_t>(raw_addr);
}
default:
UNREACHABLE();
}
}
using ImportWrapperQueue = WrapperQueue<WasmImportWrapperCache::CacheKey,
WasmImportWrapperCache::CacheKeyHash>;
class CompileImportWrapperTask final : public CancelableTask {
public:
CompileImportWrapperTask(
CancelableTaskManager* task_manager, WasmEngine* engine,
Counters* counters, NativeModule* native_module,
ImportWrapperQueue* queue,
WasmImportWrapperCache::ModificationScope* cache_scope)
: CancelableTask(task_manager),
engine_(engine),
counters_(counters),
native_module_(native_module),
queue_(queue),
cache_scope_(cache_scope) {}
void RunInternal() override {
while (base::Optional<WasmImportWrapperCache::CacheKey> key =
queue_->pop()) {
CompileImportWrapper(engine_, native_module_, counters_, key->first,
key->second, cache_scope_);
}
}
private:
WasmEngine* const engine_;
Counters* const counters_;
NativeModule* const native_module_;
ImportWrapperQueue* const queue_;
WasmImportWrapperCache::ModificationScope* const cache_scope_;
};
} // namespace
// A helper class to simplify instantiating a module from a module object.
// It closes over the {Isolate}, the {ErrorThrower}, etc.
class InstanceBuilder {
public:
InstanceBuilder(Isolate* isolate, ErrorThrower* thrower,
Handle<WasmModuleObject> module_object,
MaybeHandle<JSReceiver> ffi,
MaybeHandle<JSArrayBuffer> memory);
// Build an instance, in all of its glory.
MaybeHandle<WasmInstanceObject> Build();
// Run the start function, if any.
bool ExecuteStartFunction();
private:
// A pre-evaluated value to use in import binding.
struct SanitizedImport {
Handle<String> module_name;
Handle<String> import_name;
Handle<Object> value;
};
Isolate* isolate_;
const WasmFeatures enabled_;
const WasmModule* const module_;
ErrorThrower* thrower_;
Handle<WasmModuleObject> module_object_;
MaybeHandle<JSReceiver> ffi_;
MaybeHandle<JSArrayBuffer> memory_;
Handle<JSArrayBuffer> untagged_globals_;
Handle<FixedArray> tagged_globals_;
std::vector<Handle<WasmExceptionObject>> exception_wrappers_;
Handle<WasmExportedFunction> start_function_;
std::vector<SanitizedImport> sanitized_imports_;
// Helper routines to print out errors with imports.
#define ERROR_THROWER_WITH_MESSAGE(TYPE) \
void Report##TYPE(const char* error, uint32_t index, \
Handle<String> module_name, Handle<String> import_name) { \
thrower_->TYPE("Import #%d module=\"%s\" function=\"%s\" error: %s", \
index, module_name->ToCString().get(), \
import_name->ToCString().get(), error); \
} \
\
MaybeHandle<Object> Report##TYPE(const char* error, uint32_t index, \
Handle<String> module_name) { \
thrower_->TYPE("Import #%d module=\"%s\" error: %s", index, \
module_name->ToCString().get(), error); \
return MaybeHandle<Object>(); \
}
ERROR_THROWER_WITH_MESSAGE(LinkError)
ERROR_THROWER_WITH_MESSAGE(TypeError)
#undef ERROR_THROWER_WITH_MESSAGE
// Look up an import value in the {ffi_} object.
MaybeHandle<Object> LookupImport(uint32_t index, Handle<String> module_name,
Handle<String> import_name);
// Look up an import value in the {ffi_} object specifically for linking an
// asm.js module. This only performs non-observable lookups, which allows
// falling back to JavaScript proper (and hence re-executing all lookups) if
// module instantiation fails.
MaybeHandle<Object> LookupImportAsm(uint32_t index,
Handle<String> import_name);
// Load data segments into the memory.
void LoadDataSegments(Handle<WasmInstanceObject> instance);
void WriteGlobalValue(const WasmGlobal& global, double value);
void WriteGlobalValue(const WasmGlobal& global, int64_t num);
void WriteGlobalValue(const WasmGlobal& global,
Handle<WasmGlobalObject> value);
void WriteGlobalAnyRef(const WasmGlobal& global, Handle<Object> value);
void SanitizeImports();
// Find the imported memory buffer if there is one. This is used to see if we
// need to recompile with bounds checks before creating the instance.
MaybeHandle<JSArrayBuffer> FindImportedMemoryBuffer() const;
// Processes a single imported function.
bool ProcessImportedFunction(Handle<WasmInstanceObject> instance,
int import_index, int func_index,
Handle<String> module_name,
Handle<String> import_name,
Handle<Object> value);
// Initialize imported tables of type funcref.
bool InitializeImportedIndirectFunctionTable(
Handle<WasmInstanceObject> instance, int table_index, int import_index,
Handle<WasmTableObject> table_object);
// Process a single imported table.
bool ProcessImportedTable(Handle<WasmInstanceObject> instance,
int import_index, int table_index,
Handle<String> module_name,
Handle<String> import_name, Handle<Object> value);
// Process a single imported memory.
bool ProcessImportedMemory(Handle<WasmInstanceObject> instance,
int import_index, Handle<String> module_name,
Handle<String> import_name, Handle<Object> value);
// Process a single imported global.
bool ProcessImportedGlobal(Handle<WasmInstanceObject> instance,
int import_index, int global_index,
Handle<String> module_name,
Handle<String> import_name, Handle<Object> value);
// Process a single imported WasmGlobalObject.
bool ProcessImportedWasmGlobalObject(Handle<WasmInstanceObject> instance,
int import_index,
Handle<String> module_name,
Handle<String> import_name,
const WasmGlobal& global,
Handle<WasmGlobalObject> global_object);
// Compile import wrappers in parallel. The result goes into the native
// module's import_wrapper_cache.
void CompileImportWrappers(Handle<WasmInstanceObject> instance);
// Process the imports, including functions, tables, globals, and memory, in
// order, loading them from the {ffi_} object. Returns the number of imported
// functions.
int ProcessImports(Handle<WasmInstanceObject> instance);
template <typename T>
T* GetRawGlobalPtr(const WasmGlobal& global);
// Process initialization of globals.
void InitGlobals(Handle<WasmInstanceObject> instance);
// Allocate memory for a module instance as a new JSArrayBuffer.
Handle<JSArrayBuffer> AllocateMemory(uint32_t initial_pages,
uint32_t maximum_pages);
bool NeedsWrappers() const;
// Process the exports, creating wrappers for functions, tables, memories,
// and globals.
void ProcessExports(Handle<WasmInstanceObject> instance);
void InitializeIndirectFunctionTables(Handle<WasmInstanceObject> instance);
void LoadTableSegments(Handle<WasmInstanceObject> instance);
// Creates new exception tags for all exceptions. Note that some tags might
// already exist if they were imported, those tags will be re-used.
void InitializeExceptions(Handle<WasmInstanceObject> instance);
};
MaybeHandle<WasmInstanceObject> InstantiateToInstanceObject(
Isolate* isolate, ErrorThrower* thrower,
Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports,
MaybeHandle<JSArrayBuffer> memory) {
InstanceBuilder builder(isolate, thrower, module_object, imports, memory);
auto instance = builder.Build();
if (!instance.is_null() && builder.ExecuteStartFunction()) {
return instance;
}
DCHECK(isolate->has_pending_exception() || thrower->error());
return {};
}
InstanceBuilder::InstanceBuilder(Isolate* isolate, ErrorThrower* thrower,
Handle<WasmModuleObject> module_object,
MaybeHandle<JSReceiver> ffi,
MaybeHandle<JSArrayBuffer> memory)
: isolate_(isolate),
enabled_(module_object->native_module()->enabled_features()),
module_(module_object->module()),
thrower_(thrower),
module_object_(module_object),
ffi_(ffi),
memory_(memory) {
sanitized_imports_.reserve(module_->import_table.size());
}
// Build an instance, in all of its glory.
MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "InstanceBuilder::Build");
// 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 {};
}
SanitizeImports();
if (thrower_->error()) return {};
// From here on, we expect the build pipeline to run without exiting to JS.
DisallowJavascriptExecution no_js(isolate_);
// Record build time into correct bucket, then build instance.
TimedHistogramScope wasm_instantiate_module_time_scope(SELECT_WASM_COUNTER(
isolate_->counters(), module_->origin, wasm_instantiate, module_time));
NativeModule* native_module = module_object_->native_module();
//--------------------------------------------------------------------------
// Allocate the memory array buffer.
//--------------------------------------------------------------------------
uint32_t initial_pages = module_->initial_pages;
auto initial_pages_counter = SELECT_WASM_COUNTER(
isolate_->counters(), module_->origin, wasm, min_mem_pages_count);
initial_pages_counter->AddSample(initial_pages);
if (module_->has_maximum_pages) {
DCHECK_EQ(kWasmOrigin, module_->origin);
auto max_pages_counter =
isolate_->counters()->wasm_wasm_max_mem_pages_count();
max_pages_counter->AddSample(module_->maximum_pages);
}
// Asm.js has memory_ already set at this point, so we don't want to
// overwrite it.
if (memory_.is_null()) {
memory_ = FindImportedMemoryBuffer();
}
if (!memory_.is_null()) {
// Set externally passed ArrayBuffer non detachable.
Handle<JSArrayBuffer> memory = memory_.ToHandleChecked();
memory->set_is_detachable(false);
DCHECK_IMPLIES(native_module->use_trap_handler(),
is_asmjs_module(module_) || memory->is_wasm_memory() ||
memory->backing_store() == nullptr);
} else if (initial_pages > 0 || native_module->use_trap_handler()) {
// We need to unconditionally create a guard region if using trap handlers,
// even when the size is zero to prevent null-dereference issues
// (e.g. https://crbug.com/769637).
// Allocate memory if the initial size is more than 0 pages.
memory_ = AllocateMemory(initial_pages, module_->maximum_pages);
if (memory_.is_null()) {
// failed to allocate memory
DCHECK(isolate_->has_pending_exception() || thrower_->error());
return {};
}
}
//--------------------------------------------------------------------------
// Create the WebAssembly.Instance object.
//--------------------------------------------------------------------------
TRACE("New module instantiation for %p\n", native_module);
Handle<WasmInstanceObject> instance =
WasmInstanceObject::New(isolate_, module_object_);
NativeModuleModificationScope native_modification_scope(native_module);
//--------------------------------------------------------------------------
// Set up the globals for the new instance.
//--------------------------------------------------------------------------
uint32_t untagged_globals_buffer_size = module_->untagged_globals_buffer_size;
if (untagged_globals_buffer_size > 0) {
void* backing_store = isolate_->array_buffer_allocator()->Allocate(
untagged_globals_buffer_size);
if (backing_store == nullptr) {
thrower_->RangeError("Out of memory: wasm globals");
return {};
}
untagged_globals_ = isolate_->factory()->NewJSArrayBuffer(
SharedFlag::kNotShared, AllocationType::kOld);
constexpr bool is_external = false;
constexpr bool is_wasm_memory = false;
JSArrayBuffer::Setup(untagged_globals_, isolate_, is_external,
backing_store, untagged_globals_buffer_size,
SharedFlag::kNotShared, is_wasm_memory);
if (untagged_globals_.is_null()) {
thrower_->RangeError("Out of memory: wasm globals");
return {};
}
instance->set_globals_start(
reinterpret_cast<byte*>(untagged_globals_->backing_store()));
instance->set_untagged_globals_buffer(*untagged_globals_);
}
uint32_t tagged_globals_buffer_size = module_->tagged_globals_buffer_size;
if (tagged_globals_buffer_size > 0) {
tagged_globals_ = isolate_->factory()->NewFixedArray(
static_cast<int>(tagged_globals_buffer_size));
instance->set_tagged_globals_buffer(*tagged_globals_);
}
//--------------------------------------------------------------------------
// Set up the array of references to imported globals' array buffers.
//--------------------------------------------------------------------------
if (module_->num_imported_mutable_globals > 0) {
// TODO(binji): This allocates one slot for each mutable global, which is
// more than required if multiple globals are imported from the same
// module.
Handle<FixedArray> buffers_array = isolate_->factory()->NewFixedArray(
module_->num_imported_mutable_globals, AllocationType::kOld);
instance->set_imported_mutable_globals_buffers(*buffers_array);
}
//--------------------------------------------------------------------------
// Set up the exception table used for exception tag checks.
//--------------------------------------------------------------------------
int exceptions_count = static_cast<int>(module_->exceptions.size());
if (exceptions_count > 0) {
Handle<FixedArray> exception_table = isolate_->factory()->NewFixedArray(
exceptions_count, AllocationType::kOld);
instance->set_exceptions_table(*exception_table);
exception_wrappers_.resize(exceptions_count);
}
//--------------------------------------------------------------------------
// Set up table storage space.
//--------------------------------------------------------------------------
int table_count = static_cast<int>(module_->tables.size());
{
Handle<FixedArray> tables = isolate_->factory()->NewFixedArray(table_count);
for (int i = module_->num_imported_tables; i < table_count; i++) {
const WasmTable& table = module_->tables[i];
Handle<WasmTableObject> table_obj = WasmTableObject::New(
isolate_, table.type, table.initial_size, table.has_maximum_size,
table.maximum_size, nullptr);
tables->set(i, *table_obj);
}
instance->set_tables(*tables);
}
{
Handle<FixedArray> tables = isolate_->factory()->NewFixedArray(table_count);
// Table 0 is handled specially. See {InitializeIndirectFunctionTable} for
// the initilization. All generated and runtime code will use this optimized
// shortcut in the instance. Hence it is safe to start with table 1 in the
// iteration below.
for (int i = 1; i < table_count; ++i) {
const WasmTable& table = module_->tables[i];
if (table.type == kWasmFuncRef) {
Handle<WasmIndirectFunctionTable> table_obj =
WasmIndirectFunctionTable::New(isolate_, table.initial_size);
tables->set(i, *table_obj);
}
}
instance->set_indirect_function_tables(*tables);
}
//--------------------------------------------------------------------------
// Process the imports for the module.
//--------------------------------------------------------------------------
int num_imported_functions = ProcessImports(instance);
if (num_imported_functions < 0) return {};
//--------------------------------------------------------------------------
// Process the initialization for the module's globals.
//--------------------------------------------------------------------------
InitGlobals(instance);
//--------------------------------------------------------------------------
// Initialize the indirect tables.
//--------------------------------------------------------------------------
if (table_count > 0) {
InitializeIndirectFunctionTables(instance);
}
//--------------------------------------------------------------------------
// Initialize the exceptions table.
//--------------------------------------------------------------------------
if (exceptions_count > 0) {
InitializeExceptions(instance);
}
//--------------------------------------------------------------------------
// Create the WebAssembly.Memory object.
//--------------------------------------------------------------------------
if (module_->has_memory) {
if (!instance->has_memory_object()) {
// No memory object exists. Create one.
Handle<WasmMemoryObject> memory_object = WasmMemoryObject::New(
isolate_, memory_,
module_->maximum_pages != 0 ? module_->maximum_pages : -1);
instance->set_memory_object(*memory_object);
}
// Add the instance object to the list of instances for this memory.
Handle<WasmMemoryObject> memory_object(instance->memory_object(), isolate_);
WasmMemoryObject::AddInstance(isolate_, memory_object, instance);
if (!memory_.is_null()) {
// Double-check the {memory} array buffer matches the instance.
Handle<JSArrayBuffer> memory = memory_.ToHandleChecked();
CHECK_EQ(instance->memory_size(), memory->byte_length());
CHECK_EQ(instance->memory_start(), memory->backing_store());
}
}
// The bulk memory proposal changes the MVP behavior here; the segments are
// written as if `memory.init` and `table.init` are executed directly, and
// not bounds checked ahead of time.
if (!enabled_.bulk_memory) {
//--------------------------------------------------------------------------
// Check that indirect function table segments are within bounds.
//--------------------------------------------------------------------------
for (const WasmElemSegment& elem_segment : module_->elem_segments) {
if (!elem_segment.active) continue;
DCHECK_LT(elem_segment.table_index, table_count);
uint32_t base = EvalUint32InitExpr(instance, elem_segment.offset);
// Because of imported tables, {table_size} has to come from the table
// object itself.
auto table_object = handle(WasmTableObject::cast(instance->tables().get(
elem_segment.table_index)),
isolate_);
size_t table_size = table_object->entries().length();
if (!IsInBounds(base, elem_segment.entries.size(), table_size)) {
thrower_->LinkError("table initializer is out of bounds");
return {};
}
}
//--------------------------------------------------------------------------
// Check that memory segments are within bounds.
//--------------------------------------------------------------------------
for (const WasmDataSegment& seg : module_->data_segments) {
if (!seg.active) continue;
uint32_t base = EvalUint32InitExpr(instance, seg.dest_addr);
if (!IsInBounds(base, seg.source.length(), instance->memory_size())) {
thrower_->LinkError("data segment is out of bounds");
return {};
}
}
}
//--------------------------------------------------------------------------
// Set up the exports object for the new instance.
//--------------------------------------------------------------------------
ProcessExports(instance);
if (thrower_->error()) return {};
//--------------------------------------------------------------------------
// Initialize the indirect function tables.
//--------------------------------------------------------------------------
if (table_count > 0) {
LoadTableSegments(instance);
if (thrower_->error()) return {};
}
//--------------------------------------------------------------------------
// Initialize the memory by loading data segments.
//--------------------------------------------------------------------------
if (module_->data_segments.size() > 0) {
LoadDataSegments(instance);
if (thrower_->error()) return {};
}
//--------------------------------------------------------------------------
// Debugging support.
//--------------------------------------------------------------------------
// Set all breakpoints that were set on the shared module.
WasmModuleObject::SetBreakpointsOnNewInstance(module_object_, instance);
//--------------------------------------------------------------------------
// Create a wrapper for the start function.
//--------------------------------------------------------------------------
if (module_->start_function_index >= 0) {
int start_index = module_->start_function_index;
auto& function = module_->functions[start_index];
Handle<Code> wrapper_code =
JSToWasmWrapperCompilationUnit::CompileJSToWasmWrapper(
isolate_, function.sig, function.imported);
// TODO(clemensh): Don't generate an exported function for the start
// function. Use CWasmEntry instead.
start_function_ = WasmExportedFunction::New(
isolate_, instance, start_index,
static_cast<int>(function.sig->parameter_count()), wrapper_code);
}
DCHECK(!isolate_->has_pending_exception());
TRACE("Successfully built instance for module %p\n",
module_object_->native_module());
return instance;
}
bool InstanceBuilder::ExecuteStartFunction() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"),
"InstanceBuilder::ExecuteStartFunction");
if (start_function_.is_null()) return true; // No start function.
HandleScope scope(isolate_);
// Call the JS function.
Handle<Object> undefined = isolate_->factory()->undefined_value();
MaybeHandle<Object> retval =
Execution::Call(isolate_, start_function_, undefined, 0, nullptr);
if (retval.is_null()) {
DCHECK(isolate_->has_pending_exception());
return false;
}
return true;
}
// Look up an import value in the {ffi_} object.
MaybeHandle<Object> InstanceBuilder::LookupImport(uint32_t index,
Handle<String> module_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::GetPropertyOrElement(
isolate_, ffi_.ToHandleChecked(), module_name);
if (result.is_null()) {
return ReportTypeError("module not found", index, module_name);
}
Handle<Object> module = result.ToHandleChecked();
// Look up the value in the module.
if (!module->IsJSReceiver()) {
return ReportTypeError("module is not an object or function", index,
module_name);
}
result = Object::GetPropertyOrElement(isolate_, module, import_name);
if (result.is_null()) {
ReportLinkError("import not found", index, module_name, import_name);
return MaybeHandle<JSFunction>();
}
return result;
}
// Look up an import value in the {ffi_} object specifically for linking an
// asm.js module. This only performs non-observable lookups, which allows
// falling back to JavaScript proper (and hence re-executing all lookups) if
// module instantiation fails.
MaybeHandle<Object> InstanceBuilder::LookupImportAsm(
uint32_t index, Handle<String> import_name) {
// Check that a foreign function interface object was provided.
if (ffi_.is_null()) {
return ReportLinkError("missing imports object", index, import_name);
}
// Perform lookup of the given {import_name} without causing any observable
// side-effect. We only accept accesses that resolve to data properties,
// which is indicated by the asm.js spec in section 7 ("Linking") as well.
Handle<Object> result;
LookupIterator it = LookupIterator::PropertyOrElement(
isolate_, ffi_.ToHandleChecked(), import_name);
switch (it.state()) {
case LookupIterator::ACCESS_CHECK:
case LookupIterator::INTEGER_INDEXED_EXOTIC:
case LookupIterator::INTERCEPTOR:
case LookupIterator::JSPROXY:
case LookupIterator::ACCESSOR:
case LookupIterator::TRANSITION:
return ReportLinkError("not a data property", index, import_name);
case LookupIterator::NOT_FOUND:
// Accepting missing properties as undefined does not cause any
// observable difference from JavaScript semantics, we are lenient.
result = isolate_->factory()->undefined_value();
break;
case LookupIterator::DATA:
result = it.GetDataValue();
break;
}
return result;
}
// Load data segments into the memory.
void InstanceBuilder::LoadDataSegments(Handle<WasmInstanceObject> instance) {
Vector<const uint8_t> wire_bytes =
module_object_->native_module()->wire_bytes();
for (const WasmDataSegment& segment : module_->data_segments) {
uint32_t size = segment.source.length();
if (enabled_.bulk_memory) {
// Passive segments are not copied during instantiation.
if (!segment.active) continue;
uint32_t dest_offset = EvalUint32InitExpr(instance, segment.dest_addr);
bool ok = ClampToBounds(dest_offset, &size,
static_cast<uint32_t>(instance->memory_size()));
Address dest_addr =
reinterpret_cast<Address>(instance->memory_start()) + dest_offset;
Address src_addr = reinterpret_cast<Address>(wire_bytes.begin()) +
segment.source.offset();
memory_copy_wrapper(dest_addr, src_addr, size);
if (!ok) {
thrower_->LinkError("data segment is out of bounds");
return;
}
} else {
DCHECK(segment.active);
// Segments of size == 0 are just nops.
if (size == 0) continue;
uint32_t dest_offset = EvalUint32InitExpr(instance, segment.dest_addr);
DCHECK(IsInBounds(dest_offset, size, instance->memory_size()));
byte* dest = instance->memory_start() + dest_offset;
const byte* src = wire_bytes.begin() + segment.source.offset();
memcpy(dest, src, size);
}
}
}
void InstanceBuilder::WriteGlobalValue(const WasmGlobal& global, double num) {
TRACE("init [globals_start=%p + %u] = %lf, type = %s\n",
raw_buffer_ptr(untagged_globals_, 0), global.offset, num,
ValueTypes::TypeName(global.type));
switch (global.type) {
case kWasmI32:
WriteLittleEndianValue<int32_t>(GetRawGlobalPtr<int32_t>(global),
DoubleToInt32(num));
break;
case kWasmI64:
// The Wasm-BigInt proposal currently says that i64 globals may
// only be initialized with BigInts. See:
// https://github.com/WebAssembly/JS-BigInt-integration/issues/12
UNREACHABLE();
case kWasmF32:
WriteLittleEndianValue<float>(GetRawGlobalPtr<float>(global),
DoubleToFloat32(num));
break;
case kWasmF64:
WriteLittleEndianValue<double>(GetRawGlobalPtr<double>(global), num);
break;
default:
UNREACHABLE();
}
}
void InstanceBuilder::WriteGlobalValue(const WasmGlobal& global, int64_t num) {
TRACE("init [globals_start=%p + %u] = %" PRId64 ", type = %s\n",
raw_buffer_ptr(untagged_globals_, 0), global.offset, num,
ValueTypes::TypeName(global.type));
DCHECK_EQ(kWasmI64, global.type);
WriteLittleEndianValue<int64_t>(GetRawGlobalPtr<int64_t>(global), num);
}
void InstanceBuilder::WriteGlobalValue(const WasmGlobal& global,
Handle<WasmGlobalObject> value) {
TRACE("init [globals_start=%p + %u] = ", raw_buffer_ptr(untagged_globals_, 0),
global.offset);
switch (global.type) {
case kWasmI32: {
int32_t num = value->GetI32();
WriteLittleEndianValue<int32_t>(GetRawGlobalPtr<int32_t>(global), num);
TRACE("%d", num);
break;
}
case kWasmI64: {
int64_t num = value->GetI64();
WriteLittleEndianValue<int64_t>(GetRawGlobalPtr<int64_t>(global), num);
TRACE("%" PRId64, num);
break;
}
case kWasmF32: {
float num = value->GetF32();
WriteLittleEndianValue<float>(GetRawGlobalPtr<float>(global), num);
TRACE("%f", num);
break;
}
case kWasmF64: {
double num = value->GetF64();
WriteLittleEndianValue<double>(GetRawGlobalPtr<double>(global), num);
TRACE("%lf", num);
break;
}
case kWasmAnyRef:
case kWasmFuncRef:
case kWasmExnRef: {
tagged_globals_->set(global.offset, *value->GetRef());
break;
}
default:
UNREACHABLE();
}
TRACE(", type = %s (from WebAssembly.Global)\n",
ValueTypes::TypeName(global.type));
}
void InstanceBuilder::WriteGlobalAnyRef(const WasmGlobal& global,
Handle<Object> value) {
tagged_globals_->set(global.offset, *value, UPDATE_WRITE_BARRIER);
}
void InstanceBuilder::SanitizeImports() {
Vector<const uint8_t> wire_bytes =
module_object_->native_module()->wire_bytes();
for (size_t index = 0; index < module_->import_table.size(); ++index) {
const WasmImport& import = module_->import_table[index];
Handle<String> module_name;
MaybeHandle<String> maybe_module_name =
WasmModuleObject::ExtractUtf8StringFromModuleBytes(isolate_, wire_bytes,
import.module_name);
if (!maybe_module_name.ToHandle(&module_name)) {
thrower_->LinkError("Could not resolve module name for import %zu",
index);
return;
}
Handle<String> import_name;
MaybeHandle<String> maybe_import_name =
WasmModuleObject::ExtractUtf8StringFromModuleBytes(isolate_, wire_bytes,
import.field_name);
if (!maybe_import_name.ToHandle(&import_name)) {
thrower_->LinkError("Could not resolve import name for import %zu",
index);
return;
}
int int_index = static_cast<int>(index);
MaybeHandle<Object> result =
is_asmjs_module(module_)
? LookupImportAsm(int_index, import_name)
: LookupImport(int_index, module_name, import_name);
if (thrower_->error()) {
thrower_->LinkError("Could not find value for import %zu", index);
return;
}
Handle<Object> value = result.ToHandleChecked();
sanitized_imports_.push_back({module_name, import_name, value});
}
}
MaybeHandle<JSArrayBuffer> InstanceBuilder::FindImportedMemoryBuffer() const {
DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size());
for (size_t index = 0; index < module_->import_table.size(); index++) {
const WasmImport& import = module_->import_table[index];
if (import.kind == kExternalMemory) {
const auto& value = sanitized_imports_[index].value;
if (!value->IsWasmMemoryObject()) {
return {};
}
auto memory = Handle<WasmMemoryObject>::cast(value);
Handle<JSArrayBuffer> buffer(memory->array_buffer(), isolate_);
return buffer;
}
}
return {};
}
bool InstanceBuilder::ProcessImportedFunction(
Handle<WasmInstanceObject> instance, int import_index, int func_index,
Handle<String> module_name, Handle<String> import_name,
Handle<Object> value) {
// Function imports must be callable.
if (!value->IsCallable()) {
ReportLinkError("function import requires a callable", import_index,
module_name, import_name);
return false;
}
auto js_receiver = Handle<JSReceiver>::cast(value);
FunctionSig* expected_sig = module_->functions[func_index].sig;
auto resolved = compiler::ResolveWasmImportCall(js_receiver, expected_sig,
enabled_.bigint);
compiler::WasmImportCallKind kind = resolved.first;
js_receiver = resolved.second;
switch (kind) {
case compiler::WasmImportCallKind::kLinkError:
ReportLinkError("imported function does not match the expected type",
import_index, module_name, import_name);
return false;
case compiler::WasmImportCallKind::kWasmToWasm: {
// The imported function is a WASM function from another instance.
auto imported_function = Handle<WasmExportedFunction>::cast(js_receiver);
Handle<WasmInstanceObject> imported_instance(
imported_function->instance(), isolate_);
// The import reference is the instance object itself.
Address imported_target = imported_function->GetWasmCallTarget();
ImportedFunctionEntry entry(instance, func_index);
entry.SetWasmToWasm(*imported_instance, imported_target);
// Also store the {WasmExportedFunction} in the instance to preserve its
// identity.
WasmInstanceObject::SetWasmExportedFunction(
isolate_, instance, func_index, imported_function);
break;
}
case compiler::WasmImportCallKind::kWasmToCapi: {
NativeModule* native_module = instance->module_object().native_module();
Address host_address =
WasmCapiFunction::cast(*js_receiver).GetHostCallTarget();
WasmCodeRefScope code_ref_scope;
WasmCode* wasm_code = compiler::CompileWasmCapiCallWrapper(
isolate_->wasm_engine(), native_module, expected_sig, host_address);
isolate_->counters()->wasm_generated_code_size()->Increment(
wasm_code->instructions().length());
isolate_->counters()->wasm_reloc_size()->Increment(
wasm_code->reloc_info().length());
ImportedFunctionEntry entry(instance, func_index);
// We re-use the SetWasmToJs infrastructure because it passes the
// callable to the wrapper, which we need to get the function data.
entry.SetWasmToJs(isolate_, js_receiver, wasm_code);
break;
}
default: {
// The imported function is a callable.
NativeModule* native_module = instance->module_object().native_module();
WasmCode* wasm_code =
native_module->import_wrapper_cache()->Get(kind, expected_sig);
DCHECK_NOT_NULL(wasm_code);
ImportedFunctionEntry entry(instance, func_index);
if (wasm_code->kind() == WasmCode::kWasmToJsWrapper) {
// Wasm to JS wrappers are treated specially in the import table.
entry.SetWasmToJs(isolate_, js_receiver, wasm_code);
} else {
// Wasm math intrinsics are compiled as regular Wasm functions.
DCHECK(kind >= compiler::WasmImportCallKind::kFirstMathIntrinsic &&
kind <= compiler::WasmImportCallKind::kLastMathIntrinsic);
entry.SetWasmToWasm(*instance, wasm_code->instruction_start());
}
break;
}
}
return true;
}
bool InstanceBuilder::InitializeImportedIndirectFunctionTable(
Handle<WasmInstanceObject> instance, int table_index, int import_index,
Handle<WasmTableObject> table_object) {
int imported_table_size = table_object->entries().length();
// Allocate a new dispatch table.
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance, table_index, imported_table_size);
// Initialize the dispatch table with the (foreign) JS functions
// that are already in the table.
for (int i = 0; i < imported_table_size; ++i) {
bool is_valid;
bool is_null;
MaybeHandle<WasmInstanceObject> maybe_target_instance;
int function_index;
MaybeHandle<WasmJSFunction> maybe_js_function;
WasmTableObject::GetFunctionTableEntry(isolate_, table_object, i, &is_valid,
&is_null, &maybe_target_instance,
&function_index, &maybe_js_function);
if (!is_valid) {
thrower_->LinkError("table import %d[%d] is not a wasm function",
import_index, i);
return false;
}
if (is_null) continue;
Handle<WasmJSFunction> js_function;
if (maybe_js_function.ToHandle(&js_function)) {
WasmInstanceObject::ImportWasmJSFunctionIntoTable(
isolate_, instance, table_index, i, js_function);
continue;
}
Handle<WasmInstanceObject> target_instance =
maybe_target_instance.ToHandleChecked();
FunctionSig* sig = target_instance->module_object()
.module()
->functions[function_index]
.sig;
// Look up the signature's canonical id. If there is no canonical
// id, then the signature does not appear at all in this module,
// so putting {-1} in the table will cause checks to always fail.
IndirectFunctionTableEntry(instance, table_index, i)
.Set(module_->signature_map.Find(*sig), target_instance,
function_index);
}
return true;
}
bool InstanceBuilder::ProcessImportedTable(Handle<WasmInstanceObject> instance,
int import_index, int table_index,
Handle<String> module_name,
Handle<String> import_name,
Handle<Object> value) {
if (!value->IsWasmTableObject()) {
ReportLinkError("table import requires a WebAssembly.Table", import_index,
module_name, import_name);
return false;
}
const WasmTable& table = module_->tables[table_index];
auto table_object = Handle<WasmTableObject>::cast(value);
int imported_table_size = table_object->entries().length();
if (imported_table_size < static_cast<int>(table.initial_size)) {
thrower_->LinkError("table import %d is smaller than initial %d, got %u",
import_index, table.initial_size, imported_table_size);
return false;
}
if (table.has_maximum_size) {
if (table_object->maximum_length().IsUndefined(isolate_)) {
thrower_->LinkError("table import %d has no maximum length, expected %d",
import_index, table.maximum_size);
return false;
}
int64_t imported_maximum_size = table_object->maximum_length().Number();
if (imported_maximum_size < 0) {
thrower_->LinkError("table import %d has no maximum length, expected %d",
import_index, table.maximum_size);
return false;
}
if (imported_maximum_size > table.maximum_size) {
thrower_->LinkError("table import %d has a larger maximum size %" PRIx64
" than the module's declared maximum %u",
import_index, imported_maximum_size,
table.maximum_size);
return false;
}
}
if (table.type != table_object->type()) {
ReportLinkError("imported table does not match the expected type",
import_index, module_name, import_name);
return false;
}
if (table.type == kWasmFuncRef &&
!InitializeImportedIndirectFunctionTable(instance, table_index,
import_index, table_object)) {
return false;
}
instance->tables().set(table_index, *value);
return true;
}
bool InstanceBuilder::ProcessImportedMemory(Handle<WasmInstanceObject> instance,
int import_index,
Handle<String> module_name,
Handle<String> import_name,
Handle<Object> value) {
// Validation should have failed if more than one memory object was
// provided.
DCHECK(!instance->has_memory_object());
if (!value->IsWasmMemoryObject()) {
ReportLinkError("memory import must be a WebAssembly.Memory object",
import_index, module_name, import_name);
return false;
}
auto memory = Handle<WasmMemoryObject>::cast(value);
instance->set_memory_object(*memory);
Handle<JSArrayBuffer> buffer(memory->array_buffer(), isolate_);
// memory_ should have already been assigned in Build().
DCHECK_EQ(*memory_.ToHandleChecked(), *buffer);
uint32_t imported_cur_pages =
static_cast<uint32_t>(buffer->byte_length() / kWasmPageSize);
if (imported_cur_pages < module_->initial_pages) {
thrower_->LinkError("memory import %d is smaller than initial %u, got %u",
import_index, module_->initial_pages,
imported_cur_pages);
return false;
}
int32_t imported_maximum_pages = memory->maximum_pages();
if (module_->has_maximum_pages) {
if (imported_maximum_pages < 0) {
thrower_->LinkError(
"memory import %d has no maximum limit, expected at most %u",
import_index, imported_maximum_pages);
return false;
}
if (static_cast<uint32_t>(imported_maximum_pages) >
module_->maximum_pages) {
thrower_->LinkError(
"memory import %d has a larger maximum size %u than the "
"module's declared maximum %u",
import_index, imported_maximum_pages, module_->maximum_pages);
return false;
}
}
if (module_->has_shared_memory != buffer->is_shared()) {
thrower_->LinkError(
"mismatch in shared state of memory, declared = %d, imported = %d",
module_->has_shared_memory, buffer->is_shared());
return false;
}
return true;
}
bool InstanceBuilder::ProcessImportedWasmGlobalObject(
Handle<WasmInstanceObject> instance, int import_index,
Handle<String> module_name, Handle<String> import_name,
const WasmGlobal& global, Handle<WasmGlobalObject> global_object) {
if (global_object->is_mutable() != global.mutability) {
ReportLinkError("imported global does not match the expected mutability",
import_index, module_name, import_name);
return false;
}
bool is_sub_type = ValueTypes::IsSubType(global_object->type(), global.type);
bool is_same_type = global_object->type() == global.type;
bool valid_type = global.mutability ? is_same_type : is_sub_type;
if (!valid_type) {
ReportLinkError("imported global does not match the expected type",
import_index, module_name, import_name);
return false;
}
if (global.mutability) {
DCHECK_LT(global.index, module_->num_imported_mutable_globals);
Handle<Object> buffer;
Address address_or_offset;
if (ValueTypes::IsReferenceType(global.type)) {
static_assert(sizeof(global_object->offset()) <= sizeof(Address),
"The offset into the globals buffer does not fit into "
"the imported_mutable_globals array");
buffer = handle(global_object->tagged_buffer(), isolate_);
// For anyref globals we use a relative offset, not an absolute address.
address_or_offset = static_cast<Address>(global_object->offset());
} else {
buffer = handle(global_object->untagged_buffer(), isolate_);
// It is safe in this case to store the raw pointer to the buffer
// since the backing store of the JSArrayBuffer will not be
// relocated.
address_or_offset = reinterpret_cast<Address>(raw_buffer_ptr(
Handle<JSArrayBuffer>::cast(buffer), global_object->offset()));
}
instance->imported_mutable_globals_buffers().set(global.index, *buffer);
instance->imported_mutable_globals()[global.index] = address_or_offset;
return true;
}
WriteGlobalValue(global, global_object);
return true;
}
bool InstanceBuilder::ProcessImportedGlobal(Handle<WasmInstanceObject> instance,
int import_index, int global_index,
Handle<String> module_name,
Handle<String> import_name,
Handle<Object> value) {
// Immutable global imports are converted to numbers and written into
// the {untagged_globals_} array buffer.
//
// Mutable global imports instead have their backing array buffers
// referenced by this instance, and store the address of the imported
// global in the {imported_mutable_globals_} array.
const WasmGlobal& global = module_->globals[global_index];
// The mutable-global proposal allows importing i64 values, but only if
// they are passed as a WebAssembly.Global object.
//
// However, the bigint proposal allows importing constant i64 values,
// as non WebAssembly.Global object.
if (global.type == kWasmI64 && !enabled_.bigint &&
!value->IsWasmGlobalObject()) {
ReportLinkError("global import cannot have type i64", import_index,
module_name, import_name);
return false;
}
if (is_asmjs_module(module_)) {
// Accepting {JSFunction} on top of just primitive values here is a
// workaround to support legacy asm.js code with broken binding. Note
// that using {NaN} (or Smi::kZero) here is what using the observable
// conversion via {ToPrimitive} would produce as well.
// TODO(mstarzinger): Still observable if Function.prototype.valueOf
// or friends are patched, we might need to check for that as well.
if (value->IsJSFunction()) value = isolate_->factory()->nan_value();
if (value->IsPrimitive() && !value->IsSymbol()) {
if (global.type == kWasmI32) {
value = Object::ToInt32(isolate_, value).ToHandleChecked();
} else {
value = Object::ToNumber(isolate_, value).ToHandleChecked();
}
}
}
if (value->IsWasmGlobalObject()) {
auto global_object = Handle<WasmGlobalObject>::cast(value);
return ProcessImportedWasmGlobalObject(instance, import_index, module_name,
import_name, global, global_object);
}
if (global.mutability) {
ReportLinkError(
"imported mutable global must be a WebAssembly.Global object",
import_index, module_name, import_name);
return false;
}
if (ValueTypes::IsReferenceType(global.type)) {
// There shouldn't be any null-ref globals.
DCHECK_NE(ValueType::kWasmNullRef, global.type);
if (global.type == ValueType::kWasmFuncRef) {
if (!value->IsNull(isolate_) &&
!WasmExportedFunction::IsWasmExportedFunction(*value)) {
ReportLinkError(
"imported funcref global must be null or an exported function",
import_index, module_name, import_name);
return false;
}
}
WriteGlobalAnyRef(global, value);
return true;
}
if (value->IsNumber() && global.type != kWasmI64) {
WriteGlobalValue(global, value->Number());
return true;
}
if (enabled_.bigint && global.type == kWasmI64) {
Handle<BigInt> bigint;
if (!BigInt::FromObject(isolate_, value).ToHandle(&bigint)) {
return false;
}
WriteGlobalValue(global, bigint->AsInt64());
return true;
}
ReportLinkError("global import must be a number or WebAssembly.Global object",
import_index, module_name, import_name);
return false;
}
void InstanceBuilder::CompileImportWrappers(
Handle<WasmInstanceObject> instance) {
int num_imports = static_cast<int>(module_->import_table.size());
NativeModule* native_module = instance->module_object().native_module();
WasmImportWrapperCache::ModificationScope cache_scope(
native_module->import_wrapper_cache());
// Compilation is done in two steps:
// 1) Insert nullptr entries in the cache for wrappers that need to be
// compiled. 2) Compile wrappers in background tasks using the
// ImportWrapperQueue. This way the cache won't invalidate other iterators
// when inserting a new WasmCode, since the key will already be there.
ImportWrapperQueue import_wrapper_queue;
for (int index = 0; index < num_imports; ++index) {
Handle<Object> value = sanitized_imports_[index].value;
if (module_->import_table[index].kind != kExternalFunction ||
!value->IsCallable()) {
continue;
}
auto js_receiver = Handle<JSReceiver>::cast(value);
uint32_t func_index = module_->import_table[index].index;
FunctionSig* sig = module_->functions[func_index].sig;
auto resolved =
compiler::ResolveWasmImportCall(js_receiver, sig, enabled_.bigint);
compiler::WasmImportCallKind kind = resolved.first;
if (kind == compiler::WasmImportCallKind::kWasmToWasm ||
kind == compiler::WasmImportCallKind::kLinkError ||
kind == compiler::WasmImportCallKind::kWasmToCapi) {
continue;
}
WasmImportWrapperCache::CacheKey key(kind, sig);
if (cache_scope[key] != nullptr) {
// Cache entry already exists, no need to compile it again.
continue;
}
import_wrapper_queue.insert(key);
}
CancelableTaskManager task_manager;
const int max_background_tasks = GetMaxBackgroundTasks();
for (int i = 0; i < max_background_tasks; ++i) {
auto task = base::make_unique<CompileImportWrapperTask>(
&task_manager, isolate_->wasm_engine(), isolate_->counters(),
native_module, &import_wrapper_queue, &cache_scope);
V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task));
}
// Also compile in the current thread, in case there are no worker threads.
while (base::Optional<WasmImportWrapperCache::CacheKey> key =
import_wrapper_queue.pop()) {
CompileImportWrapper(isolate_->wasm_engine(), native_module,
isolate_->counters(), key->first, key->second,
&cache_scope);
}
task_manager.CancelAndWait();
}
// Process the imports, including functions, tables, globals, and memory, in
// order, loading them from the {ffi_} object. Returns the number of imported
// functions.
int InstanceBuilder::ProcessImports(Handle<WasmInstanceObject> instance) {
int num_imported_functions = 0;
int num_imported_tables = 0;
DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size());
CompileImportWrappers(instance);
int num_imports = static_cast<int>(module_->import_table.size());
for (int index = 0; index < num_imports; ++index) {
const WasmImport& import = module_->import_table[index];
Handle<String> module_name = sanitized_imports_[index].module_name;
Handle<String> import_name = sanitized_imports_[index].import_name;
Handle<Object> value = sanitized_imports_[index].value;
switch (import.kind) {
case kExternalFunction: {
uint32_t func_index = import.index;
DCHECK_EQ(num_imported_functions, func_index);
if (!ProcessImportedFunction(instance, index, func_index, module_name,
import_name, value)) {
return -1;
}
num_imported_functions++;
break;
}
case kExternalTable: {
uint32_t table_index = import.index;
DCHECK_EQ(table_index, num_imported_tables);
if (!ProcessImportedTable(instance, index, table_index, module_name,
import_name, value)) {
return -1;
}
num_imported_tables++;
break;
}
case kExternalMemory: {
if (!ProcessImportedMemory(instance, index, module_name, import_name,
value)) {
return -1;
}
break;
}
case kExternalGlobal: {
if (!ProcessImportedGlobal(instance, index, import.index, module_name,
import_name, value)) {
return -1;
}
break;
}
case kExternalException: {
if (!value->IsWasmExceptionObject()) {
ReportLinkError("exception import requires a WebAssembly.Exception",
index, module_name, import_name);
return -1;
}
Handle<WasmExceptionObject> imported_exception =
Handle<WasmExceptionObject>::cast(value);
if (!imported_exception->IsSignatureEqual(
module_->exceptions[import.index].sig)) {
ReportLinkError("imported exception does not match the expected type",
index, module_name, import_name);
return -1;
}
Object exception_tag = imported_exception->exception_tag();
DCHECK(instance->exceptions_table().get(import.index).IsUndefined());
instance->exceptions_table().set(import.index, exception_tag);
exception_wrappers_[import.index] = imported_exception;
break;
}
default:
UNREACHABLE();
break;
}
}
return num_imported_functions;
}
template <typename T>
T* InstanceBuilder::GetRawGlobalPtr(const WasmGlobal& global) {
return reinterpret_cast<T*>(raw_buffer_ptr(untagged_globals_, global.offset));
}
// Process initialization of globals.
void InstanceBuilder::InitGlobals(Handle<WasmInstanceObject> instance) {
for (auto global : module_->globals) {
if (global.mutability && global.imported) {
continue;
}
switch (global.init.kind) {
case WasmInitExpr::kI32Const:
WriteLittleEndianValue<int32_t>(GetRawGlobalPtr<int32_t>(global),
global.init.val.i32_const);
break;
case WasmInitExpr::kI64Const:
WriteLittleEndianValue<int64_t>(GetRawGlobalPtr<int64_t>(global),
global.init.val.i64_const);
break;
case WasmInitExpr::kF32Const:
WriteLittleEndianValue<float>(GetRawGlobalPtr<float>(global),
global.init.val.f32_const);
break;
case WasmInitExpr::kF64Const:
WriteLittleEndianValue<double>(GetRawGlobalPtr<double>(global),
global.init.val.f64_const);
break;
case WasmInitExpr::kRefNullConst:
DCHECK(enabled_.anyref || enabled_.eh);
if (global.imported) break; // We already initialized imported globals.
tagged_globals_->set(global.offset,
ReadOnlyRoots(isolate_).null_value(),
SKIP_WRITE_BARRIER);
break;
case WasmInitExpr::kRefFuncConst: {
DCHECK(enabled_.anyref);
auto function = WasmInstanceObject::GetOrCreateWasmExportedFunction(
isolate_, instance, global.init.val.function_index);
tagged_globals_->set(global.offset, *function);
break;
}
case WasmInitExpr::kGlobalIndex: {
// Initialize with another global.
uint32_t new_offset = global.offset;
uint32_t old_offset =
module_->globals[global.init.val.global_index].offset;
TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset);
if (ValueTypes::IsReferenceType(global.type)) {
DCHECK(enabled_.anyref || enabled_.eh);
tagged_globals_->set(new_offset, tagged_globals_->get(old_offset));
} else {
size_t size = (global.type == kWasmI64 || global.type == kWasmF64)
? sizeof(double)
: sizeof(int32_t);
memcpy(raw_buffer_ptr(untagged_globals_, new_offset),
raw_buffer_ptr(untagged_globals_, old_offset), size);
}
break;
}
case WasmInitExpr::kNone:
// Happens with imported globals.
break;
default:
UNREACHABLE();
break;
}
}
}
// Allocate memory for a module instance as a new JSArrayBuffer.
Handle<JSArrayBuffer> InstanceBuilder::AllocateMemory(uint32_t initial_pages,
uint32_t maximum_pages) {
if (initial_pages > max_mem_pages()) {
thrower_->RangeError("Out of memory: wasm memory too large");
return Handle<JSArrayBuffer>::null();
}
const bool is_shared_memory = module_->has_shared_memory && enabled_.threads;
Handle<JSArrayBuffer> mem_buffer;
if (is_shared_memory) {
if (!NewSharedArrayBuffer(isolate_, initial_pages * kWasmPageSize,
maximum_pages * kWasmPageSize)
.ToHandle(&mem_buffer)) {
thrower_->RangeError("Out of memory: wasm shared memory");
}
} else {
if (!NewArrayBuffer(isolate_, initial_pages * kWasmPageSize)
.ToHandle(&mem_buffer)) {
thrower_->RangeError("Out of memory: wasm memory");
}
}
return mem_buffer;
}
bool InstanceBuilder::NeedsWrappers() const {
if (module_->num_exported_functions > 0) return true;
for (auto& table : module_->tables) {
if (table.type == kWasmFuncRef) return true;
}
return false;
}
// Process the exports, creating wrappers for functions, tables, memories,
// globals, and exceptions.
void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
if (NeedsWrappers()) {
// If an imported WebAssembly function gets exported, the exported function
// has to be identical to to imported function. Therefore we cache all
// imported WebAssembly functions in the instance.
for (int index = 0, end = static_cast<int>(module_->import_table.size());
index < end; ++index) {
const WasmImport& import = module_->import_table[index];
if (import.kind == kExternalFunction) {
Handle<Object> value = sanitized_imports_[index].value;
if (WasmExportedFunction::IsWasmExportedFunction(*value)) {
WasmInstanceObject::SetWasmExportedFunction(
isolate_, instance, import.index,
Handle<WasmExportedFunction>::cast(value));
}
}
}
}
Handle<JSObject> exports_object;
MaybeHandle<String> single_function_name;
bool is_asm_js = false;
switch (module_->origin) {
case kWasmOrigin: {
// Create the "exports" object.
exports_object = isolate_->factory()->NewJSObjectWithNullProto();
break;
}
case kAsmJsSloppyOrigin:
case kAsmJsStrictOrigin: {
Handle<JSFunction> object_function = Handle<JSFunction>(
isolate_->native_context()->object_function(), isolate_);
exports_object = isolate_->factory()->NewJSObject(object_function);
single_function_name = isolate_->factory()->InternalizeUtf8String(
AsmJs::kSingleFunctionName);
is_asm_js = true;
break;
}
default:
UNREACHABLE();
}
instance->set_exports_object(*exports_object);
PropertyDescriptor desc;
desc.set_writable(is_asm_js);
desc.set_enumerable(true);
desc.set_configurable(is_asm_js);
// Process each export in the export table.
for (const WasmExport& exp : module_->export_table) {
Handle<String> name = WasmModuleObject::ExtractUtf8StringFromModuleBytes(
isolate_, module_object_, exp.name)
.ToHandleChecked();
Handle<JSObject> export_to = exports_object;
switch (exp.kind) {
case kExternalFunction: {
// Wrap and export the code as a JSFunction.
// TODO(wasm): reduce duplication with LoadElemSegment() further below
MaybeHandle<WasmExportedFunction> wasm_exported_function =
WasmInstanceObject::GetOrCreateWasmExportedFunction(
isolate_, instance, exp.index);
desc.set_value(wasm_exported_function.ToHandleChecked());
if (is_asm_js &&
String::Equals(isolate_, name,
single_function_name.ToHandleChecked())) {
export_to = instance;
}
break;
}
case kExternalTable: {
desc.set_value(handle(instance->tables().get(exp.index), isolate_));
break;
}
case kExternalMemory: {
// Export the memory as a WebAssembly.Memory object. A WasmMemoryObject
// should already be available if the module has memory, since we always
// create or import it when building an WasmInstanceObject.
DCHECK(instance->has_memory_object());
desc.set_value(
Handle<WasmMemoryObject>(instance->memory_object(), isolate_));
break;
}
case kExternalGlobal: {
const WasmGlobal& global = module_->globals[exp.index];
Handle<JSArrayBuffer> untagged_buffer;
Handle<FixedArray> tagged_buffer;
uint32_t offset;
if (global.mutability && global.imported) {
Handle<FixedArray> buffers_array(
instance->imported_mutable_globals_buffers(), isolate_);
if (ValueTypes::IsReferenceType(global.type)) {
tagged_buffer = handle(
FixedArray::cast(buffers_array->get(global.index)), isolate_);
// For anyref globals we store the relative offset in the
// imported_mutable_globals array instead of an absolute address.
Address addr = instance->imported_mutable_globals()[global.index];
DCHECK_LE(addr, static_cast<Address>(
std::numeric_limits<uint32_t>::max()));
offset = static_cast<uint32_t>(addr);
} else {
untagged_buffer =
handle(JSArrayBuffer::cast(buffers_array->get(global.index)),
isolate_);
Address global_addr =
instance->imported_mutable_globals()[global.index];
size_t buffer_size = untagged_buffer->byte_length();
Address backing_store =
reinterpret_cast<Address>(untagged_buffer->backing_store());
CHECK(global_addr >= backing_store &&
global_addr < backing_store + buffer_size);
offset = static_cast<uint32_t>(global_addr - backing_store);
}
} else {
if (ValueTypes::IsReferenceType(global.type)) {
tagged_buffer = handle(instance->tagged_globals_buffer(), isolate_);
} else {
untagged_buffer =
handle(instance->untagged_globals_buffer(), isolate_);
}
offset = global.offset;
}
// Since the global's array untagged_buffer is always provided,
// allocation should never fail.
Handle<WasmGlobalObject> global_obj =
WasmGlobalObject::New(isolate_, untagged_buffer, tagged_buffer,
global.type, offset, global.mutability)
.ToHandleChecked();
desc.set_value(global_obj);
break;
}
case kExternalException: {
const WasmException& exception = module_->exceptions[exp.index];
Handle<WasmExceptionObject> wrapper = exception_wrappers_[exp.index];
if (wrapper.is_null()) {
Handle<HeapObject> exception_tag(
HeapObject::cast(instance->exceptions_table().get(exp.index)),
isolate_);
wrapper =
WasmExceptionObject::New(isolate_, exception.sig, exception_tag);
exception_wrappers_[exp.index] = wrapper;
}
desc.set_value(wrapper);
break;
}
default:
UNREACHABLE();
break;
}
v8::Maybe<bool> status = JSReceiver::DefineOwnProperty(
isolate_, export_to, name, &desc, Just(kThrowOnError));
if (!status.IsJust()) {
DisallowHeapAllocation no_gc;
TruncatedUserString<> trunc_name(name->GetCharVector<uint8_t>(no_gc));
thrower_->LinkError("export of %.*s failed.", trunc_name.length(),
trunc_name.start());
return;
}
}
if (module_->origin == kWasmOrigin) {
v8::Maybe<bool> success =
JSReceiver::SetIntegrityLevel(exports_object, FROZEN, kDontThrow);
DCHECK(success.FromMaybe(false));
USE(success);
}
}
void InstanceBuilder::InitializeIndirectFunctionTables(
Handle<WasmInstanceObject> instance) {
for (int i = 0; i < static_cast<int>(module_->tables.size()); ++i) {
const WasmTable& table = module_->tables[i];
if (table.type == kWasmFuncRef) {
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance, i, table.initial_size);
}
}
}
bool LoadElemSegmentImpl(Isolate* isolate, Handle<WasmInstanceObject> instance,
Handle<WasmTableObject> table_object,
uint32_t table_index,
const WasmElemSegment& elem_segment, uint32_t dst,
uint32_t src, size_t count) {
// TODO(wasm): Move this functionality into wasm-objects, since it is used
// for both instantiation and in the implementation of the table.init
// instruction.
bool ok =
ClampToBounds<size_t>(dst, &count, table_object->entries().length());
// Use & instead of && so the clamp is not short-circuited.
ok &= ClampToBounds<size_t>(src, &count, elem_segment.entries.size());
const WasmModule* module = instance->module();
for (size_t i = 0; i < count; ++i) {
uint32_t func_index = elem_segment.entries[src + i];
int entry_index = static_cast<int>(dst + i);
if (func_index == WasmElemSegment::kNullIndex) {
if (table_object->type() == kWasmFuncRef) {
IndirectFunctionTableEntry(instance, table_index, entry_index).clear();
}
WasmTableObject::Set(isolate, table_object, entry_index,
isolate->factory()->null_value());
continue;
}
const WasmFunction* function = &module->functions[func_index];
// Update the local dispatch table first if necessary.
if (table_object->type() == kWasmFuncRef) {
uint32_t sig_id = module->signature_ids[function->sig_index];
IndirectFunctionTableEntry(instance, table_index, entry_index)
.Set(sig_id, instance, func_index);
}
// For AnyRef tables, we have to generate the WasmExportedFunction eagerly.
// Later we cannot know if an entry is a placeholder or not.
if (table_object->type() == kWasmAnyRef) {
Handle<WasmExportedFunction> wasm_exported_function =
WasmInstanceObject::GetOrCreateWasmExportedFunction(isolate, instance,
func_index);
WasmTableObject::Set(isolate, table_object, entry_index,
wasm_exported_function);
} else {
// Update the table object's other dispatch tables.
MaybeHandle<WasmExportedFunction> wasm_exported_function =
WasmInstanceObject::GetWasmExportedFunction(isolate, instance,
func_index);
if (wasm_exported_function.is_null()) {
// No JSFunction entry yet exists for this function. Create a {Tuple2}
// holding the information to lazily allocate one.
WasmTableObject::SetFunctionTablePlaceholder(
isolate, table_object, entry_index, instance, func_index);
} else {
table_object->entries().set(entry_index,
*wasm_exported_function.ToHandleChecked());
}
// UpdateDispatchTables() updates all other dispatch tables, since
// we have not yet added the dispatch table we are currently building.
WasmTableObject::UpdateDispatchTables(isolate, table_object, entry_index,
function->sig, instance,
func_index);
}
}
return ok;
}
void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) {
for (auto& elem_segment : module_->elem_segments) {
// Passive segments are not copied during instantiation.
if (!elem_segment.active) continue;
uint32_t table_index = elem_segment.table_index;
uint32_t dst = EvalUint32InitExpr(instance, elem_segment.offset);
uint32_t src = 0;
size_t count = elem_segment.entries.size();
bool success = LoadElemSegmentImpl(
isolate_, instance,
handle(WasmTableObject::cast(
instance->tables().get(elem_segment.table_index)),
isolate_),
table_index, elem_segment, dst, src, count);
if (enabled_.bulk_memory) {
if (!success) {
thrower_->LinkError("table initializer is out of bounds");
// Break out instead of returning; we don't want to continue to
// initialize any further element segments, but still need to add
// dispatch tables below.
break;
}
} else {
CHECK(success);
}
}
int table_count = static_cast<int>(module_->tables.size());
for (int index = 0; index < table_count; ++index) {
if (module_->tables[index].type == kWasmFuncRef) {
auto table_object = handle(
WasmTableObject::cast(instance->tables().get(index)), isolate_);
// Add the new dispatch table at the end to avoid redundant lookups.
WasmTableObject::AddDispatchTable(isolate_, table_object, instance,
index);
}
}
}
void InstanceBuilder::InitializeExceptions(
Handle<WasmInstanceObject> instance) {
Handle<FixedArray> exceptions_table(instance->exceptions_table(), isolate_);
for (int index = 0; index < exceptions_table->length(); ++index) {
if (!exceptions_table->get(index).IsUndefined(isolate_)) continue;
Handle<WasmExceptionTag> exception_tag =
WasmExceptionTag::New(isolate_, index);
exceptions_table->set(index, *exception_tag);
}
}
bool LoadElemSegment(Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t table_index, uint32_t segment_index, uint32_t dst,
uint32_t src, uint32_t count) {
auto& elem_segment = instance->module()->elem_segments[segment_index];
return LoadElemSegmentImpl(
isolate, instance,
handle(WasmTableObject::cast(instance->tables().get(table_index)),
isolate),
table_index, elem_segment, dst, src, count);
}
} // namespace wasm
} // namespace internal
} // namespace v8
#undef TRACE