| // 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 <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #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<uint32_t>(x)) & 0xFFFFFFFF) |
| #define CHECK_TRAP64(x) \ |
| CHECK_EQ(0xdeadbeefdeadbeef, (bit_cast<uint64_t>(x)) & 0xFFFFFFFFFFFFFFFF) |
| #define CHECK_TRAP(x) CHECK_TRAP32(x) |
| |
| #define WASM_RUNNER_MAX_NUM_PARAMETERS 4 |
| #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 |
| // {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; |
| linker = nullptr; |
| origin = kWasmOrigin; |
| memset(global_data, 0, sizeof(global_data)); |
| } |
| |
| ~TestingModule() { |
| if (instance->mem_start) { |
| free(instance->mem_start); |
| } |
| } |
| |
| byte* AddMemory(size_t size) { |
| CHECK_NULL(instance->mem_start); |
| CHECK_EQ(0, instance->mem_size); |
| instance->mem_start = reinterpret_cast<byte*>(malloc(size)); |
| CHECK(instance->mem_start); |
| memset(instance->mem_start, 0, size); |
| instance->mem_size = size; |
| return raw_mem_start<byte>(); |
| } |
| |
| template <typename T> |
| T* AddMemoryElems(size_t count) { |
| AddMemory(count * sizeof(T)); |
| return raw_mem_start<T>(); |
| } |
| |
| template <typename T> |
| T* AddGlobal(MachineType mem_type) { |
| WasmGlobal* global = AddGlobal(mem_type); |
| return reinterpret_cast<T*>(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<byte>(size - 1); |
| } |
| |
| template <typename T> |
| T* raw_mem_start() { |
| DCHECK(instance->mem_start); |
| return reinterpret_cast<T*>(instance->mem_start); |
| } |
| |
| template <typename T> |
| T* raw_mem_end() { |
| DCHECK(instance->mem_start); |
| return reinterpret_cast<T*>(instance->mem_start + instance->mem_size); |
| } |
| |
| template <typename T> |
| T raw_mem_at(int i) { |
| DCHECK(instance->mem_start); |
| return reinterpret_cast<T*>(instance->mem_start)[i]; |
| } |
| |
| template <typename T> |
| T raw_val_at(int i) { |
| T val; |
| memcpy(&val, reinterpret_cast<void*>(instance->mem_start + i), sizeof(T)); |
| return val; |
| } |
| |
| // Zero-initialize the memory. |
| void BlankMemory() { |
| byte* raw = raw_mem_start<byte>(); |
| memset(raw, 0, instance->mem_size); |
| } |
| |
| // Pseudo-randomly intialize the memory. |
| void RandomizeMemory(unsigned int seed = 88) { |
| byte* raw = raw_mem_start<byte>(); |
| byte* end = raw_mem_end<byte>(); |
| v8::base::RandomNumberGenerator rng; |
| rng.SetSeed(seed); |
| rng.NextBytes(raw, end - raw); |
| } |
| |
| int AddFunction(FunctionSig* sig, Handle<Code> code) { |
| 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<uint32_t>(module->functions.size()); |
| module->functions.push_back( |
| {sig, index, 0, 0, 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> code) { |
| instance->function_code[index] = code; |
| } |
| |
| void AddIndirectFunctionTable(int* functions, int table_size) { |
| Isolate* isolate = module->shared_isolate; |
| Handle<FixedArray> fixed = |
| isolate->factory()->NewFixedArray(2 * table_size); |
| instance->function_table = fixed; |
| DCHECK_EQ(0u, module->function_table.size()); |
| 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<int>(module->function_table.size()); |
| for (int i = 0; i < table_size; i++) { |
| int function_index = module->function_table[i]; |
| WasmFunction* function = &module->functions[function_index]; |
| instance->function_table->set(i, Smi::FromInt(function->sig_index)); |
| instance->function_table->set(i + table_size, |
| *instance->function_code[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) { |
| byte size = WasmOpcodes::MemSize(mem_type); |
| global_offset = (global_offset + size - 1) & ~(size - 1); // align |
| module->globals.push_back({0, 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, ModuleEnv* module, |
| FunctionSig* sig, const byte* start, |
| const byte* end) { |
| compiler::WasmGraphBuilder builder(zone, jsgraph, sig); |
| TreeResult result = |
| BuildTFGraph(zone->allocator(), &builder, module, sig, 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 <typename ReturnType> |
| 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<MachineType>::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<Node*>(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<ReturnType>().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> code_handle) { |
| NodeProperties::ChangeOp(inner_code_node_, |
| common()->HeapConstant(code_handle)); |
| } |
| |
| Handle<Code> 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<MachineRepresentation>::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<MachineType>* signature() const { return signature_; } |
| |
| private: |
| Node* inner_code_node_; |
| Handle<Code> code_; |
| Signature<MachineType>* 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()), |
| sig(sig), |
| descriptor_(nullptr), |
| testing_module_(module) { |
| if (module) { |
| // Get a new function from the testing module. |
| function_ = nullptr; |
| function_index_ = module->AddFunction(sig, Handle<Code>::null()); |
| } else { |
| // Create our own function. |
| function_ = new WasmFunction(); |
| function_->sig = sig; |
| function_index_ = 0; |
| } |
| } |
| |
| ~WasmFunctionCompiler() { |
| if (function_) delete function_; |
| } |
| |
| JSGraph jsgraph; |
| FunctionSig* sig; |
| // The call descriptor is initialized when the function is compiled. |
| CallDescriptor* descriptor_; |
| TestingModule* testing_module_; |
| WasmFunction* function_; |
| int function_index_; |
| LocalDeclEncoder local_decls; |
| |
| 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_ = testing_module_->GetWasmCallDescriptor(main_zone(), sig); |
| } |
| } |
| CallDescriptor* descriptor() { return descriptor_; } |
| |
| void Build(const byte* start, const byte* end) { |
| // Build the TurboFan graph. |
| local_decls.Prepend(&start, &end); |
| TestBuildingGraph(main_zone(), &jsgraph, testing_module_, sig, start, end); |
| delete[] start; |
| } |
| |
| byte AllocateLocal(LocalType type) { |
| uint32_t index = local_decls.AddLocals(1, type, sig); |
| byte result = static_cast<byte>(index); |
| DCHECK_EQ(index, result); |
| return result; |
| } |
| |
| Handle<Code> Compile() { |
| InitializeDescriptor(); |
| CallDescriptor* desc = descriptor_; |
| if (kPointerSize == 4) { |
| desc = testing_module_->GetI32WasmCallDescriptor(this->zone(), desc); |
| } |
| CompilationInfo info("wasm compile", this->isolate(), this->zone()); |
| Handle<Code> 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; |
| } |
| |
| uint32_t CompileAndAdd(uint16_t sig_index = 0) { |
| CHECK(testing_module_); |
| function()->sig_index = sig_index; |
| Handle<Code> code = Compile(); |
| testing_module_->SetFunctionCode(function_index_, code); |
| return static_cast<uint32_t>(function_index_); |
| } |
| |
| WasmFunction* function() { |
| if (function_) return function_; |
| return &testing_module_->module->functions[function_index_]; |
| } |
| }; |
| |
| // A helper class to build graphs from Wasm bytecode, generate machine |
| // code, and run that code. |
| template <typename ReturnType> |
| class WasmRunner { |
| public: |
| WasmRunner(MachineType p0 = MachineType::None(), |
| MachineType p1 = MachineType::None(), |
| MachineType p2 = MachineType::None(), |
| MachineType p3 = MachineType::None()) |
| : zone(&allocator_), |
| compiled_(false), |
| signature_(MachineTypeForC<ReturnType>() == 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()) |
| : zone(&allocator_), |
| compiled_(false), |
| signature_(MachineTypeForC<ReturnType>() == 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<ReturnType>(); |
| 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); |
| } |
| |
| // 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> 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 <typename P0> |
| ReturnType Call(P0 p0) { |
| return Call(p0, 0, 0, 0); |
| } |
| |
| template <typename P0, typename P1> |
| ReturnType Call(P0 p0, P1 p1) { |
| return Call(p0, p1, 0, 0); |
| } |
| |
| template <typename P0, typename P1, typename P2> |
| ReturnType Call(P0 p0, P1 p1, P2 p2) { |
| return Call(p0, p1, p2, 0); |
| } |
| |
| template <typename P0, typename P1, typename P2, typename P3> |
| ReturnType Call(P0 p0, P1 p1, P2 p2, P3 p3) { |
| CodeRunner<int32_t> runner(CcTest::InitIsolateOnce(), |
| wrapper_.GetWrapperCode(), wrapper_.signature()); |
| ReturnType return_value; |
| int32_t result = runner.Call<void*, void*, void*, void*, void*>( |
| &p0, &p1, &p2, &p3, &return_value); |
| CHECK_EQ(WASM_WRAPPER_RETURN_VALUE, result); |
| return return_value; |
| } |
| |
| byte AllocateLocal(LocalType type) { return compiler_.AllocateLocal(type); } |
| |
| protected: |
| v8::base::AccountingAllocator allocator_; |
| Zone zone; |
| bool compiled_; |
| LocalType storage_[WASM_RUNNER_MAX_NUM_PARAMETERS]; |
| FunctionSig signature_; |
| WasmFunctionCompiler compiler_; |
| WasmFunctionWrapper<ReturnType> 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 |