| // Copyright 2017 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 "test/common/wasm/wasm-run-utils.h" |
| |
| #include <optional> |
| |
| #include "src/codegen/assembler-inl.h" |
| #include "src/compiler/pipeline.h" |
| #include "src/diagnostics/code-tracer.h" |
| #include "src/heap/heap-inl.h" |
| #include "src/wasm/baseline/liftoff-compiler.h" |
| #include "src/wasm/code-space-access.h" |
| #include "src/wasm/compilation-environment-inl.h" |
| #include "src/wasm/leb-helper.h" |
| #include "src/wasm/module-compiler.h" |
| #include "src/wasm/module-instantiate.h" |
| #include "src/wasm/wasm-code-pointer-table-inl.h" |
| #include "src/wasm/wasm-engine.h" |
| #include "src/wasm/wasm-import-wrapper-cache.h" |
| #include "src/wasm/wasm-objects-inl.h" |
| #include "src/wasm/wasm-opcodes.h" |
| #include "src/wasm/wasm-subtyping.h" |
| |
| namespace v8::internal::wasm { |
| |
| // Helper Functions. |
| bool IsSameNan(uint16_t expected, uint16_t actual) { |
| // Sign is non-deterministic. |
| uint16_t expected_bits = expected & ~0x8000; |
| uint16_t actual_bits = actual & ~0x8000; |
| return (expected_bits == actual_bits); |
| } |
| |
| bool IsSameNan(float expected, float actual) { |
| // Sign is non-deterministic. |
| uint32_t expected_bits = base::bit_cast<uint32_t>(expected) & ~0x80000000; |
| uint32_t actual_bits = base::bit_cast<uint32_t>(actual) & ~0x80000000; |
| // Some implementations convert signaling NaNs to quiet NaNs. |
| return (expected_bits == actual_bits) || |
| ((expected_bits | 0x00400000) == actual_bits); |
| } |
| |
| bool IsSameNan(double expected, double actual) { |
| // Sign is non-deterministic. |
| uint64_t expected_bits = |
| base::bit_cast<uint64_t>(expected) & ~0x8000000000000000; |
| uint64_t actual_bits = base::bit_cast<uint64_t>(actual) & ~0x8000000000000000; |
| // Some implementations convert signaling NaNs to quiet NaNs. |
| return (expected_bits == actual_bits) || |
| ((expected_bits | 0x0008000000000000) == actual_bits); |
| } |
| |
| TestingModuleBuilder::TestingModuleBuilder( |
| Zone* zone, ModuleOrigin origin, ManuallyImportedJSFunction* maybe_import, |
| TestExecutionTier tier, Isolate* isolate) |
| : test_module_(std::make_shared<WasmModule>(origin)), |
| isolate_(isolate), |
| enabled_features_(WasmEnabledFeatures::FromIsolate(isolate_)), |
| execution_tier_(tier) { |
| // In this test setup, the NativeModule gets allocated before functions get |
| // added. The tiering budget array, which gets allocated in the NativeModule |
| // constructor, therefore does not have slots for functions that get added |
| // later. By disabling dynamic tiering, the tiering budget does not get |
| // accessed by generated code. |
| v8_flags.wasm_dynamic_tiering = false; |
| |
| WasmJs::Install(isolate_); |
| test_module_->untagged_globals_buffer_size = kMaxGlobalsSize; |
| // The GlobalsData must be located inside the sandbox, so allocate it from the |
| // ArrayBuffer allocator. |
| globals_data_ = reinterpret_cast<uint8_t*>( |
| isolate->array_buffer_allocator()->Allocate(kMaxGlobalsSize)); |
| |
| uint32_t maybe_import_index = 0; |
| if (maybe_import) { |
| // Manually add an imported function before any other functions. |
| // This must happen before the instance object is created, since the |
| // instance object allocates import entries. |
| maybe_import_index = AddFunction(maybe_import->sig, nullptr, kImport); |
| DCHECK_EQ(0, maybe_import_index); |
| } |
| |
| instance_object_ = InitInstanceObject(); |
| trusted_instance_data_ = |
| direct_handle(instance_object_->trusted_data(isolate_), isolate_); |
| trusted_instance_data_->set_tables( |
| ReadOnlyRoots{isolate_}.empty_fixed_array()); |
| |
| if (maybe_import) { |
| WasmCodeRefScope code_ref_scope; |
| // Manually compile an import wrapper and insert it into the instance. |
| CanonicalTypeIndex sig_index = |
| GetTypeCanonicalizer()->AddRecursiveGroup(maybe_import->sig); |
| const wasm::CanonicalSig* sig = |
| GetTypeCanonicalizer()->LookupFunctionSignature(sig_index); |
| ResolvedWasmImport resolved({}, -1, maybe_import->js_function, sig, |
| WellKnownImport::kUninstantiated); |
| ImportCallKind kind = resolved.kind(); |
| DirectHandle<JSReceiver> callable = resolved.callable(); |
| std::shared_ptr<wasm::WasmImportWrapperHandle> wrapper_handle = |
| GetWasmImportWrapperCache()->GetCompiled( |
| isolate, kind, static_cast<int>(sig->parameter_count()), kNoSuspend, |
| sig); |
| |
| ImportedFunctionEntry(trusted_instance_data_, maybe_import_index) |
| .SetWasmToWrapper(isolate_, callable, std::move(wrapper_handle), |
| resolved.suspend(), sig); |
| } |
| } |
| |
| TestingModuleBuilder::~TestingModuleBuilder() { |
| // When the native module dies and is erased from the cache, it is expected to |
| // have either valid bytes or no bytes at all. |
| native_module_->SetWireBytes({}); |
| isolate_->array_buffer_allocator()->Free(globals_data_, kMaxGlobalsSize); |
| } |
| |
| uint8_t* TestingModuleBuilder::AddMemory(uint32_t size, SharedFlag shared, |
| AddressType address_type, |
| std::optional<size_t> max_size) { |
| // The TestingModuleBuilder only supports one memory currently. |
| CHECK_EQ(0, test_module_->memories.size()); |
| CHECK_NULL(mem0_start_); |
| CHECK_EQ(0, mem0_size_); |
| CHECK_EQ(0, trusted_instance_data_->memory_objects()->length()); |
| |
| uint32_t initial_pages = RoundUp(size, kWasmPageSize) / kWasmPageSize; |
| uint32_t maximum_pages = |
| max_size.has_value() |
| ? static_cast<uint32_t>(RoundUp(max_size.value(), kWasmPageSize) / |
| kWasmPageSize) |
| : initial_pages; |
| test_module_->memories.resize(1); |
| WasmMemory* memory = &test_module_->memories[0]; |
| memory->initial_pages = initial_pages; |
| memory->maximum_pages = maximum_pages; |
| memory->address_type = address_type; |
| UpdateComputedInformation(memory, test_module_->origin); |
| |
| // Create the WasmMemoryObject. |
| DirectHandle<WasmMemoryObject> memory_object = |
| WasmMemoryObject::New(isolate_, initial_pages, maximum_pages, shared, |
| address_type) |
| .ToHandleChecked(); |
| DirectHandle<FixedArray> memory_objects = |
| isolate_->factory()->NewFixedArray(1); |
| memory_objects->set(0, *memory_object); |
| trusted_instance_data_->set_memory_objects(*memory_objects); |
| |
| // Create the memory_bases_and_sizes array. |
| DirectHandle<TrustedFixedAddressArray> memory_bases_and_sizes = |
| TrustedFixedAddressArray::New(isolate_, 2); |
| uint8_t* mem_start = reinterpret_cast<uint8_t*>( |
| memory_object->array_buffer()->backing_store()); |
| memory_bases_and_sizes->set_sandboxed_pointer( |
| 0, reinterpret_cast<Address>(mem_start)); |
| memory_bases_and_sizes->set(1, size); |
| trusted_instance_data_->set_memory_bases_and_sizes(*memory_bases_and_sizes); |
| |
| mem0_start_ = mem_start; |
| mem0_size_ = size; |
| CHECK(size == 0 || mem0_start_); |
| |
| // TODO(14616): Add shared_trusted_instance_data_. |
| WasmMemoryObject::UseInInstance(isolate_, memory_object, |
| trusted_instance_data_, |
| trusted_instance_data_, 0); |
| // TODO(wasm): Delete the following line when test-run-wasm will use a |
| // multiple of kPageSize as memory size. At the moment, the effect of these |
| // two lines is used to shrink the memory for testing purposes. |
| trusted_instance_data_->SetRawMemory(0, mem0_start_, mem0_size_); |
| return mem0_start_; |
| } |
| |
| uint32_t TestingModuleBuilder::AddFunction(const FunctionSig* sig, |
| const char* name, |
| FunctionType type) { |
| if (test_module_->functions.size() == 0) { |
| // TODO(titzer): Reserving space here to avoid the underlying WasmFunction |
| // structs from moving. |
| test_module_->functions.reserve(kMaxFunctions); |
| DCHECK_NULL(test_module_->validated_functions); |
| test_module_->validated_functions = |
| std::make_unique<std::atomic<uint8_t>[]>((kMaxFunctions + 7) / 8); |
| if (is_asmjs_module(test_module_.get())) { |
| // All asm.js functions are valid by design. |
| std::fill_n(test_module_->validated_functions.get(), |
| (kMaxFunctions + 7) / 8, 0xff); |
| } |
| test_module_->type_feedback.well_known_imports.Initialize(kMaxFunctions); |
| } |
| uint32_t index = static_cast<uint32_t>(test_module_->functions.size()); |
| test_module_->functions.push_back({sig, // sig |
| index, // func_index |
| ModuleTypeIndex{0}, // sig_index |
| {0, 0}, // code |
| false, // imported |
| false, // exported |
| false}); // declared |
| if (type == kImport) { |
| DCHECK_EQ(0, test_module_->num_declared_functions); |
| ++test_module_->num_imported_functions; |
| test_module_->functions.back().imported = true; |
| } else { |
| ++test_module_->num_declared_functions; |
| } |
| DCHECK_EQ(test_module_->functions.size(), |
| test_module_->num_imported_functions + |
| test_module_->num_declared_functions); |
| if (name) { |
| base::Vector<const uint8_t> name_vec = |
| base::Vector<const uint8_t>::cast(base::CStrVector(name)); |
| test_module_->lazily_generated_names.AddForTesting( |
| index, {AddBytes(name_vec), static_cast<uint32_t>(name_vec.length())}); |
| } |
| DCHECK_LT(index, kMaxFunctions); // limited for testing. |
| if (!trusted_instance_data_.is_null()) { |
| DirectHandle<FixedArray> func_refs = |
| isolate_->factory()->NewFixedArrayWithZeroes( |
| static_cast<int>(test_module_->functions.size())); |
| trusted_instance_data_->set_func_refs(*func_refs); |
| } |
| return index; |
| } |
| |
| void TestingModuleBuilder::InitializeWrapperCache() { |
| TypeCanonicalizer::PrepareForCanonicalTypeId( |
| isolate_, test_module_->MaxCanonicalTypeIndex()); |
| DirectHandle<FixedArray> maps = isolate_->factory()->NewFixedArray( |
| static_cast<int>(test_module_->types.size())); |
| for (uint32_t index = 0; index < test_module_->types.size(); index++) { |
| CreateMapForType(isolate_, test_module_.get(), ModuleTypeIndex{index}, |
| maps); |
| } |
| trusted_instance_data_->set_managed_object_maps(*maps); |
| } |
| |
| DirectHandle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) { |
| InitializeWrapperCache(); |
| DirectHandle<WasmFuncRef> func_ref = |
| WasmTrustedInstanceData::GetOrCreateFuncRef( |
| isolate_, trusted_instance_data_, index, kPrecreateExternal); |
| DirectHandle<WasmInternalFunction> internal{func_ref->internal(isolate_), |
| isolate_}; |
| return WasmInternalFunction::GetOrCreateExternal(internal); |
| } |
| |
| void TestingModuleBuilder::AddIndirectFunctionTable( |
| const uint16_t* function_indexes, uint32_t table_size, |
| ValueType table_type) { |
| uint32_t table_index = static_cast<uint32_t>(test_module_->tables.size()); |
| test_module_->tables.emplace_back(); |
| WasmTable& table = test_module_->tables.back(); |
| table.initial_size = table_size; |
| table.maximum_size = table_size; |
| table.has_maximum_size = true; |
| table.type = table_type; |
| |
| DirectHandle<HeapObject> value = |
| table.type.use_wasm_null() |
| ? Cast<HeapObject>(isolate_->factory()->wasm_null()) |
| : Cast<HeapObject>(isolate_->factory()->null_value()); |
| CanonicalValueType canonical_type = test_module_->canonical_type(table.type); |
| DirectHandle<WasmDispatchTable> dispatch_table; |
| DirectHandle<WasmTableObject> table_obj = WasmTableObject::New( |
| isolate_, |
| direct_handle(instance_object_->trusted_data(isolate_), isolate_), |
| table.type, canonical_type, table.initial_size, table.has_maximum_size, |
| table.maximum_size, value, |
| // TODO(clemensb): Make this configurable. |
| wasm::AddressType::kI32, &dispatch_table); |
| WasmDispatchTable::AddUse(isolate_, dispatch_table, trusted_instance_data_, |
| table_index); |
| { |
| // Store the shortcut to the dispatch table. |
| DirectHandle<ProtectedFixedArray> old_dispatch_tables{ |
| trusted_instance_data_->dispatch_tables(), isolate_}; |
| DCHECK_EQ(table_index, old_dispatch_tables->length()); |
| DirectHandle<ProtectedFixedArray> new_dispatch_tables = |
| isolate_->factory()->NewProtectedFixedArray(table_index + 1); |
| for (int i = 0; i < old_dispatch_tables->length(); ++i) { |
| new_dispatch_tables->set(i, old_dispatch_tables->get(i)); |
| } |
| new_dispatch_tables->set(table_index, *dispatch_table); |
| if (table_index == 0) { |
| trusted_instance_data_->set_dispatch_table0(*dispatch_table); |
| } |
| trusted_instance_data_->set_dispatch_tables(*new_dispatch_tables); |
| } |
| |
| if (function_indexes) { |
| WasmCodeRefScope code_ref_scope; |
| for (uint32_t i = 0; i < table_size; ++i) { |
| uint32_t function_index = function_indexes[i]; |
| WasmFunction& function = test_module_->functions[function_index]; |
| CanonicalTypeIndex sig_id = |
| test_module_->canonical_sig_id(function.sig_index); |
| FunctionTargetAndImplicitArg entry(isolate_, trusted_instance_data_, |
| function.func_index); |
| auto maybe_wrapper = |
| function_index < test_module_->num_imported_functions |
| ? trusted_instance_data_->dispatch_table_for_imports() |
| ->MaybeGetWrapperHandle(function_index) |
| : std::nullopt; |
| |
| if (maybe_wrapper) { |
| trusted_instance_data_->dispatch_table(table_index) |
| ->SetForWrapper(i, |
| TrustedCast<WasmImportData>(*entry.implicit_arg()), |
| std::move(*maybe_wrapper), sig_id, |
| #if V8_ENABLE_DRUMBRAKE |
| function.func_index, |
| #endif // !V8_ENABLE_DRUMBRAKE |
| WasmDispatchTable::kNewEntry); |
| } else { |
| trusted_instance_data_->dispatch_table(table_index) |
| ->SetForNonWrapper( |
| i, TrustedCast<WasmTrustedInstanceData>(*entry.implicit_arg()), |
| entry.call_target(), sig_id, |
| #if V8_ENABLE_DRUMBRAKE |
| function.func_index, |
| #endif // !V8_ENABLE_DRUMBRAKE |
| WasmDispatchTable::kNewEntry); |
| } |
| |
| WasmTableObject::SetFunctionTablePlaceholder( |
| isolate_, table_obj, i, trusted_instance_data_, function_indexes[i]); |
| } |
| } |
| |
| DirectHandle<FixedArray> old_tables(trusted_instance_data_->tables(), |
| isolate_); |
| DirectHandle<FixedArray> new_tables = |
| isolate_->factory()->CopyFixedArrayAndGrow(old_tables, 1); |
| new_tables->set(old_tables->length(), *table_obj); |
| trusted_instance_data_->set_tables(*new_tables); |
| } |
| |
| uint32_t TestingModuleBuilder::AddBytes(base::Vector<const uint8_t> bytes) { |
| base::Vector<const uint8_t> old_bytes = native_module_->wire_bytes(); |
| uint32_t old_size = static_cast<uint32_t>(old_bytes.size()); |
| // Avoid placing strings at offset 0, this might be interpreted as "not |
| // set", e.g. for function names. |
| uint32_t bytes_offset = old_size ? old_size : 1; |
| size_t new_size = bytes_offset + bytes.size(); |
| base::OwnedVector<uint8_t> new_bytes = |
| base::OwnedVector<uint8_t>::New(new_size); |
| if (old_size > 0) { |
| memcpy(new_bytes.begin(), old_bytes.begin(), old_size); |
| } else { |
| // Set the unused byte. It is never decoded, but the bytes are used as the |
| // key in the native module cache. |
| new_bytes[0] = 0; |
| } |
| memcpy(new_bytes.begin() + bytes_offset, bytes.begin(), bytes.length()); |
| native_module_->SetWireBytes(std::move(new_bytes)); |
| return bytes_offset; |
| } |
| |
| uint32_t TestingModuleBuilder::AddException(const FunctionSig* sig) { |
| DCHECK_EQ(0, sig->return_count()); |
| uint32_t index = static_cast<uint32_t>(test_module_->tags.size()); |
| test_module_->tags.emplace_back(sig, AddSignature(sig)); |
| DirectHandle<WasmExceptionTag> tag = WasmExceptionTag::New(isolate_, index); |
| DirectHandle<FixedArray> table(trusted_instance_data_->tags_table(), |
| isolate_); |
| table = isolate_->factory()->CopyFixedArrayAndGrow(table, 1); |
| trusted_instance_data_->set_tags_table(*table); |
| table->set(index, *tag); |
| return index; |
| } |
| |
| uint32_t TestingModuleBuilder::AddPassiveDataSegment( |
| base::Vector<const uint8_t> bytes) { |
| uint32_t index = static_cast<uint32_t>(test_module_->data_segments.size()); |
| DCHECK_EQ(index, test_module_->data_segments.size()); |
| DCHECK_EQ(index, data_segment_starts_.size()); |
| DCHECK_EQ(index, data_segment_sizes_.size()); |
| |
| // Add a passive data segment. This isn't used by function compilation, but |
| // but it keeps the index in sync. The data segment's source will not be |
| // correct, since we don't store data in the module wire bytes. |
| test_module_->data_segments.push_back(WasmDataSegment::PassiveForTesting()); |
| |
| // The num_declared_data_segments (from the DataCount section) is used |
| // to validate the segment index, during function compilation. |
| test_module_->num_declared_data_segments = index + 1; |
| |
| Address old_data_address = |
| reinterpret_cast<Address>(data_segment_data_.data()); |
| size_t old_data_size = data_segment_data_.size(); |
| data_segment_data_.resize(old_data_size + bytes.length()); |
| Address new_data_address = |
| reinterpret_cast<Address>(data_segment_data_.data()); |
| |
| memcpy(data_segment_data_.data() + old_data_size, bytes.begin(), |
| bytes.length()); |
| |
| // The data_segment_data_ offset may have moved, so update all the starts. |
| for (Address& start : data_segment_starts_) { |
| start += new_data_address - old_data_address; |
| } |
| data_segment_starts_.push_back(new_data_address + old_data_size); |
| data_segment_sizes_.push_back(bytes.length()); |
| |
| // The vector pointers may have moved, so update the instance object. |
| uint32_t size = static_cast<uint32_t>(data_segment_sizes_.size()); |
| DirectHandle<FixedAddressArray> data_segment_starts = |
| FixedAddressArray::New(isolate_, size); |
| MemCopy(data_segment_starts->begin(), data_segment_starts_.data(), |
| size * sizeof(Address)); |
| trusted_instance_data_->set_data_segment_starts(*data_segment_starts); |
| DirectHandle<FixedUInt32Array> data_segment_sizes = |
| FixedUInt32Array::New(isolate_, size); |
| MemCopy(data_segment_sizes->begin(), data_segment_sizes_.data(), |
| size * sizeof(uint32_t)); |
| trusted_instance_data_->set_data_segment_sizes(*data_segment_sizes); |
| return index; |
| } |
| |
| const WasmGlobal* TestingModuleBuilder::AddGlobal(ValueType type) { |
| uint8_t size = type.value_kind_size(); |
| global_offset_ = RoundUp(global_offset_, size); // align |
| test_module_->globals.push_back( |
| {type, true, {}, {global_offset_}, false, false, false}); |
| global_offset_ += size; |
| // limit number of globals. |
| CHECK_LT(global_offset_, kMaxGlobalsSize); |
| return &test_module_->globals.back(); |
| } |
| |
| DirectHandle<WasmInstanceObject> TestingModuleBuilder::InitInstanceObject() { |
| // Compute the estimate based on {kMaxFunctions} because we might still add |
| // functions later. Assume 1k of code per function. |
| int estimated_code_section_length = kMaxFunctions * 1024; |
| // Pretend to have `kMaxFunctions` already when allocating the `NativeModule`. |
| DCHECK_EQ(0, test_module_->num_declared_functions); |
| test_module_->num_declared_functions = kMaxFunctions; |
| size_t code_size_estimate = |
| wasm::WasmCodeManager::EstimateNativeModuleCodeSize( |
| kMaxFunctions, estimated_code_section_length); |
| auto native_module = GetWasmEngine()->NewNativeModule( |
| isolate_, enabled_features_, WasmDetectedFeatures{}, CompileTimeImports{}, |
| test_module_, code_size_estimate); |
| // Reset the declared functions; functions will be added later in the test. |
| test_module_->num_declared_functions = 0; |
| native_module->SetWireBytes(base::OwnedVector<const uint8_t>()); |
| native_module->compilation_state()->set_compilation_id(0); |
| constexpr base::Vector<const char> kNoSourceUrl{"", 0}; |
| DirectHandle<Script> script = |
| GetWasmEngine()->GetOrCreateScript(isolate_, native_module, kNoSourceUrl); |
| // Asm.js modules are expected to have "normal" scripts, not Wasm scripts. |
| if (is_asmjs_module(native_module->module())) { |
| script->set_type(Script::Type::kNormal); |
| script->set_infos(ReadOnlyRoots{isolate_}.empty_weak_fixed_array()); |
| } |
| |
| DirectHandle<WasmModuleObject> module_object = |
| WasmModuleObject::New(isolate_, native_module, script); |
| native_module_ = native_module.get(); |
| |
| DirectHandle<WasmTrustedInstanceData> trusted_data = |
| WasmTrustedInstanceData::New(isolate_, module_object, |
| std::move(native_module), false); |
| // TODO(42204563): Avoid crashing if the instance object is not available. |
| CHECK(trusted_data->has_instance_object()); |
| DirectHandle<WasmInstanceObject> instance_object( |
| trusted_data->instance_object(), isolate_); |
| trusted_data->set_tags_table(ReadOnlyRoots{isolate_}.empty_fixed_array()); |
| trusted_data->set_globals_start(globals_data_); |
| DirectHandle<FixedArray> feedback_vector = |
| isolate_->factory()->NewFixedArrayWithZeroes(kMaxFunctions); |
| trusted_data->set_feedback_vectors(*feedback_vector); |
| return instance_object; |
| } |
| |
| // This struct is just a type tag for Zone::NewArray<T>(size_t) call. |
| struct WasmFunctionCompilerBuffer {}; |
| |
| void WasmFunctionCompiler::Build(base::Vector<const uint8_t> bytes) { |
| size_t locals_size = local_decls_.Size(); |
| size_t total_size = bytes.size() + locals_size + 1; |
| uint8_t* buffer = |
| zone_->AllocateArray<uint8_t, WasmFunctionCompilerBuffer>(total_size); |
| // Prepend the local decls to the code. |
| local_decls_.Emit(buffer); |
| // Emit the code. |
| memcpy(buffer + locals_size, bytes.begin(), bytes.size()); |
| // Append an extra end opcode. |
| buffer[total_size - 1] = kExprEnd; |
| |
| bytes = base::VectorOf(buffer, total_size); |
| |
| function_->code = {builder_->AddBytes(bytes), |
| static_cast<uint32_t>(bytes.size())}; |
| |
| NativeModule* native_module = |
| builder_->trusted_instance_data()->native_module(); |
| base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes(); |
| |
| CompilationEnv env = CompilationEnv::ForModule(native_module); |
| base::ScopedVector<uint8_t> func_wire_bytes(function_->code.length()); |
| memcpy(func_wire_bytes.begin(), wire_bytes.begin() + function_->code.offset(), |
| func_wire_bytes.length()); |
| constexpr bool kIsShared = false; // TODO(14616): Extend this. |
| |
| FunctionBody func_body{function_->sig, function_->code.offset(), |
| func_wire_bytes.begin(), func_wire_bytes.end(), |
| kIsShared}; |
| ForDebugging for_debugging = |
| native_module->IsInDebugState() ? kForDebugging : kNotForDebugging; |
| |
| WasmDetectedFeatures unused_detected_features; |
| // Validate Wasm modules; asm.js is assumed to be always valid. |
| if (env.module->origin == kWasmOrigin) { |
| DecodeResult validation_result = |
| ValidateFunctionBody(zone_, env.enabled_features, env.module, |
| &unused_detected_features, func_body); |
| if (validation_result.failed()) { |
| FATAL("Validation failed: %s", |
| validation_result.error().message().c_str()); |
| } |
| env.module->set_function_validated(function_->func_index); |
| } |
| |
| if (v8_flags.wasm_jitless) return; |
| |
| std::optional<WasmCompilationResult> result; |
| if (builder_->test_execution_tier() == |
| TestExecutionTier::kLiftoffForFuzzing) { |
| result.emplace( |
| ExecuteLiftoffCompilation(&env, func_body, |
| LiftoffOptions{} |
| .set_func_index(function_->func_index) |
| .set_for_debugging(kForDebugging) |
| .set_max_steps(builder_->max_steps_ptr()) |
| .set_detect_nondeterminism(true))); |
| } else { |
| WasmCompilationUnit unit(function_->func_index, builder_->execution_tier(), |
| for_debugging); |
| result.emplace(unit.ExecuteCompilation( |
| &env, native_module->compilation_state()->GetWireBytesStorage().get(), |
| nullptr, &unused_detected_features)); |
| } |
| CHECK(result->succeeded()); |
| WasmCode* code = |
| native_module->PublishCode(native_module->AddCompiledCode(*result)); |
| DCHECK_NOT_NULL(code); |
| DisallowGarbageCollection no_gc; |
| Tagged<Script> script = |
| builder_->instance_object()->module_object()->script(); |
| std::unique_ptr<char[]> source_url = |
| Cast<String>(script->name())->ToCString(); |
| if (WasmCode::ShouldBeLogged(isolate())) { |
| code->LogCode(isolate(), source_url.get(), script->id()); |
| } |
| } |
| |
| WasmFunctionCompiler::WasmFunctionCompiler(Zone* zone, const FunctionSig* sig, |
| TestingModuleBuilder* builder, |
| const char* name) |
| : zone_(zone), builder_(builder), local_decls_(zone, sig) { |
| // Get a new function from the testing module. |
| int index = builder->AddFunction(sig, name, TestingModuleBuilder::kWasm); |
| function_ = builder_->GetFunctionAt(index); |
| } |
| |
| WasmFunctionCompiler::~WasmFunctionCompiler() = default; |
| |
| FunctionSig* WasmRunnerBase::CreateSig(MachineType return_type, |
| base::Vector<MachineType> param_types) { |
| int return_count = return_type.IsNone() ? 0 : 1; |
| int param_count = param_types.length(); |
| |
| Zone& zone = builder_.SignatureZone(); |
| |
| // Allocate storage array in zone. |
| ValueType* sig_types = |
| zone.AllocateArray<ValueType>(return_count + param_count); |
| |
| // Convert machine types to local types, and check that there are no |
| // MachineType::None()'s in the parameters. |
| int idx = 0; |
| if (return_count) sig_types[idx++] = ValueType::For(return_type); |
| for (MachineType param : param_types) { |
| CHECK_NE(MachineType::None(), param); |
| sig_types[idx++] = ValueType::For(param); |
| } |
| return zone.New<FunctionSig>(return_count, param_count, sig_types); |
| } |
| |
| } // namespace v8::internal::wasm |