• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
init_env(FunctionEnv * env,FunctionSig * sig)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:
TestingModule()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 
~TestingModule()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 
AddMemory(size_t size)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>
AddMemoryElems(size_t count)98   T* AddMemoryElems(size_t count) {
99     AddMemory(count * sizeof(T));
100     return raw_mem_start<T>();
101   }
102 
103   template <typename T>
AddGlobal(MachineType mem_type)104   T* AddGlobal(MachineType mem_type) {
105     WasmGlobal* global = AddGlobal(mem_type);
106     return reinterpret_cast<T*>(globals_area + global->offset);
107   }
108 
AddSignature(FunctionSig * sig)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>
raw_mem_start()121   T* raw_mem_start() {
122     DCHECK(mem_start);
123     return reinterpret_cast<T*>(mem_start);
124   }
125 
126   template <typename T>
raw_mem_end()127   T* raw_mem_end() {
128     DCHECK(mem_end);
129     return reinterpret_cast<T*>(mem_end);
130   }
131 
132   template <typename T>
raw_mem_at(int i)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>
raw_val_at(int i)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.
BlankMemory()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 
AddFunction(FunctionSig * sig,Handle<Code> code)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 
AddGlobal(MachineType mem_type)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   }
AllocModule()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 
TestBuildingGraph(Zone * zone,JSGraph * jsgraph,FunctionEnv * env,const byte * start,const byte * end)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)
GraphAndBuilders(main_zone ())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 
isolate()240   Isolate* isolate() { return main_isolate(); }
graph()241   Graph* graph() const { return main_graph_; }
zone()242   Zone* zone() const { return graph()->zone(); }
common()243   CommonOperatorBuilder* common() { return &main_common_; }
machine()244   MachineOperatorBuilder* machine() { return &main_machine_; }
descriptor()245   CallDescriptor* descriptor() { return descriptor_; }
246 
Build(const byte * start,const byte * end)247   void Build(const byte* start, const byte* end) {
248     TestBuildingGraph(main_zone(), &jsgraph, &env, start, end);
249   }
250 
AllocateLocal(LocalType type)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 
Compile(ModuleEnv * module)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 
CompileAndAdd(TestingModule * module)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 
env()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.
Build(const byte * start,const byte * end)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 
Call()342   ReturnType Call() { return call_wrapper_.Call(); }
343 
344   template <typename P0>
Call(P0 p0)345   ReturnType Call(P0 p0) {
346     return call_wrapper_.Call(p0);
347   }
348 
349   template <typename P0, typename P1>
Call(P0 p0,P1 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>
Call(P0 p0,P1 p1,P2 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>
Call(P0 p0,P1 p1,P2 p2,P3 p3)360   ReturnType Call(P0 p0, P1 p1, P2 p2, P3 p3) {
361     return call_wrapper_.Call(p0, p1, p2, p3);
362   }
363 
AllocateLocal(LocalType type)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 
GetParameterCount(MachineType p0,MachineType p1,MachineType p2,MachineType p3)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