// 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/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(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_), execution_mode_(mode), instance_(&module_), isolate_(CcTest::InitIsolateOnce()), global_offset(0), interpreter_(mode == kExecuteInterpreted ? new WasmInterpreter( ModuleBytesEnv(&module_, &instance_, Vector::empty()), zone->allocator()) : nullptr) { 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_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(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); } 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_) { const WasmFunction* function = &module->functions.back(); int interpreter_index = interpreter_->AddFunctionForTesting(function); CHECK_EQ(index, static_cast(interpreter_index)); } 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->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 instance_obj(0, isolate_); Handle code = instance->function_code[index]; WasmJs::InstallWasmMapsIfNeeded(isolate_, isolate_->native_context()); Handle ret_code = compiler::CompileJSToWasmWrapper(isolate_, &module_, code, index); Handle ret = WasmExportedFunction::New( isolate_, instance_obj, MaybeHandle(), static_cast(index), static_cast(this->module->functions[index].sig->parameter_count()), ret_code); 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 (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 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()); ScopedVector 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 new_bytes_str = Handle::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 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 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()); Handle> module_wrapper = Managed::New(isolate_, &module_, false); Handle