// 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 "src/base/utils/random-number-generator.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/wasm/ast-decoder.h" #include "src/wasm/wasm-js.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-opcodes.h" #include "src/zone.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/call-tester.h" #include "test/cctest/compiler/graph-builder-tester.h" // TODO(titzer): pull WASM_64 up to a common header. #if !V8_TARGET_ARCH_32_BIT || V8_TARGET_ARCH_X64 #define WASM_64 1 #else #define WASM_64 0 #endif static const uint32_t kMaxFunctions = 10; // 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_RUNNER_MAX_NUM_PARAMETERS 4 #define WASM_WRAPPER_RETURN_VALUE 8754 namespace { using namespace v8::base; using namespace v8::internal; using namespace v8::internal::compiler; using namespace v8::internal::wasm; inline void init_env(FunctionEnv* env, FunctionSig* sig) { env->module = nullptr; env->sig = sig; env->local_i32_count = 0; env->local_i64_count = 0; env->local_f32_count = 0; env->local_f64_count = 0; env->SumLocals(); } 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 // {WasmModuleInstance}. class TestingModule : public ModuleEnv { public: TestingModule() : instance_(&module_), global_offset(0) { module_.shared_isolate = CcTest::InitIsolateOnce(); module = &module_; instance = &instance_; instance->module = &module_; instance->globals_start = global_data; instance->globals_size = kMaxGlobalsSize; instance->mem_start = nullptr; instance->mem_size = 0; instance->function_code = nullptr; linker = nullptr; asm_js = false; memset(global_data, 0, sizeof(global_data)); } ~TestingModule() { if (instance->mem_start) { free(instance->mem_start); } if (instance->function_code) { delete instance->function_code; } } byte* AddMemory(size_t size) { CHECK_NULL(instance->mem_start); CHECK_EQ(0, instance->mem_size); instance->mem_start = reinterpret_cast(malloc(size)); CHECK(instance->mem_start); memset(instance->mem_start, 0, size); instance->mem_size = size; return raw_mem_start(); } template T* AddMemoryElems(size_t count) { AddMemory(count * sizeof(T)); return raw_mem_start(); } template T* AddGlobal(MachineType mem_type) { WasmGlobal* global = AddGlobal(mem_type); return reinterpret_cast(instance->globals_start + global->offset); } byte AddSignature(FunctionSig* sig) { if (!module->signatures) { module->signatures = new std::vector(); } 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 reinterpret_cast(instance->mem_start)[i]; } template T raw_val_at(int i) { T val; memcpy(&val, reinterpret_cast(instance->mem_start + i), sizeof(T)); return val; } // 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); } int AddFunction(FunctionSig* sig, Handle code) { if (module->functions == nullptr) { module->functions = new std::vector(); // TODO(titzer): Reserving space here to avoid the underlying WasmFunction // structs from moving. module->functions->reserve(kMaxFunctions); instance->function_code = new std::vector>(); } uint32_t index = static_cast(module->functions->size()); module->functions->push_back( {sig, index, 0, 0, 0, 0, 0, 0, 0, false, false}); instance->function_code->push_back(code); DCHECK_LT(index, kMaxFunctions); // limited for testing. return index; } void SetFunctionCode(uint32_t index, Handle code) { instance->function_code->at(index) = code; } void AddIndirectFunctionTable(int* functions, int table_size) { Isolate* isolate = module->shared_isolate; Handle fixed = isolate->factory()->NewFixedArray(2 * table_size); instance->function_table = fixed; module->function_table = new std::vector(); for (int i = 0; i < table_size; i++) { module->function_table->push_back(functions[i]); } } void PopulateIndirectFunctionTable() { if (instance->function_table.is_null()) return; int table_size = static_cast(module->function_table->size()); for (int i = 0; i < table_size; i++) { int function_index = module->function_table->at(i); WasmFunction* function = &module->functions->at(function_index); instance->function_table->set(i, Smi::FromInt(function->sig_index)); instance->function_table->set( i + table_size, *instance->function_code->at(function_index)); } } private: WasmModule module_; WasmModuleInstance instance_; uint32_t global_offset; V8_ALIGNED(8) byte global_data[kMaxGlobalsSize]; // preallocated global data. WasmGlobal* AddGlobal(MachineType mem_type) { if (!module->globals) { module->globals = new std::vector(); } byte size = WasmOpcodes::MemSize(mem_type); global_offset = (global_offset + size - 1) & ~(size - 1); // align module->globals->push_back({0, mem_type, global_offset, false}); global_offset += size; // limit number of globals. CHECK_LT(global_offset, kMaxGlobalsSize); return &module->globals->back(); } }; inline void TestBuildingGraph(Zone* zone, JSGraph* jsgraph, FunctionEnv* env, const byte* start, const byte* end) { compiler::WasmGraphBuilder builder(zone, jsgraph, env->sig); TreeResult result = BuildTFGraph(&builder, env, start, end); if (result.failed()) { ptrdiff_t pc = result.error_pc - result.start; ptrdiff_t pt = result.error_pt - result.start; std::ostringstream str; str << "Verification failed: " << result.error_code << " pc = +" << pc; if (result.error_pt) str << ", pt = +" << pt; str << ", msg = " << result.error_msg.get(); FATAL(str.str().c_str()); } builder.Int64LoweringForTesting(); if (FLAG_trace_turbo_graph) { OFStream os(stdout); os << AsRPO(*jsgraph->graph()); } } template class WasmFunctionWrapper : public HandleAndZoneScope, private GraphAndBuilders { public: WasmFunctionWrapper() : GraphAndBuilders(main_zone()), inner_code_node_(nullptr), signature_(nullptr) { // One additional parameter for the pointer to the return value memory. Signature::Builder sig_builder( zone(), 1, WASM_RUNNER_MAX_NUM_PARAMETERS + 1); sig_builder.AddReturn(MachineType::Int32()); for (int i = 0; i < WASM_RUNNER_MAX_NUM_PARAMETERS + 1; i++) { sig_builder.AddParam(MachineType::Pointer()); } signature_ = sig_builder.Build(); } void Init(CallDescriptor* descriptor, MachineType p0 = MachineType::None(), MachineType p1 = MachineType::None(), MachineType p2 = MachineType::None(), MachineType p3 = MachineType::None()) { // Create the TF graph for the wrapper. The wrapper always takes four // pointers as parameters, but may not pass the values of all pointers to // the actual test function. // Function, effect, and control. Node** parameters = zone()->template NewArray(WASM_RUNNER_MAX_NUM_PARAMETERS + 3); graph()->SetStart(graph()->NewNode(common()->Start(6))); Node* effect = graph()->start(); int parameter_count = 0; // Dummy node which gets replaced in SetInnerCode. inner_code_node_ = graph()->NewNode(common()->Int32Constant(0)); parameters[parameter_count++] = inner_code_node_; if (p0 != MachineType::None()) { parameters[parameter_count] = graph()->NewNode( machine()->Load(p0), graph()->NewNode(common()->Parameter(0), graph()->start()), graph()->NewNode(common()->Int32Constant(0)), effect, graph()->start()); effect = parameters[parameter_count++]; } if (p1 != MachineType::None()) { parameters[parameter_count] = graph()->NewNode( machine()->Load(p0), graph()->NewNode(common()->Parameter(1), graph()->start()), graph()->NewNode(common()->Int32Constant(0)), effect, graph()->start()); effect = parameters[parameter_count++]; } if (p2 != MachineType::None()) { parameters[parameter_count] = graph()->NewNode( machine()->Load(p0), graph()->NewNode(common()->Parameter(2), graph()->start()), graph()->NewNode(common()->Int32Constant(0)), effect, graph()->start()); effect = parameters[parameter_count++]; } if (p3 != MachineType::None()) { parameters[parameter_count] = graph()->NewNode( machine()->Load(p0), graph()->NewNode(common()->Parameter(3), graph()->start()), graph()->NewNode(common()->Int32Constant(0)), effect, graph()->start()); effect = parameters[parameter_count++]; } parameters[parameter_count++] = effect; parameters[parameter_count++] = graph()->start(); Node* call = graph()->NewNode(common()->Call(descriptor), parameter_count, parameters); effect = graph()->NewNode( machine()->Store( StoreRepresentation(MachineTypeForC().representation(), WriteBarrierKind::kNoWriteBarrier)), graph()->NewNode(common()->Parameter(WASM_RUNNER_MAX_NUM_PARAMETERS), graph()->start()), graph()->NewNode(common()->Int32Constant(0)), call, effect, graph()->start()); Node* r = graph()->NewNode( common()->Return(), graph()->NewNode(common()->Int32Constant(WASM_WRAPPER_RETURN_VALUE)), effect, graph()->start()); graph()->SetEnd(graph()->NewNode(common()->End(2), r, graph()->start())); } void SetInnerCode(Handle code_handle) { NodeProperties::ChangeOp(inner_code_node_, common()->HeapConstant(code_handle)); } Handle GetWrapperCode() { if (code_.is_null()) { Isolate* isolate = CcTest::InitIsolateOnce(); CallDescriptor* descriptor = Linkage::GetSimplifiedCDescriptor(zone(), signature_, true); if (kPointerSize == 4) { // One additional parameter for the pointer of the return value. Signature::Builder rep_builder( zone(), 1, WASM_RUNNER_MAX_NUM_PARAMETERS + 1); rep_builder.AddReturn(MachineRepresentation::kWord32); for (int i = 0; i < WASM_RUNNER_MAX_NUM_PARAMETERS + 1; i++) { rep_builder.AddParam(MachineRepresentation::kWord32); } Int64Lowering r(graph(), machine(), common(), zone(), rep_builder.Build()); r.LowerGraph(); } CompilationInfo info("testing", isolate, graph()->zone()); code_ = Pipeline::GenerateCodeForTesting(&info, descriptor, graph(), nullptr); CHECK(!code_.is_null()); #ifdef ENABLE_DISASSEMBLER if (FLAG_print_opt_code) { OFStream os(stdout); code_->Disassemble("wasm wrapper", os); } #endif } return code_; } Signature* signature() const { return signature_; } private: Node* inner_code_node_; Handle code_; Signature* signature_; }; // A helper for compiling WASM functions for testing. This class can create a // standalone function if {module} is NULL or a function within a // {TestingModule}. It contains the internal state for compilation (i.e. // TurboFan graph) and, later, interpretation. class WasmFunctionCompiler : public HandleAndZoneScope, private GraphAndBuilders { public: explicit WasmFunctionCompiler(FunctionSig* sig, TestingModule* module) : GraphAndBuilders(main_zone()), jsgraph(this->isolate(), this->graph(), this->common(), nullptr, nullptr, this->machine()), descriptor_(nullptr), testing_module_(module) { init_env(&env, sig); env.module = module; if (module) { // Get a new function from the testing module. function_ = nullptr; function_index_ = module->AddFunction(sig, Handle::null()); } else { // Create our own function. function_ = new WasmFunction(); function_->sig = sig; function_index_ = 0; } } ~WasmFunctionCompiler() { if (function_) delete function_; } JSGraph jsgraph; FunctionEnv env; // The call descriptor is initialized when the function is compiled. CallDescriptor* descriptor_; TestingModule* testing_module_; WasmFunction* function_; int function_index_; Isolate* isolate() { return main_isolate(); } Graph* graph() const { return main_graph_; } Zone* zone() const { return graph()->zone(); } CommonOperatorBuilder* common() { return &main_common_; } MachineOperatorBuilder* machine() { return &main_machine_; } void InitializeDescriptor() { if (descriptor_ == nullptr) { descriptor_ = env.module->GetWasmCallDescriptor(main_zone(), env.sig); } } CallDescriptor* descriptor() { return descriptor_; } void Build(const byte* start, const byte* end) { // Transfer local counts before compiling. function()->local_i32_count = env.local_i32_count; function()->local_i64_count = env.local_i64_count; function()->local_f32_count = env.local_f32_count; function()->local_f64_count = env.local_f64_count; // Build the TurboFan graph. TestBuildingGraph(main_zone(), &jsgraph, &env, start, end); } byte AllocateLocal(LocalType type) { int result = static_cast(env.total_locals); env.AddLocals(type, 1); byte b = static_cast(result); CHECK_EQ(result, b); return b; } // TODO(titzer): remove me. Handle Compile() { InitializeDescriptor(); CallDescriptor* desc = descriptor_; if (kPointerSize == 4) { desc = testing_module_->GetI32WasmCallDescriptor(this->zone(), desc); } CompilationInfo info("wasm compile", this->isolate(), this->zone()); Handle result = Pipeline::GenerateCodeForTesting(&info, desc, this->graph()); #ifdef ENABLE_DISASSEMBLER if (!result.is_null() && FLAG_print_opt_code) { OFStream os(stdout); result->Disassemble("wasm code", os); } #endif return result; } // TODO(titzer): remove me. uint32_t CompileAndAdd(uint16_t sig_index = 0) { CHECK(testing_module_); function()->sig_index = sig_index; Handle code = Compile(); testing_module_->SetFunctionCode(function_index_, code); return static_cast(function_index_); } WasmFunction* function() { if (function_) return function_; return &testing_module_->module->functions->at(function_index_); } }; // A helper class to build graphs from Wasm bytecode, generate machine // code, and run that code. template class WasmRunner { public: WasmRunner(MachineType p0 = MachineType::None(), MachineType p1 = MachineType::None(), MachineType p2 = MachineType::None(), MachineType p3 = MachineType::None()) : compiled_(false), signature_(MachineTypeForC() == MachineType::None() ? 0 : 1, GetParameterCount(p0, p1, p2, p3), storage_), compiler_(&signature_, nullptr) { InitSigStorage(p0, p1, p2, p3); } WasmRunner(TestingModule* module, MachineType p0 = MachineType::None(), MachineType p1 = MachineType::None(), MachineType p2 = MachineType::None(), MachineType p3 = MachineType::None()) : compiled_(false), signature_(MachineTypeForC() == MachineType::None() ? 0 : 1, GetParameterCount(p0, p1, p2, p3), storage_), compiler_(&signature_, module) { DCHECK(module); InitSigStorage(p0, p1, p2, p3); } void InitSigStorage(MachineType p0, MachineType p1, MachineType p2, MachineType p3) { int index = 0; MachineType ret = MachineTypeForC(); if (ret != MachineType::None()) { storage_[index++] = WasmOpcodes::LocalTypeFor(ret); } if (p0 != MachineType::None()) storage_[index++] = WasmOpcodes::LocalTypeFor(p0); if (p1 != MachineType::None()) storage_[index++] = WasmOpcodes::LocalTypeFor(p1); if (p2 != MachineType::None()) storage_[index++] = WasmOpcodes::LocalTypeFor(p2); if (p3 != MachineType::None()) storage_[index++] = WasmOpcodes::LocalTypeFor(p3); compiler_.InitializeDescriptor(); wrapper_.Init(compiler_.descriptor(), p0, p1, p2, p3); } FunctionEnv* env() { return &compiler_.env; } // Builds a graph from the given Wasm code and generates the machine // code and call wrapper for that graph. This method must not be called // more than once. void Build(const byte* start, const byte* end) { CHECK(!compiled_); compiled_ = true; // Build the TF graph within the compiler. compiler_.Build(start, end); // Generate code. Handle code = compiler_.Compile(); if (compiler_.testing_module_) { // Update the table of function code in the module. compiler_.testing_module_->SetFunctionCode(compiler_.function_index_, code); } wrapper_.SetInnerCode(code); } ReturnType Call() { return Call(0, 0, 0, 0); } template ReturnType Call(P0 p0) { return Call(p0, 0, 0, 0); } template ReturnType Call(P0 p0, P1 p1) { return Call(p0, p1, 0, 0); } template ReturnType Call(P0 p0, P1 p1, P2 p2) { return Call(p0, p1, p2, 0); } template ReturnType Call(P0 p0, P1 p1, P2 p2, P3 p3) { CodeRunner runner(CcTest::InitIsolateOnce(), wrapper_.GetWrapperCode(), wrapper_.signature()); ReturnType return_value; int32_t result = runner.Call( &p0, &p1, &p2, &p3, &return_value); CHECK_EQ(WASM_WRAPPER_RETURN_VALUE, result); return return_value; } byte AllocateLocal(LocalType type) { int result = static_cast(env()->total_locals); env()->AddLocals(type, 1); byte b = static_cast(result); CHECK_EQ(result, b); return b; } protected: Zone zone; bool compiled_; LocalType storage_[WASM_RUNNER_MAX_NUM_PARAMETERS]; FunctionSig signature_; WasmFunctionCompiler compiler_; WasmFunctionWrapper wrapper_; static size_t GetParameterCount(MachineType p0, MachineType p1, MachineType p2, MachineType p3) { if (p0 == MachineType::None()) return 0; if (p1 == MachineType::None()) return 1; if (p2 == MachineType::None()) return 2; if (p3 == MachineType::None()) return 3; return 4; } }; } // namespace #endif