// 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. #include "src/wasm/wasm-objects.h" #include "src/utils.h" #include "src/assembler-inl.h" #include "src/base/iterator.h" #include "src/compiler/wasm-compiler.h" #include "src/debug/debug-interface.h" #include "src/objects-inl.h" #include "src/objects/debug-objects-inl.h" #include "src/trap-handler/trap-handler.h" #include "src/wasm/module-compiler.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-code-manager.h" #include "src/wasm/wasm-code-specialization.h" #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-memory.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-text.h" #define TRACE(...) \ do { \ if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \ } while (false) #define TRACE_IFT(...) \ do { \ if (false) PrintF(__VA_ARGS__); \ } while (false) namespace v8 { namespace internal { // Import a few often used types from the wasm namespace. using WasmFunction = wasm::WasmFunction; using WasmModule = wasm::WasmModule; namespace { // Manages the natively-allocated memory for a WasmInstanceObject. Since // an instance finalizer is not guaranteed to run upon isolate shutdown, // we must use a Managed to guarantee // it is freed. // Native allocations are the signature ids and targets for indirect call // targets, as well as the call targets for imported functions. class WasmInstanceNativeAllocations { public: // Helper macro to set an internal field and the corresponding field // on an instance. #define SET(instance, field, value) \ { \ auto v = value; \ this->field##_ = v; \ instance->set_##field(v); \ } // Allocates initial native storage for a given instance. WasmInstanceNativeAllocations(Handle instance, size_t num_imported_functions) { SET(instance, imported_function_targets, reinterpret_cast( calloc(num_imported_functions, sizeof(Address)))); } ~WasmInstanceNativeAllocations() { free(); } // Frees natively-allocated storage. void free() { ::free(indirect_function_table_sig_ids_); ::free(indirect_function_table_targets_); ::free(imported_function_targets_); indirect_function_table_sig_ids_ = nullptr; indirect_function_table_targets_ = nullptr; imported_function_targets_ = nullptr; } // Resizes the indirect function table. void resize_indirect_function_table(Isolate* isolate, Handle instance, uint32_t new_size) { uint32_t old_size = instance->indirect_function_table_size(); void* new_sig_ids = nullptr; void* new_targets = nullptr; Handle new_instances; if (indirect_function_table_sig_ids_) { // Reallocate the old storage. new_sig_ids = realloc(indirect_function_table_sig_ids_, new_size * sizeof(uint32_t)); new_targets = realloc(indirect_function_table_targets_, new_size * sizeof(Address)); Handle old(instance->indirect_function_table_instances(), isolate); new_instances = isolate->factory()->CopyFixedArrayAndGrow( old, static_cast(new_size - old_size)); } else { // Allocate new storage. new_sig_ids = malloc(new_size * sizeof(uint32_t)); new_targets = malloc(new_size * sizeof(Address)); new_instances = isolate->factory()->NewFixedArray(static_cast(new_size)); } // Initialize new entries. instance->set_indirect_function_table_size(new_size); SET(instance, indirect_function_table_sig_ids, reinterpret_cast(new_sig_ids)); SET(instance, indirect_function_table_targets, reinterpret_cast(new_targets)); instance->set_indirect_function_table_instances(*new_instances); for (uint32_t j = old_size; j < new_size; j++) { IndirectFunctionTableEntry(*instance, static_cast(j)).clear(); } } uint32_t* indirect_function_table_sig_ids_ = nullptr; Address* indirect_function_table_targets_ = nullptr; Address* imported_function_targets_ = nullptr; #undef SET }; WasmInstanceNativeAllocations* GetNativeAllocations( WasmInstanceObject* instance) { return reinterpret_cast*>( instance->managed_native_allocations()) ->get(); } // An iterator that returns first the module itself, then all modules linked via // next, then all linked via prev. class CompiledModulesIterator : public v8::base::iterator> { public: CompiledModulesIterator(Isolate* isolate, Handle start_module, bool at_end) : isolate_(isolate), start_module_(start_module), current_( at_end ? Handle::null() : Handle::New(*start_module, isolate)) {} Handle operator*() const { DCHECK(!current_.is_null()); return current_; } void operator++() { Advance(); } bool operator!=(const CompiledModulesIterator& other) { DCHECK(start_module_.is_identical_to(other.start_module_)); return !current_.is_identical_to(other.current_); } private: void Advance() { DCHECK(!current_.is_null()); if (!is_backwards_) { if (current_->has_next_instance()) { *current_.location() = current_->next_instance(); return; } // No more modules in next-links, now try the previous-links. is_backwards_ = true; current_ = start_module_; } if (current_->has_prev_instance()) { *current_.location() = current_->prev_instance(); return; } current_ = Handle::null(); } friend class CompiledModuleInstancesIterator; Isolate* isolate_; Handle start_module_; Handle current_; bool is_backwards_ = false; }; // An iterator based on the CompiledModulesIterator, but it returns all live // instances, not the WasmCompiledModules itself. class CompiledModuleInstancesIterator : public v8::base::iterator> { public: CompiledModuleInstancesIterator(Isolate* isolate, Handle start_module, bool at_end) : it(isolate, start_module, at_end) { while (NeedToAdvance()) ++it; } Handle operator*() { return handle( WasmInstanceObject::cast((*it)->weak_owning_instance()->value()), it.isolate_); } void operator++() { do { ++it; } while (NeedToAdvance()); } bool operator!=(const CompiledModuleInstancesIterator& other) { return it != other.it; } private: bool NeedToAdvance() { return !it.current_.is_null() && !it.current_->has_instance(); } CompiledModulesIterator it; }; v8::base::iterator_range iterate_compiled_module_instance_chain( Isolate* isolate, Handle compiled_module) { return {CompiledModuleInstancesIterator(isolate, compiled_module, false), CompiledModuleInstancesIterator(isolate, compiled_module, true)}; } #ifdef DEBUG bool IsBreakablePosition(WasmSharedModuleData* shared, int func_index, int offset_in_func) { DisallowHeapAllocation no_gc; AccountingAllocator alloc; Zone tmp(&alloc, ZONE_NAME); wasm::BodyLocalDecls locals(&tmp); const byte* module_start = shared->module_bytes()->GetChars(); WasmFunction& func = shared->module()->functions[func_index]; wasm::BytecodeIterator iterator(module_start + func.code.offset(), module_start + func.code.end_offset(), &locals); DCHECK_LT(0, locals.encoded_size); for (uint32_t offset : iterator.offsets()) { if (offset > static_cast(offset_in_func)) break; if (offset == static_cast(offset_in_func)) return true; } return false; } #endif // DEBUG enum DispatchTableElements : int { kDispatchTableInstanceOffset, kDispatchTableIndexOffset, kDispatchTableFunctionTableOffset, // Marker: kDispatchTableNumElements }; } // namespace Handle WasmModuleObject::New( Isolate* isolate, Handle compiled_module) { Handle module_cons( isolate->native_context()->wasm_module_constructor()); auto module_object = Handle::cast( isolate->factory()->NewJSObject(module_cons)); module_object->set_compiled_module(*compiled_module); Handle link_to_module = isolate->factory()->NewWeakCell(module_object); compiled_module->set_weak_wasm_module(*link_to_module); compiled_module->LogWasmCodes(isolate); return module_object; } void WasmModuleObject::ValidateStateForTesting( Isolate* isolate, Handle module_obj) { DisallowHeapAllocation no_gc; WasmCompiledModule* compiled_module = module_obj->compiled_module(); CHECK(compiled_module->has_weak_wasm_module()); CHECK_EQ(compiled_module->weak_wasm_module()->value(), *module_obj); CHECK(!compiled_module->has_prev_instance()); CHECK(!compiled_module->has_next_instance()); CHECK(!compiled_module->has_instance()); } Handle WasmTableObject::New(Isolate* isolate, uint32_t initial, int64_t maximum, Handle* js_functions) { Handle table_ctor( isolate->native_context()->wasm_table_constructor()); auto table_obj = Handle::cast( isolate->factory()->NewJSObject(table_ctor)); *js_functions = isolate->factory()->NewFixedArray(initial); Object* null = isolate->heap()->null_value(); for (int i = 0; i < static_cast(initial); ++i) { (*js_functions)->set(i, null); } table_obj->set_functions(**js_functions); DCHECK_EQ(maximum, static_cast(maximum)); Handle max = isolate->factory()->NewNumber(maximum); table_obj->set_maximum_length(*max); table_obj->set_dispatch_tables(isolate->heap()->empty_fixed_array()); return Handle::cast(table_obj); } void WasmTableObject::AddDispatchTable(Isolate* isolate, Handle table_obj, Handle instance, int table_index) { Handle dispatch_tables(table_obj->dispatch_tables()); int old_length = dispatch_tables->length(); DCHECK_EQ(0, old_length % kDispatchTableNumElements); if (instance.is_null()) return; // TODO(titzer): use weak cells here to avoid leaking instances. // Grow the dispatch table and add a new entry at the end. Handle new_dispatch_tables = isolate->factory()->CopyFixedArrayAndGrow(dispatch_tables, kDispatchTableNumElements); new_dispatch_tables->set(old_length + kDispatchTableInstanceOffset, *instance); new_dispatch_tables->set(old_length + kDispatchTableIndexOffset, Smi::FromInt(table_index)); table_obj->set_dispatch_tables(*new_dispatch_tables); } void WasmTableObject::Grow(Isolate* isolate, uint32_t count) { if (count == 0) return; // Degenerate case: nothing to do. Handle dispatch_tables(this->dispatch_tables()); DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements); uint32_t old_size = functions()->length(); // Tables are stored in the instance object, no code patching is // necessary. We simply have to grow the raw tables in each instance // that has imported this table. // TODO(titzer): replace the dispatch table with a weak list of all // the instances that import a given table. for (int i = 0; i < dispatch_tables->length(); i += kDispatchTableNumElements) { Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); DCHECK_EQ(old_size, instance->indirect_function_table_size()); uint32_t new_size = old_size + count; WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(instance, new_size); } } void WasmTableObject::Set(Isolate* isolate, Handle table, int32_t table_index, Handle function) { Handle array(table->functions(), isolate); if (function.is_null()) { ClearDispatchTables(table, table_index); // Degenerate case of null value. array->set(table_index, isolate->heap()->null_value()); return; } // TODO(titzer): Change this to MaybeHandle DCHECK(WasmExportedFunction::IsWasmExportedFunction(*function)); auto exported_function = Handle::cast(function); Handle other_instance(exported_function->instance()); int func_index = exported_function->function_index(); auto* wasm_function = &other_instance->module()->functions[func_index]; DCHECK_NOT_NULL(wasm_function); DCHECK_NOT_NULL(wasm_function->sig); wasm::WasmCode* wasm_code = exported_function->GetWasmCode(); UpdateDispatchTables(isolate, table, table_index, wasm_function->sig, handle(exported_function->instance()), wasm_code); array->set(table_index, *function); } void WasmTableObject::UpdateDispatchTables( Isolate* isolate, Handle table, int table_index, wasm::FunctionSig* sig, Handle from_instance, wasm::WasmCode* wasm_code) { // We simply need to update the IFTs for each instance that imports // this table. DisallowHeapAllocation no_gc; FixedArray* dispatch_tables = table->dispatch_tables(); DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements); for (int i = 0; i < dispatch_tables->length(); i += kDispatchTableNumElements) { // Note that {SignatureMap::Find} may return {-1} if the signature is // not found; it will simply never match any check. WasmInstanceObject* to_instance = WasmInstanceObject::cast( dispatch_tables->get(i + kDispatchTableInstanceOffset)); auto sig_id = to_instance->module()->signature_map.Find(sig); IndirectFunctionTableEntry(to_instance, table_index) .set(sig_id, *from_instance, wasm_code); } } void WasmTableObject::ClearDispatchTables(Handle table, int index) { DisallowHeapAllocation no_gc; FixedArray* dispatch_tables = table->dispatch_tables(); DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements); for (int i = 0; i < dispatch_tables->length(); i += kDispatchTableNumElements) { WasmInstanceObject* target_instance = WasmInstanceObject::cast( dispatch_tables->get(i + kDispatchTableInstanceOffset)); DCHECK_LT(index, target_instance->indirect_function_table_size()); IndirectFunctionTableEntry(target_instance, index).clear(); } } namespace { MaybeHandle GrowMemoryBuffer(Isolate* isolate, Handle old_buffer, uint32_t pages, uint32_t maximum_pages, bool use_trap_handler) { if (!old_buffer->is_growable()) return {}; Address old_mem_start = nullptr; uint32_t old_size = 0; if (!old_buffer.is_null()) { old_mem_start = static_cast
(old_buffer->backing_store()); CHECK(old_buffer->byte_length()->ToUint32(&old_size)); } DCHECK_EQ(0, old_size % wasm::kWasmPageSize); uint32_t old_pages = old_size / wasm::kWasmPageSize; DCHECK_GE(std::numeric_limits::max(), old_size + pages * wasm::kWasmPageSize); if (old_pages > maximum_pages || pages > maximum_pages - old_pages) return {}; size_t new_size = static_cast(old_pages + pages) * wasm::kWasmPageSize; if (new_size > FLAG_wasm_max_mem_pages * wasm::kWasmPageSize || new_size > kMaxInt) { return {}; } // Reusing the backing store from externalized buffers causes problems with // Blink's array buffers. The connection between the two is lost, which can // lead to Blink not knowing about the other reference to the buffer and // freeing it too early. if (!old_buffer->is_external() && old_size != 0 && ((new_size < old_buffer->allocation_length()) || old_size == new_size)) { DCHECK_NOT_NULL(old_buffer->backing_store()); if (old_size != new_size) { // If adjusting permissions fails, propagate error back to return // failure to grow. DCHECK(!isolate->wasm_engine()->memory_tracker()->IsEmptyBackingStore( old_mem_start)); if (!i::SetPermissions(old_mem_start, new_size, PageAllocator::kReadWrite)) { return {}; } reinterpret_cast(isolate) ->AdjustAmountOfExternalAllocatedMemory(pages * wasm::kWasmPageSize); } // NOTE: We must allocate a new array buffer here because the spec // assumes that ArrayBuffers do not change size. void* backing_store = old_buffer->backing_store(); bool is_external = old_buffer->is_external(); // Disconnect buffer early so GC won't free it. i::wasm::DetachMemoryBuffer(isolate, old_buffer, false); Handle new_buffer = wasm::SetupArrayBuffer(isolate, backing_store, new_size, is_external); return new_buffer; } else { // We couldn't reuse the old backing store, so create a new one and copy the // old contents in. Handle new_buffer; if (!wasm::NewArrayBuffer(isolate, new_size, use_trap_handler) .ToHandle(&new_buffer)) { return {}; } if (old_size == 0) return new_buffer; Address new_mem_start = static_cast
(new_buffer->backing_store()); memcpy(new_mem_start, old_mem_start, old_size); DCHECK(old_buffer.is_null() || !old_buffer->is_shared()); constexpr bool free_memory = true; i::wasm::DetachMemoryBuffer(isolate, old_buffer, free_memory); return new_buffer; } } // May GC, because SetSpecializationMemInfoFrom may GC void SetInstanceMemory(Isolate* isolate, Handle instance, Handle buffer) { instance->SetRawMemory(reinterpret_cast(buffer->backing_store()), buffer->byte_length()->Number()); #if DEBUG // To flush out bugs earlier, in DEBUG mode, check that all pages of the // memory are accessible by reading and writing one byte on each page. byte* mem_start = instance->memory_start(); uintptr_t mem_size = instance->memory_size(); for (uint32_t offset = 0; offset < mem_size; offset += wasm::kWasmPageSize) { byte val = mem_start[offset]; USE(val); mem_start[offset] = val; } #endif } } // namespace Handle WasmMemoryObject::New( Isolate* isolate, MaybeHandle maybe_buffer, int32_t maximum) { // TODO(kschimpf): Do we need to add an argument that defines the // style of memory the user prefers (with/without trap handling), so // that the memory will match the style of the compiled wasm module. // See issue v8:7143 Handle memory_ctor( isolate->native_context()->wasm_memory_constructor()); auto memory_obj = Handle::cast( isolate->factory()->NewJSObject(memory_ctor, TENURED)); Handle buffer; if (maybe_buffer.is_null()) { // If no buffer was provided, create a 0-length one. buffer = wasm::SetupArrayBuffer(isolate, nullptr, 0, false); } else { buffer = maybe_buffer.ToHandleChecked(); // Paranoid check that the buffer size makes sense. uint32_t mem_size = 0; CHECK(buffer->byte_length()->ToUint32(&mem_size)); } memory_obj->set_array_buffer(*buffer); memory_obj->set_maximum_pages(maximum); return memory_obj; } uint32_t WasmMemoryObject::current_pages() { uint32_t byte_length; CHECK(array_buffer()->byte_length()->ToUint32(&byte_length)); return byte_length / wasm::kWasmPageSize; } void WasmMemoryObject::AddInstance(Isolate* isolate, Handle memory, Handle instance) { Handle old_instances = memory->has_instances() ? Handle(memory->instances(), isolate) : Handle::null(); Handle new_instances = FixedArrayOfWeakCells::Add(old_instances, instance); memory->set_instances(*new_instances); Handle buffer(memory->array_buffer(), isolate); SetInstanceMemory(isolate, instance, buffer); } void WasmMemoryObject::RemoveInstance(Isolate* isolate, Handle memory, Handle instance) { if (memory->has_instances()) { memory->instances()->Remove(instance); } } // static int32_t WasmMemoryObject::Grow(Isolate* isolate, Handle memory_object, uint32_t pages) { Handle old_buffer(memory_object->array_buffer()); if (!old_buffer->is_growable()) return -1; uint32_t old_size = 0; CHECK(old_buffer->byte_length()->ToUint32(&old_size)); DCHECK_EQ(0, old_size % wasm::kWasmPageSize); Handle new_buffer; uint32_t maximum_pages = FLAG_wasm_max_mem_pages; if (memory_object->has_maximum_pages()) { maximum_pages = Min(FLAG_wasm_max_mem_pages, static_cast(memory_object->maximum_pages())); } // TODO(kschimpf): We need to fix this by adding a field to WasmMemoryObject // that defines the style of memory being used. if (!GrowMemoryBuffer(isolate, old_buffer, pages, maximum_pages, trap_handler::IsTrapHandlerEnabled()) .ToHandle(&new_buffer)) { return -1; } if (memory_object->has_instances()) { Handle instances(memory_object->instances(), isolate); for (int i = 0; i < instances->Length(); i++) { Object* elem = instances->Get(i); if (!elem->IsWasmInstanceObject()) continue; Handle instance(WasmInstanceObject::cast(elem), isolate); SetInstanceMemory(isolate, instance, new_buffer); } } memory_object->set_array_buffer(*new_buffer); return old_size / wasm::kWasmPageSize; } // static MaybeHandle WasmGlobalObject::New( Isolate* isolate, MaybeHandle maybe_buffer, wasm::ValueType type, int32_t offset, bool is_mutable) { Handle global_ctor( isolate->native_context()->wasm_global_constructor()); auto global_obj = Handle::cast( isolate->factory()->NewJSObject(global_ctor)); uint32_t type_size = TypeSize(type); Handle buffer; if (!maybe_buffer.ToHandle(&buffer)) { // If no buffer was provided, create one long enough for the given type. buffer = isolate->factory()->NewJSArrayBuffer(SharedFlag::kNotShared, TENURED); const bool initialize = true; if (!JSArrayBuffer::SetupAllocatingData(buffer, isolate, type_size, initialize)) { return {}; } } // Check that the offset is in bounds. uint32_t buffer_size = 0; CHECK(buffer->byte_length()->ToUint32(&buffer_size)); CHECK(offset + type_size <= buffer_size); global_obj->set_array_buffer(*buffer); global_obj->set_flags(0); global_obj->set_type(type); global_obj->set_offset(offset); global_obj->set_is_mutable(is_mutable); return global_obj; } void IndirectFunctionTableEntry::clear() { instance_->indirect_function_table_sig_ids()[index_] = -1; instance_->indirect_function_table_targets()[index_] = 0; instance_->indirect_function_table_instances()->set( index_, instance_->GetIsolate()->heap()->undefined_value()); } void IndirectFunctionTableEntry::set(int sig_id, WasmInstanceObject* instance, const wasm::WasmCode* wasm_code) { TRACE_IFT("IFT entry %p[%d] = {sig_id=%d, instance=%p, target=%p}\n", instance_, index_, sig_id, instance, wasm_code->instructions().start()); instance_->indirect_function_table_sig_ids()[index_] = sig_id; instance_->indirect_function_table_targets()[index_] = wasm_code->instructions().start(); instance_->indirect_function_table_instances()->set(index_, instance); } WasmInstanceObject* IndirectFunctionTableEntry::instance() { return WasmInstanceObject::cast( instance_->indirect_function_table_instances()->get(index_)); } int IndirectFunctionTableEntry::sig_id() { return instance_->indirect_function_table_sig_ids()[index_]; } Address IndirectFunctionTableEntry::target() { return instance_->indirect_function_table_targets()[index_]; } void ImportedFunctionEntry::set(JSReceiver* callable, const wasm::WasmCode* wasm_to_js_wrapper) { TRACE_IFT("Import callable %p[%d] = {callable=%p, target=%p}\n", instance_, index_, callable, wasm_to_js_wrapper->instructions().start()); DCHECK_EQ(wasm::WasmCode::kWasmToJsWrapper, wasm_to_js_wrapper->kind()); instance_->imported_function_instances()->set(index_, instance_); instance_->imported_function_callables()->set(index_, callable); instance_->imported_function_targets()[index_] = wasm_to_js_wrapper->instructions().start(); } void ImportedFunctionEntry::set(WasmInstanceObject* instance, const wasm::WasmCode* wasm_code) { TRACE_IFT("Import WASM %p[%d] = {instance=%p, target=%p}\n", instance_, index_, instance, wasm_code->instructions().start()); instance_->imported_function_instances()->set(index_, instance); instance_->imported_function_callables()->set( index_, instance_->GetHeap()->undefined_value()); instance_->imported_function_targets()[index_] = wasm_code->instructions().start(); } WasmInstanceObject* ImportedFunctionEntry::instance() { return WasmInstanceObject::cast( instance_->imported_function_instances()->get(index_)); } JSReceiver* ImportedFunctionEntry::callable() { return JSReceiver::cast( instance_->imported_function_callables()->get(index_)); } Address ImportedFunctionEntry::target() { return instance_->imported_function_targets()[index_]; } bool ImportedFunctionEntry::is_js_receiver_entry() { return instance_->imported_function_callables()->get(index_)->IsJSReceiver(); } bool WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( Handle instance, uint32_t minimum_size) { uint32_t old_size = instance->indirect_function_table_size(); if (old_size >= minimum_size) return false; // Nothing to do. Isolate* isolate = instance->GetIsolate(); HandleScope scope(isolate); auto native_allocations = GetNativeAllocations(*instance); native_allocations->resize_indirect_function_table(isolate, instance, minimum_size); return true; } void WasmInstanceObject::SetRawMemory(byte* mem_start, uint32_t mem_size) { DCHECK_LE(mem_size, wasm::kV8MaxWasmMemoryPages * wasm::kWasmPageSize); uint32_t mem_size64 = mem_size; uint32_t mem_mask64 = base::bits::RoundUpToPowerOfTwo32(mem_size) - 1; DCHECK_LE(mem_size, mem_mask64 + 1); set_memory_start(mem_start); set_memory_size(mem_size64); set_memory_mask(mem_mask64); } WasmModuleObject* WasmInstanceObject::module_object() { return compiled_module()->wasm_module(); } WasmModule* WasmInstanceObject::module() { return compiled_module()->shared()->module(); } Handle WasmInstanceObject::GetOrCreateDebugInfo( Handle instance) { if (instance->has_debug_info()) return handle(instance->debug_info()); Handle new_info = WasmDebugInfo::New(instance); DCHECK(instance->has_debug_info()); return new_info; } Handle WasmInstanceObject::New( Isolate* isolate, Handle compiled_module) { Handle instance_cons( isolate->native_context()->wasm_instance_constructor()); Handle instance_object = isolate->factory()->NewJSObject(instance_cons, TENURED); Handle instance( reinterpret_cast(*instance_object), isolate); // Initialize the imported function arrays. auto num_imported_functions = compiled_module->shared()->module()->num_imported_functions; auto native_allocations = Managed::Allocate( isolate, instance, num_imported_functions); instance->set_managed_native_allocations(*native_allocations); Handle imported_function_instances = isolate->factory()->NewFixedArray(num_imported_functions); instance->set_imported_function_instances(*imported_function_instances); Handle imported_function_callables = isolate->factory()->NewFixedArray(num_imported_functions); instance->set_imported_function_callables(*imported_function_callables); instance->SetRawMemory(nullptr, 0); instance->set_globals_start(nullptr); instance->set_indirect_function_table_size(0); instance->set_indirect_function_table_sig_ids(nullptr); instance->set_indirect_function_table_targets(nullptr); instance->set_compiled_module(*compiled_module); return instance; } WasmInstanceObject* WasmInstanceObject::GetOwningInstance( const wasm::WasmCode* code) { DisallowHeapAllocation no_gc; Object* weak_link = nullptr; DCHECK(code->kind() == wasm::WasmCode::kFunction || code->kind() == wasm::WasmCode::kInterpreterStub); weak_link = code->native_module()->compiled_module()->weak_owning_instance(); DCHECK(weak_link->IsWeakCell()); WeakCell* cell = WeakCell::cast(weak_link); if (cell->cleared()) return nullptr; return WasmInstanceObject::cast(cell->value()); } void WasmInstanceObject::ValidateInstancesChainForTesting( Isolate* isolate, Handle module_obj, int instance_count) { CHECK_GE(instance_count, 0); DisallowHeapAllocation no_gc; WasmCompiledModule* compiled_module = module_obj->compiled_module(); CHECK_EQ(JSObject::cast(compiled_module->weak_wasm_module()->value()), *module_obj); Object* prev = nullptr; int found_instances = compiled_module->has_instance() ? 1 : 0; WasmCompiledModule* current_instance = compiled_module; while (current_instance->has_next_instance()) { CHECK((prev == nullptr && !current_instance->has_prev_instance()) || current_instance->prev_instance() == prev); CHECK_EQ(current_instance->weak_wasm_module()->value(), *module_obj); CHECK(current_instance->weak_owning_instance() ->value() ->IsWasmInstanceObject()); prev = current_instance; current_instance = WasmCompiledModule::cast(current_instance->next_instance()); ++found_instances; CHECK_LE(found_instances, instance_count); } CHECK_EQ(found_instances, instance_count); } void WasmInstanceObject::ValidateOrphanedInstanceForTesting( Isolate* isolate, Handle instance) { DisallowHeapAllocation no_gc; WasmCompiledModule* compiled_module = instance->compiled_module(); CHECK(compiled_module->has_weak_wasm_module()); CHECK(compiled_module->weak_wasm_module()->cleared()); } namespace { void InstanceFinalizer(const v8::WeakCallbackInfo& data) { DisallowHeapAllocation no_gc; JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* instance = reinterpret_cast(*p); Isolate* isolate = reinterpret_cast(data.GetIsolate()); // If a link to shared memory instances exists, update the list of memory // instances before the instance is destroyed. WasmCompiledModule* compiled_module = instance->compiled_module(); wasm::NativeModule* native_module = compiled_module->GetNativeModule(); if (native_module) { TRACE("Finalizing %zu {\n", native_module->instance_id); } else { TRACE("Finalized already cleaned up compiled module\n"); } WeakCell* weak_wasm_module = compiled_module->weak_wasm_module(); // Since the order of finalizers is not guaranteed, it can be the case // that {instance->compiled_module()->module()}, which is a // {Managed} has been collected earlier in this GC cycle. // Weak references to this instance won't be cleared until // the next GC cycle, so we need to manually break some links (such as // the weak references from {WasmMemoryObject::instances}. if (instance->has_memory_object()) { WasmMemoryObject::RemoveInstance(isolate, handle(instance->memory_object()), handle(instance)); } // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. We still want to maintain the links between instances, to // release the WasmCompiledModule corresponding to the WasmModuleInstance // being finalized here. WasmModuleObject* wasm_module = nullptr; if (!weak_wasm_module->cleared()) { wasm_module = WasmModuleObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = wasm_module->compiled_module(); DCHECK(!current_template->has_prev_instance()); if (current_template == compiled_module) { if (!compiled_module->has_next_instance()) { WasmCompiledModule::Reset(isolate, compiled_module); } else { WasmModuleObject::cast(wasm_module) ->set_compiled_module(compiled_module->next_instance()); } } } // Free raw C++ memory associated with the instance. GetNativeAllocations(instance)->free(); compiled_module->RemoveFromChain(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } } // namespace void WasmInstanceObject::InstallFinalizer(Isolate* isolate, Handle instance) { Handle global_handle = isolate->global_handles()->Create(*instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } bool WasmExportedFunction::IsWasmExportedFunction(Object* object) { if (!object->IsJSFunction()) return false; Handle js_function(JSFunction::cast(object)); if (Code::JS_TO_WASM_FUNCTION != js_function->code()->kind()) return false; #ifdef DEBUG // Any function having code of {JS_TO_WASM_FUNCTION} kind must be an exported // function and hence will have a property holding the instance object. Handle symbol( js_function->GetIsolate()->factory()->wasm_instance_symbol()); MaybeHandle result = JSObject::GetPropertyOrElement(js_function, symbol); DCHECK(result.ToHandleChecked()->IsWasmInstanceObject()); #endif return true; } WasmExportedFunction* WasmExportedFunction::cast(Object* object) { DCHECK(IsWasmExportedFunction(object)); return reinterpret_cast(object); } WasmInstanceObject* WasmExportedFunction::instance() { DisallowHeapAllocation no_allocation; Handle symbol(GetIsolate()->factory()->wasm_instance_symbol()); MaybeHandle result = JSObject::GetPropertyOrElement(handle(this), symbol); return WasmInstanceObject::cast(*(result.ToHandleChecked())); } int WasmExportedFunction::function_index() { DisallowHeapAllocation no_allocation; Handle symbol = GetIsolate()->factory()->wasm_function_index_symbol(); MaybeHandle result = JSObject::GetPropertyOrElement(handle(this), symbol); return result.ToHandleChecked()->Number(); } Handle WasmExportedFunction::New( Isolate* isolate, Handle instance, MaybeHandle maybe_name, int func_index, int arity, Handle export_wrapper) { DCHECK_EQ(Code::JS_TO_WASM_FUNCTION, export_wrapper->kind()); Handle name; if (!maybe_name.ToHandle(&name)) { EmbeddedVector buffer; int length = SNPrintF(buffer, "%d", func_index); name = isolate->factory() ->NewStringFromOneByte( Vector::cast(buffer.SubVector(0, length))) .ToHandleChecked(); } NewFunctionArgs args = NewFunctionArgs::ForWasm( name, export_wrapper, isolate->sloppy_function_without_prototype_map()); Handle js_function = isolate->factory()->NewFunction(args); // According to the spec, exported functions should not have a [[Construct]] // method. DCHECK(!js_function->IsConstructor()); js_function->shared()->set_length(arity); js_function->shared()->set_internal_formal_parameter_count(arity); Handle instance_symbol(isolate->factory()->wasm_instance_symbol()); JSObject::AddProperty(js_function, instance_symbol, instance, DONT_ENUM); Handle function_index_symbol( isolate->factory()->wasm_function_index_symbol()); JSObject::AddProperty(js_function, function_index_symbol, isolate->factory()->NewNumber(func_index), DONT_ENUM); return Handle::cast(js_function); } wasm::WasmCode* WasmExportedFunction::GetWasmCode() { DisallowHeapAllocation no_gc; Handle export_wrapper_code = handle(this->code()); DCHECK_EQ(export_wrapper_code->kind(), Code::JS_TO_WASM_FUNCTION); int mask = RelocInfo::ModeMask(RelocInfo::JS_TO_WASM_CALL); RelocIterator it(*export_wrapper_code, mask); DCHECK(!it.done()); wasm::WasmCode* target = GetIsolate()->wasm_engine()->code_manager()->LookupCode( it.rinfo()->js_to_wasm_address()); #ifdef DEBUG // There should only be this one call to wasm code. it.next(); DCHECK(it.done()); #endif return target; } WasmModule* WasmSharedModuleData::module() const { // We populate the kModuleWrapper field with a Foreign holding the // address to the address of a WasmModule. This is because we can // handle both cases when the WasmModule's lifetime is managed through // a Managed object, as well as cases when it's managed // by the embedder. CcTests fall into the latter case. return *(reinterpret_cast( Foreign::cast(module_wrapper())->foreign_address())); } Handle WasmSharedModuleData::New( Isolate* isolate, Handle module_wrapper, Handle module_bytes, Handle