// Copyright 2016 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. #ifndef WASM_RUN_UTILS_H #define WASM_RUN_UTILS_H #include #include #include #include #include #include #include "src/base/utils/random-number-generator.h" #include "src/code-stubs.h" #include "src/compiler/compiler-source-position-table.h" #include "src/compiler/graph-visualizer.h" #include "src/compiler/int64-lowering.h" #include "src/compiler/js-graph.h" #include "src/compiler/node.h" #include "src/compiler/pipeline.h" #include "src/compiler/wasm-compiler.h" #include "src/compiler/zone-stats.h" #include "src/trap-handler/trap-handler.h" #include "src/wasm/function-body-decoder.h" #include "src/wasm/local-decl-encoder.h" #include "src/wasm/wasm-external-refs.h" #include "src/wasm/wasm-interpreter.h" #include "src/wasm/wasm-js.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects.h" #include "src/wasm/wasm-opcodes.h" #include "src/zone/accounting-allocator.h" #include "src/zone/zone.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/call-tester.h" #include "test/cctest/compiler/graph-builder-tester.h" #include "test/common/wasm/flag-utils.h" static const uint32_t kMaxFunctions = 10; enum WasmExecutionMode { kExecuteInterpreted, kExecuteCompiled }; // TODO(titzer): check traps more robustly in tests. // Currently, in tests, we just return 0xdeadbeef from the function in which // the trap occurs if the runtime context is not available to throw a JavaScript // exception. #define CHECK_TRAP32(x) \ CHECK_EQ(0xdeadbeef, (bit_cast(x)) & 0xFFFFFFFF) #define CHECK_TRAP64(x) \ CHECK_EQ(0xdeadbeefdeadbeef, (bit_cast(x)) & 0xFFFFFFFFFFFFFFFF) #define CHECK_TRAP(x) CHECK_TRAP32(x) #define WASM_WRAPPER_RETURN_VALUE 8754 #define BUILD(r, ...) \ do { \ byte code[] = {__VA_ARGS__}; \ r.Build(code, code + arraysize(code)); \ } while (false) namespace { using namespace v8::base; using namespace v8::internal; using namespace v8::internal::compiler; using namespace v8::internal::wasm; const uint32_t kMaxGlobalsSize = 128; // A buildable ModuleEnv. Globals are pre-set, however, memory and code may be // progressively added by a test. In turn, we piecemeal update the runtime // objects, i.e. {WasmInstanceObject}, {WasmCompiledModule} and, if necessary, // the interpreter. class TestingModule : public ModuleEnv { public: explicit TestingModule(Zone* zone, WasmExecutionMode mode = kExecuteCompiled) : isolate_(CcTest::InitIsolateOnce()), global_offset(0), interpreter_(nullptr) { module_ = &test_module_; WasmJs::Install(isolate_); set_globals_start(global_data); test_module_.globals_size = kMaxGlobalsSize; memset(global_data, 0, sizeof(global_data)); instance_object_ = InitInstanceObject(); if (mode == kExecuteInterpreted) { interpreter_ = WasmDebugInfo::SetupForTesting(instance_object_); } } void ChangeOriginToAsmjs() { test_module_.set_origin(kAsmJsOrigin); } byte* AddMemory(uint32_t size) { CHECK(!test_module_.has_memory); CHECK_NULL(mem_start()); CHECK_EQ(0, mem_size()); DCHECK(!instance_object_->has_memory_buffer()); test_module_.has_memory = true; bool enable_guard_regions = EnableGuardRegions() && test_module_.is_wasm(); uint32_t alloc_size = enable_guard_regions ? RoundUp(size, OS::CommitPageSize()) : size; Handle new_buffer = wasm::NewArrayBuffer(isolate_, alloc_size, enable_guard_regions); CHECK(!new_buffer.is_null()); instance_object_->set_memory_buffer(*new_buffer); set_mem_start(reinterpret_cast(new_buffer->backing_store())); CHECK(size == 0 || mem_start()); memset(mem_start(), 0, size); SetMemSizeUnchecked(size); Handle compiled_module = handle(instance_object_->compiled_module()); Factory* factory = CcTest::i_isolate()->factory(); // It's not really necessary we recreate the Number objects, // if we happened to have one, but this is a reasonable inefficiencly, // given this is test. WasmCompiledModule::recreate_embedded_mem_size(compiled_module, factory, mem_size()); WasmCompiledModule::recreate_embedded_mem_start( compiled_module, factory, reinterpret_cast(mem_start())); if (interpreter_) { interpreter_->UpdateMemory(mem_start(), mem_size()); } return mem_start(); } size_t CodeTableLength() const { return function_code_.size(); } template T* AddMemoryElems(uint32_t count) { AddMemory(count * sizeof(T)); return raw_mem_start(); } template T* AddGlobal( ValueType type = WasmOpcodes::ValueTypeFor(MachineTypeForC())) { const WasmGlobal* global = AddGlobal(type); return reinterpret_cast(globals_start() + global->offset); } byte AddSignature(FunctionSig* sig) { test_module_.signatures.push_back(sig); size_t size = module()->signatures.size(); CHECK(size < 127); return static_cast(size - 1); } template T* raw_mem_start() { DCHECK(mem_start()); return reinterpret_cast(mem_start()); } template T* raw_mem_end() { DCHECK(mem_start()); return reinterpret_cast(mem_start() + mem_size()); } template T raw_mem_at(int i) { DCHECK(mem_start()); return ReadMemory(&(reinterpret_cast(mem_start())[i])); } template T raw_val_at(int i) { return ReadMemory(reinterpret_cast(mem_start() + i)); } template void WriteMemory(T* p, T val) { WriteLittleEndianValue(p, val); } template T ReadMemory(T* p) { return ReadLittleEndianValue(p); } // Zero-initialize the memory. void BlankMemory() { byte* raw = raw_mem_start(); memset(raw, 0, mem_size()); } // Pseudo-randomly intialize the memory. void RandomizeMemory(unsigned int seed = 88) { byte* raw = raw_mem_start(); byte* end = raw_mem_end(); v8::base::RandomNumberGenerator rng; rng.SetSeed(seed); rng.NextBytes(raw, end - raw); } void SetMaxMemPages(uint32_t maximum_pages) { test_module_.maximum_pages = maximum_pages; } uint32_t AddFunction(FunctionSig* sig, Handle code, const char* name) { if (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(module()->functions.size()); test_module_.functions.push_back( {sig, index, 0, {0, 0}, {0, 0}, false, false}); if (name) { Vector name_vec = Vector::cast(CStrVector(name)); test_module_.functions.back().name = { AddBytes(name_vec), static_cast(name_vec.length())}; } function_code_.push_back(code); if (interpreter_) { interpreter_->AddFunctionForTesting(&module()->functions.back()); } DCHECK_LT(index, kMaxFunctions); // limited for testing. return index; } uint32_t AddJsFunction(FunctionSig* sig, const char* source) { Handle jsfunc = Handle::cast(v8::Utils::OpenHandle( *v8::Local::Cast(CompileRun(source)))); uint32_t index = AddFunction(sig, Handle::null(), nullptr); Handle code = CompileWasmToJSWrapper( isolate_, jsfunc, sig, index, Handle::null(), Handle::null(), module()->origin()); function_code_[index] = code; return index; } Handle WrapCode(uint32_t index) { // Wrap the code so it can be called as a JS function. Handle code = function_code_[index]; Handle ret_code = compiler::CompileJSToWasmWrapper(isolate_, module(), code, index); Handle ret = WasmExportedFunction::New( isolate_, instance_object(), MaybeHandle(), static_cast(index), static_cast(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(); 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 SetFunctionCode(uint32_t index, Handle code) { function_code_[index] = code; } void AddIndirectFunctionTable(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]); table.map.FindOrInsert(test_module_.functions[function_indexes[i]].sig); } function_tables_.push_back(isolate_->factory()->NewFixedArray(table_size)); signature_tables_.push_back(isolate_->factory()->NewFixedArray(table_size)); } void PopulateIndirectFunctionTable() { if (interpret()) return; // Initialize the fixed arrays in instance->function_tables. for (uint32_t i = 0; i < function_tables().size(); i++) { WasmIndirectFunctionTable& table = test_module_.function_tables[i]; Handle function_table = function_tables()[i]; Handle signature_table = signature_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]]; signature_table->set(j, Smi::FromInt(table.map.Find(function.sig))); function_table->set(j, *function_code_[function.func_index]); } } } uint32_t AddBytes(Vector bytes) { Handle old_bytes( instance_object_->compiled_module()->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()); instance_object_->compiled_module()->shared()->set_module_bytes( *new_bytes_str); return bytes_offset; } WasmFunction* GetFunctionAt(int index) { return &test_module_.functions[index]; } WasmInterpreter* interpreter() { return interpreter_; } bool interpret() { return interpreter_ != nullptr; } Isolate* isolate() { return isolate_; } Handle instance_object() { return instance_object_; } private: WasmModule test_module_; Isolate* isolate_; uint32_t global_offset; V8_ALIGNED(8) byte global_data[kMaxGlobalsSize]; // preallocated global data. WasmInterpreter* interpreter_; Handle instance_object_; const WasmGlobal* 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 &module()->globals.back(); } Handle 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
(&module_)); Handle