blob: a34b1e14e529fc260bf717aeee15e46f04ca834d [file] [log] [blame]
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cmath>
#include <functional>
#include <limits>
#include <memory>
#include "src/base/bits.h"
#include "src/codegen/assembler.h"
#include "src/codegen/compiler.h"
#include "src/codegen/machine-type.h"
#include "src/codegen/macro-assembler.h"
#include "src/compiler/linkage.h"
#include "src/compiler/wasm-compiler.h"
#include "src/objects/objects-inl.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
#include "test/cctest/compiler/value-helper.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace {
CallDescriptor* CreateCallDescriptor(Zone* zone, int return_count,
int param_count, MachineType type) {
wasm::FunctionSig::Builder builder(zone, return_count, param_count);
for (int i = 0; i < param_count; i++) {
builder.AddParam(wasm::ValueTypes::ValueTypeFor(type));
}
for (int i = 0; i < return_count; i++) {
builder.AddReturn(wasm::ValueTypes::ValueTypeFor(type));
}
return compiler::GetWasmCallDescriptor(zone, builder.Build());
}
Node* MakeConstant(RawMachineAssembler& m, // NOLINT(runtime/references)
MachineType type, int value) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return m.Int32Constant(static_cast<int32_t>(value));
case MachineRepresentation::kWord64:
return m.Int64Constant(static_cast<int64_t>(value));
case MachineRepresentation::kFloat32:
return m.Float32Constant(static_cast<float>(value));
case MachineRepresentation::kFloat64:
return m.Float64Constant(static_cast<double>(value));
default:
UNREACHABLE();
}
}
Node* Add(RawMachineAssembler& m, // NOLINT(runtime/references)
MachineType type, Node* a, Node* b) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return m.Int32Add(a, b);
case MachineRepresentation::kWord64:
return m.Int64Add(a, b);
case MachineRepresentation::kFloat32:
return m.Float32Add(a, b);
case MachineRepresentation::kFloat64:
return m.Float64Add(a, b);
default:
UNREACHABLE();
}
}
Node* Sub(RawMachineAssembler& m, // NOLINT(runtime/references)
MachineType type, Node* a, Node* b) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return m.Int32Sub(a, b);
case MachineRepresentation::kWord64:
return m.Int64Sub(a, b);
case MachineRepresentation::kFloat32:
return m.Float32Sub(a, b);
case MachineRepresentation::kFloat64:
return m.Float64Sub(a, b);
default:
UNREACHABLE();
}
}
Node* Mul(RawMachineAssembler& m, // NOLINT(runtime/references)
MachineType type, Node* a, Node* b) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return m.Int32Mul(a, b);
case MachineRepresentation::kWord64:
return m.Int64Mul(a, b);
case MachineRepresentation::kFloat32:
return m.Float32Mul(a, b);
case MachineRepresentation::kFloat64:
return m.Float64Mul(a, b);
default:
UNREACHABLE();
}
}
Node* ToInt32(RawMachineAssembler& m, // NOLINT(runtime/references)
MachineType type, Node* a) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return a;
case MachineRepresentation::kWord64:
return m.TruncateInt64ToInt32(a);
case MachineRepresentation::kFloat32:
return m.TruncateFloat32ToInt32(a);
case MachineRepresentation::kFloat64:
return m.RoundFloat64ToInt32(a);
default:
UNREACHABLE();
}
}
std::shared_ptr<wasm::NativeModule> AllocateNativeModule(Isolate* isolate,
size_t code_size) {
std::shared_ptr<wasm::WasmModule> module(new wasm::WasmModule());
module->num_declared_functions = 1;
// We have to add the code object to a NativeModule, because the
// WasmCallDescriptor assumes that code is on the native heap and not
// within a code object.
return isolate->wasm_engine()->NewNativeModule(
isolate, wasm::kAllWasmFeatures, code_size, false, std::move(module));
}
void TestReturnMultipleValues(MachineType type) {
const int kMaxCount = 20;
const int kMaxParamCount = 9;
// Use 9 parameters as a regression test or https://crbug.com/838098.
for (int param_count : {2, kMaxParamCount}) {
for (int count = 0; count < kMaxCount; ++count) {
printf("\n==== type = %s, count = %d ====\n\n\n",
MachineReprToString(type.representation()), count);
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
CallDescriptor* desc =
CreateCallDescriptor(&zone, count, param_count, type);
HandleAndZoneScope handles;
RawMachineAssembler m(
handles.main_isolate(),
new (handles.main_zone()) Graph(handles.main_zone()), desc,
MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags());
// m.Parameter(0) is the WasmContext.
Node* p0 = m.Parameter(1);
Node* p1 = m.Parameter(2);
using Node_ptr = Node*;
std::unique_ptr<Node_ptr[]> returns(new Node_ptr[count]);
for (int i = 0; i < count; ++i) {
if (i % 3 == 0) returns[i] = Add(m, type, p0, p1);
if (i % 3 == 1) returns[i] = Sub(m, type, p0, p1);
if (i % 3 == 2) returns[i] = Mul(m, type, p0, p1);
}
m.Return(count, returns.get());
OptimizedCompilationInfo info(ArrayVector("testing"), handles.main_zone(),
Code::WASM_FUNCTION);
Handle<Code> code =
Pipeline::GenerateCodeForTesting(
&info, handles.main_isolate(), desc, m.graph(),
AssemblerOptions::Default(handles.main_isolate()), m.Export())
.ToHandleChecked();
#ifdef ENABLE_DISASSEMBLER
if (FLAG_print_code) {
StdoutStream os;
code->Disassemble("multi_value", os);
}
#endif
const int a = 47, b = 12;
int expect = 0;
for (int i = 0, sign = +1; i < count; ++i) {
if (i % 3 == 0) expect += sign * (a + b);
if (i % 3 == 1) expect += sign * (a - b);
if (i % 3 == 2) expect += sign * (a * b);
if (i % 4 == 0) sign = -sign;
}
std::shared_ptr<wasm::NativeModule> module = AllocateNativeModule(
handles.main_isolate(), code->raw_instruction_size());
wasm::WasmCodeRefScope wasm_code_ref_scope;
byte* code_start =
module->AddCodeForTesting(code)->instructions().begin();
RawMachineAssemblerTester<int32_t> mt(Code::Kind::JS_TO_WASM_FUNCTION);
const int input_count = 2 + param_count;
Node* call_inputs[2 + kMaxParamCount];
call_inputs[0] = mt.PointerConstant(code_start);
// WasmContext dummy
call_inputs[1] = mt.PointerConstant(nullptr);
// Special inputs for the test.
call_inputs[2] = MakeConstant(mt, type, a);
call_inputs[3] = MakeConstant(mt, type, b);
for (int i = 2; i < param_count; i++) {
call_inputs[2 + i] = MakeConstant(mt, type, i);
}
Node* ret_multi = mt.AddNode(mt.common()->Call(desc),
input_count, call_inputs);
Node* ret = MakeConstant(mt, type, 0);
bool sign = false;
for (int i = 0; i < count; ++i) {
Node* x = (count == 1)
? ret_multi
: mt.AddNode(mt.common()->Projection(i), ret_multi);
ret = sign ? Sub(mt, type, ret, x) : Add(mt, type, ret, x);
if (i % 4 == 0) sign = !sign;
}
mt.Return(ToInt32(mt, type, ret));
#ifdef ENABLE_DISASSEMBLER
Handle<Code> code2 = mt.GetCode();
if (FLAG_print_code) {
StdoutStream os;
code2->Disassemble("multi_value_call", os);
}
#endif
CHECK_EQ(expect, mt.Call());
}
}
}
} // namespace
#define TEST_MULTI(Type, type) \
TEST(ReturnMultiple##Type) { TestReturnMultipleValues(type); }
TEST_MULTI(Int32, MachineType::Int32())
#if (!V8_TARGET_ARCH_32_BIT)
TEST_MULTI(Int64, MachineType::Int64())
#endif
TEST_MULTI(Float32, MachineType::Float32())
TEST_MULTI(Float64, MachineType::Float64())
#undef TEST_MULTI
void ReturnLastValue(MachineType type) {
int slot_counts[] = {1, 2, 3, 600};
for (auto slot_count : slot_counts) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// The wasm-linkage provides 2 return registers at the moment, on all
// platforms.
const int return_count = 2 + slot_count;
CallDescriptor* desc = CreateCallDescriptor(&zone, return_count, 0, type);
HandleAndZoneScope handles;
RawMachineAssembler m(handles.main_isolate(),
new (handles.main_zone()) Graph(handles.main_zone()),
desc, MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags());
std::unique_ptr<Node* []> returns(new Node*[return_count]);
for (int i = 0; i < return_count; ++i) {
returns[i] = MakeConstant(m, type, i);
}
m.Return(return_count, returns.get());
OptimizedCompilationInfo info(ArrayVector("testing"), handles.main_zone(),
Code::WASM_FUNCTION);
Handle<Code> code =
Pipeline::GenerateCodeForTesting(
&info, handles.main_isolate(), desc, m.graph(),
AssemblerOptions::Default(handles.main_isolate()), m.Export())
.ToHandleChecked();
std::shared_ptr<wasm::NativeModule> module = AllocateNativeModule(
handles.main_isolate(), code->raw_instruction_size());
wasm::WasmCodeRefScope wasm_code_ref_scope;
byte* code_start = module->AddCodeForTesting(code)->instructions().begin();
// Generate caller.
int expect = return_count - 1;
RawMachineAssemblerTester<int32_t> mt;
Node* inputs[] = {mt.PointerConstant(code_start),
// WasmContext dummy
mt.PointerConstant(nullptr)};
Node* call = mt.AddNode(mt.common()->Call(desc), 2, inputs);
mt.Return(ToInt32(
mt, type, mt.AddNode(mt.common()->Projection(return_count - 1), call)));
CHECK_EQ(expect, mt.Call());
}
}
TEST(ReturnLastValueInt32) { ReturnLastValue(MachineType::Int32()); }
#if (!V8_TARGET_ARCH_32_BIT)
TEST(ReturnLastValueInt64) { ReturnLastValue(MachineType::Int64()); }
#endif
TEST(ReturnLastValueFloat32) { ReturnLastValue(MachineType::Float32()); }
TEST(ReturnLastValueFloat64) { ReturnLastValue(MachineType::Float64()); }
void ReturnSumOfReturns(MachineType type) {
for (int unused_stack_slots = 0; unused_stack_slots <= 2;
++unused_stack_slots) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Let {unused_stack_slots + 1} returns be on the stack.
// The wasm-linkage provides 2 return registers at the moment, on all
// platforms.
const int return_count = 2 + unused_stack_slots + 1;
CallDescriptor* desc = CreateCallDescriptor(&zone, return_count, 0, type);
HandleAndZoneScope handles;
RawMachineAssembler m(handles.main_isolate(),
new (handles.main_zone()) Graph(handles.main_zone()),
desc, MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags());
std::unique_ptr<Node* []> returns(new Node*[return_count]);
for (int i = 0; i < return_count; ++i) {
returns[i] = MakeConstant(m, type, i);
}
m.Return(return_count, returns.get());
OptimizedCompilationInfo info(ArrayVector("testing"), handles.main_zone(),
Code::WASM_FUNCTION);
Handle<Code> code =
Pipeline::GenerateCodeForTesting(
&info, handles.main_isolate(), desc, m.graph(),
AssemblerOptions::Default(handles.main_isolate()), m.Export())
.ToHandleChecked();
std::shared_ptr<wasm::NativeModule> module = AllocateNativeModule(
handles.main_isolate(), code->raw_instruction_size());
wasm::WasmCodeRefScope wasm_code_ref_scope;
byte* code_start = module->AddCodeForTesting(code)->instructions().begin();
// Generate caller.
RawMachineAssemblerTester<int32_t> mt;
Node* call_inputs[] = {mt.PointerConstant(code_start),
// WasmContext dummy
mt.PointerConstant(nullptr)};
Node* call = mt.AddNode(mt.common()->Call(desc), 2, call_inputs);
uint32_t expect = 0;
Node* result = mt.Int32Constant(0);
for (int i = 0; i < return_count; ++i) {
expect += i;
result = mt.Int32Add(
result,
ToInt32(mt, type, mt.AddNode(mt.common()->Projection(i), call)));
}
mt.Return(result);
CHECK_EQ(expect, mt.Call());
}
}
TEST(ReturnSumOfReturnsInt32) { ReturnSumOfReturns(MachineType::Int32()); }
#if (!V8_TARGET_ARCH_32_BIT)
TEST(ReturnSumOfReturnsInt64) { ReturnSumOfReturns(MachineType::Int64()); }
#endif
TEST(ReturnSumOfReturnsFloat32) { ReturnSumOfReturns(MachineType::Float32()); }
TEST(ReturnSumOfReturnsFloat64) { ReturnSumOfReturns(MachineType::Float64()); }
} // namespace compiler
} // namespace internal
} // namespace v8