// 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/base/iterator.h" #include "src/codegen/assembler-inl.h" #include "src/codegen/code-factory.h" #include "src/compiler/wasm-compiler.h" #include "src/debug/debug-interface.h" #include "src/logging/counters.h" #include "src/objects/debug-objects-inl.h" #include "src/objects/objects-inl.h" #include "src/objects/shared-function-info.h" #include "src/objects/struct-inl.h" #include "src/trap-handler/trap-handler.h" #include "src/utils/utils.h" #include "src/utils/vector.h" #include "src/wasm/jump-table-assembler.h" #include "src/wasm/module-compiler.h" #include "src/wasm/module-decoder.h" #include "src/wasm/module-instantiate.h" #include "src/wasm/value-type.h" #include "src/wasm/wasm-code-manager.h" #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-subtyping.h" #include "src/wasm/wasm-value.h" #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<WasmInstanceNativeAllocations> 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) \ instance->set_##field((this->field##_ = value).get()); // Allocates initial native storage for a given instance. WasmInstanceNativeAllocations(Handle<WasmInstanceObject> instance, size_t num_imported_functions, size_t num_imported_mutable_globals, size_t num_data_segments, size_t num_elem_segments) { SET(instance, imported_function_targets, std::make_unique<Address[]>(num_imported_functions)); SET(instance, imported_mutable_globals, std::make_unique<Address[]>(num_imported_mutable_globals)); SET(instance, data_segment_starts, std::make_unique<Address[]>(num_data_segments)); SET(instance, data_segment_sizes, std::make_unique<uint32_t[]>(num_data_segments)); SET(instance, dropped_elem_segments, std::make_unique<uint8_t[]>(num_elem_segments)); } uint32_t indirect_function_table_capacity() const { return indirect_function_table_capacity_; } // Resizes the indirect function table. void resize_indirect_function_table(Isolate* isolate, Handle<WasmInstanceObject> instance, uint32_t new_capacity) { uint32_t old_capacity = indirect_function_table_capacity_; DCHECK_LT(old_capacity, new_capacity); // Grow exponentially to support repeated re-allocation. new_capacity = std::max(new_capacity, 2 * old_capacity); CHECK_GE(kMaxInt, old_capacity); CHECK_GE(kMaxInt, new_capacity); SET(instance, indirect_function_table_sig_ids, grow(indirect_function_table_sig_ids_.get(), old_capacity, new_capacity)); SET(instance, indirect_function_table_targets, grow(indirect_function_table_targets_.get(), old_capacity, new_capacity)); Handle<FixedArray> old_refs(instance->indirect_function_table_refs(), isolate); Handle<FixedArray> new_refs = isolate->factory()->CopyFixedArrayAndGrow( old_refs, static_cast<int>(new_capacity - old_capacity)); instance->set_indirect_function_table_refs(*new_refs); indirect_function_table_capacity_ = new_capacity; } private: template <typename T> std::unique_ptr<T[]> grow(T* old_arr, size_t old_size, size_t new_size) { std::unique_ptr<T[]> new_arr = std::make_unique<T[]>(new_size); std::copy_n(old_arr, old_size, new_arr.get()); return new_arr; } uint32_t indirect_function_table_capacity_ = 0; std::unique_ptr<uint32_t[]> indirect_function_table_sig_ids_; std::unique_ptr<Address[]> indirect_function_table_targets_; std::unique_ptr<Address[]> imported_function_targets_; std::unique_ptr<Address[]> imported_mutable_globals_; std::unique_ptr<Address[]> data_segment_starts_; std::unique_ptr<uint32_t[]> data_segment_sizes_; std::unique_ptr<uint8_t[]> dropped_elem_segments_; #undef SET }; size_t EstimateNativeAllocationsSize(const WasmModule* module) { size_t estimate = sizeof(WasmInstanceNativeAllocations) + (1 * kSystemPointerSize * module->num_imported_mutable_globals) + (2 * kSystemPointerSize * module->num_imported_functions) + ((kSystemPointerSize + sizeof(uint32_t) + sizeof(uint8_t)) * module->num_declared_data_segments); for (auto& table : module->tables) { estimate += 3 * kSystemPointerSize * table.initial_size; } return estimate; } WasmInstanceNativeAllocations* GetNativeAllocations( WasmInstanceObject instance) { return Managed<WasmInstanceNativeAllocations>::cast( instance.managed_native_allocations()) .raw(); } enum DispatchTableElements : int { kDispatchTableInstanceOffset, kDispatchTableIndexOffset, kDispatchTableFunctionTableOffset, // Marker: kDispatchTableNumElements }; } // namespace // static Handle<WasmModuleObject> WasmModuleObject::New( Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module, Handle<Script> script) { Handle<FixedArray> export_wrappers = isolate->factory()->NewFixedArray(0); return New(isolate, std::move(native_module), script, export_wrappers); } // static Handle<WasmModuleObject> WasmModuleObject::New( Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module, Handle<Script> script, Handle<FixedArray> export_wrappers) { Handle<Managed<wasm::NativeModule>> managed_native_module; if (script->type() == Script::TYPE_WASM) { managed_native_module = handle( Managed<wasm::NativeModule>::cast(script->wasm_managed_native_module()), isolate); } else { const WasmModule* module = native_module->module(); size_t memory_estimate = native_module->committed_code_space() + wasm::WasmCodeManager::EstimateNativeModuleMetaDataSize(module); managed_native_module = Managed<wasm::NativeModule>::FromSharedPtr( isolate, memory_estimate, std::move(native_module)); } Handle<WasmModuleObject> module_object = Handle<WasmModuleObject>::cast( isolate->factory()->NewJSObject(isolate->wasm_module_constructor())); module_object->set_export_wrappers(*export_wrappers); module_object->set_managed_native_module(*managed_native_module); module_object->set_script(*script); return module_object; } Handle<String> WasmModuleObject::ExtractUtf8StringFromModuleBytes( Isolate* isolate, Handle<WasmModuleObject> module_object, wasm::WireBytesRef ref, InternalizeString internalize) { Vector<const uint8_t> wire_bytes = module_object->native_module()->wire_bytes(); return ExtractUtf8StringFromModuleBytes(isolate, wire_bytes, ref, internalize); } Handle<String> WasmModuleObject::ExtractUtf8StringFromModuleBytes( Isolate* isolate, Vector<const uint8_t> wire_bytes, wasm::WireBytesRef ref, InternalizeString internalize) { Vector<const uint8_t> name_vec = wire_bytes.SubVector(ref.offset(), ref.end_offset()); // UTF8 validation happens at decode time. DCHECK(unibrow::Utf8::ValidateEncoding(name_vec.begin(), name_vec.length())); auto* factory = isolate->factory(); return internalize ? factory->InternalizeUtf8String( Vector<const char>::cast(name_vec)) : factory->NewStringFromUtf8(Vector<const char>::cast(name_vec)) .ToHandleChecked(); } MaybeHandle<String> WasmModuleObject::GetModuleNameOrNull( Isolate* isolate, Handle<WasmModuleObject> module_object) { const WasmModule* module = module_object->module(); if (!module->name.is_set()) return {}; return ExtractUtf8StringFromModuleBytes(isolate, module_object, module->name, kNoInternalize); } MaybeHandle<String> WasmModuleObject::GetFunctionNameOrNull( Isolate* isolate, Handle<WasmModuleObject> module_object, uint32_t func_index) { DCHECK_LT(func_index, module_object->module()->functions.size()); wasm::WireBytesRef name = module_object->module()->lazily_generated_names.LookupFunctionName( wasm::ModuleWireBytes(module_object->native_module()->wire_bytes()), func_index); if (!name.is_set()) return {}; return ExtractUtf8StringFromModuleBytes(isolate, module_object, name, kNoInternalize); } Vector<const uint8_t> WasmModuleObject::GetRawFunctionName(int func_index) { if (func_index == wasm::kAnonymousFuncIndex) { return Vector<const uint8_t>({nullptr, 0}); } DCHECK_GT(module()->functions.size(), func_index); wasm::ModuleWireBytes wire_bytes(native_module()->wire_bytes()); wasm::WireBytesRef name_ref = module()->lazily_generated_names.LookupFunctionName(wire_bytes, func_index); wasm::WasmName name = wire_bytes.GetNameOrNull(name_ref); return Vector<const uint8_t>::cast(name); } Handle<WasmTableObject> WasmTableObject::New( Isolate* isolate, Handle<WasmInstanceObject> instance, wasm::ValueType type, uint32_t initial, bool has_maximum, uint32_t maximum, Handle<FixedArray>* entries) { // TODO(7748): Make this work with other types when spec clears up. { const WasmModule* module = instance.is_null() ? nullptr : instance->module(); CHECK(wasm::WasmTable::IsValidTableType(type, module)); } Handle<FixedArray> backing_store = isolate->factory()->NewFixedArray(initial); Object null = ReadOnlyRoots(isolate).null_value(); for (int i = 0; i < static_cast<int>(initial); ++i) { backing_store->set(i, null); } Handle<Object> max; if (has_maximum) { max = isolate->factory()->NewNumberFromUint(maximum); } else { max = isolate->factory()->undefined_value(); } Handle<JSFunction> table_ctor( isolate->native_context()->wasm_table_constructor(), isolate); auto table_obj = Handle<WasmTableObject>::cast( isolate->factory()->NewJSObject(table_ctor)); DisallowGarbageCollection no_gc; if (!instance.is_null()) table_obj->set_instance(*instance); table_obj->set_entries(*backing_store); table_obj->set_current_length(initial); table_obj->set_maximum_length(*max); table_obj->set_raw_type(static_cast<int>(type.raw_bit_field())); table_obj->set_dispatch_tables(ReadOnlyRoots(isolate).empty_fixed_array()); if (entries != nullptr) { *entries = backing_store; } return Handle<WasmTableObject>::cast(table_obj); } void WasmTableObject::AddDispatchTable(Isolate* isolate, Handle<WasmTableObject> table_obj, Handle<WasmInstanceObject> instance, int table_index) { Handle<FixedArray> dispatch_tables(table_obj->dispatch_tables(), isolate); 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<FixedArray> 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); } int WasmTableObject::Grow(Isolate* isolate, Handle<WasmTableObject> table, uint32_t count, Handle<Object> init_value) { uint32_t old_size = table->current_length(); if (count == 0) return old_size; // Degenerate case: nothing to do. // Check if growing by {count} is valid. uint32_t max_size; if (!table->maximum_length().ToUint32(&max_size)) { max_size = FLAG_wasm_max_table_size; } max_size = std::min(max_size, FLAG_wasm_max_table_size); DCHECK_LE(old_size, max_size); if (max_size - old_size < count) return -1; uint32_t new_size = old_size + count; // Even with 2x over-allocation, there should not be an integer overflow. STATIC_ASSERT(wasm::kV8MaxWasmTableSize <= kMaxInt / 2); DCHECK_GE(kMaxInt, new_size); int old_capacity = table->entries().length(); if (new_size > static_cast<uint32_t>(old_capacity)) { int grow = static_cast<int>(new_size) - old_capacity; // Grow at least by the old capacity, to implement exponential growing. grow = std::max(grow, old_capacity); // Never grow larger than the max size. grow = std::min(grow, static_cast<int>(max_size - old_capacity)); auto new_store = isolate->factory()->CopyFixedArrayAndGrow( handle(table->entries(), isolate), grow); table->set_entries(*new_store, WriteBarrierMode::UPDATE_WRITE_BARRIER); } table->set_current_length(new_size); Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate); DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements); // 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) { int table_index = Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value(); Handle<WasmInstanceObject> instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); DCHECK_EQ(old_size, WasmInstanceObject::IndirectFunctionTableSize( isolate, instance, table_index)); WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( instance, table_index, new_size); } for (uint32_t entry = old_size; entry < new_size; ++entry) { WasmTableObject::Set(isolate, table, entry, init_value); } return old_size; } bool WasmTableObject::IsInBounds(Isolate* isolate, Handle<WasmTableObject> table, uint32_t entry_index) { return entry_index < static_cast<uint32_t>(table->current_length()); } bool WasmTableObject::IsValidElement(Isolate* isolate, Handle<WasmTableObject> table, Handle<Object> entry) { const char* error_message; const WasmModule* module = !table->instance().IsUndefined() ? WasmInstanceObject::cast(table->instance()).module() : nullptr; return wasm::TypecheckJSObject(isolate, module, entry, table->type(), &error_message); } void WasmTableObject::SetFunctionTableEntry(Isolate* isolate, Handle<WasmTableObject> table, Handle<FixedArray> entries, int entry_index, Handle<Object> entry) { if (entry->IsNull(isolate)) { ClearDispatchTables(isolate, table, entry_index); // Degenerate case. entries->set(entry_index, ReadOnlyRoots(isolate).null_value()); return; } if (WasmExportedFunction::IsWasmExportedFunction(*entry)) { auto exported_function = Handle<WasmExportedFunction>::cast(entry); Handle<WasmInstanceObject> target_instance(exported_function->instance(), isolate); int func_index = exported_function->function_index(); auto* wasm_function = &target_instance->module()->functions[func_index]; DCHECK_NOT_NULL(wasm_function); DCHECK_NOT_NULL(wasm_function->sig); UpdateDispatchTables(isolate, table, entry_index, wasm_function->sig, target_instance, func_index); } else if (WasmJSFunction::IsWasmJSFunction(*entry)) { UpdateDispatchTables(isolate, table, entry_index, Handle<WasmJSFunction>::cast(entry)); } else { DCHECK(WasmCapiFunction::IsWasmCapiFunction(*entry)); UpdateDispatchTables(isolate, table, entry_index, Handle<WasmCapiFunction>::cast(entry)); } entries->set(entry_index, *entry); } void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table, uint32_t index, Handle<Object> entry) { // Callers need to perform bounds checks, type check, and error handling. DCHECK(IsInBounds(isolate, table, index)); DCHECK(IsValidElement(isolate, table, entry)); Handle<FixedArray> entries(table->entries(), isolate); // The FixedArray is addressed with int's. int entry_index = static_cast<int>(index); switch (table->type().heap_representation()) { case wasm::HeapType::kExtern: case wasm::HeapType::kAny: entries->set(entry_index, *entry); return; case wasm::HeapType::kFunc: SetFunctionTableEntry(isolate, table, entries, entry_index, entry); return; case wasm::HeapType::kEq: case wasm::HeapType::kData: case wasm::HeapType::kI31: // TODO(7748): Implement once we have struct/arrays/i31ref tables. UNREACHABLE(); case wasm::HeapType::kBottom: UNREACHABLE(); default: DCHECK(!table->instance().IsUndefined()); // TODO(7748): Relax this once we have struct/array/i31ref tables. DCHECK(WasmInstanceObject::cast(table->instance()) .module() ->has_signature(table->type().ref_index())); SetFunctionTableEntry(isolate, table, entries, entry_index, entry); return; } } Handle<Object> WasmTableObject::Get(Isolate* isolate, Handle<WasmTableObject> table, uint32_t index) { Handle<FixedArray> entries(table->entries(), isolate); // Callers need to perform bounds checks and error handling. DCHECK(IsInBounds(isolate, table, index)); // The FixedArray is addressed with int's. int entry_index = static_cast<int>(index); Handle<Object> entry(entries->get(entry_index), isolate); if (entry->IsNull(isolate)) { return entry; } switch (table->type().heap_representation()) { case wasm::HeapType::kExtern: return entry; case wasm::HeapType::kFunc: if (WasmExportedFunction::IsWasmExportedFunction(*entry) || WasmJSFunction::IsWasmJSFunction(*entry) || WasmCapiFunction::IsWasmCapiFunction(*entry)) { return entry; } break; case wasm::HeapType::kEq: case wasm::HeapType::kI31: case wasm::HeapType::kData: case wasm::HeapType::kAny: // TODO(7748): Implement once we have a story for struct/arrays/i31ref in // JS. UNIMPLEMENTED(); case wasm::HeapType::kBottom: UNREACHABLE(); default: DCHECK(!table->instance().IsUndefined()); // TODO(7748): Relax this once we have struct/array/i31ref tables. DCHECK(WasmInstanceObject::cast(table->instance()) .module() ->has_signature(table->type().ref_index())); if (WasmExportedFunction::IsWasmExportedFunction(*entry) || WasmJSFunction::IsWasmJSFunction(*entry) || WasmCapiFunction::IsWasmCapiFunction(*entry)) { return entry; } break; } // {entry} is not a valid entry in the table. It has to be a placeholder // for lazy initialization. Handle<Tuple2> tuple = Handle<Tuple2>::cast(entry); auto instance = handle(WasmInstanceObject::cast(tuple->value1()), isolate); int function_index = Smi::cast(tuple->value2()).value(); // Check if we already compiled a wrapper for the function but did not store // it in the table slot yet. entry = WasmInstanceObject::GetOrCreateWasmExternalFunction(isolate, instance, function_index); entries->set(entry_index, *entry); return entry; } void WasmTableObject::Fill(Isolate* isolate, Handle<WasmTableObject> table, uint32_t start, Handle<Object> entry, uint32_t count) { // Bounds checks must be done by the caller. DCHECK_LE(start, table->current_length()); DCHECK_LE(count, table->current_length()); DCHECK_LE(start + count, table->current_length()); for (uint32_t i = 0; i < count; i++) { WasmTableObject::Set(isolate, table, start + i, entry); } } void WasmTableObject::UpdateDispatchTables( Isolate* isolate, Handle<WasmTableObject> table, int entry_index, const wasm::FunctionSig* sig, Handle<WasmInstanceObject> target_instance, int target_func_index) { // We simply need to update the IFTs for each instance that imports // this table. Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate); DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements); for (int i = 0; i < dispatch_tables->length(); i += kDispatchTableNumElements) { int table_index = Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value(); Handle<WasmInstanceObject> instance( WasmInstanceObject::cast( dispatch_tables->get(i + kDispatchTableInstanceOffset)), isolate); // Note that {SignatureMap::Find} may return {-1} if the signature is // not found; it will simply never match any check. auto sig_id = instance->module()->signature_map.Find(*sig); IndirectFunctionTableEntry(instance, table_index, entry_index) .Set(sig_id, target_instance, target_func_index); } } void WasmTableObject::UpdateDispatchTables(Isolate* isolate, Handle<WasmTableObject> table, int entry_index, Handle<WasmJSFunction> function) { // We simply need to update the IFTs for each instance that imports // this table. Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate); DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements); for (int i = 0; i < dispatch_tables->length(); i += kDispatchTableNumElements) { int table_index = Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value(); Handle<WasmInstanceObject> instance( WasmInstanceObject::cast( dispatch_tables->get(i + kDispatchTableInstanceOffset)), isolate); WasmInstanceObject::ImportWasmJSFunctionIntoTable( isolate, instance, table_index, entry_index, function); } } void WasmTableObject::UpdateDispatchTables( Isolate* isolate, Handle<WasmTableObject> table, int entry_index, Handle<WasmCapiFunction> capi_function) { // We simply need to update the IFTs for each instance that imports // this table. Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate); DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements); // Reconstruct signature. // TODO(jkummerow): Unify with "SignatureHelper" in c-api.cc. PodArray<wasm::ValueType> serialized_sig = capi_function->GetSerializedSignature(); int total_count = serialized_sig.length() - 1; std::unique_ptr<wasm::ValueType[]> reps(new wasm::ValueType[total_count]); int result_count; static const wasm::ValueType kMarker = wasm::kWasmVoid; for (int i = 0, j = 0; i <= total_count; i++) { if (serialized_sig.get(i) == kMarker) { result_count = i; continue; } reps[j++] = serialized_sig.get(i); } int param_count = total_count - result_count; wasm::FunctionSig sig(result_count, param_count, reps.get()); for (int i = 0; i < dispatch_tables->length(); i += kDispatchTableNumElements) { int table_index = Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value(); Handle<WasmInstanceObject> instance( WasmInstanceObject::cast( dispatch_tables->get(i + kDispatchTableInstanceOffset)), isolate); wasm::NativeModule* native_module = instance->module_object().native_module(); wasm::WasmImportWrapperCache* cache = native_module->import_wrapper_cache(); auto kind = compiler::WasmImportCallKind::kWasmToCapi; wasm::WasmCode* wasm_code = cache->MaybeGet(kind, &sig, param_count); if (wasm_code == nullptr) { wasm::WasmCodeRefScope code_ref_scope; wasm::WasmImportWrapperCache::ModificationScope cache_scope(cache); wasm_code = compiler::CompileWasmCapiCallWrapper(isolate->wasm_engine(), native_module, &sig); wasm::WasmImportWrapperCache::CacheKey key(kind, &sig, param_count); cache_scope[key] = wasm_code; wasm_code->IncRef(); isolate->counters()->wasm_generated_code_size()->Increment( wasm_code->instructions().length()); isolate->counters()->wasm_reloc_size()->Increment( wasm_code->reloc_info().length()); } // There is a cached tuple on the {capi_function}, but it is instance- // independent, so we prefer to allocate a fresh tuple here. Handle<Tuple2> tuple = isolate->factory()->NewTuple2( instance, capi_function, AllocationType::kOld); // Note that {SignatureMap::Find} may return {-1} if the signature is // not found; it will simply never match any check. auto sig_id = instance->module()->signature_map.Find(sig); IndirectFunctionTableEntry(instance, table_index, entry_index) .Set(sig_id, wasm_code->instruction_start(), *tuple); } } void WasmTableObject::ClearDispatchTables(Isolate* isolate, Handle<WasmTableObject> table, int index) { Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate); DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements); for (int i = 0; i < dispatch_tables->length(); i += kDispatchTableNumElements) { int table_index = Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value(); Handle<WasmInstanceObject> target_instance( WasmInstanceObject::cast( dispatch_tables->get(i + kDispatchTableInstanceOffset)), isolate); DCHECK_LT(index, WasmInstanceObject::IndirectFunctionTableSize( isolate, target_instance, table_index)); IndirectFunctionTableEntry(target_instance, table_index, index).clear(); } } void WasmTableObject::SetFunctionTablePlaceholder( Isolate* isolate, Handle<WasmTableObject> table, int entry_index, Handle<WasmInstanceObject> instance, int func_index) { // Put (instance, func_index) as a Tuple2 into the table_index. // The {WasmExportedFunction} will be created lazily. Handle<Tuple2> tuple = isolate->factory()->NewTuple2( instance, Handle<Smi>(Smi::FromInt(func_index), isolate), AllocationType::kYoung); table->entries().set(entry_index, *tuple); } void WasmTableObject::GetFunctionTableEntry( Isolate* isolate, const WasmModule* module, Handle<WasmTableObject> table, int entry_index, bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance, int* function_index, MaybeHandle<WasmJSFunction>* maybe_js_function) { DCHECK(wasm::IsSubtypeOf(table->type(), wasm::kWasmFuncRef, module)); DCHECK_LT(entry_index, table->current_length()); // We initialize {is_valid} with {true}. We may change it later. *is_valid = true; Handle<Object> element(table->entries().get(entry_index), isolate); *is_null = element->IsNull(isolate); if (*is_null) return; if (WasmExportedFunction::IsWasmExportedFunction(*element)) { auto target_func = Handle<WasmExportedFunction>::cast(element); *instance = handle(target_func->instance(), isolate); *function_index = target_func->function_index(); *maybe_js_function = MaybeHandle<WasmJSFunction>(); return; } if (WasmJSFunction::IsWasmJSFunction(*element)) { *instance = MaybeHandle<WasmInstanceObject>(); *maybe_js_function = Handle<WasmJSFunction>::cast(element); return; } if (element->IsTuple2()) { auto tuple = Handle<Tuple2>::cast(element); *instance = handle(WasmInstanceObject::cast(tuple->value1()), isolate); *function_index = Smi::cast(tuple->value2()).value(); *maybe_js_function = MaybeHandle<WasmJSFunction>(); return; } *is_valid = false; } namespace { class IftNativeAllocations { public: IftNativeAllocations(Handle<WasmIndirectFunctionTable> table, uint32_t size) : sig_ids_(size), targets_(size) { table->set_sig_ids(sig_ids_.data()); table->set_targets(targets_.data()); } static size_t SizeInMemory(uint32_t size) { return size * (sizeof(Address) + sizeof(uint32_t)); } void resize(Handle<WasmIndirectFunctionTable> table, uint32_t new_size) { DCHECK_GE(new_size, sig_ids_.size()); DCHECK_EQ(this, Managed<IftNativeAllocations>::cast( table->managed_native_allocations()) .raw()); sig_ids_.resize(new_size); targets_.resize(new_size); table->set_sig_ids(sig_ids_.data()); table->set_targets(targets_.data()); } private: std::vector<uint32_t> sig_ids_; std::vector<Address> targets_; }; } // namespace Handle<WasmIndirectFunctionTable> WasmIndirectFunctionTable::New( Isolate* isolate, uint32_t size) { auto refs = isolate->factory()->NewFixedArray(static_cast<int>(size)); auto table = Handle<WasmIndirectFunctionTable>::cast( isolate->factory()->NewStruct(WASM_INDIRECT_FUNCTION_TABLE_TYPE)); table->set_size(size); table->set_refs(*refs); auto native_allocations = Managed<IftNativeAllocations>::Allocate( isolate, IftNativeAllocations::SizeInMemory(size), table, size); table->set_managed_native_allocations(*native_allocations); for (uint32_t i = 0; i < size; ++i) { IndirectFunctionTableEntry(table, static_cast<int>(i)).clear(); } return table; } void WasmIndirectFunctionTable::Resize(Isolate* isolate, Handle<WasmIndirectFunctionTable> table, uint32_t new_size) { uint32_t old_size = table->size(); if (old_size >= new_size) return; // Nothing to do. Managed<IftNativeAllocations>::cast(table->managed_native_allocations()) .raw() ->resize(table, new_size); Handle<FixedArray> old_refs(table->refs(), isolate); Handle<FixedArray> new_refs = isolate->factory()->CopyFixedArrayAndGrow( old_refs, static_cast<int>(new_size - old_size)); table->set_refs(*new_refs); table->set_size(new_size); for (uint32_t i = old_size; i < new_size; ++i) { IndirectFunctionTableEntry(table, static_cast<int>(i)).clear(); } } namespace { void SetInstanceMemory(Handle<WasmInstanceObject> instance, Handle<JSArrayBuffer> buffer) { bool is_wasm_module = instance->module()->origin == wasm::kWasmOrigin; bool use_trap_handler = instance->module_object().native_module()->use_trap_handler(); // Wasm modules compiled to use the trap handler don't have bounds checks, // so they must have a memory that has guard regions. CHECK_IMPLIES(is_wasm_module && use_trap_handler, buffer->GetBackingStore()->has_guard_regions()); instance->SetRawMemory(reinterpret_cast<byte*>(buffer->backing_store()), buffer->byte_length()); #if DEBUG if (!FLAG_mock_arraybuffer_allocator) { // 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. // Don't do this if the mock ArrayBuffer allocator is enabled. byte* mem_start = instance->memory_start(); size_t mem_size = instance->memory_size(); for (size_t offset = 0; offset < mem_size; offset += wasm::kWasmPageSize) { byte val = mem_start[offset]; USE(val); mem_start[offset] = val; } } #endif } } // namespace Handle<WasmMemoryObject> WasmMemoryObject::New( Isolate* isolate, MaybeHandle<JSArrayBuffer> maybe_buffer, int maximum) { Handle<JSArrayBuffer> buffer; if (!maybe_buffer.ToHandle(&buffer)) { // If no buffer was provided, create a zero-length one. auto backing_store = BackingStore::AllocateWasmMemory(isolate, 0, 0, SharedFlag::kNotShared); buffer = isolate->factory()->NewJSArrayBuffer(std::move(backing_store)); } Handle<JSFunction> memory_ctor( isolate->native_context()->wasm_memory_constructor(), isolate); auto memory_object = Handle<WasmMemoryObject>::cast( isolate->factory()->NewJSObject(memory_ctor, AllocationType::kOld)); memory_object->set_array_buffer(*buffer); memory_object->set_maximum_pages(maximum); if (buffer->is_shared()) { auto backing_store = buffer->GetBackingStore(); backing_store->AttachSharedWasmMemoryObject(isolate, memory_object); } // For debugging purposes we memorize a link from the JSArrayBuffer // to it's owning WasmMemoryObject instance. Handle<Symbol> symbol = isolate->factory()->array_buffer_wasm_memory_symbol(); JSObject::SetProperty(isolate, buffer, symbol, memory_object).Check(); return memory_object; } MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(Isolate* isolate, int initial, int maximum, SharedFlag shared) { bool has_maximum = maximum != kNoMaximum; int heuristic_maximum = maximum; if (!has_maximum) { heuristic_maximum = static_cast<int>(wasm::max_mem_pages()); } #ifdef V8_TARGET_ARCH_32_BIT if (shared == SharedFlag::kNotShared) { // On 32-bit platforms we need a heuristic here to balance overall memory // and address space consumption. If a maximum memory size is defined, then // we reserve that maximum size up to 1GB. If no maximum memory size is // defined, we just allocate the initial size and grow with a realloc. constexpr int kGBPages = 1024 * 1024 * 1024 / wasm::kWasmPageSize; if (initial > kGBPages || !has_maximum) { // We allocate at least the initial size. If no maximum is specified we // also start with the initial size. heuristic_maximum = initial; } else { // We reserve the maximum size, but at most 1GB. heuristic_maximum = std::min(maximum, kGBPages); } } #endif auto backing_store = BackingStore::AllocateWasmMemory( isolate, initial, heuristic_maximum, shared); if (!backing_store) return {}; Handle<JSArrayBuffer> buffer = (shared == SharedFlag::kShared) ? isolate->factory()->NewJSSharedArrayBuffer(std::move(backing_store)) : isolate->factory()->NewJSArrayBuffer(std::move(backing_store)); return New(isolate, buffer, maximum); } void WasmMemoryObject::AddInstance(Isolate* isolate, Handle<WasmMemoryObject> memory, Handle<WasmInstanceObject> instance) { Handle<WeakArrayList> old_instances = memory->has_instances() ? Handle<WeakArrayList>(memory->instances(), isolate) : handle(ReadOnlyRoots(isolate->heap()).empty_weak_array_list(), isolate); Handle<WeakArrayList> new_instances = WeakArrayList::Append( isolate, old_instances, MaybeObjectHandle::Weak(instance)); memory->set_instances(*new_instances); Handle<JSArrayBuffer> buffer(memory->array_buffer(), isolate); SetInstanceMemory(instance, buffer); } void WasmMemoryObject::update_instances(Isolate* isolate, Handle<JSArrayBuffer> buffer) { if (has_instances()) { Handle<WeakArrayList> instances(this->instances(), isolate); for (int i = 0; i < instances->length(); i++) { MaybeObject elem = instances->Get(i); HeapObject heap_object; if (elem->GetHeapObjectIfWeak(&heap_object)) { Handle<WasmInstanceObject> instance( WasmInstanceObject::cast(heap_object), isolate); SetInstanceMemory(instance, buffer); } else { DCHECK(elem->IsCleared()); } } } set_array_buffer(*buffer); } // static int32_t WasmMemoryObject::Grow(Isolate* isolate, Handle<WasmMemoryObject> memory_object, uint32_t pages) { TRACE_EVENT0("v8.wasm", "wasm.GrowMemory"); Handle<JSArrayBuffer> old_buffer(memory_object->array_buffer(), isolate); // Any buffer used as an asmjs memory cannot be detached, and // therefore this memory cannot be grown. if (old_buffer->is_asmjs_memory()) return -1; std::shared_ptr<BackingStore> backing_store = old_buffer->GetBackingStore(); if (!backing_store) return -1; // Check for maximum memory size. // Note: The {wasm::max_mem_pages()} limit is already checked in // {BackingStore::CopyWasmMemory}, and is irrelevant for // {GrowWasmMemoryInPlace} because memory is never allocated with more // capacity than that limit. size_t old_size = old_buffer->byte_length(); DCHECK_EQ(0, old_size % wasm::kWasmPageSize); size_t old_pages = old_size / wasm::kWasmPageSize; uint32_t max_pages = wasm::kSpecMaxMemoryPages; if (memory_object->has_maximum_pages()) { DCHECK_GE(max_pages, memory_object->maximum_pages()); max_pages = static_cast<uint32_t>(memory_object->maximum_pages()); } DCHECK_GE(max_pages, old_pages); if (pages > max_pages - old_pages) return -1; base::Optional<size_t> result_inplace = backing_store->GrowWasmMemoryInPlace(isolate, pages, max_pages); // Handle shared memory first. if (old_buffer->is_shared()) { // Shared memories can only be grown in place; no copying. if (!result_inplace.has_value()) { // There are different limits per platform, thus crash if the correctness // fuzzer is running. if (FLAG_correctness_fuzzer_suppressions) { FATAL("could not grow wasm memory"); } return -1; } BackingStore::BroadcastSharedWasmMemoryGrow(isolate, backing_store); // Broadcasting the update should update this memory object too. CHECK_NE(*old_buffer, memory_object->array_buffer()); size_t new_pages = result_inplace.value() + pages; // If the allocation succeeded, then this can't possibly overflow: size_t new_byte_length = new_pages * wasm::kWasmPageSize; // This is a less than check, as it is not guaranteed that the SAB // length here will be equal to the stashed length above as calls to // grow the same memory object can come in from different workers. // It is also possible that a call to Grow was in progress when // handling this call. CHECK_LE(new_byte_length, memory_object->array_buffer().byte_length()); // As {old_pages} was read racefully, we return here the synchronized // value provided by {GrowWasmMemoryInPlace}, to provide the atomic // read-modify-write behavior required by the spec. return static_cast<int32_t>(result_inplace.value()); // success } // Check if the non-shared memory could grow in-place. if (result_inplace.has_value()) { // Detach old and create a new one with the grown backing store. old_buffer->Detach(true); Handle<JSArrayBuffer> new_buffer = isolate->factory()->NewJSArrayBuffer(std::move(backing_store)); memory_object->update_instances(isolate, new_buffer); // For debugging purposes we memorize a link from the JSArrayBuffer // to it's owning WasmMemoryObject instance. Handle<Symbol> symbol = isolate->factory()->array_buffer_wasm_memory_symbol(); JSObject::SetProperty(isolate, new_buffer, symbol, memory_object).Check(); DCHECK_EQ(result_inplace.value(), old_pages); return static_cast<int32_t>(result_inplace.value()); // success } size_t new_pages = old_pages + pages; DCHECK_LT(old_pages, new_pages); // Try allocating a new backing store and copying. std::unique_ptr<BackingStore> new_backing_store = backing_store->CopyWasmMemory(isolate, new_pages); if (!new_backing_store) { // Crash on out-of-memory if the correctness fuzzer is running. if (FLAG_correctness_fuzzer_suppressions) { FATAL("could not grow wasm memory"); } return -1; } // Detach old and create a new one with the new backing store. old_buffer->Detach(true); Handle<JSArrayBuffer> new_buffer = isolate->factory()->NewJSArrayBuffer(std::move(new_backing_store)); memory_object->update_instances(isolate, new_buffer); // For debugging purposes we memorize a link from the JSArrayBuffer // to it's owning WasmMemoryObject instance. Handle<Symbol> symbol = isolate->factory()->array_buffer_wasm_memory_symbol(); JSObject::SetProperty(isolate, new_buffer, symbol, memory_object).Check(); return static_cast<int32_t>(old_pages); // success } // static MaybeHandle<WasmGlobalObject> WasmGlobalObject::New( Isolate* isolate, Handle<WasmInstanceObject> instance, MaybeHandle<JSArrayBuffer> maybe_untagged_buffer, MaybeHandle<FixedArray> maybe_tagged_buffer, wasm::ValueType type, int32_t offset, bool is_mutable) { Handle<JSFunction> global_ctor( isolate->native_context()->wasm_global_constructor(), isolate); auto global_obj = Handle<WasmGlobalObject>::cast( isolate->factory()->NewJSObject(global_ctor)); { // Disallow GC until all fields have acceptable types. DisallowGarbageCollection no_gc; if (!instance.is_null()) global_obj->set_instance(*instance); global_obj->set_type(type); global_obj->set_offset(offset); global_obj->set_is_mutable(is_mutable); } if (type.is_reference()) { DCHECK(maybe_untagged_buffer.is_null()); Handle<FixedArray> tagged_buffer; if (!maybe_tagged_buffer.ToHandle(&tagged_buffer)) { // If no buffer was provided, create one. tagged_buffer = isolate->factory()->NewFixedArray(1, AllocationType::kOld); CHECK_EQ(offset, 0); } global_obj->set_tagged_buffer(*tagged_buffer); } else { DCHECK(maybe_tagged_buffer.is_null()); uint32_t type_size = type.element_size_bytes(); Handle<JSArrayBuffer> untagged_buffer; if (!maybe_untagged_buffer.ToHandle(&untagged_buffer)) { MaybeHandle<JSArrayBuffer> result = isolate->factory()->NewJSArrayBufferAndBackingStore( offset + type_size, InitializedFlag::kZeroInitialized); if (!result.ToHandle(&untagged_buffer)) return {}; } // Check that the offset is in bounds. CHECK_LE(offset + type_size, untagged_buffer->byte_length()); global_obj->set_untagged_buffer(*untagged_buffer); } return global_obj; } void IndirectFunctionTableEntry::clear() { if (!instance_.is_null()) { instance_->indirect_function_table_sig_ids()[index_] = -1; instance_->indirect_function_table_targets()[index_] = 0; instance_->indirect_function_table_refs().set( index_, ReadOnlyRoots(instance_->GetIsolate()).undefined_value()); } else { DCHECK(!table_.is_null()); table_->sig_ids()[index_] = -1; table_->targets()[index_] = 0; table_->refs().set( index_, ReadOnlyRoots(GetIsolateFromWritableObject(*table_)).undefined_value()); } } void IndirectFunctionTableEntry::Set(int sig_id, Handle<WasmInstanceObject> target_instance, int target_func_index) { TRACE_IFT("IFT entry 0x%" PRIxPTR "[%d] = {sig_id=%d, target_instance=0x%" PRIxPTR ", target_func_index=%d}\n", instance_->ptr(), index_, sig_id, target_instance->ptr(), target_func_index); Object ref; Address call_target = 0; if (target_func_index < static_cast<int>(target_instance->module()->num_imported_functions)) { // The function in the target instance was imported. Use its imports table, // which contains a tuple needed by the import wrapper. ImportedFunctionEntry entry(target_instance, target_func_index); ref = entry.object_ref(); call_target = entry.target(); } else { // The function in the target instance was not imported. ref = *target_instance; call_target = target_instance->GetCallTarget(target_func_index); } Set(sig_id, call_target, ref); } void IndirectFunctionTableEntry::Set(int sig_id, Address call_target, Object ref) { if (!instance_.is_null()) { instance_->indirect_function_table_sig_ids()[index_] = sig_id; instance_->indirect_function_table_targets()[index_] = call_target; instance_->indirect_function_table_refs().set(index_, ref); } else { DCHECK(!table_.is_null()); table_->sig_ids()[index_] = sig_id; table_->targets()[index_] = call_target; table_->refs().set(index_, ref); } } Object IndirectFunctionTableEntry::object_ref() const { return !instance_.is_null() ? instance_->indirect_function_table_refs().get(index_) : table_->refs().get(index_); } int IndirectFunctionTableEntry::sig_id() const { return !instance_.is_null() ? instance_->indirect_function_table_sig_ids()[index_] : table_->sig_ids()[index_]; } Address IndirectFunctionTableEntry::target() const { return !instance_.is_null() ? instance_->indirect_function_table_targets()[index_] : table_->targets()[index_]; } void ImportedFunctionEntry::SetWasmToJs( Isolate* isolate, Handle<JSReceiver> callable, const wasm::WasmCode* wasm_to_js_wrapper) { TRACE_IFT("Import callable 0x%" PRIxPTR "[%d] = {callable=0x%" PRIxPTR ", target=%p}\n", instance_->ptr(), index_, callable->ptr(), wasm_to_js_wrapper->instructions().begin()); DCHECK(wasm_to_js_wrapper->kind() == wasm::WasmCode::kWasmToJsWrapper || wasm_to_js_wrapper->kind() == wasm::WasmCode::kWasmToCapiWrapper); Handle<Tuple2> tuple = isolate->factory()->NewTuple2(instance_, callable, AllocationType::kOld); instance_->imported_function_refs().set(index_, *tuple); instance_->imported_function_targets()[index_] = wasm_to_js_wrapper->instruction_start(); } void ImportedFunctionEntry::SetWasmToWasm(WasmInstanceObject instance, Address call_target) { TRACE_IFT("Import Wasm 0x%" PRIxPTR "[%d] = {instance=0x%" PRIxPTR ", target=0x%" PRIxPTR "}\n", instance_->ptr(), index_, instance.ptr(), call_target); instance_->imported_function_refs().set(index_, instance); instance_->imported_function_targets()[index_] = call_target; } WasmInstanceObject ImportedFunctionEntry::instance() { // The imported reference entry is either a target instance or a tuple // of this instance and the target callable. Object value = object_ref(); if (value.IsWasmInstanceObject()) { return WasmInstanceObject::cast(value); } Tuple2 tuple = Tuple2::cast(value); return WasmInstanceObject::cast(tuple.value1()); } // Returns an empty Object() if no callable is available, a JSReceiver // otherwise. Object ImportedFunctionEntry::maybe_callable() { Object value = object_ref(); if (!value.IsTuple2()) return Object(); Tuple2 tuple = Tuple2::cast(value); return JSReceiver::cast(tuple.value2()); } JSReceiver ImportedFunctionEntry::callable() { return JSReceiver::cast(Tuple2::cast(object_ref()).value2()); } Object ImportedFunctionEntry::object_ref() { return instance_->imported_function_refs().get(index_); } Address ImportedFunctionEntry::target() { return instance_->imported_function_targets()[index_]; } // static constexpr uint16_t WasmInstanceObject::kTaggedFieldOffsets[]; // static bool WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( Handle<WasmInstanceObject> instance, int table_index, uint32_t minimum_size) { Isolate* isolate = instance->GetIsolate(); if (table_index > 0) { DCHECK_LT(table_index, instance->indirect_function_tables().length()); auto table = handle(WasmIndirectFunctionTable::cast( instance->indirect_function_tables().get(table_index)), isolate); WasmIndirectFunctionTable::Resize(isolate, table, minimum_size); return true; } uint32_t old_size = instance->indirect_function_table_size(); if (old_size >= minimum_size) return false; // Nothing to do. auto native_allocations = GetNativeAllocations(*instance); if (native_allocations->indirect_function_table_capacity() < minimum_size) { HandleScope scope(isolate); native_allocations->resize_indirect_function_table(isolate, instance, minimum_size); DCHECK_GE(native_allocations->indirect_function_table_capacity(), minimum_size); } instance->set_indirect_function_table_size(minimum_size); for (uint32_t j = old_size; j < minimum_size; j++) { // {WasmInstanceNativeAllocations} only manages the memory of table 0. // Therefore we pass the {table_index} as a constant here. IndirectFunctionTableEntry(instance, 0, static_cast<int>(j)).clear(); } return true; } void WasmInstanceObject::SetRawMemory(byte* mem_start, size_t mem_size) { CHECK_LE(mem_size, wasm::max_mem_bytes()); #if V8_HOST_ARCH_64_BIT uint64_t mem_mask64 = base::bits::RoundUpToPowerOfTwo64(mem_size) - 1; set_memory_start(mem_start); set_memory_size(mem_size); set_memory_mask(mem_mask64); #else // Must handle memory > 2GiB specially. CHECK_LE(mem_size, size_t{kMaxUInt32}); uint32_t mem_mask32 = (mem_size > 2 * size_t{GB}) ? 0xFFFFFFFFu : base::bits::RoundUpToPowerOfTwo32(static_cast<uint32_t>(mem_size)) - 1; set_memory_start(mem_start); set_memory_size(mem_size); set_memory_mask(mem_mask32); #endif } const WasmModule* WasmInstanceObject::module() { return module_object().module(); } Handle<WasmInstanceObject> WasmInstanceObject::New( Isolate* isolate, Handle<WasmModuleObject> module_object) { Handle<JSFunction> instance_cons( isolate->native_context()->wasm_instance_constructor(), isolate); Handle<JSObject> instance_object = isolate->factory()->NewJSObject(instance_cons, AllocationType::kOld); Handle<WasmInstanceObject> instance( WasmInstanceObject::cast(*instance_object), isolate); instance->clear_padding(); // Initialize the imported function arrays. auto module = module_object->module(); auto num_imported_functions = module->num_imported_functions; auto num_imported_mutable_globals = module->num_imported_mutable_globals; auto num_data_segments = module->num_declared_data_segments; size_t native_allocations_size = EstimateNativeAllocationsSize(module); auto native_allocations = Managed<WasmInstanceNativeAllocations>::Allocate( isolate, native_allocations_size, instance, num_imported_functions, num_imported_mutable_globals, num_data_segments, module->elem_segments.size()); instance->set_managed_native_allocations(*native_allocations); Handle<FixedArray> imported_function_refs = isolate->factory()->NewFixedArray(num_imported_functions); instance->set_imported_function_refs(*imported_function_refs); instance->SetRawMemory(nullptr, 0); instance->set_isolate_root(isolate->isolate_root()); instance->set_stack_limit_address( isolate->stack_guard()->address_of_jslimit()); instance->set_real_stack_limit_address( isolate->stack_guard()->address_of_real_jslimit()); instance->set_globals_start(nullptr); instance->set_indirect_function_table_size(0); instance->set_indirect_function_table_refs( ReadOnlyRoots(isolate).empty_fixed_array()); instance->set_indirect_function_table_sig_ids(nullptr); instance->set_indirect_function_table_targets(nullptr); instance->set_native_context(*isolate->native_context()); instance->set_module_object(*module_object); instance->set_jump_table_start( module_object->native_module()->jump_table_start()); instance->set_hook_on_function_call_address( isolate->debug()->hook_on_function_call_address()); instance->set_managed_object_maps(*isolate->factory()->empty_fixed_array()); instance->set_num_liftoff_function_calls_array( module_object->native_module()->num_liftoff_function_calls_array()); instance->set_break_on_entry(module_object->script().break_on_entry()); // Insert the new instance into the scripts weak list of instances. This list // is used for breakpoints affecting all instances belonging to the script. if (module_object->script().type() == Script::TYPE_WASM) { Handle<WeakArrayList> weak_instance_list( module_object->script().wasm_weak_instance_list(), isolate); weak_instance_list = WeakArrayList::Append( isolate, weak_instance_list, MaybeObjectHandle::Weak(instance)); module_object->script().set_wasm_weak_instance_list(*weak_instance_list); } InitDataSegmentArrays(instance, module_object); InitElemSegmentArrays(instance, module_object); return instance; } // static void WasmInstanceObject::InitDataSegmentArrays( Handle<WasmInstanceObject> instance, Handle<WasmModuleObject> module_object) { auto module = module_object->module(); auto wire_bytes = module_object->native_module()->wire_bytes(); auto num_data_segments = module->num_declared_data_segments; // The number of declared data segments will be zero if there is no DataCount // section. These arrays will not be allocated nor initialized in that case, // since they cannot be used (since the validator checks that number of // declared data segments when validating the memory.init and memory.drop // instructions). DCHECK(num_data_segments == 0 || num_data_segments == module->data_segments.size()); for (size_t i = 0; i < num_data_segments; ++i) { const wasm::WasmDataSegment& segment = module->data_segments[i]; // Initialize the pointer and size of passive segments. auto source_bytes = wire_bytes.SubVector(segment.source.offset(), segment.source.end_offset()); instance->data_segment_starts()[i] = reinterpret_cast<Address>(source_bytes.begin()); // Set the active segments to being already dropped, since memory.init on // a dropped passive segment and an active segment have the same // behavior. instance->data_segment_sizes()[i] = segment.active ? 0 : source_bytes.length(); } } void WasmInstanceObject::InitElemSegmentArrays( Handle<WasmInstanceObject> instance, Handle<WasmModuleObject> module_object) { auto module = module_object->module(); auto num_elem_segments = module->elem_segments.size(); for (size_t i = 0; i < num_elem_segments; ++i) { instance->dropped_elem_segments()[i] = module->elem_segments[i].status == wasm::WasmElemSegment::kStatusDeclarative ? 1 : 0; } } Address WasmInstanceObject::GetCallTarget(uint32_t func_index) { wasm::NativeModule* native_module = module_object().native_module(); if (func_index < native_module->num_imported_functions()) { return imported_function_targets()[func_index]; } return native_module->GetCallTargetForFunction(func_index); } int WasmInstanceObject::IndirectFunctionTableSize( Isolate* isolate, Handle<WasmInstanceObject> instance, uint32_t table_index) { if (table_index == 0) { return instance->indirect_function_table_size(); } auto table = handle(WasmIndirectFunctionTable::cast( instance->indirect_function_tables().get(table_index)), isolate); return table->size(); } // static bool WasmInstanceObject::CopyTableEntries(Isolate* isolate, Handle<WasmInstanceObject> instance, uint32_t table_dst_index, uint32_t table_src_index, uint32_t dst, uint32_t src, uint32_t count) { CHECK_LT(table_dst_index, instance->tables().length()); CHECK_LT(table_src_index, instance->tables().length()); auto table_dst = handle( WasmTableObject::cast(instance->tables().get(table_dst_index)), isolate); auto table_src = handle( WasmTableObject::cast(instance->tables().get(table_src_index)), isolate); uint32_t max_dst = table_dst->current_length(); uint32_t max_src = table_src->current_length(); bool copy_backward = src < dst; if (!base::IsInBounds(dst, count, max_dst) || !base::IsInBounds(src, count, max_src)) { return false; } // no-op if ((dst == src && table_dst_index == table_src_index) || count == 0) { return true; } for (uint32_t i = 0; i < count; ++i) { uint32_t src_index = copy_backward ? (src + count - i - 1) : src + i; uint32_t dst_index = copy_backward ? (dst + count - i - 1) : dst + i; auto value = WasmTableObject::Get(isolate, table_src, src_index); WasmTableObject::Set(isolate, table_dst, dst_index, value); } return true; } // static bool WasmInstanceObject::InitTableEntries(Isolate* isolate, Handle<WasmInstanceObject> instance, uint32_t table_index, uint32_t segment_index, uint32_t dst, uint32_t src, uint32_t count) { // Note that this implementation just calls through to module instantiation. // This is intentional, so that the runtime only depends on the object // methods, and not the module instantiation logic. return wasm::LoadElemSegment(isolate, instance, table_index, segment_index, dst, src, count); } MaybeHandle<WasmExternalFunction> WasmInstanceObject::GetWasmExternalFunction( Isolate* isolate, Handle<WasmInstanceObject> instance, int index) { MaybeHandle<WasmExternalFunction> result; if (instance->has_wasm_external_functions()) { Object val = instance->wasm_external_functions().get(index); if (!val.IsUndefined(isolate)) { result = Handle<WasmExternalFunction>(WasmExternalFunction::cast(val), isolate); } } return result; } Handle<WasmExternalFunction> WasmInstanceObject::GetOrCreateWasmExternalFunction( Isolate* isolate, Handle<WasmInstanceObject> instance, int function_index) { MaybeHandle<WasmExternalFunction> maybe_result = WasmInstanceObject::GetWasmExternalFunction(isolate, instance, function_index); Handle<WasmExternalFunction> result; if (maybe_result.ToHandle(&result)) { return result; } Handle<WasmModuleObject> module_object(instance->module_object(), isolate); const WasmModule* module = module_object->module(); const WasmFunction& function = module->functions[function_index]; int wrapper_index = GetExportWrapperIndex(module, function.sig, function.imported); Handle<Object> entry = FixedArray::get(module_object->export_wrappers(), wrapper_index, isolate); Handle<Code> wrapper; if (entry->IsCode()) { wrapper = Handle<Code>::cast(entry); } else { // The wrapper may not exist yet if no function in the exports section has // this signature. We compile it and store the wrapper in the module for // later use. wrapper = wasm::JSToWasmWrapperCompilationUnit::CompileJSToWasmWrapper( isolate, function.sig, instance->module(), function.imported); module_object->export_wrappers().set(wrapper_index, *wrapper); } result = Handle<WasmExternalFunction>::cast(WasmExportedFunction::New( isolate, instance, function_index, static_cast<int>(function.sig->parameter_count()), wrapper)); WasmInstanceObject::SetWasmExternalFunction(isolate, instance, function_index, result); return result; } void WasmInstanceObject::SetWasmExternalFunction( Isolate* isolate, Handle<WasmInstanceObject> instance, int index, Handle<WasmExternalFunction> val) { Handle<FixedArray> functions; if (!instance->has_wasm_external_functions()) { // Lazily allocate the wasm external functions array. functions = isolate->factory()->NewFixedArray( static_cast<int>(instance->module()->functions.size())); instance->set_wasm_external_functions(*functions); } else { functions = Handle<FixedArray>(instance->wasm_external_functions(), isolate); } functions->set(index, *val); } // static void WasmInstanceObject::ImportWasmJSFunctionIntoTable( Isolate* isolate, Handle<WasmInstanceObject> instance, int table_index, int entry_index, Handle<WasmJSFunction> js_function) { // Deserialize the signature encapsulated with the {WasmJSFunction}. // Note that {SignatureMap::Find} may return {-1} if the signature is // not found; it will simply never match any check. Zone zone(isolate->allocator(), ZONE_NAME); const wasm::FunctionSig* sig = js_function->GetSignature(&zone); auto sig_id = instance->module()->signature_map.Find(*sig); // Compile a wrapper for the target callable. Handle<JSReceiver> callable(js_function->GetCallable(), isolate); wasm::WasmCodeRefScope code_ref_scope; Address call_target = kNullAddress; if (sig_id >= 0) { wasm::NativeModule* native_module = instance->module_object().native_module(); // TODO(wasm): Cache and reuse wrapper code. const wasm::WasmFeatures enabled = native_module->enabled_features(); auto resolved = compiler::ResolveWasmImportCall( callable, sig, instance->module(), enabled); compiler::WasmImportCallKind kind = resolved.first; callable = resolved.second; // Update to ultimate target. DCHECK_NE(compiler::WasmImportCallKind::kLinkError, kind); wasm::CompilationEnv env = native_module->CreateCompilationEnv(); // {expected_arity} should only be used if kind != kJSFunctionArityMismatch. int expected_arity = -1; if (kind == compiler::WasmImportCallKind ::kJSFunctionArityMismatch) { expected_arity = Handle<JSFunction>::cast(callable) ->shared() .internal_formal_parameter_count(); } wasm::WasmCompilationResult result = compiler::CompileWasmImportCallWrapper( isolate->wasm_engine(), &env, kind, sig, false, expected_arity); std::unique_ptr<wasm::WasmCode> wasm_code = native_module->AddCode( result.func_index, result.code_desc, result.frame_slot_count, result.tagged_parameter_slots, result.protected_instructions_data.as_vector(), result.source_positions.as_vector(), GetCodeKind(result), wasm::ExecutionTier::kNone, wasm::kNoDebugging); wasm::WasmCode* published_code = native_module->PublishCode(std::move(wasm_code)); isolate->counters()->wasm_generated_code_size()->Increment( published_code->instructions().length()); isolate->counters()->wasm_reloc_size()->Increment( published_code->reloc_info().length()); call_target = published_code->instruction_start(); } // Update the dispatch table. Handle<Tuple2> tuple = isolate->factory()->NewTuple2(instance, callable, AllocationType::kOld); IndirectFunctionTableEntry(instance, table_index, entry_index) .Set(sig_id, call_target, *tuple); } // static uint8_t* WasmInstanceObject::GetGlobalStorage( Handle<WasmInstanceObject> instance, const wasm::WasmGlobal& global) { DCHECK(!global.type.is_reference()); if (global.mutability && global.imported) { return reinterpret_cast<byte*>( instance->imported_mutable_globals()[global.index]); } else { return instance->globals_start() + global.offset; } } // static std::pair<Handle<FixedArray>, uint32_t> WasmInstanceObject::GetGlobalBufferAndIndex(Handle<WasmInstanceObject> instance, const wasm::WasmGlobal& global) { DCHECK(global.type.is_reference()); Isolate* isolate = instance->GetIsolate(); if (global.mutability && global.imported) { Handle<FixedArray> buffer( FixedArray::cast( instance->imported_mutable_globals_buffers().get(global.index)), isolate); Address idx = instance->imported_mutable_globals()[global.index]; DCHECK_LE(idx, std::numeric_limits<uint32_t>::max()); return {buffer, static_cast<uint32_t>(idx)}; } return {handle(instance->tagged_globals_buffer(), isolate), global.offset}; } // static wasm::WasmValue WasmInstanceObject::GetGlobalValue( Handle<WasmInstanceObject> instance, const wasm::WasmGlobal& global) { Isolate* isolate = instance->GetIsolate(); if (global.type.is_reference()) { Handle<FixedArray> global_buffer; // The buffer of the global. uint32_t global_index = 0; // The index into the buffer. std::tie(global_buffer, global_index) = GetGlobalBufferAndIndex(instance, global); return wasm::WasmValue(handle(global_buffer->get(global_index), isolate), global.type); } Address ptr = reinterpret_cast<Address>(GetGlobalStorage(instance, global)); using wasm::Simd128; switch (global.type.kind()) { #define CASE_TYPE(valuetype, ctype) \ case wasm::valuetype: \ return wasm::WasmValue(base::ReadLittleEndianValue<ctype>(ptr)); FOREACH_WASMVALUE_CTYPES(CASE_TYPE) #undef CASE_TYPE default: UNREACHABLE(); } } wasm::WasmValue WasmStruct::GetFieldValue(uint32_t index) { wasm::ValueType field_type = type()->field(index); int field_offset = WasmStruct::kHeaderSize + type()->field_offset(index); Address field_address = GetFieldAddress(field_offset); using wasm::Simd128; switch (field_type.kind()) { #define CASE_TYPE(valuetype, ctype) \ case wasm::valuetype: \ return wasm::WasmValue(base::ReadLittleEndianValue<ctype>(field_address)); CASE_TYPE(kI8, int8_t) CASE_TYPE(kI16, int16_t) FOREACH_WASMVALUE_CTYPES(CASE_TYPE) #undef CASE_TYPE case wasm::kRef: case wasm::kOptRef: { Handle<Object> ref(TaggedField<Object>::load(*this, field_offset), GetIsolateFromWritableObject(*this)); return wasm::WasmValue(ref, field_type); } case wasm::kRtt: case wasm::kRttWithDepth: // TODO(7748): Expose RTTs to DevTools. UNIMPLEMENTED(); case wasm::kVoid: case wasm::kBottom: UNREACHABLE(); } } wasm::WasmValue WasmArray::GetElement(uint32_t index) { wasm::ValueType element_type = type()->element_type(); int element_offset = WasmArray::kHeaderSize + index * element_type.element_size_bytes(); Address element_address = GetFieldAddress(element_offset); using wasm::Simd128; switch (element_type.kind()) { #define CASE_TYPE(value_type, ctype) \ case wasm::value_type: \ return wasm::WasmValue(base::ReadLittleEndianValue<ctype>(element_address)); CASE_TYPE(kI8, int8_t) CASE_TYPE(kI16, int16_t) FOREACH_WASMVALUE_CTYPES(CASE_TYPE) #undef CASE_TYPE case wasm::kRef: case wasm::kOptRef: { Handle<Object> ref(TaggedField<Object>::load(*this, element_offset), GetIsolateFromWritableObject(*this)); return wasm::WasmValue(ref, element_type); } case wasm::kRtt: case wasm::kRttWithDepth: // TODO(7748): Expose RTTs to DevTools. UNIMPLEMENTED(); case wasm::kVoid: case wasm::kBottom: UNREACHABLE(); } } ObjectSlot WasmArray::ElementSlot(uint32_t index) { DCHECK_LE(index, length()); DCHECK(type()->element_type().is_reference()); return RawField(kHeaderSize + kTaggedSize * index); } // static Handle<WasmExceptionObject> WasmExceptionObject::New( Isolate* isolate, const wasm::FunctionSig* sig, Handle<HeapObject> exception_tag) { Handle<JSFunction> exception_cons( isolate->native_context()->wasm_exception_constructor(), isolate); // Serialize the signature. DCHECK_EQ(0, sig->return_count()); DCHECK_LE(sig->parameter_count(), std::numeric_limits<int>::max()); int sig_size = static_cast<int>(sig->parameter_count()); Handle<PodArray<wasm::ValueType>> serialized_sig = PodArray<wasm::ValueType>::New(isolate, sig_size, AllocationType::kOld); int index = 0; // Index into the {PodArray} above. for (wasm::ValueType param : sig->parameters()) { serialized_sig->set(index++, param); } Handle<JSObject> exception_object = isolate->factory()->NewJSObject(exception_cons, AllocationType::kOld); Handle<WasmExceptionObject> exception = Handle<WasmExceptionObject>::cast(exception_object); exception->set_serialized_signature(*serialized_sig); exception->set_exception_tag(*exception_tag); return exception; } // TODO(9495): Update this if function type variance is introduced. bool WasmExceptionObject::MatchesSignature(const wasm::FunctionSig* sig) { DCHECK_EQ(0, sig->return_count()); DCHECK_LE(sig->parameter_count(), std::numeric_limits<int>::max()); int sig_size = static_cast<int>(sig->parameter_count()); if (sig_size != serialized_signature().length()) return false; for (int index = 0; index < sig_size; ++index) { if (sig->GetParam(index) != serialized_signature().get(index)) { return false; } } return true; } // TODO(9495): Update this if function type variance is introduced. bool WasmCapiFunction::MatchesSignature(const wasm::FunctionSig* sig) const { // TODO(jkummerow): Unify with "SignatureHelper" in c-api.cc. int param_count = static_cast<int>(sig->parameter_count()); int result_count = static_cast<int>(sig->return_count()); PodArray<wasm::ValueType> serialized_sig = shared().wasm_capi_function_data().serialized_signature(); if (param_count + result_count + 1 != serialized_sig.length()) return false; int serialized_index = 0; for (int i = 0; i < result_count; i++, serialized_index++) { if (sig->GetReturn(i) != serialized_sig.get(serialized_index)) { return false; } } if (serialized_sig.get(serialized_index) != wasm::kWasmVoid) return false; serialized_index++; for (int i = 0; i < param_count; i++, serialized_index++) { if (sig->GetParam(i) != serialized_sig.get(serialized_index)) return false; } return true; } // static Handle<WasmExceptionPackage> WasmExceptionPackage::New( Isolate* isolate, Handle<WasmExceptionTag> exception_tag, int size) { Handle<Object> exception = isolate->factory()->NewWasmRuntimeError( MessageTemplate::kWasmExceptionError); CHECK(!Object::SetProperty(isolate, exception, isolate->factory()->wasm_exception_tag_symbol(), exception_tag, StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError)) .is_null()); Handle<FixedArray> values = isolate->factory()->NewFixedArray(size); CHECK(!Object::SetProperty(isolate, exception, isolate->factory()->wasm_exception_values_symbol(), values, StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError)) .is_null()); return Handle<WasmExceptionPackage>::cast(exception); } // static Handle<Object> WasmExceptionPackage::GetExceptionTag( Isolate* isolate, Handle<WasmExceptionPackage> exception_package) { Handle<Object> tag; if (JSReceiver::GetProperty(isolate, exception_package, isolate->factory()->wasm_exception_tag_symbol()) .ToHandle(&tag)) { return tag; } return ReadOnlyRoots(isolate).undefined_value_handle(); } // static Handle<Object> WasmExceptionPackage::GetExceptionValues( Isolate* isolate, Handle<WasmExceptionPackage> exception_package) { Handle<Object> values; if (JSReceiver::GetProperty( isolate, exception_package, isolate->factory()->wasm_exception_values_symbol()) .ToHandle(&values)) { DCHECK(values->IsFixedArray()); return values; } return ReadOnlyRoots(isolate).undefined_value_handle(); } #ifdef DEBUG namespace { constexpr uint32_t kBytesPerExceptionValuesArrayElement = 2; size_t ComputeEncodedElementSize(wasm::ValueType type) { size_t byte_size = type.element_size_bytes(); DCHECK_EQ(byte_size % kBytesPerExceptionValuesArrayElement, 0); DCHECK_LE(1, byte_size / kBytesPerExceptionValuesArrayElement); return byte_size / kBytesPerExceptionValuesArrayElement; } } // namespace #endif // DEBUG // static uint32_t WasmExceptionPackage::GetEncodedSize( const wasm::WasmException* exception) { const wasm::WasmExceptionSig* sig = exception->sig; uint32_t encoded_size = 0; for (size_t i = 0; i < sig->parameter_count(); ++i) { switch (sig->GetParam(i).kind()) { case wasm::kI32: case wasm::kF32: DCHECK_EQ(2, ComputeEncodedElementSize(sig->GetParam(i))); encoded_size += 2; break; case wasm::kI64: case wasm::kF64: DCHECK_EQ(4, ComputeEncodedElementSize(sig->GetParam(i))); encoded_size += 4; break; case wasm::kS128: DCHECK_EQ(8, ComputeEncodedElementSize(sig->GetParam(i))); encoded_size += 8; break; case wasm::kRef: case wasm::kOptRef: encoded_size += 1; break; case wasm::kRtt: case wasm::kRttWithDepth: case wasm::kVoid: case wasm::kBottom: case wasm::kI8: case wasm::kI16: UNREACHABLE(); } } return encoded_size; } bool WasmExportedFunction::IsWasmExportedFunction(Object object) { if (!object.IsJSFunction()) return false; JSFunction js_function = JSFunction::cast(object); if (CodeKind::JS_TO_WASM_FUNCTION != js_function.code().kind() && js_function.code().builtin_index() != Builtin::kGenericJSToWasmWrapper) { return false; } DCHECK(js_function.shared().HasWasmExportedFunctionData()); return true; } bool WasmCapiFunction::IsWasmCapiFunction(Object object) { if (!object.IsJSFunction()) return false; JSFunction js_function = JSFunction::cast(object); // TODO(jkummerow): Enable this when there is a JavaScript wrapper // able to call this function. // if (js_function->code()->kind() != CodeKind::WASM_TO_CAPI_FUNCTION) { // return false; // } // DCHECK(js_function->shared()->HasWasmCapiFunctionData()); // return true; return js_function.shared().HasWasmCapiFunctionData(); } Handle<WasmCapiFunction> WasmCapiFunction::New( Isolate* isolate, Address call_target, Handle<Foreign> embedder_data, Handle<PodArray<wasm::ValueType>> serialized_signature) { // TODO(jkummerow): Install a JavaScript wrapper. For now, calling // these functions directly is unsupported; they can only be called // from Wasm code. // To support simulator builds, we potentially have to redirect the // call target (which is an address pointing into the C++ binary). call_target = ExternalReference::Create(call_target).address(); Handle<WasmCapiFunctionData> fun_data = isolate->factory()->NewWasmCapiFunctionData( call_target, embedder_data, isolate->builtins()->builtin_handle(Builtin::kIllegal), serialized_signature); Handle<SharedFunctionInfo> shared = isolate->factory()->NewSharedFunctionInfoForWasmCapiFunction(fun_data); return Handle<WasmCapiFunction>::cast( Factory::JSFunctionBuilder{isolate, shared, isolate->native_context()} .Build()); } WasmInstanceObject WasmExportedFunction::instance() { return shared().wasm_exported_function_data().instance(); } int WasmExportedFunction::function_index() { return shared().wasm_exported_function_data().function_index(); } Handle<WasmExportedFunction> WasmExportedFunction::New( Isolate* isolate, Handle<WasmInstanceObject> instance, int func_index, int arity, Handle<Code> export_wrapper) { DCHECK(CodeKind::JS_TO_WASM_FUNCTION == export_wrapper->kind() || (export_wrapper->is_builtin() && export_wrapper->builtin_index() == Builtin::kGenericJSToWasmWrapper)); int num_imported_functions = instance->module()->num_imported_functions; Handle<Object> ref = func_index >= num_imported_functions ? instance : handle(instance->imported_function_refs().get(func_index), isolate); Factory* factory = isolate->factory(); const wasm::FunctionSig* sig = instance->module()->functions[func_index].sig; Address call_target = instance->GetCallTarget(func_index); Handle<WasmExportedFunctionData> function_data = factory->NewWasmExportedFunctionData( export_wrapper, instance, call_target, ref, func_index, reinterpret_cast<Address>(sig), wasm::kGenericWrapperBudget); MaybeHandle<String> maybe_name; bool is_asm_js_module = instance->module_object().is_asm_js(); if (is_asm_js_module) { // We can use the function name only for asm.js. For WebAssembly, the // function name is specified as the function_index.toString(). maybe_name = WasmModuleObject::GetFunctionNameOrNull( isolate, handle(instance->module_object(), isolate), func_index); } Handle<String> name; if (!maybe_name.ToHandle(&name)) { EmbeddedVector<char, 16> buffer; int length = SNPrintF(buffer, "%d", func_index); name = factory ->NewStringFromOneByte( Vector<uint8_t>::cast(buffer.SubVector(0, length))) .ToHandleChecked(); } Handle<Map> function_map; switch (instance->module()->origin) { case wasm::kWasmOrigin: if (instance->module_object() .native_module() ->enabled_features() .has_gc()) { uint32_t sig_index = instance->module()->functions[func_index].sig_index; function_map = handle( Map::cast(instance->managed_object_maps().get(sig_index)), isolate); } else { function_map = isolate->wasm_exported_function_map(); } break; case wasm::kAsmJsSloppyOrigin: function_map = isolate->sloppy_function_map(); break; case wasm::kAsmJsStrictOrigin: function_map = isolate->strict_function_map(); break; } Handle<NativeContext> context(isolate->native_context()); Handle<SharedFunctionInfo> shared = factory->NewSharedFunctionInfoForWasmExportedFunction(name, function_data); Handle<JSFunction> js_function = Factory::JSFunctionBuilder{isolate, shared, context} .set_map(function_map) .Build(); // According to the spec, exported functions should not have a [[Construct]] // method. This does not apply to functions exported from asm.js however. DCHECK_EQ(is_asm_js_module, js_function->IsConstructor()); shared->set_length(arity); shared->set_internal_formal_parameter_count(arity); shared->set_script(instance->module_object().script()); return Handle<WasmExportedFunction>::cast(js_function); } Address WasmExportedFunction::GetWasmCallTarget() { return instance().GetCallTarget(function_index()); } const wasm::FunctionSig* WasmExportedFunction::sig() { return instance().module()->functions[function_index()].sig; } bool WasmExportedFunction::MatchesSignature( const WasmModule* other_module, const wasm::FunctionSig* other_sig) { const wasm::FunctionSig* sig = this->sig(); if (sig->parameter_count() != other_sig->parameter_count() || sig->return_count() != other_sig->return_count()) { return false; } for (int i = 0; i < sig->all().size(); i++) { if (!wasm::EquivalentTypes(sig->all()[i], other_sig->all()[i], this->instance().module(), other_module)) { return false; } } return true; } // static std::unique_ptr<char[]> WasmExportedFunction::GetDebugName( const wasm::FunctionSig* sig) { constexpr const char kPrefix[] = "js-to-wasm:"; // prefix + parameters + delimiter + returns + zero byte size_t len = strlen(kPrefix) + sig->all().size() + 2; auto buffer = OwnedVector<char>::New(len); memcpy(buffer.start(), kPrefix, strlen(kPrefix)); PrintSignature(buffer.as_vector() + strlen(kPrefix), sig); return buffer.ReleaseData(); } // static bool WasmJSFunction::IsWasmJSFunction(Object object) { if (!object.IsJSFunction()) return false; JSFunction js_function = JSFunction::cast(object); return js_function.shared().HasWasmJSFunctionData(); } Handle<WasmJSFunction> WasmJSFunction::New(Isolate* isolate, const wasm::FunctionSig* sig, Handle<JSReceiver> callable) { DCHECK_LE(sig->all().size(), kMaxInt); int sig_size = static_cast<int>(sig->all().size()); int return_count = static_cast<int>(sig->return_count()); int parameter_count = static_cast<int>(sig->parameter_count()); Handle<PodArray<wasm::ValueType>> serialized_sig = PodArray<wasm::ValueType>::New(isolate, sig_size, AllocationType::kOld); if (sig_size > 0) { serialized_sig->copy_in(0, sig->all().begin(), sig_size); } // TODO(wasm): Think about caching and sharing the JS-to-JS wrappers per // signature instead of compiling a new one for every instantiation. Handle<Code> wrapper_code = compiler::CompileJSToJSWrapper(isolate, sig, nullptr).ToHandleChecked(); // WasmJSFunctions use on-heap Code objects as call targets, so we can't // cache the target address, unless the WasmJSFunction wraps a // WasmExportedFunction. Address call_target = kNullAddress; if (WasmExportedFunction::IsWasmExportedFunction(*callable)) { call_target = WasmExportedFunction::cast(*callable).GetWasmCallTarget(); } Factory* factory = isolate->factory(); Handle<WasmJSFunctionData> function_data = factory->NewWasmJSFunctionData( call_target, callable, return_count, parameter_count, serialized_sig, wrapper_code); if (wasm::WasmFeatures::FromIsolate(isolate).has_typed_funcref()) { using CK = compiler::WasmImportCallKind; int expected_arity = parameter_count; CK kind = compiler::kDefaultImportCallKind; if (callable->IsJSFunction()) { SharedFunctionInfo shared = Handle<JSFunction>::cast(callable)->shared(); expected_arity = shared.internal_formal_parameter_count(); if (expected_arity != parameter_count) { kind = CK::kJSFunctionArityMismatch; } } // TODO(wasm): Think about caching and sharing the wasm-to-JS wrappers per // signature instead of compiling a new one for every instantiation. Handle<Code> wasm_to_js_wrapper_code = compiler::CompileWasmToJSWrapper(isolate, sig, kind, expected_arity) .ToHandleChecked(); function_data->set_wasm_to_js_wrapper_code(*wasm_to_js_wrapper_code); } Handle<String> name = factory->Function_string(); if (callable->IsJSFunction()) { name = JSFunction::GetDebugName(Handle<JSFunction>::cast(callable)); name = String::Flatten(isolate, name); } Handle<Map> function_map = Map::Copy(isolate, isolate->wasm_exported_function_map(), "fresh function map for WasmJSFunction::New"); Handle<NativeContext> context(isolate->native_context()); Handle<SharedFunctionInfo> shared = factory->NewSharedFunctionInfoForWasmJSFunction(name, function_data); Handle<JSFunction> js_function = Factory::JSFunctionBuilder{isolate, shared, context} .set_map(function_map) .Build(); js_function->shared().set_internal_formal_parameter_count(parameter_count); return Handle<WasmJSFunction>::cast(js_function); } JSReceiver WasmJSFunction::GetCallable() const { return JSReceiver::cast( Tuple2::cast(shared().wasm_js_function_data().ref()).value2()); } const wasm::FunctionSig* WasmJSFunction::GetSignature(Zone* zone) { WasmJSFunctionData function_data = shared().wasm_js_function_data(); int sig_size = function_data.serialized_signature().length(); wasm::ValueType* types = zone->NewArray<wasm::ValueType>(sig_size); if (sig_size > 0) { function_data.serialized_signature().copy_out(0, types, sig_size); } int return_count = function_data.serialized_return_count(); int parameter_count = function_data.serialized_parameter_count(); return zone->New<wasm::FunctionSig>(return_count, parameter_count, types); } // TODO(9495): Update this if function type variance is introduced. bool WasmJSFunction::MatchesSignature(const wasm::FunctionSig* sig) { DCHECK_LE(sig->all().size(), kMaxInt); int sig_size = static_cast<int>(sig->all().size()); int return_count = static_cast<int>(sig->return_count()); int parameter_count = static_cast<int>(sig->parameter_count()); WasmJSFunctionData function_data = shared().wasm_js_function_data(); if (return_count != function_data.serialized_return_count() || parameter_count != function_data.serialized_parameter_count()) { return false; } if (sig_size == 0) return true; // Prevent undefined behavior. const wasm::ValueType* expected = sig->all().begin(); return function_data.serialized_signature().matches(expected, sig_size); } PodArray<wasm::ValueType> WasmCapiFunction::GetSerializedSignature() const { return shared().wasm_capi_function_data().serialized_signature(); } bool WasmExternalFunction::IsWasmExternalFunction(Object object) { return WasmExportedFunction::IsWasmExportedFunction(object) || WasmJSFunction::IsWasmJSFunction(object); } Handle<WasmExceptionTag> WasmExceptionTag::New(Isolate* isolate, int index) { Handle<WasmExceptionTag> result = Handle<WasmExceptionTag>::cast(isolate->factory()->NewStruct( WASM_EXCEPTION_TAG_TYPE, AllocationType::kOld)); result->set_index(index); return result; } Handle<AsmWasmData> AsmWasmData::New( Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module, Handle<FixedArray> export_wrappers, Handle<HeapNumber> uses_bitset) { const WasmModule* module = native_module->module(); const bool kUsesLiftoff = false; size_t memory_estimate = wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module, kUsesLiftoff) + wasm::WasmCodeManager::EstimateNativeModuleMetaDataSize(module); Handle<Managed<wasm::NativeModule>> managed_native_module = Managed<wasm::NativeModule>::FromSharedPtr(isolate, memory_estimate, std::move(native_module)); Handle<AsmWasmData> result = Handle<AsmWasmData>::cast( isolate->factory()->NewStruct(ASM_WASM_DATA_TYPE, AllocationType::kOld)); result->set_managed_native_module(*managed_native_module); result->set_export_wrappers(*export_wrappers); result->set_uses_bitset(*uses_bitset); return result; } static_assert(wasm::kV8MaxWasmArrayLength <= (Smi::kMaxValue - WasmArray::kHeaderSize) / kDoubleSize, "max Wasm array size must fit into max object size"); namespace wasm { bool TypecheckJSObject(Isolate* isolate, const WasmModule* module, Handle<Object> value, ValueType expected, const char** error_message) { DCHECK(expected.is_reference()); switch (expected.kind()) { case kOptRef: if (value->IsNull(isolate)) return true; V8_FALLTHROUGH; case kRef: switch (expected.heap_representation()) { case HeapType::kFunc: { if (!(WasmExternalFunction::IsWasmExternalFunction(*value) || WasmCapiFunction::IsWasmCapiFunction(*value))) { *error_message = "function-typed object must be null (if nullable) or a Wasm " "function object"; return false; } return true; } case HeapType::kExtern: case HeapType::kAny: return true; case HeapType::kData: case HeapType::kEq: case HeapType::kI31: { // TODO(7748): Change this when we have a decision on the JS API for // structs/arrays. if (!FLAG_wasm_gc_js_interop) { Handle<Name> key = isolate->factory()->wasm_wrapped_object_symbol(); LookupIterator it(isolate, value, key, LookupIterator::OWN_SKIP_INTERCEPTOR); if (it.state() != LookupIterator::DATA) { *error_message = "eqref/dataref/i31ref object must be null (if nullable) or " "wrapped with the wasm object wrapper"; return false; } value = it.GetDataValue(); } if (expected.is_reference_to(HeapType::kEq)) return true; if (expected.is_reference_to(HeapType::kData)) { if (value->IsSmi()) { *error_message = "dataref-typed object must be a heap object"; return false; } return true; } else { DCHECK(expected.is_reference_to(HeapType::kI31)); if (!value->IsSmi()) { *error_message = "i31ref-typed object cannot be a heap object"; return false; } return true; } } default: if (module == nullptr) { *error_message = "an object defined in JavaScript cannot be compatible with a " "type defined in a Webassembly module"; return false; } DCHECK(module->has_type(expected.ref_index())); if (module->has_signature(expected.ref_index())) { if (WasmExportedFunction::IsWasmExportedFunction(*value)) { WasmExportedFunction function = WasmExportedFunction::cast(*value); const WasmModule* exporting_module = function.instance().module(); ValueType real_type = ValueType::Ref( exporting_module->functions[function.function_index()] .sig_index, kNonNullable); if (!IsSubtypeOf(real_type, expected, exporting_module, module)) { *error_message = "assigned exported function has to be a subtype of the " "expected type"; return false; } return true; } if (WasmJSFunction::IsWasmJSFunction(*value)) { // Since a WasmJSFunction cannot refer to indexed types (definable // only in a module), we do not need full function subtyping. // TODO(manoskouk): Change this if wasm types can be exported. if (!WasmJSFunction::cast(*value).MatchesSignature( module->signature(expected.ref_index()))) { *error_message = "assigned WasmJSFunction has to be a subtype of the " "expected type"; return false; } return true; } if (WasmCapiFunction::IsWasmCapiFunction(*value)) { // Since a WasmCapiFunction cannot refer to indexed types // (definable only in a module), we do not need full function // subtyping. // TODO(manoskouk): Change this if wasm types can be exported. if (!WasmCapiFunction::cast(*value).MatchesSignature( module->signature(expected.ref_index()))) { *error_message = "assigned WasmCapiFunction has to be a subtype of the " "expected type"; return false; } return true; } *error_message = "function-typed object must be null (if nullable) or a Wasm " "function object"; return false; } // TODO(7748): Implement when the JS API for structs/arrays is decided // on. *error_message = "passing struct/array-typed objects between Webassembly and " "Javascript is not supported yet."; return false; } case kRtt: case kRttWithDepth: // TODO(7748): Implement when the JS API for rtts is decided on. *error_message = "passing rtts between Webassembly and Javascript is not supported " "yet."; return false; case kI8: case kI16: case kI32: case kI64: case kF32: case kF64: case kS128: case kVoid: case kBottom: UNREACHABLE(); } } } // namespace wasm } // namespace internal } // namespace v8 #undef TRACE_IFT