// 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/wasm/wasm-memory.h" #include "src/wasm/wasm-objects-inl.h" namespace v8 { namespace internal { namespace wasm { TestingModuleBuilder::TestingModuleBuilder( Zone* zone, ManuallyImportedJSFunction* maybe_import, WasmExecutionMode mode, compiler::RuntimeExceptionSupport exception_support, LowerSimd lower_simd) : test_module_ptr_(&test_module_), isolate_(CcTest::InitIsolateOnce()), execution_mode_(mode), runtime_exception_support_(exception_support), lower_simd_(lower_simd) { WasmJs::Install(isolate_, true); test_module_.globals_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 objectis created, since the // instance object allocates import entries. maybe_import_index = AddFunction(maybe_import->sig, nullptr); DCHECK_EQ(0, maybe_import_index); test_module_.num_imported_functions = 1; test_module_.functions[0].imported = true; } instance_object_ = InitInstanceObject(); if (maybe_import) { // Manually compile a wasm to JS wrapper and insert it into the instance. CodeSpaceMemoryModificationScope modification_scope(isolate_->heap()); Handle code = compiler::CompileWasmToJSWrapper( isolate_, maybe_import->js_function, maybe_import->sig, maybe_import_index, test_module_.origin(), trap_handler::IsTrapHandlerEnabled()); native_module_->ResizeCodeTableForTesting(maybe_import_index + 1, kMaxFunctions); auto wasm_to_js_wrapper = native_module_->AddCodeCopy( code, wasm::WasmCode::kWasmToJsWrapper, maybe_import_index); auto entry = instance_object()->imported_function_entry_at(maybe_import_index); entry.set(maybe_import->js_function, wasm_to_js_wrapper); } if (mode == kExecuteInterpreter) { 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()); test_module_.has_memory = true; const bool enable_guard_regions = trap_handler::IsTrapHandlerEnabled() && test_module_.is_wasm(); uint32_t alloc_size = enable_guard_regions ? RoundUp(size, CommitPageSize()) : size; Handle new_buffer; CHECK(wasm::NewArrayBuffer(isolate_, alloc_size, enable_guard_regions) .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) { 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()); if (native_module_) { native_module_->ResizeCodeTableForTesting(index + 1, kMaxFunctions); } test_module_.functions.push_back({sig, index, 0, {0, 0}, false, false}); if (name) { Vector name_vec = Vector::cast(CStrVector(name)); test_module_.AddNameForTesting( 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(); wasm::WasmCode* code = native_module_->GetCode(index); Handle compiled_module( instance_object()->compiled_module(), isolate_); Handle weak_instance(compiled_module->weak_owning_instance(), isolate_); Handle ret_code = compiler::CompileJSToWasmWrapper( isolate_, &test_module_, weak_instance, code, index, trap_handler::IsTrapHandlerEnabled()); Handle ret = WasmExportedFunction::New( isolate_, instance_object(), MaybeHandle(), static_cast(index), static_cast(test_module_.functions[index].sig->parameter_count()), ret_code); // Add weak reference to exported functions. Handle old_arr(compiled_module->weak_exported_functions(), isolate_); Handle new_arr = isolate_->factory()->NewFixedArray(old_arr->length() + 1); old_arr->CopyTo(0, *new_arr, 0, old_arr->length()); Handle weak_fn = isolate_->factory()->NewWeakCell(ret); new_arr->set(old_arr->length(), *weak_fn); compiled_module->set_weak_exported_functions(*new_arr); return ret; } void TestingModuleBuilder::AddIndirectFunctionTable( const uint16_t* function_indexes, uint32_t table_size) { test_module_.function_tables.emplace_back(); WasmIndirectFunctionTable& table = test_module_.function_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++) { WasmIndirectFunctionTable& table = test_module_.function_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 wasm_code = native_module_->GetCode(function.func_index); auto entry = instance->indirect_function_table_entry_at(j); entry.set(sig_id, instance, wasm_code); } } } uint32_t TestingModuleBuilder::AddBytes(Vector bytes) { Handle shared( instance_object_->compiled_module()->shared(), isolate_); Handle old_bytes(shared->module_bytes(), isolate_); uint32_t old_size = static_cast(old_bytes->length()); // 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; ScopedVector new_bytes(bytes_offset + bytes.length()); memcpy(new_bytes.start(), old_bytes->GetChars(), old_size); memcpy(new_bytes.start() + bytes_offset, bytes.start(), bytes.length()); Handle new_bytes_str = Handle::cast( isolate_->factory()->NewStringFromOneByte(new_bytes).ToHandleChecked()); shared->set_module_bytes(*new_bytes_str); return bytes_offset; } compiler::ModuleEnv TestingModuleBuilder::CreateModuleEnv() { return {&test_module_, trap_handler::IsTrapHandlerEnabled()}; } const WasmGlobal* TestingModuleBuilder::AddGlobal(ValueType type) { byte size = WasmOpcodes::MemSize(WasmOpcodes::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 empty_string = Handle::cast( isolate_->factory()->NewStringFromOneByte({}).ToHandleChecked()); // The lifetime of the wasm module is tied to this object's, and we cannot // rely on the mechanics of Managed. Handle module_wrapper = isolate_->factory()->NewForeign( reinterpret_cast
(&test_module_ptr_)); Handle