// 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/api.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, WasmExecutionMode mode, compiler::RuntimeExceptionSupport exception_support, LowerSimd lower_simd) : test_module_ptr_(&test_module_), isolate_(CcTest::InitIsolateOnce()), global_offset(0), mem_start_(nullptr), mem_size_(0), interpreter_(nullptr), 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_)); instance_object_ = InitInstanceObject(); 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; uint32_t alloc_size = RoundUp(size, kWasmPageSize); Handle new_buffer; CHECK(wasm::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_->wasm_context()->get()->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()); native_module_->ResizeCodeTableForTest(index); 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; } uint32_t TestingModuleBuilder::AddJsFunction( FunctionSig* sig, const char* source, Handle js_imports_table) { Handle jsfunc = Handle::cast(v8::Utils::OpenHandle( *v8::Local::Cast(CompileRun(source)))); uint32_t index = AddFunction(sig, nullptr); js_imports_table->set(0, *isolate_->native_context()); // TODO(6792): No longer needed once WebAssembly code is off heap. CodeSpaceMemoryModificationScope modification_scope(isolate_->heap()); Handle code = compiler::CompileWasmToJSWrapper( isolate_, jsfunc, sig, index, test_module_.origin(), trap_handler::IsTrapHandlerEnabled(), js_imports_table); native_module_->ResizeCodeTableForTest(index); native_module_->AddCodeCopy(code, wasm::WasmCode::kWasmToJsWrapper, index); 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); byte* context_address = test_module_.has_memory ? reinterpret_cast(instance_object_->wasm_context()->get()) : nullptr; Handle ret_code = compiler::CompileJSToWasmWrapper( isolate_, &test_module_, code, index, context_address, 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 compiled_module( instance_object()->compiled_module(), isolate_); 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]); } FixedArray* func_table = *isolate_->factory()->NewFixedArray( table_size * compiler::kFunctionTableEntrySize); function_tables_.push_back( isolate_->global_handles()->Create(func_table).address()); WasmContext* wasm_context = instance_object()->wasm_context()->get(); wasm_context->table = reinterpret_cast( calloc(table_size, sizeof(IndirectFunctionTableEntry))); wasm_context->table_size = table_size; for (uint32_t i = 0; i < table_size; i++) { wasm_context->table[i].sig_id = -1; } } void TestingModuleBuilder::PopulateIndirectFunctionTable() { if (interpret()) return; // Initialize the fixed arrays in instance->function_tables. WasmContext* wasm_context = instance_object()->wasm_context()->get(); for (uint32_t i = 0; i < function_tables_.size(); i++) { WasmIndirectFunctionTable& table = test_module_.function_tables[i]; Handle function_table( reinterpret_cast(function_tables_[i])); int table_size = static_cast(table.values.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); function_table->set(compiler::FunctionTableSigOffset(j), Smi::FromInt(sig_id)); auto start = native_module_->GetCode(function.func_index)->instructions().start(); wasm_context->table[j].context = wasm_context; wasm_context->table[j].sig_id = sig_id; wasm_context->table[j].target = start; } } } 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_, function_tables_, 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