1 // Copyright 2016 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef WASM_RUN_UTILS_H 6 #define WASM_RUN_UTILS_H 7 8 #include <stdint.h> 9 #include <stdlib.h> 10 #include <string.h> 11 12 #include "src/base/utils/random-number-generator.h" 13 14 #include "src/compiler/graph-visualizer.h" 15 #include "src/compiler/js-graph.h" 16 #include "src/compiler/wasm-compiler.h" 17 18 #include "src/wasm/ast-decoder.h" 19 #include "src/wasm/wasm-js.h" 20 #include "src/wasm/wasm-module.h" 21 #include "src/wasm/wasm-opcodes.h" 22 23 #include "test/cctest/cctest.h" 24 #include "test/cctest/compiler/codegen-tester.h" 25 #include "test/cctest/compiler/graph-builder-tester.h" 26 27 // TODO(titzer): pull WASM_64 up to a common header. 28 #if !V8_TARGET_ARCH_32_BIT || V8_TARGET_ARCH_X64 29 #define WASM_64 1 30 #else 31 #define WASM_64 0 32 #endif 33 34 // TODO(titzer): check traps more robustly in tests. 35 // Currently, in tests, we just return 0xdeadbeef from the function in which 36 // the trap occurs if the runtime context is not available to throw a JavaScript 37 // exception. 38 #define CHECK_TRAP32(x) \ 39 CHECK_EQ(0xdeadbeef, (bit_cast<uint32_t>(x)) & 0xFFFFFFFF) 40 #define CHECK_TRAP64(x) \ 41 CHECK_EQ(0xdeadbeefdeadbeef, (bit_cast<uint64_t>(x)) & 0xFFFFFFFFFFFFFFFF) 42 #define CHECK_TRAP(x) CHECK_TRAP32(x) 43 44 namespace { 45 using namespace v8::base; 46 using namespace v8::internal; 47 using namespace v8::internal::compiler; 48 using namespace v8::internal::wasm; 49 50 inline void init_env(FunctionEnv* env, FunctionSig* sig) { 51 env->module = nullptr; 52 env->sig = sig; 53 env->local_int32_count = 0; 54 env->local_int64_count = 0; 55 env->local_float32_count = 0; 56 env->local_float64_count = 0; 57 env->SumLocals(); 58 } 59 60 const uint32_t kMaxGlobalsSize = 128; 61 62 // A helper for module environments that adds the ability to allocate memory 63 // and global variables. 64 class TestingModule : public ModuleEnv { 65 public: 66 TestingModule() : mem_size(0), global_offset(0) { 67 globals_area = 0; 68 mem_start = 0; 69 mem_end = 0; 70 module = nullptr; 71 linker = nullptr; 72 function_code = nullptr; 73 asm_js = false; 74 memset(global_data, 0, sizeof(global_data)); 75 } 76 77 ~TestingModule() { 78 if (mem_start) { 79 free(raw_mem_start<byte>()); 80 } 81 if (function_code) delete function_code; 82 if (module) delete module; 83 } 84 85 byte* AddMemory(size_t size) { 86 CHECK_EQ(0, mem_start); 87 CHECK_EQ(0, mem_size); 88 mem_start = reinterpret_cast<uintptr_t>(malloc(size)); 89 CHECK(mem_start); 90 byte* raw = raw_mem_start<byte>(); 91 memset(raw, 0, size); 92 mem_end = mem_start + size; 93 mem_size = size; 94 return raw_mem_start<byte>(); 95 } 96 97 template <typename T> 98 T* AddMemoryElems(size_t count) { 99 AddMemory(count * sizeof(T)); 100 return raw_mem_start<T>(); 101 } 102 103 template <typename T> 104 T* AddGlobal(MachineType mem_type) { 105 WasmGlobal* global = AddGlobal(mem_type); 106 return reinterpret_cast<T*>(globals_area + global->offset); 107 } 108 109 byte AddSignature(FunctionSig* sig) { 110 AllocModule(); 111 if (!module->signatures) { 112 module->signatures = new std::vector<FunctionSig*>(); 113 } 114 module->signatures->push_back(sig); 115 size_t size = module->signatures->size(); 116 CHECK(size < 127); 117 return static_cast<byte>(size - 1); 118 } 119 120 template <typename T> 121 T* raw_mem_start() { 122 DCHECK(mem_start); 123 return reinterpret_cast<T*>(mem_start); 124 } 125 126 template <typename T> 127 T* raw_mem_end() { 128 DCHECK(mem_end); 129 return reinterpret_cast<T*>(mem_end); 130 } 131 132 template <typename T> 133 T raw_mem_at(int i) { 134 DCHECK(mem_start); 135 return reinterpret_cast<T*>(mem_start)[i]; 136 } 137 138 template <typename T> 139 T raw_val_at(int i) { 140 T val; 141 memcpy(&val, reinterpret_cast<void*>(mem_start + i), sizeof(T)); 142 return val; 143 } 144 145 // Zero-initialize the memory. 146 void BlankMemory() { 147 byte* raw = raw_mem_start<byte>(); 148 memset(raw, 0, mem_size); 149 } 150 151 // Pseudo-randomly intialize the memory. 152 void RandomizeMemory(unsigned int seed = 88) { 153 byte* raw = raw_mem_start<byte>(); 154 byte* end = raw_mem_end<byte>(); 155 v8::base::RandomNumberGenerator rng; 156 rng.SetSeed(seed); 157 rng.NextBytes(raw, end - raw); 158 } 159 160 WasmFunction* AddFunction(FunctionSig* sig, Handle<Code> code) { 161 AllocModule(); 162 if (module->functions == nullptr) { 163 module->functions = new std::vector<WasmFunction>(); 164 function_code = new std::vector<Handle<Code>>(); 165 } 166 module->functions->push_back({sig, 0, 0, 0, 0, 0, 0, 0, false, false}); 167 function_code->push_back(code); 168 return &module->functions->back(); 169 } 170 171 private: 172 size_t mem_size; 173 uint32_t global_offset; 174 byte global_data[kMaxGlobalsSize]; 175 176 WasmGlobal* AddGlobal(MachineType mem_type) { 177 AllocModule(); 178 if (globals_area == 0) { 179 globals_area = reinterpret_cast<uintptr_t>(global_data); 180 module->globals = new std::vector<WasmGlobal>(); 181 } 182 byte size = WasmOpcodes::MemSize(mem_type); 183 global_offset = (global_offset + size - 1) & ~(size - 1); // align 184 module->globals->push_back({0, mem_type, global_offset, false}); 185 global_offset += size; 186 // limit number of globals. 187 CHECK_LT(global_offset, kMaxGlobalsSize); 188 return &module->globals->back(); 189 } 190 void AllocModule() { 191 if (module == nullptr) { 192 module = new WasmModule(); 193 module->shared_isolate = CcTest::InitIsolateOnce(); 194 module->globals = nullptr; 195 module->functions = nullptr; 196 module->data_segments = nullptr; 197 } 198 } 199 }; 200 201 202 inline void TestBuildingGraph(Zone* zone, JSGraph* jsgraph, FunctionEnv* env, 203 const byte* start, const byte* end) { 204 compiler::WasmGraphBuilder builder(zone, jsgraph, env->sig); 205 TreeResult result = BuildTFGraph(&builder, env, start, end); 206 if (result.failed()) { 207 ptrdiff_t pc = result.error_pc - result.start; 208 ptrdiff_t pt = result.error_pt - result.start; 209 std::ostringstream str; 210 str << "Verification failed: " << result.error_code << " pc = +" << pc; 211 if (result.error_pt) str << ", pt = +" << pt; 212 str << ", msg = " << result.error_msg.get(); 213 FATAL(str.str().c_str()); 214 } 215 if (FLAG_trace_turbo_graph) { 216 OFStream os(stdout); 217 os << AsRPO(*jsgraph->graph()); 218 } 219 } 220 221 222 // A helper for compiling functions that are only internally callable WASM code. 223 class WasmFunctionCompiler : public HandleAndZoneScope, 224 private GraphAndBuilders { 225 public: 226 explicit WasmFunctionCompiler(FunctionSig* sig, ModuleEnv* module = nullptr) 227 : GraphAndBuilders(main_zone()), 228 jsgraph(this->isolate(), this->graph(), this->common(), nullptr, 229 nullptr, this->machine()), 230 descriptor_(nullptr) { 231 init_env(&env, sig); 232 env.module = module; 233 } 234 235 JSGraph jsgraph; 236 FunctionEnv env; 237 // The call descriptor is initialized when the function is compiled. 238 CallDescriptor* descriptor_; 239 240 Isolate* isolate() { return main_isolate(); } 241 Graph* graph() const { return main_graph_; } 242 Zone* zone() const { return graph()->zone(); } 243 CommonOperatorBuilder* common() { return &main_common_; } 244 MachineOperatorBuilder* machine() { return &main_machine_; } 245 CallDescriptor* descriptor() { return descriptor_; } 246 247 void Build(const byte* start, const byte* end) { 248 TestBuildingGraph(main_zone(), &jsgraph, &env, start, end); 249 } 250 251 byte AllocateLocal(LocalType type) { 252 int result = static_cast<int>(env.total_locals); 253 env.AddLocals(type, 1); 254 byte b = static_cast<byte>(result); 255 CHECK_EQ(result, b); 256 return b; 257 } 258 259 Handle<Code> Compile(ModuleEnv* module) { 260 descriptor_ = module->GetWasmCallDescriptor(this->zone(), env.sig); 261 CompilationInfo info("wasm compile", this->isolate(), this->zone()); 262 Handle<Code> result = 263 Pipeline::GenerateCodeForTesting(&info, descriptor_, this->graph()); 264 #ifdef ENABLE_DISASSEMBLER 265 if (!result.is_null() && FLAG_print_opt_code) { 266 OFStream os(stdout); 267 result->Disassemble("wasm code", os); 268 } 269 #endif 270 271 return result; 272 } 273 274 uint32_t CompileAndAdd(TestingModule* module) { 275 uint32_t index = 0; 276 if (module->module && module->module->functions) { 277 index = static_cast<uint32_t>(module->module->functions->size()); 278 } 279 module->AddFunction(env.sig, Compile(module)); 280 return index; 281 } 282 }; 283 284 285 // A helper class to build graphs from Wasm bytecode, generate machine 286 // code, and run that code. 287 template <typename ReturnType> 288 class WasmRunner { 289 public: 290 WasmRunner(MachineType p0 = MachineType::None(), 291 MachineType p1 = MachineType::None(), 292 MachineType p2 = MachineType::None(), 293 MachineType p3 = MachineType::None()) 294 : signature_(MachineTypeForC<ReturnType>() == MachineType::None() ? 0 : 1, 295 GetParameterCount(p0, p1, p2, p3), storage_), 296 compiler_(&signature_), 297 call_wrapper_(p0, p1, p2, p3), 298 compilation_done_(false) { 299 int index = 0; 300 MachineType ret = MachineTypeForC<ReturnType>(); 301 if (ret != MachineType::None()) { 302 storage_[index++] = WasmOpcodes::LocalTypeFor(ret); 303 } 304 if (p0 != MachineType::None()) 305 storage_[index++] = WasmOpcodes::LocalTypeFor(p0); 306 if (p1 != MachineType::None()) 307 storage_[index++] = WasmOpcodes::LocalTypeFor(p1); 308 if (p2 != MachineType::None()) 309 storage_[index++] = WasmOpcodes::LocalTypeFor(p2); 310 if (p3 != MachineType::None()) 311 storage_[index++] = WasmOpcodes::LocalTypeFor(p3); 312 } 313 314 315 FunctionEnv* env() { return &compiler_.env; } 316 317 318 // Builds a graph from the given Wasm code, and generates the machine 319 // code and call wrapper for that graph. This method must not be called 320 // more than once. 321 void Build(const byte* start, const byte* end) { 322 DCHECK(!compilation_done_); 323 compilation_done_ = true; 324 // Build the TF graph. 325 compiler_.Build(start, end); 326 // Generate code. 327 Handle<Code> code = compiler_.Compile(env()->module); 328 329 // Construct the call wrapper. 330 Node* inputs[5]; 331 int input_count = 0; 332 inputs[input_count++] = call_wrapper_.HeapConstant(code); 333 for (size_t i = 0; i < signature_.parameter_count(); i++) { 334 inputs[input_count++] = call_wrapper_.Parameter(i); 335 } 336 337 call_wrapper_.Return(call_wrapper_.AddNode( 338 call_wrapper_.common()->Call(compiler_.descriptor()), input_count, 339 inputs)); 340 } 341 342 ReturnType Call() { return call_wrapper_.Call(); } 343 344 template <typename P0> 345 ReturnType Call(P0 p0) { 346 return call_wrapper_.Call(p0); 347 } 348 349 template <typename P0, typename P1> 350 ReturnType Call(P0 p0, P1 p1) { 351 return call_wrapper_.Call(p0, p1); 352 } 353 354 template <typename P0, typename P1, typename P2> 355 ReturnType Call(P0 p0, P1 p1, P2 p2) { 356 return call_wrapper_.Call(p0, p1, p2); 357 } 358 359 template <typename P0, typename P1, typename P2, typename P3> 360 ReturnType Call(P0 p0, P1 p1, P2 p2, P3 p3) { 361 return call_wrapper_.Call(p0, p1, p2, p3); 362 } 363 364 byte AllocateLocal(LocalType type) { 365 int result = static_cast<int>(env()->total_locals); 366 env()->AddLocals(type, 1); 367 byte b = static_cast<byte>(result); 368 CHECK_EQ(result, b); 369 return b; 370 } 371 372 private: 373 LocalType storage_[5]; 374 FunctionSig signature_; 375 WasmFunctionCompiler compiler_; 376 BufferedRawMachineAssemblerTester<ReturnType> call_wrapper_; 377 bool compilation_done_; 378 379 static size_t GetParameterCount(MachineType p0, MachineType p1, 380 MachineType p2, MachineType p3) { 381 if (p0 == MachineType::None()) return 0; 382 if (p1 == MachineType::None()) return 1; 383 if (p2 == MachineType::None()) return 2; 384 if (p3 == MachineType::None()) return 3; 385 return 4; 386 } 387 }; 388 389 } // namespace 390 391 #endif 392