// 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/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 helper for module environments that adds the ability to allocate memory // and global variables. Contains a built-in {WasmModule} and // {WasmInstance}. class TestingModule : public ModuleEnv { public: explicit TestingModule(Zone* zone, WasmExecutionMode mode = kExecuteCompiled) : ModuleEnv(&module_, &instance_), instance_(&module_), isolate_(CcTest::InitIsolateOnce()), global_offset(0), interpreter_(nullptr) { WasmJs::Install(isolate_); instance->module = &module_; instance->globals_start = global_data; module_.globals_size = kMaxGlobalsSize; instance->mem_start = nullptr; instance->mem_size = 0; memset(global_data, 0, sizeof(global_data)); instance_object_ = InitInstanceObject(); if (mode == kExecuteInterpreted) { interpreter_ = WasmDebugInfo::SetupForTesting(instance_object_, &instance_); } } void ChangeOriginToAsmjs() { module_.set_origin(kAsmJsOrigin); } byte* AddMemory(uint32_t size) { CHECK(!module_.has_memory); CHECK_NULL(instance->mem_start); CHECK_EQ(0, instance->mem_size); DCHECK(!instance_object_->has_memory_buffer()); module_.has_memory = true; bool enable_guard_regions = EnableGuardRegions() && 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); instance->mem_start = reinterpret_cast(new_buffer->backing_store()); CHECK(size == 0 || instance->mem_start); memset(instance->mem_start, 0, size); instance->mem_size = size; return instance->mem_start; } 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(instance->globals_start + global->offset); } byte AddSignature(FunctionSig* sig) { 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(instance->mem_start); return reinterpret_cast(instance->mem_start); } template T* raw_mem_end() { DCHECK(instance->mem_start); return reinterpret_cast(instance->mem_start + instance->mem_size); } template T raw_mem_at(int i) { DCHECK(instance->mem_start); return ReadMemory(&(reinterpret_cast(instance->mem_start)[i])); } template T raw_val_at(int i) { return ReadMemory(reinterpret_cast(instance->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, instance->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 max_mem_pages) { module_.max_mem_pages = max_mem_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. module_.functions.reserve(kMaxFunctions); } uint32_t index = static_cast(module->functions.size()); module_.functions.push_back({sig, index, 0, 0, 0, 0, 0, false, false}); if (name) { Vector name_vec = Vector::cast(CStrVector(name)); module_.functions.back().name_offset = AddBytes(name_vec); module_.functions.back().name_length = name_vec.length(); } instance->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->get_origin()); instance->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 = instance->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(this->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) { instance->function_code[index] = code; } void AddIndirectFunctionTable(uint16_t* function_indexes, uint32_t table_size) { module_.function_tables.push_back({table_size, table_size, true, std::vector(), false, false, SignatureMap()}); WasmIndirectFunctionTable& table = module_.function_tables.back(); table.min_size = table_size; table.max_size = table_size; for (uint32_t i = 0; i < table_size; ++i) { table.values.push_back(function_indexes[i]); table.map.FindOrInsert(module_.functions[function_indexes[i]].sig); } instance->function_tables.push_back( isolate_->factory()->NewFixedArray(table_size)); instance->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 < instance->function_tables.size(); i++) { WasmIndirectFunctionTable& table = module_.function_tables[i]; Handle function_table = instance->function_tables[i]; Handle signature_table = instance->signature_tables[i]; int table_size = static_cast(table.values.size()); for (int j = 0; j < table_size; j++) { WasmFunction& function = module_.functions[table.values[j]]; signature_table->set(j, Smi::FromInt(table.map.Find(function.sig))); function_table->set(j, *instance->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 &module_.functions[index]; } WasmInterpreter* interpreter() { return interpreter_; } bool interpret() { return interpreter_ != nullptr; } Isolate* isolate() { return isolate_; } Handle instance_object() { return instance_object_; } private: WasmModule module_; WasmInstance instance_; 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 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