1 // Copyright 2014 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 #include <cmath>
6 #include <functional>
7 #include <limits>
8 #include <memory>
9 
10 #include "src/base/bits.h"
11 #include "src/codegen/assembler.h"
12 #include "src/codegen/compiler.h"
13 #include "src/codegen/machine-type.h"
14 #include "src/codegen/macro-assembler.h"
15 #include "src/compiler/linkage.h"
16 #include "src/compiler/wasm-compiler.h"
17 #include "src/objects/objects-inl.h"
18 #include "src/wasm/function-compiler.h"
19 #include "src/wasm/wasm-engine.h"
20 #include "src/wasm/wasm-objects-inl.h"
21 #include "src/wasm/wasm-opcodes.h"
22 #include "test/cctest/cctest.h"
23 #include "test/cctest/compiler/codegen-tester.h"
24 #include "test/cctest/compiler/value-helper.h"
25 
26 namespace v8 {
27 namespace internal {
28 namespace compiler {
29 
30 namespace {
31 
CreateCallDescriptor(Zone * zone,int return_count,int param_count,MachineType type)32 CallDescriptor* CreateCallDescriptor(Zone* zone, int return_count,
33                                      int param_count, MachineType type) {
34   wasm::FunctionSig::Builder builder(zone, return_count, param_count);
35 
36   for (int i = 0; i < param_count; i++) {
37     builder.AddParam(wasm::ValueType::For(type));
38   }
39 
40   for (int i = 0; i < return_count; i++) {
41     builder.AddReturn(wasm::ValueType::For(type));
42   }
43   return compiler::GetWasmCallDescriptor(zone, builder.Build());
44 }
45 
MakeConstant(RawMachineAssembler * m,MachineType type,int value)46 Node* MakeConstant(RawMachineAssembler* m, MachineType type, int value) {
47   switch (type.representation()) {
48     case MachineRepresentation::kWord32:
49       return m->Int32Constant(static_cast<int32_t>(value));
50     case MachineRepresentation::kWord64:
51       return m->Int64Constant(static_cast<int64_t>(value));
52     case MachineRepresentation::kFloat32:
53       return m->Float32Constant(static_cast<float>(value));
54     case MachineRepresentation::kFloat64:
55       return m->Float64Constant(static_cast<double>(value));
56     default:
57       UNREACHABLE();
58   }
59 }
60 
Add(RawMachineAssembler * m,MachineType type,Node * a,Node * b)61 Node* Add(RawMachineAssembler* m, MachineType type, Node* a, Node* b) {
62   switch (type.representation()) {
63     case MachineRepresentation::kWord32:
64       return m->Int32Add(a, b);
65     case MachineRepresentation::kWord64:
66       return m->Int64Add(a, b);
67     case MachineRepresentation::kFloat32:
68       return m->Float32Add(a, b);
69     case MachineRepresentation::kFloat64:
70       return m->Float64Add(a, b);
71     default:
72       UNREACHABLE();
73   }
74 }
75 
Sub(RawMachineAssembler * m,MachineType type,Node * a,Node * b)76 Node* Sub(RawMachineAssembler* m, MachineType type, Node* a, Node* b) {
77   switch (type.representation()) {
78     case MachineRepresentation::kWord32:
79       return m->Int32Sub(a, b);
80     case MachineRepresentation::kWord64:
81       return m->Int64Sub(a, b);
82     case MachineRepresentation::kFloat32:
83       return m->Float32Sub(a, b);
84     case MachineRepresentation::kFloat64:
85       return m->Float64Sub(a, b);
86     default:
87       UNREACHABLE();
88   }
89 }
90 
Mul(RawMachineAssembler * m,MachineType type,Node * a,Node * b)91 Node* Mul(RawMachineAssembler* m, MachineType type, Node* a, Node* b) {
92   switch (type.representation()) {
93     case MachineRepresentation::kWord32:
94       return m->Int32Mul(a, b);
95     case MachineRepresentation::kWord64:
96       return m->Int64Mul(a, b);
97     case MachineRepresentation::kFloat32:
98       return m->Float32Mul(a, b);
99     case MachineRepresentation::kFloat64:
100       return m->Float64Mul(a, b);
101     default:
102       UNREACHABLE();
103   }
104 }
105 
ToInt32(RawMachineAssembler * m,MachineType type,Node * a)106 Node* ToInt32(RawMachineAssembler* m, MachineType type, Node* a) {
107   switch (type.representation()) {
108     case MachineRepresentation::kWord32:
109       return a;
110     case MachineRepresentation::kWord64:
111       return m->TruncateInt64ToInt32(a);
112     case MachineRepresentation::kFloat32:
113       return m->TruncateFloat32ToInt32(a, TruncateKind::kArchitectureDefault);
114     case MachineRepresentation::kFloat64:
115       return m->RoundFloat64ToInt32(a);
116     default:
117       UNREACHABLE();
118   }
119 }
120 
AllocateNativeModule(Isolate * isolate,size_t code_size)121 std::shared_ptr<wasm::NativeModule> AllocateNativeModule(Isolate* isolate,
122                                                          size_t code_size) {
123   std::shared_ptr<wasm::WasmModule> module(new wasm::WasmModule());
124   module->num_declared_functions = 1;
125   // We have to add the code object to a NativeModule, because the
126   // WasmCallDescriptor assumes that code is on the native heap and not
127   // within a code object.
128   auto native_module = wasm::GetWasmEngine()->NewNativeModule(
129       isolate, wasm::WasmFeatures::All(), std::move(module), code_size);
130   native_module->SetWireBytes({});
131   return native_module;
132 }
133 
134 template <int kMinParamCount, int kMaxParamCount>
TestReturnMultipleValues(MachineType type,int min_count,int max_count)135 void TestReturnMultipleValues(MachineType type, int min_count, int max_count) {
136   for (int param_count : {kMinParamCount, kMaxParamCount}) {
137     for (int count = min_count; count < max_count; ++count) {
138       printf("\n==== type = %s, parameter_count = %d, count = %d ====\n\n\n",
139              MachineReprToString(type.representation()), param_count, count);
140       v8::internal::AccountingAllocator allocator;
141       Zone zone(&allocator, ZONE_NAME);
142       CallDescriptor* desc =
143           CreateCallDescriptor(&zone, count, param_count, type);
144       HandleAndZoneScope handles(kCompressGraphZone);
145       RawMachineAssembler m(
146           handles.main_isolate(),
147           handles.main_zone()->New<Graph>(handles.main_zone()), desc,
148           MachineType::PointerRepresentation(),
149           InstructionSelector::SupportedMachineOperatorFlags());
150 
151       // m.Parameter(0) is the WasmContext.
152       Node* p0 = m.Parameter(1);
153       Node* p1 = m.Parameter(2);
154       using Node_ptr = Node*;
155       std::unique_ptr<Node_ptr[]> returns(new Node_ptr[count]);
156       for (int i = 0; i < count; ++i) {
157         if (i % 3 == 0) returns[i] = Add(&m, type, p0, p1);
158         if (i % 3 == 1) returns[i] = Sub(&m, type, p0, p1);
159         if (i % 3 == 2) returns[i] = Mul(&m, type, p0, p1);
160       }
161       m.Return(count, returns.get());
162 
163       OptimizedCompilationInfo info(base::ArrayVector("testing"),
164                                     handles.main_zone(),
165                                     CodeKind::WASM_FUNCTION);
166       Handle<Code> code = Pipeline::GenerateCodeForTesting(
167                               &info, handles.main_isolate(), desc, m.graph(),
168                               AssemblerOptions::Default(handles.main_isolate()),
169                               m.ExportForTest())
170                               .ToHandleChecked();
171 #ifdef ENABLE_DISASSEMBLER
172       if (FLAG_print_code) {
173         StdoutStream os;
174         code->Disassemble("multi_value", os, handles.main_isolate());
175       }
176 #endif
177 
178       const int a = 47, b = 12;
179       int expect = 0;
180       for (int i = 0, sign = +1; i < count; ++i) {
181         if (i % 3 == 0) expect += sign * (a + b);
182         if (i % 3 == 1) expect += sign * (a - b);
183         if (i % 3 == 2) expect += sign * (a * b);
184         if (i % 4 == 0) sign = -sign;
185       }
186 
187       std::shared_ptr<wasm::NativeModule> module = AllocateNativeModule(
188           handles.main_isolate(), code->raw_instruction_size());
189       wasm::WasmCodeRefScope wasm_code_ref_scope;
190       byte* code_start =
191           module->AddCodeForTesting(code)->instructions().begin();
192 
193       RawMachineAssemblerTester<int32_t> mt(CodeKind::JS_TO_WASM_FUNCTION);
194       const int input_count = 2 + param_count;
195       Node* call_inputs[2 + kMaxParamCount];
196       call_inputs[0] = mt.PointerConstant(code_start);
197       // WasmContext dummy
198       call_inputs[1] = mt.PointerConstant(nullptr);
199       // Special inputs for the test.
200       call_inputs[2] = MakeConstant(&mt, type, a);
201       call_inputs[3] = MakeConstant(&mt, type, b);
202       for (int i = 2; i < param_count; i++) {
203         call_inputs[2 + i] = MakeConstant(&mt, type, i);
204       }
205 
206       Node* ret_multi = mt.AddNode(mt.common()->Call(desc),
207                                    input_count, call_inputs);
208       Node* ret = MakeConstant(&mt, type, 0);
209       bool sign = false;
210       for (int i = 0; i < count; ++i) {
211         Node* x = (count == 1)
212                       ? ret_multi
213                       : mt.AddNode(mt.common()->Projection(i), ret_multi);
214         ret = sign ? Sub(&mt, type, ret, x) : Add(&mt, type, ret, x);
215         if (i % 4 == 0) sign = !sign;
216       }
217       mt.Return(ToInt32(&mt, type, ret));
218 #ifdef ENABLE_DISASSEMBLER
219       Handle<Code> code2 = mt.GetCode();
220       if (FLAG_print_code) {
221         StdoutStream os;
222         code2->Disassemble("multi_value_call", os, handles.main_isolate());
223       }
224 #endif
225       CHECK_EQ(expect, mt.Call());
226     }
227   }
228 }
229 
230 }  // namespace
231 
232 // Use 9 parameters as a regression test or https://crbug.com/838098.
233 #define TEST_MULTI(Type, type) \
234   TEST(ReturnMultiple##Type) { TestReturnMultipleValues<2, 9>(type, 0, 20); }
235 
236 // Create a frame larger than UINT16_MAX to force TF to use an extra register
237 // when popping the frame.
TEST(TestReturnMultipleValuesLargeFrame)238 TEST(TestReturnMultipleValuesLargeFrame) {
239   TestReturnMultipleValues<20000, 20000>(MachineType::Int32(), 2, 3);
240 }
241 
TEST_MULTI(Int32,MachineType::Int32 ())242 TEST_MULTI(Int32, MachineType::Int32())
243 #if (!V8_TARGET_ARCH_32_BIT)
244 TEST_MULTI(Int64, MachineType::Int64())
245 #endif
246 TEST_MULTI(Float32, MachineType::Float32())
247 TEST_MULTI(Float64, MachineType::Float64())
248 
249 #undef TEST_MULTI
250 
251 void ReturnLastValue(MachineType type) {
252   int slot_counts[] = {1, 2, 3, 600};
253   for (auto slot_count : slot_counts) {
254     v8::internal::AccountingAllocator allocator;
255     Zone zone(&allocator, ZONE_NAME);
256     // The wasm-linkage provides 2 return registers at the moment, on all
257     // platforms.
258     const int return_count = 2 + slot_count;
259 
260     CallDescriptor* desc = CreateCallDescriptor(&zone, return_count, 0, type);
261 
262     HandleAndZoneScope handles(kCompressGraphZone);
263     RawMachineAssembler m(handles.main_isolate(),
264                           handles.main_zone()->New<Graph>(handles.main_zone()),
265                           desc, MachineType::PointerRepresentation(),
266                           InstructionSelector::SupportedMachineOperatorFlags());
267 
268     std::unique_ptr<Node* []> returns(new Node*[return_count]);
269 
270     for (int i = 0; i < return_count; ++i) {
271       returns[i] = MakeConstant(&m, type, i);
272     }
273 
274     m.Return(return_count, returns.get());
275 
276     OptimizedCompilationInfo info(base::ArrayVector("testing"),
277                                   handles.main_zone(), CodeKind::WASM_FUNCTION);
278     Handle<Code> code = Pipeline::GenerateCodeForTesting(
279                             &info, handles.main_isolate(), desc, m.graph(),
280                             AssemblerOptions::Default(handles.main_isolate()),
281                             m.ExportForTest())
282                             .ToHandleChecked();
283 
284     std::shared_ptr<wasm::NativeModule> module = AllocateNativeModule(
285         handles.main_isolate(), code->raw_instruction_size());
286     wasm::WasmCodeRefScope wasm_code_ref_scope;
287     byte* code_start = module->AddCodeForTesting(code)->instructions().begin();
288 
289     // Generate caller.
290     int expect = return_count - 1;
291     RawMachineAssemblerTester<int32_t> mt;
292     Node* inputs[] = {mt.PointerConstant(code_start),
293                       // WasmContext dummy
294                       mt.PointerConstant(nullptr)};
295 
296     Node* call = mt.AddNode(mt.common()->Call(desc), 2, inputs);
297 
298     mt.Return(
299         ToInt32(&mt, type,
300                 mt.AddNode(mt.common()->Projection(return_count - 1), call)));
301 
302     CHECK_EQ(expect, mt.Call());
303   }
304 }
305 
TEST(ReturnLastValueInt32)306 TEST(ReturnLastValueInt32) { ReturnLastValue(MachineType::Int32()); }
307 #if (!V8_TARGET_ARCH_32_BIT)
TEST(ReturnLastValueInt64)308 TEST(ReturnLastValueInt64) { ReturnLastValue(MachineType::Int64()); }
309 #endif
TEST(ReturnLastValueFloat32)310 TEST(ReturnLastValueFloat32) { ReturnLastValue(MachineType::Float32()); }
TEST(ReturnLastValueFloat64)311 TEST(ReturnLastValueFloat64) { ReturnLastValue(MachineType::Float64()); }
312 
ReturnSumOfReturns(MachineType type)313 void ReturnSumOfReturns(MachineType type) {
314   for (int unused_stack_slots = 0; unused_stack_slots <= 2;
315        ++unused_stack_slots) {
316     v8::internal::AccountingAllocator allocator;
317     Zone zone(&allocator, ZONE_NAME);
318     // Let {unused_stack_slots + 1} returns be on the stack.
319     // The wasm-linkage provides 2 return registers at the moment, on all
320     // platforms.
321     const int return_count = 2 + unused_stack_slots + 1;
322 
323     CallDescriptor* desc = CreateCallDescriptor(&zone, return_count, 0, type);
324 
325     HandleAndZoneScope handles(kCompressGraphZone);
326     RawMachineAssembler m(handles.main_isolate(),
327                           handles.main_zone()->New<Graph>(handles.main_zone()),
328                           desc, MachineType::PointerRepresentation(),
329                           InstructionSelector::SupportedMachineOperatorFlags());
330 
331     std::unique_ptr<Node* []> returns(new Node*[return_count]);
332 
333     for (int i = 0; i < return_count; ++i) {
334       returns[i] = MakeConstant(&m, type, i);
335     }
336 
337     m.Return(return_count, returns.get());
338 
339     OptimizedCompilationInfo info(base::ArrayVector("testing"),
340                                   handles.main_zone(), CodeKind::WASM_FUNCTION);
341     Handle<Code> code = Pipeline::GenerateCodeForTesting(
342                             &info, handles.main_isolate(), desc, m.graph(),
343                             AssemblerOptions::Default(handles.main_isolate()),
344                             m.ExportForTest())
345                             .ToHandleChecked();
346 
347     std::shared_ptr<wasm::NativeModule> module = AllocateNativeModule(
348         handles.main_isolate(), code->raw_instruction_size());
349     wasm::WasmCodeRefScope wasm_code_ref_scope;
350     byte* code_start = module->AddCodeForTesting(code)->instructions().begin();
351 
352     // Generate caller.
353     RawMachineAssemblerTester<int32_t> mt;
354     Node* call_inputs[] = {mt.PointerConstant(code_start),
355                            // WasmContext dummy
356                            mt.PointerConstant(nullptr)};
357 
358     Node* call = mt.AddNode(mt.common()->Call(desc), 2, call_inputs);
359 
360     uint32_t expect = 0;
361     Node* result = mt.Int32Constant(0);
362 
363     for (int i = 0; i < return_count; ++i) {
364       expect += i;
365       result = mt.Int32Add(
366           result,
367           ToInt32(&mt, type, mt.AddNode(mt.common()->Projection(i), call)));
368     }
369 
370     mt.Return(result);
371 
372     CHECK_EQ(expect, mt.Call());
373   }
374 }
375 
TEST(ReturnSumOfReturnsInt32)376 TEST(ReturnSumOfReturnsInt32) { ReturnSumOfReturns(MachineType::Int32()); }
377 #if (!V8_TARGET_ARCH_32_BIT)
TEST(ReturnSumOfReturnsInt64)378 TEST(ReturnSumOfReturnsInt64) { ReturnSumOfReturns(MachineType::Int64()); }
379 #endif
TEST(ReturnSumOfReturnsFloat32)380 TEST(ReturnSumOfReturnsFloat32) { ReturnSumOfReturns(MachineType::Float32()); }
TEST(ReturnSumOfReturnsFloat64)381 TEST(ReturnSumOfReturnsFloat64) { ReturnSumOfReturns(MachineType::Float64()); }
382 
383 }  // namespace compiler
384 }  // namespace internal
385 }  // namespace v8
386