Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame^] | 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 |