| // 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 <setjmp.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <array> |
| #include <memory> |
| |
| #include "src/base/utils/random-number-generator.h" |
| #include "src/zone/accounting-allocator.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/wasm/function-body-decoder.h" |
| #include "src/wasm/wasm-external-refs.h" |
| #include "src/wasm/wasm-interpreter.h" |
| #include "src/wasm/wasm-js.h" |
| #include "src/wasm/wasm-macro-gen.h" |
| #include "src/wasm/wasm-module.h" |
| #include "src/wasm/wasm-objects.h" |
| #include "src/wasm/wasm-opcodes.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" |
| |
| 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<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_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_), |
| execution_mode_(mode), |
| instance_(&module_), |
| isolate_(CcTest::InitIsolateOnce()), |
| global_offset(0), |
| interpreter_(mode == kExecuteInterpreted |
| ? new WasmInterpreter( |
| ModuleBytesEnv(&module_, &instance_, |
| Vector<const byte>::empty()), |
| zone->allocator()) |
| : 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(); |
| } |
| |
| ~TestingModule() { |
| if (instance->mem_start) { |
| free(instance->mem_start); |
| } |
| if (interpreter_) delete interpreter_; |
| } |
| |
| void ChangeOriginToAsmjs() { module_.origin = kAsmJsOrigin; } |
| |
| byte* AddMemory(uint32_t size) { |
| CHECK(!module_.has_memory); |
| CHECK_NULL(instance->mem_start); |
| CHECK_EQ(0, instance->mem_size); |
| module_.has_memory = true; |
| 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(uint32_t count) { |
| AddMemory(count * sizeof(T)); |
| return raw_mem_start<T>(); |
| } |
| |
| template <typename T> |
| T* AddGlobal( |
| ValueType type = WasmOpcodes::ValueTypeFor(MachineTypeForC<T>())) { |
| const WasmGlobal* global = AddGlobal(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 ReadMemory(&(reinterpret_cast<T*>(instance->mem_start)[i])); |
| } |
| |
| template <typename T> |
| T raw_val_at(int i) { |
| return ReadMemory(reinterpret_cast<T*>(instance->mem_start + i)); |
| } |
| |
| template <typename T> |
| void WriteMemory(T* p, T val) { |
| WriteLittleEndianValue<T>(p, val); |
| } |
| |
| template <typename T> |
| T ReadMemory(T* p) { |
| return ReadLittleEndianValue<T>(p); |
| } |
| |
| // 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); |
| } |
| |
| void SetMaxMemPages(uint32_t max_mem_pages) { |
| module_.max_mem_pages = max_mem_pages; |
| } |
| |
| uint32_t AddFunction(FunctionSig* sig, Handle<Code> 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<uint32_t>(module->functions.size()); |
| module_.functions.push_back({sig, index, 0, 0, 0, 0, 0, false, false}); |
| if (name) { |
| Vector<const byte> name_vec = Vector<const byte>::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_) { |
| const WasmFunction* function = &module->functions.back(); |
| int interpreter_index = interpreter_->AddFunctionForTesting(function); |
| CHECK_EQ(index, static_cast<uint32_t>(interpreter_index)); |
| } |
| DCHECK_LT(index, kMaxFunctions); // limited for testing. |
| return index; |
| } |
| |
| uint32_t AddJsFunction(FunctionSig* sig, const char* source) { |
| Handle<JSFunction> jsfunc = Handle<JSFunction>::cast(v8::Utils::OpenHandle( |
| *v8::Local<v8::Function>::Cast(CompileRun(source)))); |
| uint32_t index = AddFunction(sig, Handle<Code>::null(), nullptr); |
| Handle<Code> code = CompileWasmToJSWrapper( |
| isolate_, jsfunc, sig, index, Handle<String>::null(), |
| Handle<String>::null(), module->origin); |
| instance->function_code[index] = code; |
| return index; |
| } |
| |
| Handle<JSFunction> WrapCode(uint32_t index) { |
| // Wrap the code so it can be called as a JS function. |
| Handle<Code> code = instance->function_code[index]; |
| Handle<Code> ret_code = |
| compiler::CompileJSToWasmWrapper(isolate_, &module_, code, index); |
| Handle<JSFunction> ret = WasmExportedFunction::New( |
| isolate_, instance_object(), MaybeHandle<String>(), |
| static_cast<int>(index), |
| static_cast<int>(this->module->functions[index].sig->parameter_count()), |
| ret_code); |
| |
| // Add weak reference to exported functions. |
| Handle<WasmCompiledModule> compiled_module( |
| instance_object()->compiled_module(), isolate_); |
| Handle<FixedArray> old_arr = compiled_module->weak_exported_functions(); |
| Handle<FixedArray> new_arr = |
| isolate_->factory()->NewFixedArray(old_arr->length() + 1); |
| old_arr->CopyTo(0, *new_arr, 0, old_arr->length()); |
| Handle<WeakCell> 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> 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<int32_t>(), 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 (execution_mode_ == kExecuteInterpreted) 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<FixedArray> function_table = instance->function_tables[i]; |
| Handle<FixedArray> signature_table = instance->signature_tables[i]; |
| int table_size = static_cast<int>(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<const byte> bytes) { |
| Handle<SeqOneByteString> old_bytes( |
| instance_object_->compiled_module()->module_bytes(), isolate_); |
| uint32_t old_size = static_cast<uint32_t>(old_bytes->length()); |
| ScopedVector<byte> new_bytes(old_size + bytes.length()); |
| memcpy(new_bytes.start(), old_bytes->GetChars(), old_size); |
| memcpy(new_bytes.start() + old_size, bytes.start(), bytes.length()); |
| Handle<SeqOneByteString> new_bytes_str = Handle<SeqOneByteString>::cast( |
| isolate_->factory()->NewStringFromOneByte(new_bytes).ToHandleChecked()); |
| instance_object_->compiled_module()->shared()->set_module_bytes( |
| *new_bytes_str); |
| return old_size; |
| } |
| |
| WasmFunction* GetFunctionAt(int index) { return &module_.functions[index]; } |
| |
| WasmInterpreter* interpreter() { return interpreter_; } |
| WasmExecutionMode execution_mode() { return execution_mode_; } |
| Isolate* isolate() { return isolate_; } |
| Handle<WasmInstanceObject> instance_object() { return instance_object_; } |
| |
| private: |
| WasmExecutionMode execution_mode_; |
| WasmModule module_; |
| WasmInstance instance_; |
| Isolate* isolate_; |
| uint32_t global_offset; |
| V8_ALIGNED(8) byte global_data[kMaxGlobalsSize]; // preallocated global data. |
| WasmInterpreter* interpreter_; |
| Handle<WasmInstanceObject> 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<WasmInstanceObject> InitInstanceObject() { |
| Handle<SeqOneByteString> empty_string = Handle<SeqOneByteString>::cast( |
| isolate_->factory()->NewStringFromOneByte({}).ToHandleChecked()); |
| Handle<Managed<wasm::WasmModule>> module_wrapper = |
| Managed<wasm::WasmModule>::New(isolate_, &module_, false); |
| Handle<Script> script = |
| isolate_->factory()->NewScript(isolate_->factory()->empty_string()); |
| script->set_type(Script::TYPE_WASM); |
| Handle<WasmSharedModuleData> shared_module_data = |
| WasmSharedModuleData::New(isolate_, module_wrapper, empty_string, |
| script, Handle<ByteArray>::null()); |
| Handle<WasmCompiledModule> compiled_module = |
| WasmCompiledModule::New(isolate_, shared_module_data); |
| // Minimally initialize the compiled module such that IsWasmCompiledModule |
| // passes. |
| // If tests need more (correct) information, add it later. |
| compiled_module->set_min_mem_pages(0); |
| compiled_module->set_max_mem_pages(Smi::kMaxValue); |
| Handle<FixedArray> code_table = isolate_->factory()->NewFixedArray(0); |
| compiled_module->set_code_table(code_table); |
| Handle<FixedArray> weak_exported = isolate_->factory()->NewFixedArray(0); |
| compiled_module->set_weak_exported_functions(weak_exported); |
| DCHECK(WasmCompiledModule::IsWasmCompiledModule(*compiled_module)); |
| return WasmInstanceObject::New(isolate_, compiled_module); |
| } |
| }; |
| |
| inline void TestBuildingGraph(Zone* zone, JSGraph* jsgraph, ModuleEnv* module, |
| FunctionSig* sig, |
| SourcePositionTable* source_position_table, |
| const byte* start, const byte* end) { |
| compiler::WasmGraphBuilder builder(module, zone, jsgraph, sig, |
| source_position_table); |
| DecodeResult result = |
| BuildTFGraph(zone->allocator(), &builder, sig, start, end); |
| if (result.failed()) { |
| if (!FLAG_trace_wasm_decoder) { |
| // Retry the compilation with the tracing flag on, to help in debugging. |
| FLAG_trace_wasm_decoder = true; |
| result = BuildTFGraph(zone->allocator(), &builder, sig, start, end); |
| } |
| |
| 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 (!CpuFeatures::SupportsSimd128()) { |
| builder.SimdScalarLoweringForTesting(); |
| } |
| } |
| |
| class WasmFunctionWrapper : private GraphAndBuilders { |
| public: |
| explicit WasmFunctionWrapper(Zone* zone, int num_params) |
| : GraphAndBuilders(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, num_params + 1); |
| |
| sig_builder.AddReturn(MachineType::Int32()); |
| for (int i = 0; i < num_params + 1; i++) { |
| sig_builder.AddParam(MachineType::Pointer()); |
| } |
| signature_ = sig_builder.Build(); |
| } |
| |
| void Init(CallDescriptor* descriptor, MachineType return_type, |
| Vector<MachineType> param_types) { |
| DCHECK_NOT_NULL(descriptor); |
| DCHECK_EQ(signature_->parameter_count(), param_types.length() + 1); |
| |
| // Create the TF graph for the wrapper. |
| |
| // Function, effect, and control. |
| Node** parameters = zone()->NewArray<Node*>(param_types.length() + 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_; |
| |
| int param_idx = 0; |
| for (MachineType t : param_types) { |
| DCHECK_NE(MachineType::None(), t); |
| parameters[parameter_count] = graph()->NewNode( |
| machine()->Load(t), |
| graph()->NewNode(common()->Parameter(param_idx++), 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); |
| |
| if (!return_type.IsNone()) { |
| effect = graph()->NewNode( |
| machine()->Store(StoreRepresentation( |
| return_type.representation(), WriteBarrierKind::kNoWriteBarrier)), |
| graph()->NewNode(common()->Parameter(param_types.length()), |
| graph()->start()), |
| graph()->NewNode(common()->Int32Constant(0)), call, effect, |
| graph()->start()); |
| } |
| Node* zero = graph()->NewNode(common()->Int32Constant(0)); |
| Node* r = graph()->NewNode( |
| common()->Return(), zero, |
| graph()->NewNode(common()->Int32Constant(WASM_WRAPPER_RETURN_VALUE)), |
| effect, graph()->start()); |
| graph()->SetEnd(graph()->NewNode(common()->End(2), r, graph()->start())); |
| } |
| |
| template <typename ReturnType, typename... ParamTypes> |
| void Init(CallDescriptor* descriptor) { |
| std::array<MachineType, sizeof...(ParamTypes)> param_machine_types{ |
| {MachineTypeForC<ParamTypes>()...}}; |
| Vector<MachineType> param_vec(param_machine_types.data(), |
| param_machine_types.size()); |
| Init(descriptor, MachineTypeForC<ReturnType>(), param_vec); |
| } |
| |
| 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) { |
| size_t num_params = signature_->parameter_count(); |
| // One additional parameter for the pointer of the return value. |
| Signature<MachineRepresentation>::Builder rep_builder(zone(), 1, |
| num_params + 1); |
| |
| rep_builder.AddReturn(MachineRepresentation::kWord32); |
| for (size_t i = 0; i < num_params + 1; i++) { |
| rep_builder.AddParam(MachineRepresentation::kWord32); |
| } |
| Int64Lowering r(graph(), machine(), common(), zone(), |
| rep_builder.Build()); |
| r.LowerGraph(); |
| } |
| |
| CompilationInfo info(ArrayVector("testing"), isolate, graph()->zone(), |
| Code::ComputeFlags(Code::STUB)); |
| 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. |
| // It contains the internal state for compilation (i.e. TurboFan graph) and |
| // interpretation (by adding to the interpreter manually). |
| class WasmFunctionCompiler : private GraphAndBuilders { |
| public: |
| Isolate* isolate() { return testing_module_->isolate(); } |
| Graph* graph() const { return main_graph_; } |
| Zone* zone() const { return graph()->zone(); } |
| CommonOperatorBuilder* common() { return &main_common_; } |
| MachineOperatorBuilder* machine() { return &main_machine_; } |
| CallDescriptor* descriptor() { |
| if (descriptor_ == nullptr) { |
| descriptor_ = testing_module_->GetWasmCallDescriptor(zone(), sig); |
| } |
| return descriptor_; |
| } |
| uint32_t function_index() { return function_->func_index; } |
| |
| void Build(const byte* start, const byte* end) { |
| size_t locals_size = local_decls.Size(); |
| size_t total_size = end - start + locals_size + 1; |
| byte* buffer = static_cast<byte*>(zone()->New(total_size)); |
| // Prepend the local decls to the code. |
| local_decls.Emit(buffer); |
| // Emit the code. |
| memcpy(buffer + locals_size, start, end - start); |
| // Append an extra end opcode. |
| buffer[total_size - 1] = kExprEnd; |
| |
| start = buffer; |
| end = buffer + total_size; |
| |
| CHECK_GE(kMaxInt, end - start); |
| int len = static_cast<int>(end - start); |
| function_->code_start_offset = |
| testing_module_->AddBytes(Vector<const byte>(start, len)); |
| function_->code_end_offset = function_->code_start_offset + len; |
| |
| if (interpreter_) { |
| // Add the code to the interpreter. |
| CHECK(interpreter_->SetFunctionCodeForTesting(function_, start, end)); |
| return; |
| } |
| |
| // Build the TurboFan graph. |
| TestBuildingGraph(zone(), &jsgraph, testing_module_, sig, |
| &source_position_table_, start, end); |
| Handle<Code> code = Compile(); |
| testing_module_->SetFunctionCode(function_index(), code); |
| |
| // Add to code table. |
| Handle<WasmCompiledModule> compiled_module( |
| testing_module_->instance_object()->compiled_module(), isolate()); |
| Handle<FixedArray> code_table = compiled_module->code_table(); |
| code_table = FixedArray::SetAndGrow(code_table, function_index(), code); |
| compiled_module->set_code_table(code_table); |
| } |
| |
| byte AllocateLocal(ValueType type) { |
| uint32_t index = local_decls.AddLocals(1, type); |
| byte result = static_cast<byte>(index); |
| DCHECK_EQ(index, result); |
| return result; |
| } |
| |
| void SetSigIndex(int sig_index) { function_->sig_index = sig_index; } |
| |
| private: |
| friend class WasmRunnerBase; |
| |
| explicit WasmFunctionCompiler(Zone* zone, FunctionSig* sig, |
| TestingModule* module, const char* name) |
| : GraphAndBuilders(zone), |
| jsgraph(module->isolate(), this->graph(), this->common(), nullptr, |
| nullptr, this->machine()), |
| sig(sig), |
| descriptor_(nullptr), |
| testing_module_(module), |
| local_decls(zone, sig), |
| source_position_table_(this->graph()), |
| interpreter_(module->interpreter()) { |
| // Get a new function from the testing module. |
| int index = module->AddFunction(sig, Handle<Code>::null(), name); |
| function_ = testing_module_->GetFunctionAt(index); |
| } |
| |
| Handle<Code> Compile() { |
| CallDescriptor* desc = descriptor(); |
| if (kPointerSize == 4) { |
| desc = testing_module_->GetI32WasmCallDescriptor(this->zone(), desc); |
| } |
| CompilationInfo info(CStrVector("wasm"), this->isolate(), this->zone(), |
| Code::ComputeFlags(Code::WASM_FUNCTION)); |
| std::unique_ptr<CompilationJob> job(Pipeline::NewWasmCompilationJob( |
| &info, &jsgraph, desc, &source_position_table_, nullptr)); |
| if (job->ExecuteJob() != CompilationJob::SUCCEEDED || |
| job->FinalizeJob() != CompilationJob::SUCCEEDED) |
| return Handle<Code>::null(); |
| |
| Handle<Code> code = info.code(); |
| |
| // Deopt data holds <WeakCell<wasm_instance>, func_index>. |
| DCHECK(code->deoptimization_data() == nullptr || |
| code->deoptimization_data()->length() == 0); |
| Handle<FixedArray> deopt_data = |
| isolate()->factory()->NewFixedArray(2, TENURED); |
| Handle<Object> weak_instance = |
| isolate()->factory()->NewWeakCell(testing_module_->instance_object()); |
| deopt_data->set(0, *weak_instance); |
| deopt_data->set(1, Smi::FromInt(static_cast<int>(function_index()))); |
| deopt_data->set_length(2); |
| code->set_deoptimization_data(*deopt_data); |
| |
| #ifdef ENABLE_DISASSEMBLER |
| if (FLAG_print_opt_code) { |
| OFStream os(stdout); |
| code->Disassemble("wasm code", os); |
| } |
| #endif |
| |
| return code; |
| } |
| |
| JSGraph jsgraph; |
| FunctionSig* sig; |
| // The call descriptor is initialized when the function is compiled. |
| CallDescriptor* descriptor_; |
| TestingModule* testing_module_; |
| Vector<const char> debug_name_; |
| WasmFunction* function_; |
| LocalDeclEncoder local_decls; |
| 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: |
| explicit WasmRunnerBase(WasmExecutionMode execution_mode, int num_params) |
| : zone_(&allocator_, ZONE_NAME), |
| module_(&zone_, execution_mode), |
| 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 <typename ReturnType, typename... ParamTypes> |
| WasmFunctionCompiler& NewFunction(const char* name = nullptr) { |
| return NewFunction(CreateSig<ReturnType, ParamTypes...>(), 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, &module_, 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() { return functions_[0]->interpreter_; } |
| bool possible_nondeterminism() { return possible_nondeterminism_; } |
| TestingModule& module() { return module_; } |
| Zone* zone() { return &zone_; } |
| |
| // Set the context, such that e.g. runtime functions can be called. |
| void SetModuleContext() { |
| if (!module_.instance->context.is_null()) { |
| CHECK(module_.instance->context.is_identical_to( |
| main_isolate()->native_context())); |
| return; |
| } |
| module_.instance->context = main_isolate()->native_context(); |
| } |
| |
| private: |
| FunctionSig* CreateSig(MachineType return_type, |
| Vector<MachineType> param_types) { |
| int return_count = return_type.IsNone() ? 0 : 1; |
| int param_count = param_types.length(); |
| |
| // Allocate storage array in zone. |
| ValueType* sig_types = |
| zone_.NewArray<ValueType>(return_count + param_count); |
| |
| // Convert machine types to local types, and check that there are no |
| // MachineType::None()'s in the parameters. |
| int idx = 0; |
| if (return_count) sig_types[idx++] = WasmOpcodes::ValueTypeFor(return_type); |
| for (MachineType param : param_types) { |
| CHECK_NE(MachineType::None(), param); |
| sig_types[idx++] = WasmOpcodes::ValueTypeFor(param); |
| } |
| return new (&zone_) FunctionSig(return_count, param_count, sig_types); |
| } |
| |
| template <typename ReturnType, typename... ParamTypes> |
| FunctionSig* CreateSig() { |
| std::array<MachineType, sizeof...(ParamTypes)> param_machine_types{ |
| {MachineTypeForC<ParamTypes>()...}}; |
| Vector<MachineType> param_vec(param_machine_types.data(), |
| param_machine_types.size()); |
| return CreateSig(MachineTypeForC<ReturnType>(), param_vec); |
| } |
| |
| protected: |
| v8::internal::AccountingAllocator allocator_; |
| Zone zone_; |
| TestingModule module_; |
| std::vector<std::unique_ptr<WasmFunctionCompiler>> functions_; |
| WasmFunctionWrapper wrapper_; |
| bool compiled_ = false; |
| bool possible_nondeterminism_ = false; |
| |
| bool interpret() { return module_.execution_mode() == kExecuteInterpreted; } |
| |
| public: |
| // This field has to be static. Otherwise, gcc complains about the using in |
| // the lambda context below. |
| static jmp_buf jump_buffer; |
| }; |
| |
| template <typename ReturnType, typename... ParamTypes> |
| class WasmRunner : public WasmRunnerBase { |
| public: |
| explicit WasmRunner(WasmExecutionMode execution_mode, |
| const char* main_fn_name = "main") |
| : WasmRunnerBase(execution_mode, sizeof...(ParamTypes)) { |
| NewFunction<ReturnType, ParamTypes...>(main_fn_name); |
| if (!interpret()) { |
| wrapper_.Init<ReturnType, ParamTypes...>(functions_[0]->descriptor()); |
| } |
| } |
| |
| ReturnType Call(ParamTypes... p) { |
| DCHECK(compiled_); |
| if (interpret()) return CallInterpreter(p...); |
| |
| // Use setjmp/longjmp to deal with traps in WebAssembly code. |
| ReturnType return_value = static_cast<ReturnType>(0xdeadbeefdeadbeef); |
| static int setjmp_ret; |
| setjmp_ret = setjmp(WasmRunnerBase::jump_buffer); |
| // setjmp returns 0 on the first return, 1 (passed to longjmp) after trap. |
| if (setjmp_ret == 0) { |
| DoCall(static_cast<void*>(&p)..., static_cast<void*>(&return_value)); |
| } |
| return return_value; |
| } |
| |
| ReturnType CallInterpreter(ParamTypes... p) { |
| WasmInterpreter::Thread* thread = interpreter()->GetThread(0); |
| thread->Reset(); |
| std::array<WasmVal, sizeof...(p)> args{{WasmVal(p)...}}; |
| thread->PushFrame(function(), args.data()); |
| if (thread->Run() == WasmInterpreter::FINISHED) { |
| WasmVal val = thread->GetReturnValue(); |
| possible_nondeterminism_ |= thread->PossibleNondeterminism(); |
| return val.to<ReturnType>(); |
| } else if (thread->state() == WasmInterpreter::TRAPPED) { |
| // TODO(titzer): return the correct trap code |
| int64_t result = 0xdeadbeefdeadbeef; |
| return static_cast<ReturnType>(result); |
| } else { |
| // TODO(titzer): falling off end |
| return ReturnType{0}; |
| } |
| } |
| |
| private: |
| // Don't inline this function. The setjmp above should be followed immediately |
| // by a call. |
| template <typename... Ptrs> |
| V8_NOINLINE void DoCall(Ptrs... ptrs) { |
| auto trap_callback = []() -> void { |
| set_trap_callback_for_testing(nullptr); |
| longjmp(WasmRunnerBase::jump_buffer, 1); |
| }; |
| set_trap_callback_for_testing(trap_callback); |
| |
| wrapper_.SetInnerCode( |
| module_.GetFunctionCode(functions_[0]->function_index())); |
| CodeRunner<int32_t> runner(CcTest::InitIsolateOnce(), |
| wrapper_.GetWrapperCode(), wrapper_.signature()); |
| int32_t result = runner.Call(ptrs...); |
| // If we arrive here, no trap happened. |
| CHECK_EQ(WASM_WRAPPER_RETURN_VALUE, result); |
| } |
| }; |
| |
| // Declare static variable. |
| jmp_buf WasmRunnerBase::jump_buffer; |
| |
| // A macro to define tests that run in different engine configurations. |
| #define WASM_EXEC_TEST(name) \ |
| void RunWasm_##name(WasmExecutionMode execution_mode); \ |
| TEST(RunWasmCompiled_##name) { RunWasm_##name(kExecuteCompiled); } \ |
| TEST(RunWasmInterpreted_##name) { RunWasm_##name(kExecuteInterpreted); } \ |
| void RunWasm_##name(WasmExecutionMode execution_mode) |
| |
| #define WASM_EXEC_TEST_WITH_TRAP(name) \ |
| void RunWasm_##name(WasmExecutionMode execution_mode); \ |
| TEST(RunWasmCompiled_##name) { RunWasm_##name(kExecuteCompiled); } \ |
| void RunWasm_##name(WasmExecutionMode execution_mode); \ |
| TEST(RunWasmCompiledWithoutTrapIf_##name) { \ |
| bool trap_if = FLAG_wasm_trap_if; \ |
| FLAG_wasm_trap_if = false; \ |
| RunWasm_##name(kExecuteCompiled); \ |
| FLAG_wasm_trap_if = trap_if; \ |
| } \ |
| TEST(RunWasmInterpreted_##name) { RunWasm_##name(kExecuteInterpreted); } \ |
| void RunWasm_##name(WasmExecutionMode execution_mode) |
| |
| #define WASM_EXEC_COMPILED_TEST(name) \ |
| void RunWasm_##name(WasmExecutionMode execution_mode); \ |
| TEST(RunWasmCompiled_##name) { RunWasm_##name(kExecuteCompiled); } \ |
| void RunWasm_##name(WasmExecutionMode execution_mode) |
| |
| } // namespace |
| |
| #endif |