// 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-code-manager.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-inl.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" namespace v8 { namespace internal { namespace wasm { constexpr uint32_t kMaxFunctions = 10; constexpr uint32_t kMaxGlobalsSize = 128; enum WasmExecutionMode { kExecuteInterpreter, kExecuteTurbofan, kExecuteLiftoff, // TODO(bug:7028): Introduce another enum for simd lowering. kExecuteSimdLowered }; using compiler::CallDescriptor; using compiler::MachineTypeForC; using compiler::Node; // 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) // A Wasm module builder. 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 TestingModuleBuilder { public: TestingModuleBuilder(Zone*, WasmExecutionMode, compiler::RuntimeExceptionSupport); void ChangeOriginToAsmjs() { test_module_.set_origin(kAsmJsOrigin); } byte* AddMemory(uint32_t size); size_t CodeTableLength() const { if (FLAG_wasm_jit_to_native) { return native_module_->FunctionCount(); } else { 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_data_ + global->offset); } byte AddSignature(FunctionSig* sig) { DCHECK_EQ(test_module_.signatures.size(), test_module_.signature_ids.size()); test_module_.signatures.push_back(sig); auto canonical_sig_num = test_module_.signature_map.FindOrInsert(sig); test_module_.signature_ids.push_back(canonical_sig_num); size_t size = test_module_.signatures.size(); CHECK_GT(127, size); 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; if (instance_object()->has_memory_object()) { instance_object()->memory_object()->set_maximum_pages(maximum_pages); } } void SetHasSharedMemory() { test_module_.has_shared_memory = true; } uint32_t AddFunction(FunctionSig* sig, const char* name); uint32_t AddJsFunction(FunctionSig* sig, const char* source, Handle js_imports_table); Handle WrapCode(uint32_t index); void SetFunctionCode(uint32_t index, Handle code) { function_code_[index] = code; } void AddIndirectFunctionTable(uint16_t* function_indexes, uint32_t table_size); void PopulateIndirectFunctionTable(); uint32_t AddBytes(Vector bytes); WasmFunction* GetFunctionAt(int index) { return &test_module_.functions[index]; } WasmInterpreter* interpreter() { return interpreter_; } bool interpret() { return interpreter_ != nullptr; } bool lower_simd() { return lower_simd_; } Isolate* isolate() { return isolate_; } Handle instance_object() { return instance_object_; } WasmCodeWrapper GetFunctionCode(uint32_t index) { if (FLAG_wasm_jit_to_native) { return WasmCodeWrapper(native_module_->GetCode(index)); } else { return WasmCodeWrapper(function_code_[index]); } } void SetFunctionCode(int index, Handle code) { function_code_[index] = code; } Address globals_start() { return reinterpret_cast
(globals_data_); } void Link() { if (!FLAG_wasm_jit_to_native) return; if (!linked_) { native_module_->LinkAll(); linked_ = true; native_module_->SetExecutable(true); } } compiler::ModuleEnv CreateModuleEnv(); WasmExecutionMode execution_mode() const { return execution_mode_; } compiler::RuntimeExceptionSupport runtime_exception_support() const { return runtime_exception_support_; } private: WasmModule test_module_; WasmModule* test_module_ptr_; Isolate* isolate_; uint32_t global_offset; byte* mem_start_; uint32_t mem_size_; std::vector> function_code_; std::vector function_tables_; std::vector signature_tables_; V8_ALIGNED(16) byte globals_data_[kMaxGlobalsSize]; WasmInterpreter* interpreter_; WasmExecutionMode execution_mode_; Handle instance_object_; NativeModule* native_module_; bool linked_ = false; compiler::RuntimeExceptionSupport runtime_exception_support_; bool lower_simd_; const WasmGlobal* AddGlobal(ValueType type); Handle InitInstanceObject(); }; void TestBuildingGraph( Zone* zone, compiler::JSGraph* jsgraph, compiler::ModuleEnv* module, FunctionSig* sig, compiler::SourcePositionTable* source_position_table, const byte* start, const byte* end, compiler::RuntimeExceptionSupport runtime_exception_support); class WasmFunctionWrapper : private compiler::GraphAndBuilders { public: WasmFunctionWrapper(Zone* zone, int num_params); void Init(CallDescriptor* descriptor, MachineType return_type, Vector param_types); template void Init(CallDescriptor* descriptor) { std::array param_machine_types{ {MachineTypeForC()...}}; Vector param_vec(param_machine_types.data(), param_machine_types.size()); Init(descriptor, MachineTypeForC(), param_vec); } void SetInnerCode(WasmCodeWrapper code) { if (FLAG_wasm_jit_to_native) { intptr_t address = reinterpret_cast( code.GetWasmCode()->instructions().start()); compiler::NodeProperties::ChangeOp( inner_code_node_, kPointerSize == 8 ? common()->RelocatableInt64Constant(address, RelocInfo::WASM_CALL) : common()->RelocatableInt32Constant(static_cast(address), RelocInfo::WASM_CALL)); } else { compiler::NodeProperties::ChangeOp( inner_code_node_, common()->HeapConstant(code.GetCode())); } } const compiler::Operator* IntPtrConstant(intptr_t value) { return machine()->Is32() ? common()->Int32Constant(static_cast(value)) : common()->Int64Constant(static_cast(value)); } void SetContextAddress(uintptr_t value) { auto rmode = RelocInfo::WASM_CONTEXT_REFERENCE; auto op = kPointerSize == 8 ? common()->RelocatableInt64Constant( static_cast(value), rmode) : common()->RelocatableInt32Constant( static_cast(value), rmode); compiler::NodeProperties::ChangeOp(context_address_, op); } Handle GetWrapperCode(); Signature* signature() const { return signature_; } private: Node* inner_code_node_; Node* context_address_; Handle code_; Signature* signature_; }; // A helper for compiling wasm functions for testing. // It contains the internal state for compilation (i.e. TurboFan graph) and // interpretation (by adding to the interpreter manually). class WasmFunctionCompiler : public compiler::GraphAndBuilders { public: ~WasmFunctionCompiler(); Isolate* isolate() { return builder_->isolate(); } CallDescriptor* descriptor() { if (descriptor_ == nullptr) { descriptor_ = compiler::GetWasmCallDescriptor(zone(), sig); } return descriptor_; } uint32_t function_index() { return function_->func_index; } void Build(const byte* start, const byte* end); byte AllocateLocal(ValueType type) { uint32_t index = local_decls.AddLocals(1, type); byte result = static_cast(index); DCHECK_EQ(index, result); return result; } void SetSigIndex(int sig_index) { function_->sig_index = sig_index; } private: friend class WasmRunnerBase; WasmFunctionCompiler(Zone* zone, FunctionSig* sig, TestingModuleBuilder* builder, const char* name); compiler::JSGraph jsgraph; FunctionSig* sig; // The call descriptor is initialized when the function is compiled. CallDescriptor* descriptor_; TestingModuleBuilder* builder_; WasmFunction* function_; LocalDeclEncoder local_decls; compiler::SourcePositionTable source_position_table_; WasmInterpreter* interpreter_; }; // A helper class to build a module around Wasm bytecode, generate machine // code, and run that code. class WasmRunnerBase : public HandleAndZoneScope { public: WasmRunnerBase(WasmExecutionMode execution_mode, int num_params, compiler::RuntimeExceptionSupport runtime_exception_support) : zone_(&allocator_, ZONE_NAME), builder_(&zone_, execution_mode, runtime_exception_support), wrapper_(&zone_, num_params) {} // 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; functions_[0]->Build(start, end); } // Resets the state for building the next function. // The main function called will always be the first function. template WasmFunctionCompiler& NewFunction(const char* name = nullptr) { return NewFunction(CreateSig(), name); } // Resets the state for building the next function. // The main function called will be the last generated function. // Returns the index of the previously built function. WasmFunctionCompiler& NewFunction(FunctionSig* sig, const char* name = nullptr) { functions_.emplace_back( new WasmFunctionCompiler(&zone_, sig, &builder_, name)); return *functions_.back(); } byte AllocateLocal(ValueType type) { return functions_[0]->AllocateLocal(type); } uint32_t function_index() { return functions_[0]->function_index(); } WasmFunction* function() { return functions_[0]->function_; } WasmInterpreter* interpreter() { DCHECK(interpret()); return functions_[0]->interpreter_; } bool possible_nondeterminism() { return possible_nondeterminism_; } TestingModuleBuilder& builder() { return builder_; } Zone* zone() { return &zone_; } bool interpret() { return builder_.interpret(); } template FunctionSig* CreateSig() { std::array param_machine_types{ {MachineTypeForC()...}}; Vector param_vec(param_machine_types.data(), param_machine_types.size()); return CreateSig(MachineTypeForC(), param_vec); } private: FunctionSig* CreateSig(MachineType return_type, Vector param_types); protected: v8::internal::AccountingAllocator allocator_; Zone zone_; TestingModuleBuilder builder_; std::vector> functions_; WasmFunctionWrapper wrapper_; bool compiled_ = false; bool possible_nondeterminism_ = false; public: // This field has to be static. Otherwise, gcc complains about the use in // the lambda context below. static bool trap_happened; }; template class WasmRunner : public WasmRunnerBase { public: WasmRunner(WasmExecutionMode execution_mode, const char* main_fn_name = "main", compiler::RuntimeExceptionSupport runtime_exception_support = compiler::kNoRuntimeExceptionSupport) : WasmRunnerBase(execution_mode, sizeof...(ParamTypes), runtime_exception_support) { NewFunction(main_fn_name); if (!interpret()) { wrapper_.Init(functions_[0]->descriptor()); } } ReturnType Call(ParamTypes... p) { DCHECK(compiled_); if (interpret()) return CallInterpreter(p...); ReturnType return_value = static_cast(0xDEADBEEFDEADBEEF); WasmRunnerBase::trap_happened = false; auto trap_callback = []() -> void { WasmRunnerBase::trap_happened = true; set_trap_callback_for_testing(nullptr); }; set_trap_callback_for_testing(trap_callback); wrapper_.SetInnerCode(builder_.GetFunctionCode(0)); WasmContext* wasm_context = builder().instance_object()->wasm_context()->get(); wrapper_.SetContextAddress(reinterpret_cast(wasm_context)); builder().Link(); Handle wrapper_code = wrapper_.GetWrapperCode(); compiler::CodeRunner runner(CcTest::InitIsolateOnce(), wrapper_code, wrapper_.signature()); int32_t result = runner.Call(static_cast(&p)..., static_cast(&return_value)); CHECK_EQ(WASM_WRAPPER_RETURN_VALUE, result); return WasmRunnerBase::trap_happened ? static_cast(0xDEADBEEFDEADBEEF) : return_value; } ReturnType CallInterpreter(ParamTypes... p) { WasmInterpreter::Thread* thread = interpreter()->GetThread(0); thread->Reset(); std::array args{{WasmValue(p)...}}; thread->InitFrame(function(), args.data()); WasmInterpreter::HeapObjectsScope heap_objects_scope( interpreter(), builder().instance_object()); if (thread->Run() == WasmInterpreter::FINISHED) { WasmValue val = thread->GetReturnValue(); possible_nondeterminism_ |= thread->PossibleNondeterminism(); return val.to(); } else if (thread->state() == WasmInterpreter::TRAPPED) { // TODO(titzer): return the correct trap code int64_t result = 0xDEADBEEFDEADBEEF; return static_cast(result); } else { // TODO(titzer): falling off end return ReturnType{0}; } } Handle GetWrapperCode() { return wrapper_.GetWrapperCode(); } }; // A macro to define tests that run in different engine configurations. #define WASM_EXEC_TEST(name) \ void RunWasm_##name(WasmExecutionMode execution_mode); \ TEST(RunWasmTurbofan_##name) { RunWasm_##name(kExecuteTurbofan); } \ TEST(RunWasmLiftoff_##name) { RunWasm_##name(kExecuteLiftoff); } \ TEST(RunWasmInterpreter_##name) { RunWasm_##name(kExecuteInterpreter); } \ void RunWasm_##name(WasmExecutionMode execution_mode) #define WASM_COMPILED_EXEC_TEST(name) \ void RunWasm_##name(WasmExecutionMode execution_mode); \ TEST(RunWasmTurbofan_##name) { RunWasm_##name(kExecuteTurbofan); } \ TEST(RunWasmLiftoff_##name) { RunWasm_##name(kExecuteLiftoff); } \ void RunWasm_##name(WasmExecutionMode execution_mode) #define WASM_EXEC_TEST_WITH_TRAP(name) \ void RunWasm_##name(WasmExecutionMode execution_mode); \ TEST(RunWasmTurbofan_##name) { \ if (trap_handler::UseTrapHandler()) return; \ RunWasm_##name(kExecuteTurbofan); \ } \ TEST(RunWasmLiftoff_##name) { \ if (trap_handler::UseTrapHandler()) return; \ RunWasm_##name(kExecuteLiftoff); \ } \ TEST(RunWasmInterpreter_##name) { \ if (trap_handler::UseTrapHandler()) return; \ RunWasm_##name(kExecuteInterpreter); \ } \ void RunWasm_##name(WasmExecutionMode execution_mode) } // namespace wasm } // namespace internal } // namespace v8 #endif