| // Copyright 2019 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/wasm/module-instantiate.h" |
| |
| #include "src/api/api.h" |
| #include "src/asmjs/asm-js.h" |
| #include "src/logging/counters.h" |
| #include "src/logging/metrics.h" |
| #include "src/numbers/conversions-inl.h" |
| #include "src/objects/property-descriptor.h" |
| #include "src/tracing/trace-event.h" |
| #include "src/utils/utils.h" |
| #include "src/wasm/module-compiler.h" |
| #include "src/wasm/wasm-constants.h" |
| #include "src/wasm/wasm-external-refs.h" |
| #include "src/wasm/wasm-import-wrapper-cache.h" |
| #include "src/wasm/wasm-module.h" |
| #include "src/wasm/wasm-objects-inl.h" |
| #include "src/wasm/wasm-subtyping.h" |
| |
| #define TRACE(...) \ |
| do { \ |
| if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \ |
| } while (false) |
| |
| namespace v8 { |
| namespace internal { |
| namespace wasm { |
| |
| using base::ReadLittleEndianValue; |
| using base::WriteLittleEndianValue; |
| |
| namespace { |
| byte* raw_buffer_ptr(MaybeHandle<JSArrayBuffer> buffer, int offset) { |
| return static_cast<byte*>(buffer.ToHandleChecked()->backing_store()) + offset; |
| } |
| |
| uint32_t EvalUint32InitExpr(Handle<WasmInstanceObject> instance, |
| const WasmInitExpr& expr) { |
| switch (expr.kind()) { |
| case WasmInitExpr::kI32Const: |
| return expr.immediate().i32_const; |
| case WasmInitExpr::kGlobalGet: { |
| uint32_t offset = |
| instance->module()->globals[expr.immediate().index].offset; |
| auto raw_addr = reinterpret_cast<Address>( |
| instance->untagged_globals_buffer().backing_store()) + |
| offset; |
| return ReadLittleEndianValue<uint32_t>(raw_addr); |
| } |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| using ImportWrapperQueue = WrapperQueue<WasmImportWrapperCache::CacheKey, |
| WasmImportWrapperCache::CacheKeyHash>; |
| |
| class CompileImportWrapperJob final : public JobTask { |
| public: |
| CompileImportWrapperJob( |
| WasmEngine* engine, Counters* counters, NativeModule* native_module, |
| ImportWrapperQueue* queue, |
| WasmImportWrapperCache::ModificationScope* cache_scope) |
| : engine_(engine), |
| counters_(counters), |
| native_module_(native_module), |
| queue_(queue), |
| cache_scope_(cache_scope) {} |
| |
| size_t GetMaxConcurrency(size_t worker_count) const override { |
| size_t flag_limit = |
| static_cast<size_t>(std::max(1, FLAG_wasm_num_compilation_tasks)); |
| // Add {worker_count} to the queue size because workers might still be |
| // processing units that have already been popped from the queue. |
| return std::min(flag_limit, worker_count + queue_->size()); |
| } |
| |
| void Run(JobDelegate* delegate) override { |
| while (base::Optional<WasmImportWrapperCache::CacheKey> key = |
| queue_->pop()) { |
| CompileImportWrapper(engine_, native_module_, counters_, key->kind, |
| key->signature, key->expected_arity, cache_scope_); |
| if (delegate->ShouldYield()) return; |
| } |
| } |
| |
| private: |
| WasmEngine* const engine_; |
| Counters* const counters_; |
| NativeModule* const native_module_; |
| ImportWrapperQueue* const queue_; |
| WasmImportWrapperCache::ModificationScope* const cache_scope_; |
| }; |
| |
| } // namespace |
| |
| // TODO(jkummerow): Move these elsewhere. |
| Handle<Map> CreateStructMap(Isolate* isolate, const WasmModule* module, |
| int struct_index, Handle<Map> rtt_parent) { |
| const wasm::StructType* type = module->struct_type(struct_index); |
| const int inobject_properties = 0; |
| DCHECK_LE(type->total_fields_size(), kMaxInt - WasmStruct::kHeaderSize); |
| const int instance_size = |
| WasmStruct::kHeaderSize + static_cast<int>(type->total_fields_size()); |
| const InstanceType instance_type = WASM_STRUCT_TYPE; |
| // TODO(jkummerow): If NO_ELEMENTS were supported, we could use that here. |
| const ElementsKind elements_kind = TERMINAL_FAST_ELEMENTS_KIND; |
| Handle<WasmTypeInfo> type_info = isolate->factory()->NewWasmTypeInfo( |
| reinterpret_cast<Address>(type), rtt_parent); |
| Handle<Map> map = isolate->factory()->NewMap( |
| instance_type, instance_size, elements_kind, inobject_properties); |
| map->set_wasm_type_info(*type_info); |
| return map; |
| } |
| |
| Handle<Map> CreateArrayMap(Isolate* isolate, const WasmModule* module, |
| int array_index, Handle<Map> rtt_parent) { |
| const wasm::ArrayType* type = module->array_type(array_index); |
| const int inobject_properties = 0; |
| const int instance_size = kVariableSizeSentinel; |
| const InstanceType instance_type = WASM_ARRAY_TYPE; |
| const ElementsKind elements_kind = TERMINAL_FAST_ELEMENTS_KIND; |
| Handle<WasmTypeInfo> type_info = isolate->factory()->NewWasmTypeInfo( |
| reinterpret_cast<Address>(type), rtt_parent); |
| Handle<Map> map = isolate->factory()->NewMap( |
| instance_type, instance_size, elements_kind, inobject_properties); |
| map->set_wasm_type_info(*type_info); |
| return map; |
| } |
| |
| Handle<Map> CreateGenericRtt(Isolate* isolate, const WasmModule* module, |
| Handle<Map> rtt_parent) { |
| const int inobject_properties = 0; |
| const int instance_size = 0; |
| const InstanceType instance_type = WASM_STRUCT_TYPE; // Fake; good enough. |
| const ElementsKind elements_kind = TERMINAL_FAST_ELEMENTS_KIND; |
| Handle<WasmTypeInfo> type_info = |
| isolate->factory()->NewWasmTypeInfo(0, rtt_parent); |
| Handle<Map> map = isolate->factory()->NewMap( |
| instance_type, instance_size, elements_kind, inobject_properties); |
| map->set_wasm_type_info(*type_info); |
| return map; |
| } |
| |
| namespace { |
| |
| // TODO(7748): Consider storing this array in Maps' |
| // "transitions_or_prototype_info" slot. |
| // Also consider being more memory-efficient, e.g. use inline storage for |
| // single entries, and/or adapt the growth strategy. |
| class RttSubtypes : public ArrayList { |
| public: |
| static Handle<ArrayList> Insert(Isolate* isolate, Handle<ArrayList> array, |
| uint32_t type_index, Handle<Map> sub_rtt) { |
| Handle<Smi> key = handle(Smi::FromInt(type_index), isolate); |
| return Add(isolate, array, key, sub_rtt); |
| } |
| |
| static Map SearchSubtype(Handle<ArrayList> array, uint32_t type_index) { |
| // Linear search for now. |
| // TODO(7748): Consider keeping the array sorted and using binary search |
| // here, if empirical data indicates that that would be worthwhile. |
| int count = array->Length(); |
| for (int i = 0; i < count; i += 2) { |
| if (Smi::cast(array->Get(i)).value() == static_cast<int>(type_index)) { |
| return Map::cast(array->Get(i + 1)); |
| } |
| } |
| return {}; |
| } |
| }; |
| |
| } // namespace |
| |
| Handle<Map> AllocateSubRtt(Isolate* isolate, |
| Handle<WasmInstanceObject> instance, uint32_t type, |
| Handle<Map> parent) { |
| // Check for an existing RTT first. |
| DCHECK(parent->IsWasmStructMap() || parent->IsWasmArrayMap()); |
| Handle<ArrayList> cache(parent->wasm_type_info().subtypes(), isolate); |
| Map maybe_cached = RttSubtypes::SearchSubtype(cache, type); |
| if (!maybe_cached.is_null()) return handle(maybe_cached, isolate); |
| |
| // Allocate a fresh RTT otherwise. |
| const wasm::WasmModule* module = instance->module(); |
| Handle<Map> rtt; |
| if (wasm::HeapType(type).is_generic()) { |
| rtt = wasm::CreateGenericRtt(isolate, module, parent); |
| } else if (module->has_struct(type)) { |
| rtt = wasm::CreateStructMap(isolate, module, type, parent); |
| } else if (module->has_array(type)) { |
| rtt = wasm::CreateArrayMap(isolate, module, type, parent); |
| } else { |
| DCHECK(module->has_signature(type)); |
| // Currently, parent rtts for functions are meaningless, |
| // since (rtt.test func rtt) iff (func.map == rtt). |
| // Therefore, we simply create a fresh function map here. |
| // TODO(7748): Canonicalize rtts to make them work for identical function |
| // types. |
| rtt = Map::Copy(isolate, isolate->wasm_exported_function_map(), |
| "fresh function map for AllocateSubRtt"); |
| } |
| cache = RttSubtypes::Insert(isolate, cache, type, rtt); |
| parent->wasm_type_info().set_subtypes(*cache); |
| return rtt; |
| } |
| |
| // A helper class to simplify instantiating a module from a module object. |
| // It closes over the {Isolate}, the {ErrorThrower}, etc. |
| class InstanceBuilder { |
| public: |
| InstanceBuilder(Isolate* isolate, v8::metrics::Recorder::ContextId context_id, |
| ErrorThrower* thrower, Handle<WasmModuleObject> module_object, |
| MaybeHandle<JSReceiver> ffi, |
| MaybeHandle<JSArrayBuffer> memory_buffer); |
| |
| // Build an instance, in all of its glory. |
| MaybeHandle<WasmInstanceObject> Build(); |
| // Run the start function, if any. |
| bool ExecuteStartFunction(); |
| |
| private: |
| // A pre-evaluated value to use in import binding. |
| struct SanitizedImport { |
| Handle<String> module_name; |
| Handle<String> import_name; |
| Handle<Object> value; |
| }; |
| |
| Isolate* isolate_; |
| v8::metrics::Recorder::ContextId context_id_; |
| const WasmFeatures enabled_; |
| const WasmModule* const module_; |
| ErrorThrower* thrower_; |
| Handle<WasmModuleObject> module_object_; |
| MaybeHandle<JSReceiver> ffi_; |
| MaybeHandle<JSArrayBuffer> memory_buffer_; |
| Handle<WasmMemoryObject> memory_object_; |
| Handle<JSArrayBuffer> untagged_globals_; |
| Handle<FixedArray> tagged_globals_; |
| std::vector<Handle<WasmExceptionObject>> exception_wrappers_; |
| Handle<WasmExportedFunction> start_function_; |
| std::vector<SanitizedImport> sanitized_imports_; |
| |
| // Helper routines to print out errors with imports. |
| #define ERROR_THROWER_WITH_MESSAGE(TYPE) \ |
| void Report##TYPE(const char* error, uint32_t index, \ |
| Handle<String> module_name, Handle<String> import_name) { \ |
| thrower_->TYPE("Import #%d module=\"%s\" function=\"%s\" error: %s", \ |
| index, module_name->ToCString().get(), \ |
| import_name->ToCString().get(), error); \ |
| } \ |
| \ |
| MaybeHandle<Object> Report##TYPE(const char* error, uint32_t index, \ |
| Handle<String> module_name) { \ |
| thrower_->TYPE("Import #%d module=\"%s\" error: %s", index, \ |
| module_name->ToCString().get(), error); \ |
| return MaybeHandle<Object>(); \ |
| } |
| |
| ERROR_THROWER_WITH_MESSAGE(LinkError) |
| ERROR_THROWER_WITH_MESSAGE(TypeError) |
| |
| #undef ERROR_THROWER_WITH_MESSAGE |
| |
| // Look up an import value in the {ffi_} object. |
| MaybeHandle<Object> LookupImport(uint32_t index, Handle<String> module_name, |
| Handle<String> import_name); |
| |
| // Look up an import value in the {ffi_} object specifically for linking an |
| // asm.js module. This only performs non-observable lookups, which allows |
| // falling back to JavaScript proper (and hence re-executing all lookups) if |
| // module instantiation fails. |
| MaybeHandle<Object> LookupImportAsm(uint32_t index, |
| Handle<String> import_name); |
| |
| // Load data segments into the memory. |
| void LoadDataSegments(Handle<WasmInstanceObject> instance); |
| |
| void WriteGlobalValue(const WasmGlobal& global, double value); |
| void WriteGlobalValue(const WasmGlobal& global, int64_t num); |
| void WriteGlobalValue(const WasmGlobal& global, |
| Handle<WasmGlobalObject> value); |
| |
| void WriteGlobalExternRef(const WasmGlobal& global, Handle<Object> value); |
| |
| void SanitizeImports(); |
| |
| // Find the imported memory if there is one. |
| bool FindImportedMemory(); |
| |
| // Allocate the memory. |
| bool AllocateMemory(); |
| |
| // Processes a single imported function. |
| bool ProcessImportedFunction(Handle<WasmInstanceObject> instance, |
| int import_index, int func_index, |
| Handle<String> module_name, |
| Handle<String> import_name, |
| Handle<Object> value); |
| |
| // Initialize imported tables of type funcref. |
| bool InitializeImportedIndirectFunctionTable( |
| Handle<WasmInstanceObject> instance, int table_index, int import_index, |
| Handle<WasmTableObject> table_object); |
| |
| // Process a single imported table. |
| bool ProcessImportedTable(Handle<WasmInstanceObject> instance, |
| int import_index, int table_index, |
| Handle<String> module_name, |
| Handle<String> import_name, Handle<Object> value); |
| |
| // Process a single imported memory. |
| bool ProcessImportedMemory(Handle<WasmInstanceObject> instance, |
| int import_index, Handle<String> module_name, |
| Handle<String> import_name, Handle<Object> value); |
| |
| // Process a single imported global. |
| bool ProcessImportedGlobal(Handle<WasmInstanceObject> instance, |
| int import_index, int global_index, |
| Handle<String> module_name, |
| Handle<String> import_name, Handle<Object> value); |
| |
| // Process a single imported WasmGlobalObject. |
| bool ProcessImportedWasmGlobalObject(Handle<WasmInstanceObject> instance, |
| int import_index, |
| Handle<String> module_name, |
| Handle<String> import_name, |
| const WasmGlobal& global, |
| Handle<WasmGlobalObject> global_object); |
| |
| // Compile import wrappers in parallel. The result goes into the native |
| // module's import_wrapper_cache. |
| void CompileImportWrappers(Handle<WasmInstanceObject> instance); |
| |
| // Process the imports, including functions, tables, globals, and memory, in |
| // order, loading them from the {ffi_} object. Returns the number of imported |
| // functions. |
| int ProcessImports(Handle<WasmInstanceObject> instance); |
| |
| template <typename T> |
| T* GetRawGlobalPtr(const WasmGlobal& global); |
| |
| // Process initialization of globals. |
| void InitGlobals(Handle<WasmInstanceObject> instance); |
| |
| Handle<Object> RecursivelyEvaluateGlobalInitializer( |
| const WasmInitExpr& init, Handle<WasmInstanceObject> instance); |
| |
| // Process the exports, creating wrappers for functions, tables, memories, |
| // and globals. |
| void ProcessExports(Handle<WasmInstanceObject> instance); |
| |
| void InitializeIndirectFunctionTables(Handle<WasmInstanceObject> instance); |
| |
| void LoadTableSegments(Handle<WasmInstanceObject> instance); |
| |
| // Creates new exception tags for all exceptions. Note that some tags might |
| // already exist if they were imported, those tags will be re-used. |
| void InitializeExceptions(Handle<WasmInstanceObject> instance); |
| }; |
| |
| MaybeHandle<WasmInstanceObject> InstantiateToInstanceObject( |
| Isolate* isolate, ErrorThrower* thrower, |
| Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports, |
| MaybeHandle<JSArrayBuffer> memory_buffer) { |
| v8::metrics::Recorder::ContextId context_id = |
| isolate->GetOrRegisterRecorderContextId(isolate->native_context()); |
| InstanceBuilder builder(isolate, context_id, thrower, module_object, imports, |
| memory_buffer); |
| auto instance = builder.Build(); |
| if (!instance.is_null() && builder.ExecuteStartFunction()) { |
| return instance; |
| } |
| DCHECK(isolate->has_pending_exception() || thrower->error()); |
| return {}; |
| } |
| |
| InstanceBuilder::InstanceBuilder(Isolate* isolate, |
| v8::metrics::Recorder::ContextId context_id, |
| ErrorThrower* thrower, |
| Handle<WasmModuleObject> module_object, |
| MaybeHandle<JSReceiver> ffi, |
| MaybeHandle<JSArrayBuffer> memory_buffer) |
| : isolate_(isolate), |
| context_id_(context_id), |
| enabled_(module_object->native_module()->enabled_features()), |
| module_(module_object->module()), |
| thrower_(thrower), |
| module_object_(module_object), |
| ffi_(ffi), |
| memory_buffer_(memory_buffer) { |
| sanitized_imports_.reserve(module_->import_table.size()); |
| } |
| |
| // Build an instance, in all of its glory. |
| MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), |
| "wasm.InstanceBuilder.Build"); |
| // Check that an imports argument was provided, if the module requires it. |
| // No point in continuing otherwise. |
| if (!module_->import_table.empty() && ffi_.is_null()) { |
| thrower_->TypeError( |
| "Imports argument must be present and must be an object"); |
| return {}; |
| } |
| |
| SanitizeImports(); |
| if (thrower_->error()) return {}; |
| |
| // From here on, we expect the build pipeline to run without exiting to JS. |
| DisallowJavascriptExecution no_js(isolate_); |
| // Record build time into correct bucket, then build instance. |
| TimedHistogramScope wasm_instantiate_module_time_scope(SELECT_WASM_COUNTER( |
| isolate_->counters(), module_->origin, wasm_instantiate, module_time)); |
| v8::metrics::WasmModuleInstantiated wasm_module_instantiated; |
| base::ElapsedTimer timer; |
| timer.Start(); |
| NativeModule* native_module = module_object_->native_module(); |
| |
| //-------------------------------------------------------------------------- |
| // Set up the memory buffer and memory objects. |
| //-------------------------------------------------------------------------- |
| uint32_t initial_pages = module_->initial_pages; |
| auto initial_pages_counter = SELECT_WASM_COUNTER( |
| isolate_->counters(), module_->origin, wasm, min_mem_pages_count); |
| initial_pages_counter->AddSample(initial_pages); |
| if (module_->has_maximum_pages) { |
| DCHECK_EQ(kWasmOrigin, module_->origin); |
| auto max_pages_counter = |
| isolate_->counters()->wasm_wasm_max_mem_pages_count(); |
| max_pages_counter->AddSample(module_->maximum_pages); |
| } |
| |
| if (is_asmjs_module(module_)) { |
| Handle<JSArrayBuffer> buffer; |
| if (memory_buffer_.ToHandle(&buffer)) { |
| // asm.js instantiation should have changed the state of the buffer. |
| CHECK(!buffer->is_detachable()); |
| CHECK(buffer->is_asmjs_memory()); |
| } else { |
| // Use an empty JSArrayBuffer for degenerate asm.js modules. |
| memory_buffer_ = isolate_->factory()->NewJSArrayBufferAndBackingStore( |
| 0, InitializedFlag::kUninitialized); |
| if (!memory_buffer_.ToHandle(&buffer)) { |
| thrower_->RangeError("Out of memory: asm.js memory"); |
| return {}; |
| } |
| buffer->set_is_asmjs_memory(true); |
| buffer->set_is_detachable(false); |
| } |
| |
| // The maximum number of pages isn't strictly necessary for memory |
| // objects used for asm.js, as they are never visible, but we might |
| // as well make it accurate. |
| auto maximum_pages = static_cast<uint32_t>( |
| RoundUp(buffer->byte_length(), wasm::kWasmPageSize) / |
| wasm::kWasmPageSize); |
| memory_object_ = |
| WasmMemoryObject::New(isolate_, memory_buffer_, maximum_pages); |
| } else { |
| // Actual wasm module must have either imported or created memory. |
| CHECK(memory_buffer_.is_null()); |
| if (!FindImportedMemory()) { |
| if (module_->has_memory && !AllocateMemory()) { |
| DCHECK(isolate_->has_pending_exception() || thrower_->error()); |
| return {}; |
| } |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Create the WebAssembly.Instance object. |
| //-------------------------------------------------------------------------- |
| TRACE("New module instantiation for %p\n", native_module); |
| Handle<WasmInstanceObject> instance = |
| WasmInstanceObject::New(isolate_, module_object_); |
| |
| //-------------------------------------------------------------------------- |
| // Attach the memory to the instance. |
| //-------------------------------------------------------------------------- |
| if (module_->has_memory) { |
| DCHECK(!memory_object_.is_null()); |
| if (!instance->has_memory_object()) { |
| instance->set_memory_object(*memory_object_); |
| } |
| // Add the instance object to the list of instances for this memory. |
| WasmMemoryObject::AddInstance(isolate_, memory_object_, instance); |
| |
| // Double-check the {memory} array buffer matches the instance. |
| Handle<JSArrayBuffer> memory = memory_buffer_.ToHandleChecked(); |
| CHECK_EQ(instance->memory_size(), memory->byte_length()); |
| CHECK_EQ(instance->memory_start(), memory->backing_store()); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Set up the globals for the new instance. |
| //-------------------------------------------------------------------------- |
| uint32_t untagged_globals_buffer_size = module_->untagged_globals_buffer_size; |
| if (untagged_globals_buffer_size > 0) { |
| MaybeHandle<JSArrayBuffer> result = |
| isolate_->factory()->NewJSArrayBufferAndBackingStore( |
| untagged_globals_buffer_size, InitializedFlag::kZeroInitialized, |
| AllocationType::kOld); |
| |
| if (!result.ToHandle(&untagged_globals_)) { |
| thrower_->RangeError("Out of memory: wasm globals"); |
| return {}; |
| } |
| |
| instance->set_untagged_globals_buffer(*untagged_globals_); |
| instance->set_globals_start( |
| reinterpret_cast<byte*>(untagged_globals_->backing_store())); |
| } |
| |
| uint32_t tagged_globals_buffer_size = module_->tagged_globals_buffer_size; |
| if (tagged_globals_buffer_size > 0) { |
| tagged_globals_ = isolate_->factory()->NewFixedArray( |
| static_cast<int>(tagged_globals_buffer_size)); |
| instance->set_tagged_globals_buffer(*tagged_globals_); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Set up the array of references to imported globals' array buffers. |
| //-------------------------------------------------------------------------- |
| if (module_->num_imported_mutable_globals > 0) { |
| // TODO(binji): This allocates one slot for each mutable global, which is |
| // more than required if multiple globals are imported from the same |
| // module. |
| Handle<FixedArray> buffers_array = isolate_->factory()->NewFixedArray( |
| module_->num_imported_mutable_globals, AllocationType::kOld); |
| instance->set_imported_mutable_globals_buffers(*buffers_array); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Set up the exception table used for exception tag checks. |
| //-------------------------------------------------------------------------- |
| int exceptions_count = static_cast<int>(module_->exceptions.size()); |
| if (exceptions_count > 0) { |
| Handle<FixedArray> exception_table = isolate_->factory()->NewFixedArray( |
| exceptions_count, AllocationType::kOld); |
| instance->set_exceptions_table(*exception_table); |
| exception_wrappers_.resize(exceptions_count); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Set up table storage space. |
| //-------------------------------------------------------------------------- |
| int table_count = static_cast<int>(module_->tables.size()); |
| { |
| for (int i = 0; i < table_count; i++) { |
| const WasmTable& table = module_->tables[i]; |
| if (table.initial_size > FLAG_wasm_max_table_size) { |
| thrower_->RangeError( |
| "initial table size (%u elements) is larger than implementation " |
| "limit (%u elements)", |
| table.initial_size, FLAG_wasm_max_table_size); |
| return {}; |
| } |
| } |
| |
| Handle<FixedArray> tables = isolate_->factory()->NewFixedArray(table_count); |
| for (int i = module_->num_imported_tables; i < table_count; i++) { |
| const WasmTable& table = module_->tables[i]; |
| Handle<WasmTableObject> table_obj = WasmTableObject::New( |
| isolate_, instance, table.type, table.initial_size, |
| table.has_maximum_size, table.maximum_size, nullptr); |
| tables->set(i, *table_obj); |
| } |
| instance->set_tables(*tables); |
| } |
| |
| { |
| Handle<FixedArray> tables = isolate_->factory()->NewFixedArray(table_count); |
| // Table 0 is handled specially. See {InitializeIndirectFunctionTable} for |
| // the initilization. All generated and runtime code will use this optimized |
| // shortcut in the instance. Hence it is safe to start with table 1 in the |
| // iteration below. |
| for (int i = 1; i < table_count; ++i) { |
| const WasmTable& table = module_->tables[i]; |
| if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) { |
| Handle<WasmIndirectFunctionTable> table_obj = |
| WasmIndirectFunctionTable::New(isolate_, table.initial_size); |
| tables->set(i, *table_obj); |
| } |
| } |
| instance->set_indirect_function_tables(*tables); |
| } |
| |
| NativeModuleModificationScope native_modification_scope(native_module); |
| |
| //-------------------------------------------------------------------------- |
| // Process the imports for the module. |
| //-------------------------------------------------------------------------- |
| int num_imported_functions = ProcessImports(instance); |
| if (num_imported_functions < 0) return {}; |
| wasm_module_instantiated.imported_function_count = num_imported_functions; |
| |
| //-------------------------------------------------------------------------- |
| // Create maps for managed objects (GC proposal). |
| // Must happen before {InitGlobals} because globals can refer to these maps. |
| //-------------------------------------------------------------------------- |
| if (enabled_.has_gc()) { |
| Handle<FixedArray> maps = isolate_->factory()->NewUninitializedFixedArray( |
| static_cast<int>(module_->type_kinds.size())); |
| // TODO(7748): Do we want a different sentinel here? |
| Handle<Map> anyref_sentinel_map = isolate_->factory()->null_map(); |
| for (int map_index = 0; |
| map_index < static_cast<int>(module_->type_kinds.size()); |
| map_index++) { |
| Handle<Map> map; |
| switch (module_->type_kinds[map_index]) { |
| case kWasmStructTypeCode: |
| map = CreateStructMap(isolate_, module_, map_index, |
| anyref_sentinel_map); |
| break; |
| case kWasmArrayTypeCode: |
| map = |
| CreateArrayMap(isolate_, module_, map_index, anyref_sentinel_map); |
| break; |
| case kWasmFunctionTypeCode: |
| // TODO(7748): Think about canonicalizing rtts to make them work for |
| // identical function types. |
| map = Map::Copy(isolate_, isolate_->wasm_exported_function_map(), |
| "fresh function map for function type canonical rtt " |
| "initialization"); |
| break; |
| } |
| maps->set(map_index, *map); |
| } |
| instance->set_managed_object_maps(*maps); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Process the initialization for the module's globals. |
| //-------------------------------------------------------------------------- |
| InitGlobals(instance); |
| |
| //-------------------------------------------------------------------------- |
| // Initialize the indirect tables. |
| //-------------------------------------------------------------------------- |
| if (table_count > 0) { |
| InitializeIndirectFunctionTables(instance); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Initialize the exceptions table. |
| //-------------------------------------------------------------------------- |
| if (exceptions_count > 0) { |
| InitializeExceptions(instance); |
| } |
| |
| // The bulk memory proposal changes the MVP behavior here; the segments are |
| // written as if `memory.init` and `table.init` are executed directly, and |
| // not bounds checked ahead of time. |
| if (!enabled_.has_bulk_memory()) { |
| //-------------------------------------------------------------------------- |
| // Check that indirect function table segments are within bounds. |
| //-------------------------------------------------------------------------- |
| for (const WasmElemSegment& elem_segment : module_->elem_segments) { |
| if (elem_segment.status != WasmElemSegment::kStatusActive) continue; |
| DCHECK_LT(elem_segment.table_index, table_count); |
| uint32_t base = EvalUint32InitExpr(instance, elem_segment.offset); |
| // Because of imported tables, {table_size} has to come from the table |
| // object itself. |
| auto table_object = handle(WasmTableObject::cast(instance->tables().get( |
| elem_segment.table_index)), |
| isolate_); |
| uint32_t table_size = table_object->current_length(); |
| if (!base::IsInBounds<uint32_t>( |
| base, static_cast<uint32_t>(elem_segment.entries.size()), |
| table_size)) { |
| thrower_->LinkError("table initializer is out of bounds"); |
| return {}; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Check that memory segments are within bounds. |
| //-------------------------------------------------------------------------- |
| for (const WasmDataSegment& seg : module_->data_segments) { |
| if (!seg.active) continue; |
| uint32_t base = EvalUint32InitExpr(instance, seg.dest_addr); |
| if (!base::IsInBounds<uint64_t>(base, seg.source.length(), |
| instance->memory_size())) { |
| thrower_->LinkError("data segment is out of bounds"); |
| return {}; |
| } |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Set up the exports object for the new instance. |
| //-------------------------------------------------------------------------- |
| ProcessExports(instance); |
| if (thrower_->error()) return {}; |
| |
| //-------------------------------------------------------------------------- |
| // Initialize the indirect function tables. |
| //-------------------------------------------------------------------------- |
| if (table_count > 0) { |
| LoadTableSegments(instance); |
| if (thrower_->error()) return {}; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Initialize the memory by loading data segments. |
| //-------------------------------------------------------------------------- |
| if (module_->data_segments.size() > 0) { |
| LoadDataSegments(instance); |
| if (thrower_->error()) return {}; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Create a wrapper for the start function. |
| //-------------------------------------------------------------------------- |
| if (module_->start_function_index >= 0) { |
| int start_index = module_->start_function_index; |
| auto& function = module_->functions[start_index]; |
| Handle<Code> wrapper_code = |
| JSToWasmWrapperCompilationUnit::CompileJSToWasmWrapper( |
| isolate_, function.sig, module_, function.imported); |
| // TODO(clemensb): Don't generate an exported function for the start |
| // function. Use CWasmEntry instead. |
| start_function_ = WasmExportedFunction::New( |
| isolate_, instance, start_index, |
| static_cast<int>(function.sig->parameter_count()), wrapper_code); |
| |
| if (function.imported) { |
| ImportedFunctionEntry entry(instance, module_->start_function_index); |
| Object callable = entry.maybe_callable(); |
| if (callable.IsJSFunction()) { |
| // If the start function was imported and calls into Blink, we have |
| // to pretend that the V8 API was used to enter its correct context. |
| // To get that context to {ExecuteStartFunction} below, we install it |
| // as the context of the wrapper we just compiled. That's a bit of a |
| // hack because it's not really the wrapper's context, only its wrapped |
| // target's context, but the end result is the same, and since the |
| // start function wrapper doesn't leak, neither does this |
| // implementation detail. |
| start_function_->set_context(JSFunction::cast(callable).context()); |
| } |
| } |
| } |
| |
| DCHECK(!isolate_->has_pending_exception()); |
| TRACE("Successfully built instance for module %p\n", |
| module_object_->native_module()); |
| wasm_module_instantiated.success = true; |
| wasm_module_instantiated.wall_clock_duration_in_us = |
| timer.Elapsed().InMicroseconds(); |
| timer.Stop(); |
| isolate_->metrics_recorder()->DelayMainThreadEvent(wasm_module_instantiated, |
| context_id_); |
| return instance; |
| } |
| |
| bool InstanceBuilder::ExecuteStartFunction() { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), |
| "wasm.ExecuteStartFunction"); |
| if (start_function_.is_null()) return true; // No start function. |
| |
| HandleScope scope(isolate_); |
| // In case the start function calls out to Blink, we have to make sure that |
| // the correct "entered context" is available. This is the equivalent of |
| // v8::Context::Enter() and must happen in addition to the function call |
| // sequence doing the compiled version of "isolate->set_context(...)". |
| HandleScopeImplementer* hsi = isolate_->handle_scope_implementer(); |
| hsi->EnterContext(start_function_->context()); |
| |
| // Call the JS function. |
| Handle<Object> undefined = isolate_->factory()->undefined_value(); |
| MaybeHandle<Object> retval = |
| Execution::Call(isolate_, start_function_, undefined, 0, nullptr); |
| hsi->LeaveContext(); |
| |
| if (retval.is_null()) { |
| DCHECK(isolate_->has_pending_exception()); |
| return false; |
| } |
| return true; |
| } |
| |
| // Look up an import value in the {ffi_} object. |
| MaybeHandle<Object> InstanceBuilder::LookupImport(uint32_t index, |
| Handle<String> module_name, |
| Handle<String> import_name) { |
| // We pre-validated in the js-api layer that the ffi object is present, and |
| // a JSObject, if the module has imports. |
| DCHECK(!ffi_.is_null()); |
| // Look up the module first. |
| MaybeHandle<Object> result = Object::GetPropertyOrElement( |
| isolate_, ffi_.ToHandleChecked(), module_name); |
| if (result.is_null()) { |
| return ReportTypeError("module not found", index, module_name); |
| } |
| |
| Handle<Object> module = result.ToHandleChecked(); |
| |
| // Look up the value in the module. |
| if (!module->IsJSReceiver()) { |
| return ReportTypeError("module is not an object or function", index, |
| module_name); |
| } |
| |
| result = Object::GetPropertyOrElement(isolate_, module, import_name); |
| if (result.is_null()) { |
| ReportLinkError("import not found", index, module_name, import_name); |
| return MaybeHandle<JSFunction>(); |
| } |
| |
| return result; |
| } |
| |
| // Look up an import value in the {ffi_} object specifically for linking an |
| // asm.js module. This only performs non-observable lookups, which allows |
| // falling back to JavaScript proper (and hence re-executing all lookups) if |
| // module instantiation fails. |
| MaybeHandle<Object> InstanceBuilder::LookupImportAsm( |
| uint32_t index, Handle<String> import_name) { |
| // Check that a foreign function interface object was provided. |
| if (ffi_.is_null()) { |
| return ReportLinkError("missing imports object", index, import_name); |
| } |
| |
| // Perform lookup of the given {import_name} without causing any observable |
| // side-effect. We only accept accesses that resolve to data properties, |
| // which is indicated by the asm.js spec in section 7 ("Linking") as well. |
| Handle<Object> result; |
| LookupIterator::Key key(isolate_, Handle<Name>::cast(import_name)); |
| LookupIterator it(isolate_, ffi_.ToHandleChecked(), key); |
| switch (it.state()) { |
| case LookupIterator::ACCESS_CHECK: |
| case LookupIterator::INTEGER_INDEXED_EXOTIC: |
| case LookupIterator::INTERCEPTOR: |
| case LookupIterator::JSPROXY: |
| case LookupIterator::ACCESSOR: |
| case LookupIterator::TRANSITION: |
| return ReportLinkError("not a data property", index, import_name); |
| case LookupIterator::NOT_FOUND: |
| // Accepting missing properties as undefined does not cause any |
| // observable difference from JavaScript semantics, we are lenient. |
| result = isolate_->factory()->undefined_value(); |
| break; |
| case LookupIterator::DATA: |
| result = it.GetDataValue(); |
| break; |
| } |
| |
| return result; |
| } |
| |
| // Load data segments into the memory. |
| void InstanceBuilder::LoadDataSegments(Handle<WasmInstanceObject> instance) { |
| Vector<const uint8_t> wire_bytes = |
| module_object_->native_module()->wire_bytes(); |
| for (const WasmDataSegment& segment : module_->data_segments) { |
| uint32_t size = segment.source.length(); |
| |
| if (enabled_.has_bulk_memory()) { |
| // Passive segments are not copied during instantiation. |
| if (!segment.active) continue; |
| |
| uint32_t dest_offset = EvalUint32InitExpr(instance, segment.dest_addr); |
| bool ok = base::ClampToBounds( |
| dest_offset, &size, static_cast<uint32_t>(instance->memory_size())); |
| if (!ok) { |
| thrower_->RuntimeError("data segment is out of bounds"); |
| return; |
| } |
| // No need to copy empty segments. |
| if (size == 0) continue; |
| std::memcpy(instance->memory_start() + dest_offset, |
| wire_bytes.begin() + segment.source.offset(), size); |
| } else { |
| DCHECK(segment.active); |
| // Segments of size == 0 are just nops. |
| if (size == 0) continue; |
| |
| uint32_t dest_offset = EvalUint32InitExpr(instance, segment.dest_addr); |
| DCHECK(base::IsInBounds<uint64_t>(dest_offset, size, |
| instance->memory_size())); |
| byte* dest = instance->memory_start() + dest_offset; |
| const byte* src = wire_bytes.begin() + segment.source.offset(); |
| memcpy(dest, src, size); |
| } |
| } |
| } |
| |
| void InstanceBuilder::WriteGlobalValue(const WasmGlobal& global, double num) { |
| TRACE("init [globals_start=%p + %u] = %lf, type = %s\n", |
| raw_buffer_ptr(untagged_globals_, 0), global.offset, num, |
| global.type.name().c_str()); |
| switch (global.type.kind()) { |
| case ValueType::kI32: |
| WriteLittleEndianValue<int32_t>(GetRawGlobalPtr<int32_t>(global), |
| DoubleToInt32(num)); |
| break; |
| case ValueType::kI64: |
| // The Wasm-BigInt proposal currently says that i64 globals may |
| // only be initialized with BigInts. See: |
| // https://github.com/WebAssembly/JS-BigInt-integration/issues/12 |
| UNREACHABLE(); |
| case ValueType::kF32: |
| WriteLittleEndianValue<float>(GetRawGlobalPtr<float>(global), |
| DoubleToFloat32(num)); |
| break; |
| case ValueType::kF64: |
| WriteLittleEndianValue<double>(GetRawGlobalPtr<double>(global), num); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void InstanceBuilder::WriteGlobalValue(const WasmGlobal& global, int64_t num) { |
| TRACE("init [globals_start=%p + %u] = %" PRId64 ", type = %s\n", |
| raw_buffer_ptr(untagged_globals_, 0), global.offset, num, |
| global.type.name().c_str()); |
| DCHECK_EQ(kWasmI64, global.type); |
| WriteLittleEndianValue<int64_t>(GetRawGlobalPtr<int64_t>(global), num); |
| } |
| |
| void InstanceBuilder::WriteGlobalValue(const WasmGlobal& global, |
| Handle<WasmGlobalObject> value) { |
| TRACE("init [globals_start=%p + %u] = ", raw_buffer_ptr(untagged_globals_, 0), |
| global.offset); |
| switch (global.type.kind()) { |
| case ValueType::kI32: { |
| int32_t num = value->GetI32(); |
| WriteLittleEndianValue<int32_t>(GetRawGlobalPtr<int32_t>(global), num); |
| TRACE("%d", num); |
| break; |
| } |
| case ValueType::kI64: { |
| int64_t num = value->GetI64(); |
| WriteLittleEndianValue<int64_t>(GetRawGlobalPtr<int64_t>(global), num); |
| TRACE("%" PRId64, num); |
| break; |
| } |
| case ValueType::kF32: { |
| float num = value->GetF32(); |
| WriteLittleEndianValue<float>(GetRawGlobalPtr<float>(global), num); |
| TRACE("%f", num); |
| break; |
| } |
| case ValueType::kF64: { |
| double num = value->GetF64(); |
| WriteLittleEndianValue<double>(GetRawGlobalPtr<double>(global), num); |
| TRACE("%lf", num); |
| break; |
| } |
| case ValueType::kRtt: |
| case ValueType::kRef: |
| case ValueType::kOptRef: { |
| tagged_globals_->set(global.offset, *value->GetRef()); |
| break; |
| } |
| case ValueType::kStmt: |
| case ValueType::kS128: |
| case ValueType::kBottom: |
| case ValueType::kI8: |
| case ValueType::kI16: |
| UNREACHABLE(); |
| } |
| TRACE(", type = %s (from WebAssembly.Global)\n", global.type.name().c_str()); |
| } |
| |
| void InstanceBuilder::WriteGlobalExternRef(const WasmGlobal& global, |
| Handle<Object> value) { |
| tagged_globals_->set(global.offset, *value, UPDATE_WRITE_BARRIER); |
| } |
| |
| void InstanceBuilder::SanitizeImports() { |
| Vector<const uint8_t> wire_bytes = |
| module_object_->native_module()->wire_bytes(); |
| for (size_t index = 0; index < module_->import_table.size(); ++index) { |
| const WasmImport& import = module_->import_table[index]; |
| |
| Handle<String> module_name = |
| WasmModuleObject::ExtractUtf8StringFromModuleBytes( |
| isolate_, wire_bytes, import.module_name, kInternalize); |
| |
| Handle<String> import_name = |
| WasmModuleObject::ExtractUtf8StringFromModuleBytes( |
| isolate_, wire_bytes, import.field_name, kInternalize); |
| |
| int int_index = static_cast<int>(index); |
| MaybeHandle<Object> result = |
| is_asmjs_module(module_) |
| ? LookupImportAsm(int_index, import_name) |
| : LookupImport(int_index, module_name, import_name); |
| if (thrower_->error()) { |
| thrower_->LinkError("Could not find value for import %zu", index); |
| return; |
| } |
| Handle<Object> value = result.ToHandleChecked(); |
| sanitized_imports_.push_back({module_name, import_name, value}); |
| } |
| } |
| |
| bool InstanceBuilder::FindImportedMemory() { |
| DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size()); |
| for (size_t index = 0; index < module_->import_table.size(); index++) { |
| WasmImport import = module_->import_table[index]; |
| |
| if (import.kind == kExternalMemory) { |
| auto& value = sanitized_imports_[index].value; |
| if (!value->IsWasmMemoryObject()) return false; |
| memory_object_ = Handle<WasmMemoryObject>::cast(value); |
| memory_buffer_ = |
| Handle<JSArrayBuffer>(memory_object_->array_buffer(), isolate_); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool InstanceBuilder::ProcessImportedFunction( |
| Handle<WasmInstanceObject> instance, int import_index, int func_index, |
| Handle<String> module_name, Handle<String> import_name, |
| Handle<Object> value) { |
| // Function imports must be callable. |
| if (!value->IsCallable()) { |
| ReportLinkError("function import requires a callable", import_index, |
| module_name, import_name); |
| return false; |
| } |
| // Store any {WasmExternalFunction} callable in the instance before the call |
| // is resolved to preserve its identity. This handles exported functions as |
| // well as functions constructed via other means (e.g. WebAssembly.Function). |
| if (WasmExternalFunction::IsWasmExternalFunction(*value)) { |
| WasmInstanceObject::SetWasmExternalFunction( |
| isolate_, instance, func_index, |
| Handle<WasmExternalFunction>::cast(value)); |
| } |
| auto js_receiver = Handle<JSReceiver>::cast(value); |
| const FunctionSig* expected_sig = module_->functions[func_index].sig; |
| auto resolved = compiler::ResolveWasmImportCall(js_receiver, expected_sig, |
| module_, enabled_); |
| compiler::WasmImportCallKind kind = resolved.first; |
| js_receiver = resolved.second; |
| switch (kind) { |
| case compiler::WasmImportCallKind::kLinkError: |
| ReportLinkError("imported function does not match the expected type", |
| import_index, module_name, import_name); |
| return false; |
| case compiler::WasmImportCallKind::kWasmToWasm: { |
| // The imported function is a Wasm function from another instance. |
| auto imported_function = Handle<WasmExportedFunction>::cast(js_receiver); |
| Handle<WasmInstanceObject> imported_instance( |
| imported_function->instance(), isolate_); |
| // The import reference is the instance object itself. |
| Address imported_target = imported_function->GetWasmCallTarget(); |
| ImportedFunctionEntry entry(instance, func_index); |
| entry.SetWasmToWasm(*imported_instance, imported_target); |
| break; |
| } |
| case compiler::WasmImportCallKind::kWasmToCapi: { |
| NativeModule* native_module = instance->module_object().native_module(); |
| Address host_address = |
| WasmCapiFunction::cast(*js_receiver).GetHostCallTarget(); |
| WasmCodeRefScope code_ref_scope; |
| WasmCode* wasm_code = compiler::CompileWasmCapiCallWrapper( |
| isolate_->wasm_engine(), native_module, expected_sig, host_address); |
| isolate_->counters()->wasm_generated_code_size()->Increment( |
| wasm_code->instructions().length()); |
| isolate_->counters()->wasm_reloc_size()->Increment( |
| wasm_code->reloc_info().length()); |
| |
| ImportedFunctionEntry entry(instance, func_index); |
| // We re-use the SetWasmToJs infrastructure because it passes the |
| // callable to the wrapper, which we need to get the function data. |
| entry.SetWasmToJs(isolate_, js_receiver, wasm_code); |
| break; |
| } |
| default: { |
| // The imported function is a callable. |
| |
| int expected_arity = static_cast<int>(expected_sig->parameter_count()); |
| if (kind == compiler::WasmImportCallKind::kJSFunctionArityMismatch) { |
| Handle<JSFunction> function = Handle<JSFunction>::cast(js_receiver); |
| SharedFunctionInfo shared = function->shared(); |
| expected_arity = shared.internal_formal_parameter_count(); |
| } |
| |
| NativeModule* native_module = instance->module_object().native_module(); |
| WasmCode* wasm_code = native_module->import_wrapper_cache()->Get( |
| kind, expected_sig, expected_arity); |
| DCHECK_NOT_NULL(wasm_code); |
| ImportedFunctionEntry entry(instance, func_index); |
| if (wasm_code->kind() == WasmCode::kWasmToJsWrapper) { |
| // Wasm to JS wrappers are treated specially in the import table. |
| entry.SetWasmToJs(isolate_, js_receiver, wasm_code); |
| } else { |
| // Wasm math intrinsics are compiled as regular Wasm functions. |
| DCHECK(kind >= compiler::WasmImportCallKind::kFirstMathIntrinsic && |
| kind <= compiler::WasmImportCallKind::kLastMathIntrinsic); |
| entry.SetWasmToWasm(*instance, wasm_code->instruction_start()); |
| } |
| break; |
| } |
| } |
| return true; |
| } |
| |
| bool InstanceBuilder::InitializeImportedIndirectFunctionTable( |
| Handle<WasmInstanceObject> instance, int table_index, int import_index, |
| Handle<WasmTableObject> table_object) { |
| int imported_table_size = table_object->current_length(); |
| // Allocate a new dispatch table. |
| WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( |
| instance, table_index, imported_table_size); |
| // Initialize the dispatch table with the (foreign) JS functions |
| // that are already in the table. |
| for (int i = 0; i < imported_table_size; ++i) { |
| bool is_valid; |
| bool is_null; |
| MaybeHandle<WasmInstanceObject> maybe_target_instance; |
| int function_index; |
| MaybeHandle<WasmJSFunction> maybe_js_function; |
| WasmTableObject::GetFunctionTableEntry( |
| isolate_, module_, table_object, i, &is_valid, &is_null, |
| &maybe_target_instance, &function_index, &maybe_js_function); |
| if (!is_valid) { |
| thrower_->LinkError("table import %d[%d] is not a wasm function", |
| import_index, i); |
| return false; |
| } |
| if (is_null) continue; |
| Handle<WasmJSFunction> js_function; |
| if (maybe_js_function.ToHandle(&js_function)) { |
| WasmInstanceObject::ImportWasmJSFunctionIntoTable( |
| isolate_, instance, table_index, i, js_function); |
| continue; |
| } |
| |
| Handle<WasmInstanceObject> target_instance = |
| maybe_target_instance.ToHandleChecked(); |
| const FunctionSig* sig = target_instance->module_object() |
| .module() |
| ->functions[function_index] |
| .sig; |
| |
| // Look up the signature's canonical id. If there is no canonical |
| // id, then the signature does not appear at all in this module, |
| // so putting {-1} in the table will cause checks to always fail. |
| IndirectFunctionTableEntry(instance, table_index, i) |
| .Set(module_->signature_map.Find(*sig), target_instance, |
| function_index); |
| } |
| return true; |
| } |
| |
| bool InstanceBuilder::ProcessImportedTable(Handle<WasmInstanceObject> instance, |
| int import_index, int table_index, |
| Handle<String> module_name, |
| Handle<String> import_name, |
| Handle<Object> value) { |
| if (!value->IsWasmTableObject()) { |
| ReportLinkError("table import requires a WebAssembly.Table", import_index, |
| module_name, import_name); |
| return false; |
| } |
| const WasmTable& table = module_->tables[table_index]; |
| |
| auto table_object = Handle<WasmTableObject>::cast(value); |
| |
| uint32_t imported_table_size = |
| static_cast<uint32_t>(table_object->current_length()); |
| if (imported_table_size < table.initial_size) { |
| thrower_->LinkError("table import %d is smaller than initial %u, got %u", |
| import_index, table.initial_size, imported_table_size); |
| return false; |
| } |
| |
| if (table.has_maximum_size) { |
| if (table_object->maximum_length().IsUndefined(isolate_)) { |
| thrower_->LinkError("table import %d has no maximum length, expected %u", |
| import_index, table.maximum_size); |
| return false; |
| } |
| int64_t imported_maximum_size = table_object->maximum_length().Number(); |
| if (imported_maximum_size < 0) { |
| thrower_->LinkError("table import %d has no maximum length, expected %u", |
| import_index, table.maximum_size); |
| return false; |
| } |
| if (imported_maximum_size > table.maximum_size) { |
| thrower_->LinkError("table import %d has a larger maximum size %" PRIx64 |
| " than the module's declared maximum %u", |
| import_index, imported_maximum_size, |
| table.maximum_size); |
| return false; |
| } |
| } |
| |
| const WasmModule* table_type_module = |
| !table_object->instance().IsUndefined() |
| ? WasmInstanceObject::cast(table_object->instance()).module() |
| : instance->module(); |
| |
| if (!EquivalentTypes(table.type, table_object->type(), module_, |
| table_type_module)) { |
| ReportLinkError("imported table does not match the expected type", |
| import_index, module_name, import_name); |
| return false; |
| } |
| |
| if (IsSubtypeOf(table.type, kWasmFuncRef, module_) && |
| !InitializeImportedIndirectFunctionTable(instance, table_index, |
| import_index, table_object)) { |
| return false; |
| } |
| |
| instance->tables().set(table_index, *value); |
| return true; |
| } |
| |
| bool InstanceBuilder::ProcessImportedMemory(Handle<WasmInstanceObject> instance, |
| int import_index, |
| Handle<String> module_name, |
| Handle<String> import_name, |
| Handle<Object> value) { |
| if (!value->IsWasmMemoryObject()) { |
| ReportLinkError("memory import must be a WebAssembly.Memory object", |
| import_index, module_name, import_name); |
| return false; |
| } |
| auto memory_object = Handle<WasmMemoryObject>::cast(value); |
| |
| // The imported memory should have been already set up early. |
| CHECK_EQ(instance->memory_object(), *memory_object); |
| |
| Handle<JSArrayBuffer> buffer(memory_object_->array_buffer(), isolate_); |
| // memory_ should have already been assigned in Build(). |
| DCHECK_EQ(*memory_buffer_.ToHandleChecked(), *buffer); |
| uint32_t imported_cur_pages = |
| static_cast<uint32_t>(buffer->byte_length() / kWasmPageSize); |
| if (imported_cur_pages < module_->initial_pages) { |
| thrower_->LinkError("memory import %d is smaller than initial %u, got %u", |
| import_index, module_->initial_pages, |
| imported_cur_pages); |
| return false; |
| } |
| int32_t imported_maximum_pages = memory_object_->maximum_pages(); |
| if (module_->has_maximum_pages) { |
| if (imported_maximum_pages < 0) { |
| thrower_->LinkError( |
| "memory import %d has no maximum limit, expected at most %u", |
| import_index, imported_maximum_pages); |
| return false; |
| } |
| if (static_cast<uint32_t>(imported_maximum_pages) > |
| module_->maximum_pages) { |
| thrower_->LinkError( |
| "memory import %d has a larger maximum size %u than the " |
| "module's declared maximum %u", |
| import_index, imported_maximum_pages, module_->maximum_pages); |
| return false; |
| } |
| } |
| if (module_->has_shared_memory != buffer->is_shared()) { |
| thrower_->LinkError( |
| "mismatch in shared state of memory, declared = %d, imported = %d", |
| module_->has_shared_memory, buffer->is_shared()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool InstanceBuilder::ProcessImportedWasmGlobalObject( |
| Handle<WasmInstanceObject> instance, int import_index, |
| Handle<String> module_name, Handle<String> import_name, |
| const WasmGlobal& global, Handle<WasmGlobalObject> global_object) { |
| if (static_cast<bool>(global_object->is_mutable()) != global.mutability) { |
| ReportLinkError("imported global does not match the expected mutability", |
| import_index, module_name, import_name); |
| return false; |
| } |
| |
| const WasmModule* global_type_module = |
| !global_object->instance().IsUndefined() |
| ? WasmInstanceObject::cast(global_object->instance()).module() |
| : instance->module(); |
| |
| bool valid_type = |
| global.mutability |
| ? EquivalentTypes(global_object->type(), global.type, |
| global_type_module, instance->module()) |
| : IsSubtypeOf(global_object->type(), global.type, global_type_module, |
| instance->module()); |
| |
| if (!valid_type) { |
| ReportLinkError("imported global does not match the expected type", |
| import_index, module_name, import_name); |
| return false; |
| } |
| if (global.mutability) { |
| DCHECK_LT(global.index, module_->num_imported_mutable_globals); |
| Handle<Object> buffer; |
| Address address_or_offset; |
| if (global.type.is_reference_type()) { |
| static_assert(sizeof(global_object->offset()) <= sizeof(Address), |
| "The offset into the globals buffer does not fit into " |
| "the imported_mutable_globals array"); |
| buffer = handle(global_object->tagged_buffer(), isolate_); |
| // For externref globals we use a relative offset, not an absolute |
| // address. |
| address_or_offset = static_cast<Address>(global_object->offset()); |
| } else { |
| buffer = handle(global_object->untagged_buffer(), isolate_); |
| // It is safe in this case to store the raw pointer to the buffer |
| // since the backing store of the JSArrayBuffer will not be |
| // relocated. |
| address_or_offset = reinterpret_cast<Address>(raw_buffer_ptr( |
| Handle<JSArrayBuffer>::cast(buffer), global_object->offset())); |
| } |
| instance->imported_mutable_globals_buffers().set(global.index, *buffer); |
| instance->imported_mutable_globals()[global.index] = address_or_offset; |
| return true; |
| } |
| |
| WriteGlobalValue(global, global_object); |
| return true; |
| } |
| |
| bool InstanceBuilder::ProcessImportedGlobal(Handle<WasmInstanceObject> instance, |
| int import_index, int global_index, |
| Handle<String> module_name, |
| Handle<String> import_name, |
| Handle<Object> value) { |
| // Immutable global imports are converted to numbers and written into |
| // the {untagged_globals_} array buffer. |
| // |
| // Mutable global imports instead have their backing array buffers |
| // referenced by this instance, and store the address of the imported |
| // global in the {imported_mutable_globals_} array. |
| const WasmGlobal& global = module_->globals[global_index]; |
| |
| // The mutable-global proposal allows importing i64 values, but only if |
| // they are passed as a WebAssembly.Global object. |
| // |
| // However, the bigint proposal allows importing constant i64 values, |
| // as non WebAssembly.Global object. |
| if (global.type == kWasmI64 && !enabled_.has_bigint() && |
| !value->IsWasmGlobalObject()) { |
| ReportLinkError("global import cannot have type i64", import_index, |
| module_name, import_name); |
| return false; |
| } |
| |
| // SIMD proposal allows modules to define an imported v128 global, and only |
| // supports importing a WebAssembly.Global object for this global, but also |
| // defines constructing a WebAssembly.Global of v128 to be a TypeError. |
| // We *should* never hit this case in the JS API, but the module should should |
| // be allowed to declare such a global (no validation error). |
| if (global.type == kWasmS128 && !value->IsWasmGlobalObject()) { |
| ReportLinkError("global import of type v128 must be a WebAssembly.Global", |
| import_index, module_name, import_name); |
| return false; |
| } |
| |
| if (is_asmjs_module(module_)) { |
| // Accepting {JSFunction} on top of just primitive values here is a |
| // workaround to support legacy asm.js code with broken binding. Note |
| // that using {NaN} (or Smi::zero()) here is what using the observable |
| // conversion via {ToPrimitive} would produce as well. |
| // TODO(wasm): Still observable if Function.prototype.valueOf or friends |
| // are patched, we might need to check for that as well. |
| if (value->IsJSFunction()) value = isolate_->factory()->nan_value(); |
| if (value->IsPrimitive() && !value->IsSymbol()) { |
| if (global.type == kWasmI32) { |
| value = Object::ToInt32(isolate_, value).ToHandleChecked(); |
| } else { |
| value = Object::ToNumber(isolate_, value).ToHandleChecked(); |
| } |
| } |
| } |
| |
| if (value->IsWasmGlobalObject()) { |
| auto global_object = Handle<WasmGlobalObject>::cast(value); |
| return ProcessImportedWasmGlobalObject(instance, import_index, module_name, |
| import_name, global, global_object); |
| } |
| |
| if (global.mutability) { |
| ReportLinkError( |
| "imported mutable global must be a WebAssembly.Global object", |
| import_index, module_name, import_name); |
| return false; |
| } |
| |
| if (global.type.is_reference_type()) { |
| const char* error_message; |
| if (!wasm::TypecheckJSObject(isolate_, module_, value, global.type, |
| &error_message)) { |
| ReportLinkError(error_message, global_index, module_name, import_name); |
| return false; |
| } |
| WriteGlobalExternRef(global, value); |
| return true; |
| } |
| |
| if (value->IsNumber() && global.type != kWasmI64) { |
| WriteGlobalValue(global, value->Number()); |
| return true; |
| } |
| |
| if (enabled_.has_bigint() && global.type == kWasmI64 && value->IsBigInt()) { |
| WriteGlobalValue(global, BigInt::cast(*value).AsInt64()); |
| return true; |
| } |
| |
| ReportLinkError( |
| "global import must be a number, valid Wasm reference, or " |
| "WebAssembly.Global object", |
| import_index, module_name, import_name); |
| return false; |
| } |
| |
| void InstanceBuilder::CompileImportWrappers( |
| Handle<WasmInstanceObject> instance) { |
| int num_imports = static_cast<int>(module_->import_table.size()); |
| NativeModule* native_module = instance->module_object().native_module(); |
| WasmImportWrapperCache::ModificationScope cache_scope( |
| native_module->import_wrapper_cache()); |
| |
| // Compilation is done in two steps: |
| // 1) Insert nullptr entries in the cache for wrappers that need to be |
| // compiled. 2) Compile wrappers in background tasks using the |
| // ImportWrapperQueue. This way the cache won't invalidate other iterators |
| // when inserting a new WasmCode, since the key will already be there. |
| ImportWrapperQueue import_wrapper_queue; |
| for (int index = 0; index < num_imports; ++index) { |
| Handle<Object> value = sanitized_imports_[index].value; |
| if (module_->import_table[index].kind != kExternalFunction || |
| !value->IsCallable()) { |
| continue; |
| } |
| auto js_receiver = Handle<JSReceiver>::cast(value); |
| uint32_t func_index = module_->import_table[index].index; |
| const FunctionSig* sig = module_->functions[func_index].sig; |
| auto resolved = |
| compiler::ResolveWasmImportCall(js_receiver, sig, module_, enabled_); |
| compiler::WasmImportCallKind kind = resolved.first; |
| if (kind == compiler::WasmImportCallKind::kWasmToWasm || |
| kind == compiler::WasmImportCallKind::kLinkError || |
| kind == compiler::WasmImportCallKind::kWasmToCapi) { |
| continue; |
| } |
| |
| int expected_arity = static_cast<int>(sig->parameter_count()); |
| if (resolved.first == |
| compiler::WasmImportCallKind::kJSFunctionArityMismatch) { |
| Handle<JSFunction> function = Handle<JSFunction>::cast(resolved.second); |
| SharedFunctionInfo shared = function->shared(); |
| expected_arity = shared.internal_formal_parameter_count(); |
| } |
| |
| WasmImportWrapperCache::CacheKey key(kind, sig, expected_arity); |
| if (cache_scope[key] != nullptr) { |
| // Cache entry already exists, no need to compile it again. |
| continue; |
| } |
| import_wrapper_queue.insert(key); |
| } |
| |
| auto compile_job_task = std::make_unique<CompileImportWrapperJob>( |
| isolate_->wasm_engine(), isolate_->counters(), native_module, |
| &import_wrapper_queue, &cache_scope); |
| auto compile_job = V8::GetCurrentPlatform()->PostJob( |
| TaskPriority::kUserVisible, std::move(compile_job_task)); |
| |
| // Wait for the job to finish, while contributing in this thread. |
| compile_job->Join(); |
| } |
| |
| // Process the imports, including functions, tables, globals, and memory, in |
| // order, loading them from the {ffi_} object. Returns the number of imported |
| // functions. |
| int InstanceBuilder::ProcessImports(Handle<WasmInstanceObject> instance) { |
| int num_imported_functions = 0; |
| int num_imported_tables = 0; |
| |
| DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size()); |
| |
| CompileImportWrappers(instance); |
| int num_imports = static_cast<int>(module_->import_table.size()); |
| for (int index = 0; index < num_imports; ++index) { |
| const WasmImport& import = module_->import_table[index]; |
| |
| Handle<String> module_name = sanitized_imports_[index].module_name; |
| Handle<String> import_name = sanitized_imports_[index].import_name; |
| Handle<Object> value = sanitized_imports_[index].value; |
| |
| switch (import.kind) { |
| case kExternalFunction: { |
| uint32_t func_index = import.index; |
| DCHECK_EQ(num_imported_functions, func_index); |
| if (!ProcessImportedFunction(instance, index, func_index, module_name, |
| import_name, value)) { |
| return -1; |
| } |
| num_imported_functions++; |
| break; |
| } |
| case kExternalTable: { |
| uint32_t table_index = import.index; |
| DCHECK_EQ(table_index, num_imported_tables); |
| if (!ProcessImportedTable(instance, index, table_index, module_name, |
| import_name, value)) { |
| return -1; |
| } |
| num_imported_tables++; |
| break; |
| } |
| case kExternalMemory: { |
| if (!ProcessImportedMemory(instance, index, module_name, import_name, |
| value)) { |
| return -1; |
| } |
| break; |
| } |
| case kExternalGlobal: { |
| if (!ProcessImportedGlobal(instance, index, import.index, module_name, |
| import_name, value)) { |
| return -1; |
| } |
| break; |
| } |
| case kExternalException: { |
| if (!value->IsWasmExceptionObject()) { |
| ReportLinkError("exception import requires a WebAssembly.Exception", |
| index, module_name, import_name); |
| return -1; |
| } |
| Handle<WasmExceptionObject> imported_exception = |
| Handle<WasmExceptionObject>::cast(value); |
| if (!imported_exception->MatchesSignature( |
| module_->exceptions[import.index].sig)) { |
| ReportLinkError("imported exception does not match the expected type", |
| index, module_name, import_name); |
| return -1; |
| } |
| Object exception_tag = imported_exception->exception_tag(); |
| DCHECK(instance->exceptions_table().get(import.index).IsUndefined()); |
| instance->exceptions_table().set(import.index, exception_tag); |
| exception_wrappers_[import.index] = imported_exception; |
| break; |
| } |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| } |
| return num_imported_functions; |
| } |
| |
| template <typename T> |
| T* InstanceBuilder::GetRawGlobalPtr(const WasmGlobal& global) { |
| return reinterpret_cast<T*>(raw_buffer_ptr(untagged_globals_, global.offset)); |
| } |
| |
| Handle<Object> InstanceBuilder::RecursivelyEvaluateGlobalInitializer( |
| const WasmInitExpr& init, Handle<WasmInstanceObject> instance) { |
| switch (init.kind()) { |
| case WasmInitExpr::kI32Const: |
| case WasmInitExpr::kI64Const: |
| case WasmInitExpr::kF32Const: |
| case WasmInitExpr::kF64Const: |
| case WasmInitExpr::kS128Const: |
| case WasmInitExpr::kRefNullConst: |
| case WasmInitExpr::kRefFuncConst: |
| case WasmInitExpr::kNone: |
| // Handled directly by {InitGlobals()}, can't occur as recursive case. |
| UNREACHABLE(); |
| break; |
| case WasmInitExpr::kGlobalGet: { |
| // We can only get here for reference-type globals, but we don't have |
| // enough information to DCHECK that directly. |
| DCHECK(enabled_.has_reftypes() || enabled_.has_eh()); |
| uint32_t old_offset = module_->globals[init.immediate().index].offset; |
| DCHECK(static_cast<int>(old_offset) < tagged_globals_->length()); |
| return handle(tagged_globals_->get(old_offset), isolate_); |
| } |
| case WasmInitExpr::kRttCanon: { |
| switch (init.immediate().heap_type) { |
| case wasm::HeapType::kEq: |
| return isolate_->root_handle(RootIndex::kWasmRttEqrefMap); |
| case wasm::HeapType::kExtern: |
| return isolate_->root_handle(RootIndex::kWasmRttExternrefMap); |
| case wasm::HeapType::kFunc: |
| return isolate_->root_handle(RootIndex::kWasmRttFuncrefMap); |
| case wasm::HeapType::kI31: |
| return isolate_->root_handle(RootIndex::kWasmRttI31refMap); |
| case wasm::HeapType::kExn: |
| UNIMPLEMENTED(); // TODO(jkummerow): This is going away? |
| case wasm::HeapType::kBottom: |
| UNREACHABLE(); |
| } |
| // Non-generic types fall through. |
| int map_index = init.immediate().heap_type; |
| return handle(instance->managed_object_maps().get(map_index), isolate_); |
| } |
| case WasmInitExpr::kRttSub: { |
| uint32_t type = static_cast<uint32_t>(init.immediate().heap_type); |
| Handle<Object> parent = |
| RecursivelyEvaluateGlobalInitializer(*init.operand(), instance); |
| return AllocateSubRtt(isolate_, instance, type, |
| Handle<Map>::cast(parent)); |
| } |
| } |
| } |
| |
| // Process initialization of globals. |
| void InstanceBuilder::InitGlobals(Handle<WasmInstanceObject> instance) { |
| for (const WasmGlobal& global : module_->globals) { |
| if (global.mutability && global.imported) { |
| continue; |
| } |
| |
| switch (global.init.kind()) { |
| case WasmInitExpr::kI32Const: |
| WriteLittleEndianValue<int32_t>(GetRawGlobalPtr<int32_t>(global), |
| global.init.immediate().i32_const); |
| break; |
| case WasmInitExpr::kI64Const: |
| WriteLittleEndianValue<int64_t>(GetRawGlobalPtr<int64_t>(global), |
| global.init.immediate().i64_const); |
| break; |
| case WasmInitExpr::kF32Const: |
| WriteLittleEndianValue<float>(GetRawGlobalPtr<float>(global), |
| global.init.immediate().f32_const); |
| break; |
| case WasmInitExpr::kF64Const: |
| WriteLittleEndianValue<double>(GetRawGlobalPtr<double>(global), |
| global.init.immediate().f64_const); |
| break; |
| case WasmInitExpr::kS128Const: |
| DCHECK(enabled_.has_simd()); |
| WriteLittleEndianValue<std::array<uint8_t, kSimd128Size>>( |
| GetRawGlobalPtr<std::array<uint8_t, kSimd128Size>>(global), |
| global.init.immediate().s128_const); |
| break; |
| case WasmInitExpr::kRefNullConst: |
| DCHECK(enabled_.has_reftypes() || enabled_.has_eh()); |
| if (global.imported) break; // We already initialized imported globals. |
| |
| tagged_globals_->set(global.offset, |
| ReadOnlyRoots(isolate_).null_value(), |
| SKIP_WRITE_BARRIER); |
| break; |
| case WasmInitExpr::kRefFuncConst: { |
| DCHECK(enabled_.has_reftypes()); |
| auto function = WasmInstanceObject::GetOrCreateWasmExternalFunction( |
| isolate_, instance, global.init.immediate().index); |
| tagged_globals_->set(global.offset, *function); |
| break; |
| } |
| case WasmInitExpr::kGlobalGet: { |
| // Initialize with another global. |
| uint32_t new_offset = global.offset; |
| uint32_t old_offset = |
| module_->globals[global.init.immediate().index].offset; |
| TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); |
| if (global.type.is_reference_type()) { |
| DCHECK(enabled_.has_reftypes() || enabled_.has_eh()); |
| tagged_globals_->set(new_offset, tagged_globals_->get(old_offset)); |
| } else { |
| size_t size = (global.type == kWasmI64 || global.type == kWasmF64) |
| ? sizeof(double) |
| : sizeof(int32_t); |
| memcpy(raw_buffer_ptr(untagged_globals_, new_offset), |
| raw_buffer_ptr(untagged_globals_, old_offset), size); |
| } |
| break; |
| } |
| case WasmInitExpr::kRttCanon: |
| case WasmInitExpr::kRttSub: |
| tagged_globals_->set( |
| global.offset, |
| *RecursivelyEvaluateGlobalInitializer(global.init, instance)); |
| break; |
| case WasmInitExpr::kNone: |
| // Happens with imported globals. |
| break; |
| } |
| } |
| } |
| |
| // Allocate memory for a module instance as a new JSArrayBuffer. |
| bool InstanceBuilder::AllocateMemory() { |
| uint32_t initial_pages = module_->initial_pages; |
| uint32_t maximum_pages = |
| module_->has_maximum_pages ? module_->maximum_pages : max_mem_pages(); |
| if (initial_pages > max_mem_pages()) { |
| thrower_->RangeError("Out of memory: wasm memory too large"); |
| return false; |
| } |
| auto shared = (module_->has_shared_memory && enabled_.has_threads()) |
| ? SharedFlag::kShared |
| : SharedFlag::kNotShared; |
| |
| MaybeHandle<WasmMemoryObject> result = |
| WasmMemoryObject::New(isolate_, initial_pages, maximum_pages, shared); |
| |
| if (!result.ToHandle(&memory_object_)) { |
| thrower_->RangeError("Out of memory: wasm memory"); |
| return false; |
| } |
| memory_buffer_ = |
| Handle<JSArrayBuffer>(memory_object_->array_buffer(), isolate_); |
| return true; |
| } |
| |
| // Process the exports, creating wrappers for functions, tables, memories, |
| // globals, and exceptions. |
| void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) { |
| std::unordered_map<int, Handle<Object>> imported_globals; |
| |
| // If an imported WebAssembly function or global gets exported, the export |
| // has to be identical to to import. Therefore we cache all imported |
| // WebAssembly functions in the instance, and all imported globals in a map |
| // here. |
| for (int index = 0, end = static_cast<int>(module_->import_table.size()); |
| index < end; ++index) { |
| const WasmImport& import = module_->import_table[index]; |
| if (import.kind == kExternalFunction) { |
| Handle<Object> value = sanitized_imports_[index].value; |
| if (WasmExternalFunction::IsWasmExternalFunction(*value)) { |
| WasmInstanceObject::SetWasmExternalFunction( |
| isolate_, instance, import.index, |
| Handle<WasmExternalFunction>::cast(value)); |
| } |
| } else if (import.kind == kExternalGlobal) { |
| Handle<Object> value = sanitized_imports_[index].value; |
| if (value->IsWasmGlobalObject()) { |
| imported_globals[import.index] = value; |
| } |
| } |
| } |
| |
| Handle<JSObject> exports_object; |
| MaybeHandle<String> single_function_name; |
| bool is_asm_js = is_asmjs_module(module_); |
| if (is_asm_js) { |
| Handle<JSFunction> object_function = Handle<JSFunction>( |
| isolate_->native_context()->object_function(), isolate_); |
| exports_object = isolate_->factory()->NewJSObject(object_function); |
| single_function_name = |
| isolate_->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName); |
| } else { |
| exports_object = isolate_->factory()->NewJSObjectWithNullProto(); |
| } |
| instance->set_exports_object(*exports_object); |
| |
| PropertyDescriptor desc; |
| desc.set_writable(is_asm_js); |
| desc.set_enumerable(true); |
| desc.set_configurable(is_asm_js); |
| |
| // Process each export in the export table. |
| for (const WasmExport& exp : module_->export_table) { |
| Handle<String> name = WasmModuleObject::ExtractUtf8StringFromModuleBytes( |
| isolate_, module_object_, exp.name, kInternalize); |
| Handle<JSObject> export_to = exports_object; |
| switch (exp.kind) { |
| case kExternalFunction: { |
| // Wrap and export the code as a JSFunction. |
| // TODO(wasm): reduce duplication with LoadElemSegment() further below |
| Handle<WasmExternalFunction> wasm_external_function = |
| WasmInstanceObject::GetOrCreateWasmExternalFunction( |
| isolate_, instance, exp.index); |
| desc.set_value(wasm_external_function); |
| |
| if (is_asm_js && |
| String::Equals(isolate_, name, |
| single_function_name.ToHandleChecked())) { |
| export_to = instance; |
| } |
| break; |
| } |
| case kExternalTable: { |
| desc.set_value(handle(instance->tables().get(exp.index), isolate_)); |
| break; |
| } |
| case kExternalMemory: { |
| // Export the memory as a WebAssembly.Memory object. A WasmMemoryObject |
| // should already be available if the module has memory, since we always |
| // create or import it when building an WasmInstanceObject. |
| DCHECK(instance->has_memory_object()); |
| desc.set_value( |
| Handle<WasmMemoryObject>(instance->memory_object(), isolate_)); |
| break; |
| } |
| case kExternalGlobal: { |
| const WasmGlobal& global = module_->globals[exp.index]; |
| if (global.imported) { |
| auto cached_global = imported_globals.find(exp.index); |
| if (cached_global != imported_globals.end()) { |
| desc.set_value(cached_global->second); |
| break; |
| } |
| } |
| Handle<JSArrayBuffer> untagged_buffer; |
| Handle<FixedArray> tagged_buffer; |
| uint32_t offset; |
| |
| if (global.mutability && global.imported) { |
| Handle<FixedArray> buffers_array( |
| instance->imported_mutable_globals_buffers(), isolate_); |
| if (global.type.is_reference_type()) { |
| tagged_buffer = handle( |
| FixedArray::cast(buffers_array->get(global.index)), isolate_); |
| // For externref globals we store the relative offset in the |
| // imported_mutable_globals array instead of an absolute address. |
| Address addr = instance->imported_mutable_globals()[global.index]; |
| DCHECK_LE(addr, static_cast<Address>( |
| std::numeric_limits<uint32_t>::max())); |
| offset = static_cast<uint32_t>(addr); |
| } else { |
| untagged_buffer = |
| handle(JSArrayBuffer::cast(buffers_array->get(global.index)), |
| isolate_); |
| Address global_addr = |
| instance->imported_mutable_globals()[global.index]; |
| |
| size_t buffer_size = untagged_buffer->byte_length(); |
| Address backing_store = |
| reinterpret_cast<Address>(untagged_buffer->backing_store()); |
| CHECK(global_addr >= backing_store && |
| global_addr < backing_store + buffer_size); |
| offset = static_cast<uint32_t>(global_addr - backing_store); |
| } |
| } else { |
| if (global.type.is_reference_type()) { |
| tagged_buffer = handle(instance->tagged_globals_buffer(), isolate_); |
| } else { |
| untagged_buffer = |
| handle(instance->untagged_globals_buffer(), isolate_); |
| } |
| offset = global.offset; |
| } |
| |
| // Since the global's array untagged_buffer is always provided, |
| // allocation should never fail. |
| Handle<WasmGlobalObject> global_obj = |
| WasmGlobalObject::New(isolate_, instance, untagged_buffer, |
| tagged_buffer, global.type, offset, |
| global.mutability) |
| .ToHandleChecked(); |
| desc.set_value(global_obj); |
| break; |
| } |
| case kExternalException: { |
| const WasmException& exception = module_->exceptions[exp.index]; |
| Handle<WasmExceptionObject> wrapper = exception_wrappers_[exp.index]; |
| if (wrapper.is_null()) { |
| Handle<HeapObject> exception_tag( |
| HeapObject::cast(instance->exceptions_table().get(exp.index)), |
| isolate_); |
| wrapper = |
| WasmExceptionObject::New(isolate_, exception.sig, exception_tag); |
| exception_wrappers_[exp.index] = wrapper; |
| } |
| desc.set_value(wrapper); |
| break; |
| } |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| |
| v8::Maybe<bool> status = JSReceiver::DefineOwnProperty( |
| isolate_, export_to, name, &desc, Just(kThrowOnError)); |
| if (!status.IsJust()) { |
| DisallowHeapAllocation no_gc; |
| TruncatedUserString<> trunc_name(name->GetCharVector<uint8_t>(no_gc)); |
| thrower_->LinkError("export of %.*s failed.", trunc_name.length(), |
| trunc_name.start()); |
| return; |
| } |
| } |
| |
| if (module_->origin == kWasmOrigin) { |
| v8::Maybe<bool> success = |
| JSReceiver::SetIntegrityLevel(exports_object, FROZEN, kDontThrow); |
| DCHECK(success.FromMaybe(false)); |
| USE(success); |
| } |
| } |
| |
| void InstanceBuilder::InitializeIndirectFunctionTables( |
| Handle<WasmInstanceObject> instance) { |
| for (int i = 0; i < static_cast<int>(module_->tables.size()); ++i) { |
| const WasmTable& table = module_->tables[i]; |
| |
| if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) { |
| WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( |
| instance, i, table.initial_size); |
| } |
| } |
| } |
| |
| bool LoadElemSegmentImpl(Isolate* isolate, Handle<WasmInstanceObject> instance, |
| Handle<WasmTableObject> table_object, |
| uint32_t table_index, uint32_t segment_index, |
| uint32_t dst, uint32_t src, size_t count) { |
| DCHECK_LT(segment_index, instance->module()->elem_segments.size()); |
| auto& elem_segment = instance->module()->elem_segments[segment_index]; |
| // TODO(wasm): Move this functionality into wasm-objects, since it is used |
| // for both instantiation and in the implementation of the table.init |
| // instruction. |
| if (!base::IsInBounds<uint64_t>(dst, count, table_object->current_length()) || |
| !base::IsInBounds<uint64_t>( |
| src, count, |
| instance->dropped_elem_segments()[segment_index] == 0 |
| ? elem_segment.entries.size() |
| : 0)) { |
| return false; |
| } |
| |
| const WasmModule* module = instance->module(); |
| for (size_t i = 0; i < count; ++i) { |
| uint32_t func_index = elem_segment.entries[src + i]; |
| int entry_index = static_cast<int>(dst + i); |
| |
| if (func_index == WasmElemSegment::kNullIndex) { |
| if (IsSubtypeOf(table_object->type(), kWasmFuncRef, module)) { |
| IndirectFunctionTableEntry(instance, table_index, entry_index).clear(); |
| } |
| WasmTableObject::Set(isolate, table_object, entry_index, |
| isolate->factory()->null_value()); |
| continue; |
| } |
| |
| const WasmFunction* function = &module->functions[func_index]; |
| |
| // Update the local dispatch table first if necessary. |
| if (IsSubtypeOf(table_object->type(), kWasmFuncRef, module)) { |
| uint32_t sig_id = module->canonicalized_type_ids[function->sig_index]; |
| IndirectFunctionTableEntry(instance, table_index, entry_index) |
| .Set(sig_id, instance, func_index); |
| } |
| |
| // For ExternRef tables, we have to generate the WasmExternalFunction |
| // eagerly. Later we cannot know if an entry is a placeholder or not. |
| if (table_object->type().is_reference_to(HeapType::kExtern)) { |
| Handle<WasmExternalFunction> wasm_external_function = |
| WasmInstanceObject::GetOrCreateWasmExternalFunction(isolate, instance, |
| func_index); |
| WasmTableObject::Set(isolate, table_object, entry_index, |
| wasm_external_function); |
| } else { |
| // Update the table object's other dispatch tables. |
| MaybeHandle<WasmExternalFunction> wasm_external_function = |
| WasmInstanceObject::GetWasmExternalFunction(isolate, instance, |
| func_index); |
| if (wasm_external_function.is_null()) { |
| // No JSFunction entry yet exists for this function. Create a {Tuple2} |
| // holding the information to lazily allocate one. |
| WasmTableObject::SetFunctionTablePlaceholder( |
| isolate, table_object, entry_index, instance, func_index); |
| } else { |
| table_object->entries().set(entry_index, |
| *wasm_external_function.ToHandleChecked()); |
| } |
| // UpdateDispatchTables() updates all other dispatch tables, since |
| // we have not yet added the dispatch table we are currently building. |
| WasmTableObject::UpdateDispatchTables(isolate, table_object, entry_index, |
| function->sig, instance, |
| func_index); |
| } |
| } |
| return true; |
| } |
| |
| void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) { |
| for (uint32_t segment_index = 0; |
| segment_index < module_->elem_segments.size(); ++segment_index) { |
| auto& elem_segment = instance->module()->elem_segments[segment_index]; |
| // Passive segments are not copied during instantiation. |
| if (elem_segment.status != WasmElemSegment::kStatusActive) continue; |
| |
| uint32_t table_index = elem_segment.table_index; |
| uint32_t dst = EvalUint32InitExpr(instance, elem_segment.offset); |
| uint32_t src = 0; |
| size_t count = elem_segment.entries.size(); |
| |
| bool success = LoadElemSegmentImpl( |
| isolate_, instance, |
| handle(WasmTableObject::cast( |
| instance->tables().get(elem_segment.table_index)), |
| isolate_), |
| table_index, segment_index, dst, src, count); |
| // 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_elem_segments()[segment_index] = 1; |
| if (enabled_.has_bulk_memory()) { |
| if (!success) { |
| thrower_->RuntimeError("table initializer is out of bounds"); |
| // Break out instead of returning; we don't want to continue to |
| // initialize any further element segments, but still need to add |
| // dispatch tables below. |
| break; |
| } |
| } else { |
| CHECK(success); |
| } |
| } |
| |
| int table_count = static_cast<int>(module_->tables.size()); |
| for (int index = 0; index < table_count; ++index) { |
| if (IsSubtypeOf(module_->tables[index].type, kWasmFuncRef, module_)) { |
| auto table_object = handle( |
| WasmTableObject::cast(instance->tables().get(index)), isolate_); |
| |
| // Add the new dispatch table at the end to avoid redundant lookups. |
| WasmTableObject::AddDispatchTable(isolate_, table_object, instance, |
| index); |
| } |
| } |
| } |
| |
| void InstanceBuilder::InitializeExceptions( |
| Handle<WasmInstanceObject> instance) { |
| Handle<FixedArray> exceptions_table(instance->exceptions_table(), isolate_); |
| for (int index = 0; index < exceptions_table->length(); ++index) { |
| if (!exceptions_table->get(index).IsUndefined(isolate_)) continue; |
| Handle<WasmExceptionTag> exception_tag = |
| WasmExceptionTag::New(isolate_, index); |
| exceptions_table->set(index, *exception_tag); |
| } |
| } |
| |
| bool LoadElemSegment(Isolate* isolate, Handle<WasmInstanceObject> instance, |
| uint32_t table_index, uint32_t segment_index, uint32_t dst, |
| uint32_t src, uint32_t count) { |
| return LoadElemSegmentImpl( |
| isolate, instance, |
| handle(WasmTableObject::cast(instance->tables().get(table_index)), |
| isolate), |
| table_index, segment_index, dst, src, count); |
| } |
| |
| } // namespace wasm |
| } // namespace internal |
| } // namespace v8 |
| |
| #undef TRACE |