// 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/code-factory.h" #include "src/compiler/wasm-compiler.h" #include "src/counters.h" #include "src/debug/debug-interface.h" #include "src/objects-inl.h" #include "src/objects/debug-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/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-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<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) \ { \ auto v = value; \ this->field##_ = v; \ instance->set_##field(v); \ } // 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, reinterpret_cast<Address*>( calloc(num_imported_functions, sizeof(Address)))); SET(instance, imported_mutable_globals, reinterpret_cast<Address*>( calloc(num_imported_mutable_globals, sizeof(Address)))); SET(instance, data_segment_starts, reinterpret_cast<Address*>(calloc(num_data_segments, sizeof(Address)))); SET(instance, data_segment_sizes, reinterpret_cast<uint32_t*>( calloc(num_data_segments, sizeof(uint32_t)))); SET(instance, dropped_data_segments, reinterpret_cast<uint8_t*>(calloc(num_data_segments, sizeof(uint8_t)))); SET(instance, dropped_elem_segments, reinterpret_cast<uint8_t*>(calloc(num_elem_segments, sizeof(uint8_t)))); } ~WasmInstanceNativeAllocations() { ::free(indirect_function_table_sig_ids_); indirect_function_table_sig_ids_ = nullptr; ::free(indirect_function_table_targets_); indirect_function_table_targets_ = nullptr; ::free(imported_function_targets_); imported_function_targets_ = nullptr; ::free(imported_mutable_globals_); imported_mutable_globals_ = nullptr; ::free(data_segment_starts_); data_segment_starts_ = nullptr; ::free(data_segment_sizes_); data_segment_sizes_ = nullptr; ::free(dropped_data_segments_); dropped_data_segments_ = nullptr; ::free(dropped_elem_segments_); dropped_elem_segments_ = nullptr; } // Resizes the indirect function table. void resize_indirect_function_table(Isolate* isolate, Handle<WasmInstanceObject> instance, uint32_t new_size) { uint32_t old_size = instance->indirect_function_table_size(); void* new_sig_ids = nullptr; void* new_targets = nullptr; Handle<FixedArray> new_refs; 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<FixedArray> old(instance->indirect_function_table_refs(), isolate); new_refs = isolate->factory()->CopyFixedArrayAndGrow( old, static_cast<int>(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_refs = isolate->factory()->NewFixedArray(static_cast<int>(new_size)); } // Initialize new entries. instance->set_indirect_function_table_size(new_size); SET(instance, indirect_function_table_sig_ids, reinterpret_cast<uint32_t*>(new_sig_ids)); SET(instance, indirect_function_table_targets, reinterpret_cast<Address*>(new_targets)); instance->set_indirect_function_table_refs(*new_refs); for (uint32_t j = old_size; j < new_size; j++) { IndirectFunctionTableEntry(instance, static_cast<int>(j)).clear(); } } uint32_t* indirect_function_table_sig_ids_ = nullptr; Address* indirect_function_table_targets_ = nullptr; Address* imported_function_targets_ = nullptr; Address* imported_mutable_globals_ = nullptr; Address* data_segment_starts_ = nullptr; uint32_t* data_segment_sizes_ = nullptr; uint8_t* dropped_data_segments_ = nullptr; uint8_t* dropped_elem_segments_ = nullptr; #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(); } #ifdef DEBUG bool IsBreakablePosition(wasm::NativeModule* native_module, int func_index, int offset_in_func) { AccountingAllocator alloc; Zone tmp(&alloc, ZONE_NAME); wasm::BodyLocalDecls locals(&tmp); const byte* module_start = native_module->wire_bytes().begin(); const WasmFunction& func = native_module->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<uint32_t>(offset_in_func)) break; if (offset == static_cast<uint32_t>(offset_in_func)) return true; } return false; } #endif // DEBUG enum DispatchTableElements : int { kDispatchTableInstanceOffset, kDispatchTableIndexOffset, kDispatchTableFunctionTableOffset, // Marker: kDispatchTableNumElements }; } // namespace // static Handle<WasmModuleObject> WasmModuleObject::New( Isolate* isolate, const wasm::WasmFeatures& enabled, std::shared_ptr<const wasm::WasmModule> shared_module, OwnedVector<const uint8_t> wire_bytes, Handle<Script> script, Handle<ByteArray> asm_js_offset_table) { // Create a new {NativeModule} first. size_t code_size_estimate = wasm::WasmCodeManager::EstimateNativeModuleCodeSize(shared_module.get()); auto native_module = isolate->wasm_engine()->NewNativeModule( isolate, enabled, code_size_estimate, wasm::NativeModule::kCanAllocateMoreMemory, std::move(shared_module)); native_module->SetWireBytes(std::move(wire_bytes)); native_module->SetRuntimeStubs(isolate); // Delegate to the shared {WasmModuleObject::New} allocator. Handle<WasmModuleObject> module_object = New(isolate, std::move(native_module), script, code_size_estimate); if (!asm_js_offset_table.is_null()) { module_object->set_asm_js_offset_table(*asm_js_offset_table); } return module_object; } // static Handle<WasmModuleObject> WasmModuleObject::New( Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module, Handle<Script> script, size_t code_size_estimate) { const WasmModule* module = native_module->module(); int export_wrapper_size = static_cast<int>(module->num_exported_functions); Handle<FixedArray> export_wrappers = isolate->factory()->NewFixedArray( export_wrapper_size, AllocationType::kOld); return New(isolate, std::move(native_module), script, export_wrappers, code_size_estimate); } // static Handle<WasmModuleObject> WasmModuleObject::New( Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module, Handle<Script> script, Handle<FixedArray> export_wrappers, size_t code_size_estimate) { const WasmModule* module = native_module->module(); // Use the given shared {NativeModule}, but increase its reference count by // allocating a new {Managed<T>} that the {WasmModuleObject} references. size_t memory_estimate = code_size_estimate + wasm::WasmCodeManager::EstimateNativeModuleNonCodeSize(module); Handle<Managed<wasm::NativeModule>> 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); if (script->type() == Script::TYPE_WASM) { script->set_wasm_module_object(*module_object); } module_object->set_script(*script); module_object->set_weak_instance_list( ReadOnlyRoots(isolate).empty_weak_array_list()); module_object->set_managed_native_module(*managed_native_module); return module_object; } bool WasmModuleObject::SetBreakPoint(Handle<WasmModuleObject> module_object, int* position, Handle<BreakPoint> break_point) { Isolate* isolate = module_object->GetIsolate(); // Find the function for this breakpoint. int func_index = module_object->GetContainingFunction(*position); if (func_index < 0) return false; const WasmFunction& func = module_object->module()->functions[func_index]; int offset_in_func = *position - func.code.offset(); // According to the current design, we should only be called with valid // breakable positions. DCHECK(IsBreakablePosition(module_object->native_module(), func_index, offset_in_func)); // Insert new break point into break_positions of module object. WasmModuleObject::AddBreakpoint(module_object, *position, break_point); // Iterate over all instances of this module and tell them to set this new // breakpoint. We do this using the weak list of all instances. Handle<WeakArrayList> weak_instance_list(module_object->weak_instance_list(), isolate); for (int i = 0; i < weak_instance_list->length(); ++i) { MaybeObject maybe_instance = weak_instance_list->Get(i); if (maybe_instance->IsWeak()) { Handle<WasmInstanceObject> instance( WasmInstanceObject::cast(maybe_instance->GetHeapObjectAssumeWeak()), isolate); Handle<WasmDebugInfo> debug_info = WasmInstanceObject::GetOrCreateDebugInfo(instance); WasmDebugInfo::SetBreakpoint(debug_info, func_index, offset_in_func); } } return true; } namespace { int GetBreakpointPos(Isolate* isolate, Object break_point_info_or_undef) { if (break_point_info_or_undef->IsUndefined(isolate)) return kMaxInt; return BreakPointInfo::cast(break_point_info_or_undef)->source_position(); } int FindBreakpointInfoInsertPos(Isolate* isolate, Handle<FixedArray> breakpoint_infos, int position) { // Find insert location via binary search, taking care of undefined values on // the right. Position is always greater than zero. DCHECK_LT(0, position); int left = 0; // inclusive int right = breakpoint_infos->length(); // exclusive while (right - left > 1) { int mid = left + (right - left) / 2; Object mid_obj = breakpoint_infos->get(mid); if (GetBreakpointPos(isolate, mid_obj) <= position) { left = mid; } else { right = mid; } } int left_pos = GetBreakpointPos(isolate, breakpoint_infos->get(left)); return left_pos < position ? left + 1 : left; } } // namespace void WasmModuleObject::AddBreakpoint(Handle<WasmModuleObject> module_object, int position, Handle<BreakPoint> break_point) { Isolate* isolate = module_object->GetIsolate(); Handle<FixedArray> breakpoint_infos; if (module_object->has_breakpoint_infos()) { breakpoint_infos = handle(module_object->breakpoint_infos(), isolate); } else { breakpoint_infos = isolate->factory()->NewFixedArray(4, AllocationType::kOld); module_object->set_breakpoint_infos(*breakpoint_infos); } int insert_pos = FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position); // If a BreakPointInfo object already exists for this position, add the new // breakpoint object and return. if (insert_pos < breakpoint_infos->length() && GetBreakpointPos(isolate, breakpoint_infos->get(insert_pos)) == position) { Handle<BreakPointInfo> old_info( BreakPointInfo::cast(breakpoint_infos->get(insert_pos)), isolate); BreakPointInfo::SetBreakPoint(isolate, old_info, break_point); return; } // Enlarge break positions array if necessary. bool need_realloc = !breakpoint_infos->get(breakpoint_infos->length() - 1) ->IsUndefined(isolate); Handle<FixedArray> new_breakpoint_infos = breakpoint_infos; if (need_realloc) { new_breakpoint_infos = isolate->factory()->NewFixedArray( 2 * breakpoint_infos->length(), AllocationType::kOld); module_object->set_breakpoint_infos(*new_breakpoint_infos); // Copy over the entries [0, insert_pos). for (int i = 0; i < insert_pos; ++i) new_breakpoint_infos->set(i, breakpoint_infos->get(i)); } // Move elements [insert_pos, ...] up by one. for (int i = breakpoint_infos->length() - 1; i >= insert_pos; --i) { Object entry = breakpoint_infos->get(i); if (entry->IsUndefined(isolate)) continue; new_breakpoint_infos->set(i + 1, entry); } // Generate new BreakpointInfo. Handle<BreakPointInfo> breakpoint_info = isolate->factory()->NewBreakPointInfo(position); BreakPointInfo::SetBreakPoint(isolate, breakpoint_info, break_point); // Now insert new position at insert_pos. new_breakpoint_infos->set(insert_pos, *breakpoint_info); } void WasmModuleObject::SetBreakpointsOnNewInstance( Handle<WasmModuleObject> module_object, Handle<WasmInstanceObject> instance) { if (!module_object->has_breakpoint_infos()) return; Isolate* isolate = module_object->GetIsolate(); Handle<WasmDebugInfo> debug_info = WasmInstanceObject::GetOrCreateDebugInfo(instance); Handle<FixedArray> breakpoint_infos(module_object->breakpoint_infos(), isolate); // If the array exists, it should not be empty. DCHECK_LT(0, breakpoint_infos->length()); for (int i = 0, e = breakpoint_infos->length(); i < e; ++i) { Handle<Object> obj(breakpoint_infos->get(i), isolate); if (obj->IsUndefined(isolate)) { for (; i < e; ++i) { DCHECK(breakpoint_infos->get(i)->IsUndefined(isolate)); } break; } Handle<BreakPointInfo> breakpoint_info = Handle<BreakPointInfo>::cast(obj); int position = breakpoint_info->source_position(); // Find the function for this breakpoint, and set the breakpoint. int func_index = module_object->GetContainingFunction(position); DCHECK_LE(0, func_index); const WasmFunction& func = module_object->module()->functions[func_index]; int offset_in_func = position - func.code.offset(); WasmDebugInfo::SetBreakpoint(debug_info, func_index, offset_in_func); } } namespace { enum AsmJsOffsetTableEntryLayout { kOTEByteOffset, kOTECallPosition, kOTENumberConvPosition, kOTESize }; Handle<ByteArray> GetDecodedAsmJsOffsetTable( Handle<WasmModuleObject> module_object, Isolate* isolate) { DCHECK(module_object->is_asm_js()); Handle<ByteArray> offset_table(module_object->asm_js_offset_table(), isolate); // The last byte in the asm_js_offset_tables ByteArray tells whether it is // still encoded (0) or decoded (1). enum AsmJsTableType : int { Encoded = 0, Decoded = 1 }; int table_type = offset_table->get(offset_table->length() - 1); DCHECK(table_type == Encoded || table_type == Decoded); if (table_type == Decoded) return offset_table; wasm::AsmJsOffsets asm_offsets; { DisallowHeapAllocation no_gc; byte* bytes_start = offset_table->GetDataStartAddress(); byte* bytes_end = reinterpret_cast<byte*>( reinterpret_cast<Address>(bytes_start) + offset_table->length() - 1); asm_offsets = wasm::DecodeAsmJsOffsets(bytes_start, bytes_end).value(); } // Wasm bytes must be valid and must contain asm.js offset table. DCHECK_GE(kMaxInt, asm_offsets.size()); int num_functions = static_cast<int>(asm_offsets.size()); int num_imported_functions = static_cast<int>(module_object->module()->num_imported_functions); DCHECK_EQ(module_object->module()->functions.size(), static_cast<size_t>(num_functions) + num_imported_functions); int num_entries = 0; for (int func = 0; func < num_functions; ++func) { size_t new_size = asm_offsets[func].size(); DCHECK_LE(new_size, static_cast<size_t>(kMaxInt) - num_entries); num_entries += static_cast<int>(new_size); } // One byte to encode that this is a decoded table. DCHECK_GE(kMaxInt, 1 + static_cast<uint64_t>(num_entries) * kOTESize * kIntSize); int total_size = 1 + num_entries * kOTESize * kIntSize; Handle<ByteArray> decoded_table = isolate->factory()->NewByteArray(total_size, AllocationType::kOld); decoded_table->set(total_size - 1, AsmJsTableType::Decoded); module_object->set_asm_js_offset_table(*decoded_table); int idx = 0; const std::vector<WasmFunction>& wasm_funs = module_object->module()->functions; for (int func = 0; func < num_functions; ++func) { std::vector<wasm::AsmJsOffsetEntry>& func_asm_offsets = asm_offsets[func]; if (func_asm_offsets.empty()) continue; int func_offset = wasm_funs[num_imported_functions + func].code.offset(); for (wasm::AsmJsOffsetEntry& e : func_asm_offsets) { // Byte offsets must be strictly monotonously increasing: DCHECK_IMPLIES(idx > 0, func_offset + e.byte_offset > decoded_table->get_int(idx - kOTESize)); decoded_table->set_int(idx + kOTEByteOffset, func_offset + e.byte_offset); decoded_table->set_int(idx + kOTECallPosition, e.source_position_call); decoded_table->set_int(idx + kOTENumberConvPosition, e.source_position_number_conversion); idx += kOTESize; } } DCHECK_EQ(total_size, idx * kIntSize + 1); return decoded_table; } } // namespace int WasmModuleObject::GetSourcePosition(Handle<WasmModuleObject> module_object, uint32_t func_index, uint32_t byte_offset, bool is_at_number_conversion) { Isolate* isolate = module_object->GetIsolate(); const WasmModule* module = module_object->module(); if (module->origin != wasm::kAsmJsOrigin) { // for non-asm.js modules, we just add the function's start offset // to make a module-relative position. return byte_offset + module_object->GetFunctionOffset(func_index); } // asm.js modules have an additional offset table that must be searched. Handle<ByteArray> offset_table = GetDecodedAsmJsOffsetTable(module_object, isolate); DCHECK_LT(func_index, module->functions.size()); uint32_t func_code_offset = module->functions[func_index].code.offset(); uint32_t total_offset = func_code_offset + byte_offset; // Binary search for the total byte offset. int left = 0; // inclusive int right = offset_table->length() / kIntSize / kOTESize; // exclusive DCHECK_LT(left, right); while (right - left > 1) { int mid = left + (right - left) / 2; int mid_entry = offset_table->get_int(kOTESize * mid); DCHECK_GE(kMaxInt, mid_entry); if (static_cast<uint32_t>(mid_entry) <= total_offset) { left = mid; } else { right = mid; } } // There should be an entry for each position that could show up on the stack // trace: DCHECK_EQ(total_offset, offset_table->get_int(kOTESize * left)); int idx = is_at_number_conversion ? kOTENumberConvPosition : kOTECallPosition; return offset_table->get_int(kOTESize * left + idx); } v8::debug::WasmDisassembly WasmModuleObject::DisassembleFunction( int func_index) { DisallowHeapAllocation no_gc; if (func_index < 0 || static_cast<uint32_t>(func_index) >= module()->functions.size()) return {}; wasm::ModuleWireBytes wire_bytes(native_module()->wire_bytes()); std::ostringstream disassembly_os; v8::debug::WasmDisassembly::OffsetTable offset_table; PrintWasmText(module(), wire_bytes, static_cast<uint32_t>(func_index), disassembly_os, &offset_table); return {disassembly_os.str(), std::move(offset_table)}; } bool WasmModuleObject::GetPossibleBreakpoints( const v8::debug::Location& start, const v8::debug::Location& end, std::vector<v8::debug::BreakLocation>* locations) { DisallowHeapAllocation no_gc; const std::vector<WasmFunction>& functions = module()->functions; if (start.GetLineNumber() < 0 || start.GetColumnNumber() < 0 || (!end.IsEmpty() && (end.GetLineNumber() < 0 || end.GetColumnNumber() < 0))) return false; // start_func_index, start_offset and end_func_index is inclusive. // end_offset is exclusive. // start_offset and end_offset are module-relative byte offsets. uint32_t start_func_index = start.GetLineNumber(); if (start_func_index >= functions.size()) return false; int start_func_len = functions[start_func_index].code.length(); if (start.GetColumnNumber() > start_func_len) return false; uint32_t start_offset = functions[start_func_index].code.offset() + start.GetColumnNumber(); uint32_t end_func_index; uint32_t end_offset; if (end.IsEmpty()) { // Default: everything till the end of the Script. end_func_index = static_cast<uint32_t>(functions.size() - 1); end_offset = functions[end_func_index].code.end_offset(); } else { // If end is specified: Use it and check for valid input. end_func_index = static_cast<uint32_t>(end.GetLineNumber()); // Special case: Stop before the start of the next function. Change to: Stop // at the end of the function before, such that we don't disassemble the // next function also. if (end.GetColumnNumber() == 0 && end_func_index > 0) { --end_func_index; end_offset = functions[end_func_index].code.end_offset(); } else { if (end_func_index >= functions.size()) return false; end_offset = functions[end_func_index].code.offset() + end.GetColumnNumber(); if (end_offset > functions[end_func_index].code.end_offset()) return false; } } AccountingAllocator alloc; Zone tmp(&alloc, ZONE_NAME); const byte* module_start = native_module()->wire_bytes().begin(); for (uint32_t func_idx = start_func_index; func_idx <= end_func_index; ++func_idx) { const WasmFunction& func = functions[func_idx]; if (func.code.length() == 0) continue; wasm::BodyLocalDecls locals(&tmp); wasm::BytecodeIterator iterator(module_start + func.code.offset(), module_start + func.code.end_offset(), &locals); DCHECK_LT(0u, locals.encoded_size); for (uint32_t offset : iterator.offsets()) { uint32_t total_offset = func.code.offset() + offset; if (total_offset >= end_offset) { DCHECK_EQ(end_func_index, func_idx); break; } if (total_offset < start_offset) continue; locations->emplace_back(func_idx, offset, debug::kCommonBreakLocation); } } return true; } MaybeHandle<FixedArray> WasmModuleObject::CheckBreakPoints( Isolate* isolate, Handle<WasmModuleObject> module_object, int position) { if (!module_object->has_breakpoint_infos()) return {}; Handle<FixedArray> breakpoint_infos(module_object->breakpoint_infos(), isolate); int insert_pos = FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position); if (insert_pos >= breakpoint_infos->length()) return {}; Handle<Object> maybe_breakpoint_info(breakpoint_infos->get(insert_pos), isolate); if (maybe_breakpoint_info->IsUndefined(isolate)) return {}; Handle<BreakPointInfo> breakpoint_info = Handle<BreakPointInfo>::cast(maybe_breakpoint_info); if (breakpoint_info->source_position() != position) return {}; // There is no support for conditional break points. Just assume that every // break point always hits. Handle<Object> break_points(breakpoint_info->break_points(), isolate); if (break_points->IsFixedArray()) { return Handle<FixedArray>::cast(break_points); } Handle<FixedArray> break_points_hit = isolate->factory()->NewFixedArray(1); break_points_hit->set(0, *break_points); return break_points_hit; } MaybeHandle<String> WasmModuleObject::ExtractUtf8StringFromModuleBytes( Isolate* isolate, Handle<WasmModuleObject> module_object, wasm::WireBytesRef ref) { // TODO(wasm): cache strings from modules if it's a performance win. Vector<const uint8_t> wire_bytes = module_object->native_module()->wire_bytes(); return ExtractUtf8StringFromModuleBytes(isolate, wire_bytes, ref); } MaybeHandle<String> WasmModuleObject::ExtractUtf8StringFromModuleBytes( Isolate* isolate, Vector<const uint8_t> wire_bytes, wasm::WireBytesRef ref) { Vector<const uint8_t> name_vec = wire_bytes + ref.offset(); name_vec.Truncate(ref.length()); // UTF8 validation happens at decode time. DCHECK(unibrow::Utf8::ValidateEncoding(name_vec.begin(), name_vec.length())); return isolate->factory()->NewStringFromUtf8( Vector<const char>::cast(name_vec)); } 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); } 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()->LookupFunctionName( wasm::ModuleWireBytes(module_object->native_module()->wire_bytes()), func_index); if (!name.is_set()) return {}; return ExtractUtf8StringFromModuleBytes(isolate, module_object, name); } Handle<String> WasmModuleObject::GetFunctionName( Isolate* isolate, Handle<WasmModuleObject> module_object, uint32_t func_index) { MaybeHandle<String> name = GetFunctionNameOrNull(isolate, module_object, func_index); if (!name.is_null()) return name.ToHandleChecked(); EmbeddedVector<char, 32> buffer; int length = SNPrintF(buffer, "wasm-function[%u]", func_index); return isolate->factory() ->NewStringFromOneByte(Vector<uint8_t>::cast(buffer.SubVector(0, length))) .ToHandleChecked(); } Vector<const uint8_t> WasmModuleObject::GetRawFunctionName( uint32_t func_index) { DCHECK_GT(module()->functions.size(), func_index); wasm::ModuleWireBytes wire_bytes(native_module()->wire_bytes()); wasm::WireBytesRef name_ref = module()->LookupFunctionName(wire_bytes, func_index); wasm::WasmName name = wire_bytes.GetNameOrNull(name_ref); return Vector<const uint8_t>::cast(name); } int WasmModuleObject::GetFunctionOffset(uint32_t func_index) { const std::vector<WasmFunction>& functions = module()->functions; if (static_cast<uint32_t>(func_index) >= functions.size()) return -1; DCHECK_GE(kMaxInt, functions[func_index].code.offset()); return static_cast<int>(functions[func_index].code.offset()); } int WasmModuleObject::GetContainingFunction(uint32_t byte_offset) { const std::vector<WasmFunction>& functions = module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast<int>(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code.offset() <= byte_offset) { left = mid; } else { right = mid; } } // If the found function does not contains the given position, return -1. const WasmFunction& func = functions[left]; if (byte_offset < func.code.offset() || byte_offset >= func.code.end_offset()) { return -1; } return left; } bool WasmModuleObject::GetPositionInfo(uint32_t position, Script::PositionInfo* info) { if (script()->source_mapping_url()->IsString()) { if (module()->functions.size() == 0) return false; info->line = 0; info->column = position; info->line_start = module()->functions[0].code.offset(); info->line_end = module()->functions.back().code.end_offset(); return true; } int func_index = GetContainingFunction(position); if (func_index < 0) return false; const WasmFunction& function = module()->functions[func_index]; info->line = func_index; info->column = position - function.code.offset(); info->line_start = function.code.offset(); info->line_end = function.code.end_offset(); return true; } Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, wasm::ValueType type, uint32_t initial, bool has_maximum, uint32_t maximum, Handle<FixedArray>* entries) { 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<JSFunction> table_ctor( isolate->native_context()->wasm_table_constructor(), isolate); auto table_obj = Handle<WasmTableObject>::cast( isolate->factory()->NewJSObject(table_ctor)); table_obj->set_raw_type(static_cast<int>(type)); table_obj->set_entries(*backing_store); Handle<Object> max; if (has_maximum) { max = isolate->factory()->NewNumberFromUint(maximum); } else { max = isolate->factory()->undefined_value(); } table_obj->set_maximum_length(*max); 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; } DCHECK_LE(old_size, max_size); if (max_size - old_size < count) return -1; uint32_t new_size = old_size + count; auto new_store = isolate->factory()->CopyFixedArrayAndGrow( handle(table->entries(), isolate), count); table->set_entries(*new_store, WriteBarrierMode::UPDATE_WRITE_BARRIER); 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(); if (table_index > 0) { continue; } // For Table 0 we have to update the indirect function table. Handle<WasmInstanceObject> instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); DCHECK_EQ(old_size, instance->indirect_function_table_size()); WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(instance, 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>(std::numeric_limits<int>::max()) && static_cast<int>(entry_index) < table->entries()->length()); } bool WasmTableObject::IsValidElement(Isolate* isolate, Handle<WasmTableObject> table, Handle<Object> entry) { // Anyref tables take everything. if (table->type() == wasm::kWasmAnyRef) return true; // Anyfunc tables can store {null} or {WasmExportedFunction} or // {WasmCapiFunction} objects. if (entry->IsNull(isolate)) return true; return WasmExportedFunction::IsWasmExportedFunction(*entry) || WasmCapiFunction::IsWasmCapiFunction(*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); if (table->type() == wasm::kWasmAnyRef) { entries->set(entry_index, *entry); return; } 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 { DCHECK(WasmCapiFunction::IsWasmCapiFunction(*entry)); UpdateDispatchTables(isolate, table, entry_index, Handle<WasmCapiFunction>::cast(entry)); } entries->set(entry_index, *entry); } 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); // First we handle the easy anyref table case. if (table->type() == wasm::kWasmAnyRef) return entry; // Now we handle the anyfunc case. if (WasmExportedFunction::IsWasmExportedFunction(*entry) || WasmCapiFunction::IsWasmCapiFunction(*entry)) { return entry; } if (entry->IsNull(isolate)) { return entry; } // {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::GetOrCreateWasmExportedFunction(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_LT(start, table->entries()->length()); DCHECK_LE(count, table->entries()->length()); DCHECK_LE(start + count, table->entries()->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, 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(); if (table_index > 0) { // Only table 0 has a dispatch table in the instance at the moment. // TODO(ahaas): Introduce dispatch tables for the other tables as well. continue; } 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, entry_index) .Set(sig_id, target_instance, target_func_index); } } 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::kWasmStmt; 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(); if (table_index > 0) { // Only table 0 has a dispatch table in the instance at the moment. // TODO(ahaas): Introduce dispatch tables for the other tables as well. continue; } Handle<WasmInstanceObject> instance( WasmInstanceObject::cast( dispatch_tables->get(i + kDispatchTableInstanceOffset)), isolate); // TODO(jkummerow): Find a way to avoid recompiling wrappers. wasm::NativeModule* native_module = instance->module_object()->native_module(); Address host_address = capi_function->GetHostCallTarget(); wasm::WasmCodeRefScope code_ref_scope; wasm::WasmCode* wasm_code = compiler::CompileWasmCapiCallWrapper( isolate->wasm_engine(), native_module, &sig, host_address); isolate->counters()->wasm_generated_code_size()->Increment( wasm_code->instructions().length()); isolate->counters()->wasm_reloc_size()->Increment( wasm_code->reloc_info().length()); 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, 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(); if (table_index > 0) { // Only table 0 has a dispatch table in the instance at the moment. continue; } Handle<WasmInstanceObject> target_instance( WasmInstanceObject::cast( dispatch_tables->get(i + kDispatchTableInstanceOffset)), isolate); DCHECK_LT(index, target_instance->indirect_function_table_size()); IndirectFunctionTableEntry(target_instance, 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, Handle<WasmTableObject> table, int entry_index, bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance, int* function_index) { DCHECK_EQ(table->type(), wasm::kWasmAnyFunc); DCHECK_LT(entry_index, table->entries()->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(); return; } else if (element->IsTuple2()) { auto tuple = Handle<Tuple2>::cast(element); *instance = handle(WasmInstanceObject::cast(tuple->value1()), isolate); *function_index = Smi::cast(tuple->value2()).value(); return; } *is_valid = false; } namespace { bool AdjustBufferPermissions(Isolate* isolate, Handle<JSArrayBuffer> old_buffer, size_t new_size) { if (new_size > old_buffer->allocation_length()) return false; void* old_mem_start = old_buffer->backing_store(); size_t old_size = old_buffer->byte_length(); if (old_size != new_size) { DCHECK_NOT_NULL(old_mem_start); DCHECK_GE(new_size, old_size); // If adjusting permissions fails, propagate error back to return // failure to grow. if (!i::SetPermissions(GetPlatformPageAllocator(), old_mem_start, new_size, PageAllocator::kReadWrite)) { return false; } reinterpret_cast<v8::Isolate*>(isolate) ->AdjustAmountOfExternalAllocatedMemory(new_size - old_size); } return true; } MaybeHandle<JSArrayBuffer> MemoryGrowBuffer(Isolate* isolate, Handle<JSArrayBuffer> old_buffer, size_t new_size) { CHECK_EQ(0, new_size % wasm::kWasmPageSize); // 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() || new_size > old_buffer->allocation_length()) { // We couldn't reuse the old backing store, so create a new one and copy the // old contents in. Handle<JSArrayBuffer> new_buffer; if (!wasm::NewArrayBuffer(isolate, new_size).ToHandle(&new_buffer)) { return {}; } void* old_mem_start = old_buffer->backing_store(); size_t old_size = old_buffer->byte_length(); if (old_size == 0) return new_buffer; memcpy(new_buffer->backing_store(), 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; } else { if (!AdjustBufferPermissions(isolate, old_buffer, new_size)) return {}; // 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<JSArrayBuffer> new_buffer = wasm::SetupArrayBuffer(isolate, backing_store, new_size, is_external); return new_buffer; } } // May GC, because SetSpecializationMemInfoFrom may GC void SetInstanceMemory(Handle<WasmInstanceObject> instance, Handle<JSArrayBuffer> buffer) { 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, uint32_t maximum) { Handle<JSArrayBuffer> buffer; if (!maybe_buffer.ToHandle(&buffer)) { // If no buffer was provided, create a 0-length one. buffer = wasm::SetupArrayBuffer(isolate, nullptr, 0, false); } // 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<JSFunction> memory_ctor( isolate->native_context()->wasm_memory_constructor(), isolate); auto memory_obj = Handle<WasmMemoryObject>::cast( isolate->factory()->NewJSObject(memory_ctor, AllocationType::kOld)); memory_obj->set_array_buffer(*buffer); memory_obj->set_maximum_pages(maximum); return memory_obj; } MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(Isolate* isolate, uint32_t initial, uint32_t maximum, bool is_shared_memory) { Handle<JSArrayBuffer> buffer; size_t size = static_cast<size_t>(i::wasm::kWasmPageSize) * static_cast<size_t>(initial); if (is_shared_memory) { size_t max_size = static_cast<size_t>(i::wasm::kWasmPageSize) * static_cast<size_t>(maximum); if (!i::wasm::NewSharedArrayBuffer(isolate, size, max_size) .ToHandle(&buffer)) { return {}; } } else { if (!i::wasm::NewArrayBuffer(isolate, size).ToHandle(&buffer)) { return {}; } } 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::AddToEnd( 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(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "GrowMemory"); Handle<JSArrayBuffer> old_buffer(memory_object->array_buffer(), isolate); if (old_buffer->is_shared() && !FLAG_wasm_grow_shared_memory) return -1; auto* memory_tracker = isolate->wasm_engine()->memory_tracker(); if (!memory_tracker->IsWasmMemoryGrowable(old_buffer)) return -1; // Checks for maximum memory size, compute new size. uint32_t maximum_pages = wasm::max_mem_pages(); if (memory_object->has_maximum_pages()) { maximum_pages = std::min( maximum_pages, static_cast<uint32_t>(memory_object->maximum_pages())); } CHECK_GE(wasm::max_mem_pages(), maximum_pages); size_t old_size = old_buffer->byte_length(); CHECK_EQ(0, old_size % wasm::kWasmPageSize); size_t old_pages = old_size / wasm::kWasmPageSize; CHECK_GE(wasm::max_mem_pages(), old_pages); if ((pages > maximum_pages - old_pages) || // exceeds remaining (pages > wasm::max_mem_pages() - old_pages)) { // exceeds limit return -1; } size_t new_size = static_cast<size_t>(old_pages + pages) * wasm::kWasmPageSize; // Memory is grown, but the memory objects and instances are not yet updated. // Handle this in the interrupt handler so that it's safe for all the isolates // that share this buffer to be updated safely. Handle<JSArrayBuffer> new_buffer; if (old_buffer->is_shared()) { // Adjust protections for the buffer. if (!AdjustBufferPermissions(isolate, old_buffer, new_size)) { return -1; } void* backing_store = old_buffer->backing_store(); if (memory_tracker->IsWasmSharedMemory(backing_store)) { // This memory is shared between different isolates. DCHECK(old_buffer->is_shared()); // Update pending grow state, and trigger a grow interrupt on all the // isolates that share this buffer. memory_tracker->SetPendingUpdateOnGrow(old_buffer, new_size); // Handle interrupts for this isolate so that the instances with this // isolate are updated. isolate->stack_guard()->HandleInterrupts(); // Failure to allocate, or adjust pemissions already handled here, and // updates to instances handled in the interrupt handler safe to return. return static_cast<uint32_t>(old_size / wasm::kWasmPageSize); } // SharedArrayBuffer, but not shared across isolates. Setup a new buffer // with updated permissions and update the instances. new_buffer = wasm::SetupArrayBuffer(isolate, backing_store, new_size, old_buffer->is_external(), SharedFlag::kShared); memory_object->update_instances(isolate, new_buffer); } else { if (!MemoryGrowBuffer(isolate, old_buffer, new_size) .ToHandle(&new_buffer)) { return -1; } } // Update instances if any. memory_object->update_instances(isolate, new_buffer); return static_cast<uint32_t>(old_size / wasm::kWasmPageSize); } // static MaybeHandle<WasmGlobalObject> WasmGlobalObject::New( Isolate* isolate, 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)); if (wasm::ValueTypes::IsReferenceType(type)) { 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()); Handle<JSArrayBuffer> untagged_buffer; uint32_t type_size = wasm::ValueTypes::ElementSizeInBytes(type); if (!maybe_untagged_buffer.ToHandle(&untagged_buffer)) { // If no buffer was provided, create one long enough for the given type. untagged_buffer = isolate->factory()->NewJSArrayBuffer( SharedFlag::kNotShared, AllocationType::kOld); const bool initialize = true; if (!JSArrayBuffer::SetupAllocatingData(untagged_buffer, isolate, type_size, initialize)) { return {}; } } // Check that the offset is in bounds. CHECK_LE(offset + type_size, untagged_buffer->byte_length()); global_obj->set_untagged_buffer(*untagged_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_refs()->set( index_, ReadOnlyRoots(instance_->GetIsolate()).undefined_value()); } void IndirectFunctionTableEntry::Set(int sig_id, Handle<WasmInstanceObject> target_instance, int target_func_index) { TRACE_IFT( "IFT entry %p[%d] = {sig_id=%d, target_instance=%p, " "target_func_index=%d}\n", reinterpret_cast<void*>(instance_->ptr()), index_, sig_id, reinterpret_cast<void*>(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) { 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); } Object IndirectFunctionTableEntry::object_ref() { return instance_->indirect_function_table_refs()->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 IndirectFunctionTableEntry::CopyFrom( const IndirectFunctionTableEntry& that) { instance_->indirect_function_table_sig_ids()[index_] = that.instance_->indirect_function_table_sig_ids()[that.index_]; instance_->indirect_function_table_targets()[index_] = that.instance_->indirect_function_table_targets()[that.index_]; instance_->indirect_function_table_refs()->set( index_, that.instance_->indirect_function_table_refs()->get(that.index_)); } void ImportedFunctionEntry::SetWasmToJs( Isolate* isolate, Handle<JSReceiver> callable, const wasm::WasmCode* wasm_to_js_wrapper) { TRACE_IFT("Import callable %p[%d] = {callable=%p, target=%p}\n", reinterpret_cast<void*>(instance_->ptr()), index_, reinterpret_cast<void*>(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 %p[%d] = {instance=%p, target=%" PRIuPTR "}\n", reinterpret_cast<void*>(instance_->ptr()), index_, reinterpret_cast<void*>(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 = instance_->imported_function_refs()->get(index_); if (value->IsWasmInstanceObject()) { return WasmInstanceObject::cast(value); } Tuple2 tuple = Tuple2::cast(value); return WasmInstanceObject::cast(tuple->value1()); } 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, 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, 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<WasmDebugInfo> WasmInstanceObject::GetOrCreateDebugInfo( Handle<WasmInstanceObject> instance) { if (instance->has_debug_info()) { return handle(instance->debug_info(), instance->GetIsolate()); } Handle<WasmDebugInfo> new_info = WasmDebugInfo::New(instance); DCHECK(instance->has_debug_info()); return new_info; } 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); Handle<Code> centry_stub = CodeFactory::CEntry(isolate); instance->set_centry_stub(*centry_stub); 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_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_undefined_value(ReadOnlyRoots(isolate).undefined_value()); instance->set_null_value(ReadOnlyRoots(isolate).null_value()); instance->set_jump_table_start( module_object->native_module()->jump_table_start()); // Insert the new instance into the modules weak list of instances. // TODO(mstarzinger): Allow to reuse holes in the {WeakArrayList} below. Handle<WeakArrayList> weak_instance_list(module_object->weak_instance_list(), isolate); weak_instance_list = WeakArrayList::AddToEnd( isolate, weak_instance_list, MaybeObjectHandle::Weak(instance)); module_object->set_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]; // 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->dropped_data_segments()[i] = segment.active ? 1 : 0; // 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()); instance->data_segment_sizes()[i] = 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) { const wasm::WasmElemSegment& segment = module->elem_segments[i]; // Set the active segments to being already dropped, since table.init on // a dropped passive segment and an active segment have the same // behavior. instance->dropped_elem_segments()[i] = segment.active ? 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); } namespace { void CopyTableEntriesImpl(Handle<WasmInstanceObject> instance, uint32_t dst, uint32_t src, uint32_t count, bool copy_backward) { DCHECK(IsInBounds(dst, count, instance->indirect_function_table_size())); if (copy_backward) { for (uint32_t i = count; i > 0; i--) { auto to_entry = IndirectFunctionTableEntry(instance, dst + i - 1); auto from_entry = IndirectFunctionTableEntry(instance, src + i - 1); to_entry.CopyFrom(from_entry); } } else { for (uint32_t i = 0; i < count; i++) { auto to_entry = IndirectFunctionTableEntry(instance, dst + i); auto from_entry = IndirectFunctionTableEntry(instance, src + i); to_entry.CopyFrom(from_entry); } } } } // namespace // static bool WasmInstanceObject::CopyTableEntries(Isolate* isolate, Handle<WasmInstanceObject> instance, uint32_t table_src_index, uint32_t table_dst_index, uint32_t dst, uint32_t src, uint32_t count) { if (static_cast<int>(table_dst_index) >= instance->tables()->length()) { return false; } if (static_cast<int>(table_src_index) >= instance->tables()->length()) { return false; } // TODO(titzer): multiple tables in TableCopy CHECK_EQ(0, table_src_index); CHECK_EQ(0, table_dst_index); auto max = instance->indirect_function_table_size(); bool copy_backward = src < dst && dst - src < count; bool ok = ClampToBounds(dst, &count, max); // Use & instead of && so the clamp is not short-circuited. ok &= ClampToBounds(src, &count, max); // If performing a partial copy when copying backward, then the first access // will be out-of-bounds, so no entries should be copied. if (copy_backward && !ok) return ok; if (dst == src || count == 0) return ok; // no-op // TODO(titzer): multiple tables in TableCopy auto table = handle( WasmTableObject::cast(instance->tables()->get(table_src_index)), isolate); // Broadcast table copy operation to all instances that import this table. Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate); for (int i = 0; i < dispatch_tables->length(); i += kDispatchTableNumElements) { Handle<WasmInstanceObject> target_instance( WasmInstanceObject::cast( dispatch_tables->get(i + kDispatchTableInstanceOffset)), isolate); CopyTableEntriesImpl(target_instance, dst, src, count, copy_backward); } // Copy the function entries. auto dst_table = handle( WasmTableObject::cast(instance->tables()->get(table_dst_index)), isolate); auto src_table = handle( WasmTableObject::cast(instance->tables()->get(table_src_index)), isolate); if (copy_backward) { for (uint32_t i = count; i > 0; i--) { dst_table->entries()->set(dst + i - 1, src_table->entries()->get(src + i - 1)); } } else { for (uint32_t i = 0; i < count; i++) { dst_table->entries()->set(dst + i, src_table->entries()->get(src + i)); } } return ok; } // 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<WasmExportedFunction> WasmInstanceObject::GetWasmExportedFunction( Isolate* isolate, Handle<WasmInstanceObject> instance, int index) { MaybeHandle<WasmExportedFunction> result; if (instance->has_wasm_exported_functions()) { Object val = instance->wasm_exported_functions()->get(index); if (!val->IsUndefined(isolate)) { result = Handle<WasmExportedFunction>(WasmExportedFunction::cast(val), isolate); } } return result; } Handle<WasmExportedFunction> WasmInstanceObject::GetOrCreateWasmExportedFunction( Isolate* isolate, Handle<WasmInstanceObject> instance, int function_index) { MaybeHandle<WasmExportedFunction> maybe_result = WasmInstanceObject::GetWasmExportedFunction(isolate, instance, function_index); Handle<WasmExportedFunction> result; if (maybe_result.ToHandle(&result)) { return result; } const WasmModule* module = instance->module_object()->module(); const WasmFunction& function = module->functions[function_index]; Handle<Code> wrapper_code = compiler::CompileJSToWasmWrapper(isolate, function.sig, function.imported) .ToHandleChecked(); result = WasmExportedFunction::New( isolate, instance, function_index, static_cast<int>(function.sig->parameter_count()), wrapper_code); WasmInstanceObject::SetWasmExportedFunction(isolate, instance, function_index, result); return result; } void WasmInstanceObject::SetWasmExportedFunction( Isolate* isolate, Handle<WasmInstanceObject> instance, int index, Handle<WasmExportedFunction> val) { Handle<FixedArray> functions; if (!instance->has_wasm_exported_functions()) { // lazily-allocate the wasm exported functions. functions = isolate->factory()->NewFixedArray( static_cast<int>(instance->module()->functions.size())); instance->set_wasm_exported_functions(*functions); } else { functions = Handle<FixedArray>(instance->wasm_exported_functions(), isolate); } functions->set(index, *val); } // 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; } bool WasmExceptionObject::IsSignatureEqual(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; } bool WasmCapiFunction::IsSignatureEqual(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::kWasmStmt) 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<JSReceiver> 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<JSReceiver>::cast(exception); } // static Handle<Object> WasmExceptionPackage::GetExceptionTag( Isolate* isolate, Handle<Object> exception_object) { if (exception_object->IsJSReceiver()) { Handle<JSReceiver> exception = Handle<JSReceiver>::cast(exception_object); Handle<Object> tag; if (JSReceiver::GetProperty(isolate, exception, isolate->factory()->wasm_exception_tag_symbol()) .ToHandle(&tag)) { return tag; } } return ReadOnlyRoots(isolate).undefined_value_handle(); } // static Handle<Object> WasmExceptionPackage::GetExceptionValues( Isolate* isolate, Handle<Object> exception_object) { if (exception_object->IsJSReceiver()) { Handle<JSReceiver> exception = Handle<JSReceiver>::cast(exception_object); Handle<Object> values; if (JSReceiver::GetProperty( isolate, exception, 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 = static_cast<size_t>(wasm::ValueTypes::ElementSizeInBytes(type)); 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)) { case wasm::kWasmI32: case wasm::kWasmF32: DCHECK_EQ(2, ComputeEncodedElementSize(sig->GetParam(i))); encoded_size += 2; break; case wasm::kWasmI64: case wasm::kWasmF64: DCHECK_EQ(4, ComputeEncodedElementSize(sig->GetParam(i))); encoded_size += 4; break; case wasm::kWasmS128: DCHECK_EQ(8, ComputeEncodedElementSize(sig->GetParam(i))); encoded_size += 8; break; case wasm::kWasmAnyRef: case wasm::kWasmAnyFunc: case wasm::kWasmExceptRef: encoded_size += 1; break; default: UNREACHABLE(); } } return encoded_size; } bool WasmExportedFunction::IsWasmExportedFunction(Object object) { if (!object->IsJSFunction()) return false; JSFunction js_function = JSFunction::cast(object); if (Code::JS_TO_WASM_FUNCTION != js_function->code()->kind()) 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() != Code::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, void* embedder_data, Handle<PodArray<wasm::ValueType>> serialized_signature) { Handle<WasmCapiFunctionData> fun_data = Handle<WasmCapiFunctionData>::cast(isolate->factory()->NewStruct( WASM_CAPI_FUNCTION_DATA_TYPE, AllocationType::kOld)); fun_data->set_call_target(call_target); fun_data->set_embedder_data(embedder_data); fun_data->set_serialized_signature(*serialized_signature); // TODO(jkummerow): Install a JavaScript wrapper. For now, calling // these functions directly is unsupported; they can only be called // from Wasm code. fun_data->set_wrapper_code(isolate->builtins()->builtin(Builtins::kIllegal)); Handle<SharedFunctionInfo> shared = isolate->factory()->NewSharedFunctionInfoForWasmCapiFunction(fun_data); return Handle<WasmCapiFunction>::cast( isolate->factory()->NewFunctionFromSharedFunctionInfo( shared, isolate->native_context())); } 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_EQ(Code::JS_TO_WASM_FUNCTION, export_wrapper->kind()); int num_imported_functions = instance->module()->num_imported_functions; int jump_table_offset = -1; if (func_index >= num_imported_functions) { ptrdiff_t jump_table_diff = instance->module_object()->native_module()->jump_table_offset( func_index); DCHECK(jump_table_diff >= 0 && jump_table_diff <= INT_MAX); jump_table_offset = static_cast<int>(jump_table_diff); } Handle<WasmExportedFunctionData> function_data = Handle<WasmExportedFunctionData>::cast(isolate->factory()->NewStruct( WASM_EXPORTED_FUNCTION_DATA_TYPE, AllocationType::kOld)); function_data->set_wrapper_code(*export_wrapper); function_data->set_instance(*instance); function_data->set_jump_table_offset(jump_table_offset); function_data->set_function_index(func_index); MaybeHandle<String> maybe_name; if (instance->module()->origin == wasm::kAsmJsOrigin) { // 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 = isolate->factory() ->NewStringFromOneByte( Vector<uint8_t>::cast(buffer.SubVector(0, length))) .ToHandleChecked(); } bool is_asm_js_module = instance->module_object()->is_asm_js(); Handle<Map> function_map = is_asm_js_module ? isolate->sloppy_function_map() : isolate->sloppy_function_without_prototype_map(); NewFunctionArgs args = NewFunctionArgs::ForWasm(name, function_data, function_map); Handle<JSFunction> js_function = isolate->factory()->NewFunction(args); // 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()); js_function->shared()->set_length(arity); js_function->shared()->set_internal_formal_parameter_count(arity); return Handle<WasmExportedFunction>::cast(js_function); } Address WasmExportedFunction::GetWasmCallTarget() { return instance()->GetCallTarget(function_index()); } wasm::FunctionSig* WasmExportedFunction::sig() { return instance()->module()->functions[function_index()].sig; } Address WasmCapiFunction::GetHostCallTarget() const { return shared()->wasm_capi_function_data()->call_target(); } PodArray<wasm::ValueType> WasmCapiFunction::GetSerializedSignature() const { return shared()->wasm_capi_function_data()->serialized_signature(); } 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<ByteArray> asm_js_offset_table, Handle<HeapNumber> uses_bitset) { const WasmModule* module = native_module->module(); size_t memory_estimate = wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module) + wasm::WasmCodeManager::EstimateNativeModuleNonCodeSize(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_asm_js_offset_table(*asm_js_offset_table); result->set_uses_bitset(*uses_bitset); return result; } #undef TRACE #undef TRACE_IFT } // namespace internal } // namespace v8