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