// 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/cctest/wasm/wasm-run-utils.h" #include "src/assembler-inl.h" #include "src/code-tracer.h" #include "src/wasm/wasm-memory.h" #include "src/wasm/wasm-objects-inl.h" namespace v8 { namespace internal { namespace wasm { TestingModuleBuilder::TestingModuleBuilder( Zone* zone, ManuallyImportedJSFunction* maybe_import, ExecutionTier tier, RuntimeExceptionSupport exception_support, LowerSimd lower_simd) : test_module_(std::make_shared()), test_module_ptr_(test_module_.get()), isolate_(CcTest::InitIsolateOnce()), enabled_features_(WasmFeaturesFromIsolate(isolate_)), execution_tier_(tier), runtime_exception_support_(exception_support), lower_simd_(lower_simd) { WasmJs::Install(isolate_, true); test_module_->globals_buffer_size = kMaxGlobalsSize; memset(globals_data_, 0, sizeof(globals_data_)); 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(); if (maybe_import) { // Manually compile a wasm to JS wrapper and insert it into the instance. CodeSpaceMemoryModificationScope modification_scope(isolate_->heap()); MaybeHandle code = compiler::CompileWasmToJSWrapper( isolate_, maybe_import->js_function, maybe_import->sig, maybe_import_index, test_module_->origin, trap_handler::IsTrapHandlerEnabled() ? kUseTrapHandler : kNoTrapHandler); auto wasm_to_js_wrapper = native_module_->AddCodeCopy( code.ToHandleChecked(), WasmCode::kWasmToJsWrapper, maybe_import_index); ImportedFunctionEntry(instance_object_, maybe_import_index) .set_wasm_to_js(*maybe_import->js_function, wasm_to_js_wrapper); } if (tier == ExecutionTier::kInterpreter) { interpreter_ = WasmDebugInfo::SetupForTesting(instance_object_); } } byte* TestingModuleBuilder::AddMemory(uint32_t size) { CHECK(!test_module_->has_memory); CHECK_NULL(mem_start_); CHECK_EQ(0, mem_size_); DCHECK(!instance_object_->has_memory_object()); DCHECK_IMPLIES(test_module_->origin == kWasmOrigin, size % kWasmPageSize == 0); test_module_->has_memory = true; uint32_t alloc_size = RoundUp(size, kWasmPageSize); Handle new_buffer; CHECK(NewArrayBuffer(isolate_, alloc_size).ToHandle(&new_buffer)); CHECK(!new_buffer.is_null()); mem_start_ = reinterpret_cast(new_buffer->backing_store()); mem_size_ = size; CHECK(size == 0 || mem_start_); memset(mem_start_, 0, size); // Create the WasmMemoryObject. Handle memory_object = WasmMemoryObject::New( isolate_, new_buffer, (test_module_->maximum_pages != 0) ? test_module_->maximum_pages : -1); instance_object_->set_memory_object(*memory_object); WasmMemoryObject::AddInstance(isolate_, memory_object, instance_object_); // TODO(wasm): Delete the following two lines 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. instance_object_->SetRawMemory(mem_start_, mem_size_); return mem_start_; } uint32_t TestingModuleBuilder::AddFunction(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); } uint32_t index = static_cast(test_module_->functions.size()); test_module_->functions.push_back({sig, index, 0, {0, 0}, false, false}); 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) { Vector name_vec = Vector::cast(CStrVector(name)); test_module_->AddFunctionNameForTesting( index, {AddBytes(name_vec), static_cast(name_vec.length())}); } if (interpreter_) { interpreter_->AddFunctionForTesting(&test_module_->functions.back()); } DCHECK_LT(index, kMaxFunctions); // limited for testing. return index; } Handle TestingModuleBuilder::WrapCode(uint32_t index) { // Wrap the code so it can be called as a JS function. Link(); FunctionSig* sig = test_module_->functions[index].sig; MaybeHandle maybe_ret_code = compiler::CompileJSToWasmWrapper( isolate_, native_module_, sig, false, trap_handler::IsTrapHandlerEnabled() ? kUseTrapHandler : kNoTrapHandler); Handle ret_code = maybe_ret_code.ToHandleChecked(); Handle ret = WasmExportedFunction::New( isolate_, instance_object(), MaybeHandle(), static_cast(index), static_cast(sig->parameter_count()), ret_code); // Add reference to the exported wrapper code. Handle module_object(instance_object()->module_object(), isolate_); Handle old_arr(module_object->export_wrappers(), isolate_); Handle new_arr = isolate_->factory()->NewFixedArray(old_arr->length() + 1); old_arr->CopyTo(0, *new_arr, 0, old_arr->length()); new_arr->set(old_arr->length(), *ret_code); module_object->set_export_wrappers(*new_arr); return ret; } void TestingModuleBuilder::AddIndirectFunctionTable( const uint16_t* function_indexes, uint32_t table_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; for (uint32_t i = 0; i < table_size; ++i) { table.values.push_back(function_indexes[i]); } WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( instance_object(), table_size); } void TestingModuleBuilder::PopulateIndirectFunctionTable() { if (interpret()) return; auto instance = instance_object(); uint32_t num_tables = 1; // TODO(titzer): multiple tables. for (uint32_t i = 0; i < num_tables; i++) { WasmTable& table = test_module_->tables[i]; int table_size = static_cast(instance->indirect_function_table_size()); for (int j = 0; j < table_size; j++) { WasmFunction& function = test_module_->functions[table.values[j]]; int sig_id = test_module_->signature_map.Find(*function.sig); auto target = native_module_->GetCallTargetForFunction(function.func_index); IndirectFunctionTableEntry(instance, j).set(sig_id, *instance, target); } } } uint32_t TestingModuleBuilder::AddBytes(Vector bytes) { Vector old_bytes = native_module_->wire_bytes(); uint32_t old_size = static_cast(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(); OwnedVector new_bytes = OwnedVector::New(new_size); memcpy(new_bytes.start(), old_bytes.start(), old_size); memcpy(new_bytes.start() + bytes_offset, bytes.start(), bytes.length()); native_module_->set_wire_bytes(std::move(new_bytes)); return bytes_offset; } ModuleEnv TestingModuleBuilder::CreateModuleEnv() { return { test_module_ptr_, trap_handler::IsTrapHandlerEnabled() ? kUseTrapHandler : kNoTrapHandler, runtime_exception_support_, lower_simd()}; } const WasmGlobal* TestingModuleBuilder::AddGlobal(ValueType type) { byte size = ValueTypes::MemSize(ValueTypes::MachineTypeFor(type)); global_offset = (global_offset + size - 1) & ~(size - 1); // align test_module_->globals.push_back( {type, true, WasmInitExpr(), {global_offset}, false, false}); global_offset += size; // limit number of globals. CHECK_LT(global_offset, kMaxGlobalsSize); return &test_module_->globals.back(); } Handle TestingModuleBuilder::InitInstanceObject() { Handle