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