// Copyright 2015 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 "src/assembler.h" #include "src/codegen.h" #include "src/compiler/linkage.h" #include "src/compiler/raw-machine-assembler.h" #include "src/machine-type.h" #include "src/register-configuration.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/codegen-tester.h" #include "test/cctest/compiler/graph-builder-tester.h" #include "test/cctest/compiler/value-helper.h" namespace v8 { namespace internal { namespace compiler { const auto GetRegConfig = RegisterConfiguration::Turbofan; namespace { typedef float float32; typedef double float64; // Picks a representative pair of integers from the given range. // If there are less than {max_pairs} possible pairs, do them all, otherwise try // to select a representative set. class Pairs { public: Pairs(int max_pairs, int range, const int* codes) : range_(range), codes_(codes), max_pairs_(std::min(max_pairs, range_ * range_)), counter_(0) {} bool More() { return counter_ < max_pairs_; } void Next(int* r0, int* r1, bool same_is_ok) { do { // Find the next pair. if (exhaustive()) { *r0 = codes_[counter_ % range_]; *r1 = codes_[counter_ / range_]; } else { // Try each integer at least once for both r0 and r1. int index = counter_ / 2; if (counter_ & 1) { *r0 = codes_[index % range_]; *r1 = codes_[index / range_]; } else { *r1 = codes_[index % range_]; *r0 = codes_[index / range_]; } } counter_++; if ((same_is_ok) || (*r0 != *r1)) break; if (counter_ == max_pairs_) { // For the last hurrah, reg#0 with reg#n-1 *r0 = codes_[0]; *r1 = codes_[range_ - 1]; break; } } while (true); } private: int range_; const int* codes_; int max_pairs_; int counter_; bool exhaustive() { return max_pairs_ == (range_ * range_); } }; // Pairs of general purpose registers. class RegisterPairs : public Pairs { public: RegisterPairs() : Pairs(100, GetRegConfig()->num_allocatable_general_registers(), GetRegConfig()->allocatable_general_codes()) {} }; // Pairs of double registers. class Float32RegisterPairs : public Pairs { public: Float32RegisterPairs() : Pairs( 100, #if V8_TARGET_ARCH_ARM // TODO(bbudge) Modify wasm linkage to allow use of all float regs. GetRegConfig()->num_allocatable_double_registers() / 2 - 2, #else GetRegConfig()->num_allocatable_double_registers(), #endif GetRegConfig()->allocatable_double_codes()) { } }; // Pairs of double registers. class Float64RegisterPairs : public Pairs { public: Float64RegisterPairs() : Pairs(100, GetRegConfig()->num_allocatable_double_registers(), GetRegConfig()->allocatable_double_codes()) {} }; // Helper for allocating either an GP or FP reg, or the next stack slot. struct Allocator { Allocator(int* gp, int gpc, int* fp, int fpc) : gp_count(gpc), gp_offset(0), gp_regs(gp), fp_count(fpc), fp_offset(0), fp_regs(fp), stack_offset(0) {} int gp_count; int gp_offset; int* gp_regs; int fp_count; int fp_offset; int* fp_regs; int stack_offset; LinkageLocation Next(MachineType type) { if (IsFloatingPoint(type.representation())) { // Allocate a floating point register/stack location. if (fp_offset < fp_count) { int code = fp_regs[fp_offset++]; #if V8_TARGET_ARCH_ARM // TODO(bbudge) Modify wasm linkage to allow use of all float regs. if (type.representation() == MachineRepresentation::kFloat32) code *= 2; #endif return LinkageLocation::ForRegister(code); } else { int offset = -1 - stack_offset; stack_offset += StackWords(type); return LinkageLocation::ForCallerFrameSlot(offset); } } else { // Allocate a general purpose register/stack location. if (gp_offset < gp_count) { return LinkageLocation::ForRegister(gp_regs[gp_offset++]); } else { int offset = -1 - stack_offset; stack_offset += StackWords(type); return LinkageLocation::ForCallerFrameSlot(offset); } } } int StackWords(MachineType type) { int size = 1 << ElementSizeLog2Of(type.representation()); return size <= kPointerSize ? 1 : size / kPointerSize; } void Reset() { fp_offset = 0; gp_offset = 0; stack_offset = 0; } }; class RegisterConfig { public: RegisterConfig(Allocator& p, Allocator& r) : params(p), rets(r) {} CallDescriptor* Create(Zone* zone, MachineSignature* msig) { rets.Reset(); params.Reset(); LocationSignature::Builder locations(zone, msig->return_count(), msig->parameter_count()); // Add return location(s). const int return_count = static_cast<int>(locations.return_count_); for (int i = 0; i < return_count; i++) { locations.AddReturn(rets.Next(msig->GetReturn(i))); } // Add register and/or stack parameter(s). const int parameter_count = static_cast<int>(msig->parameter_count()); for (int i = 0; i < parameter_count; i++) { locations.AddParam(params.Next(msig->GetParam(i))); } const RegList kCalleeSaveRegisters = 0; const RegList kCalleeSaveFPRegisters = 0; MachineType target_type = MachineType::AnyTagged(); LinkageLocation target_loc = LinkageLocation::ForAnyRegister(); int stack_param_count = params.stack_offset; return new (zone) CallDescriptor( // -- CallDescriptor::kCallCodeObject, // kind target_type, // target MachineType target_loc, // target location msig, // machine_sig locations.Build(), // location_sig stack_param_count, // stack_parameter_count compiler::Operator::kNoProperties, // properties kCalleeSaveRegisters, // callee-saved registers kCalleeSaveFPRegisters, // callee-saved fp regs CallDescriptor::kUseNativeStack, // flags "c-call"); } private: Allocator& params; Allocator& rets; }; const int kMaxParamCount = 64; MachineType kIntTypes[kMaxParamCount + 1] = { MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32(), MachineType::Int32()}; // For making uniform int32 signatures shorter. class Int32Signature : public MachineSignature { public: explicit Int32Signature(int param_count) : MachineSignature(1, param_count, kIntTypes) { CHECK(param_count <= kMaxParamCount); } }; Handle<Code> CompileGraph(const char* name, CallDescriptor* desc, Graph* graph, Schedule* schedule = nullptr) { Isolate* isolate = CcTest::InitIsolateOnce(); CompilationInfo info(ArrayVector("testing"), isolate, graph->zone()); Handle<Code> code = Pipeline::GenerateCodeForTesting(&info, desc, graph, schedule); CHECK(!code.is_null()); #ifdef ENABLE_DISASSEMBLER if (FLAG_print_opt_code) { OFStream os(stdout); code->Disassemble(name, os); } #endif return code; } Handle<Code> WrapWithCFunction(Handle<Code> inner, CallDescriptor* desc) { Zone zone(inner->GetIsolate()->allocator()); MachineSignature* msig = const_cast<MachineSignature*>(desc->GetMachineSignature()); int param_count = static_cast<int>(msig->parameter_count()); GraphAndBuilders caller(&zone); { GraphAndBuilders& b = caller; Node* start = b.graph()->NewNode(b.common()->Start(param_count + 3)); b.graph()->SetStart(start); Node* target = b.graph()->NewNode(b.common()->HeapConstant(inner)); // Add arguments to the call. Node** args = zone.NewArray<Node*>(param_count + 3); int index = 0; args[index++] = target; for (int i = 0; i < param_count; i++) { args[index] = b.graph()->NewNode(b.common()->Parameter(i), start); index++; } args[index++] = start; // effect. args[index++] = start; // control. // Build the call and return nodes. Node* call = b.graph()->NewNode(b.common()->Call(desc), param_count + 3, args); Node* ret = b.graph()->NewNode(b.common()->Return(), call, call, start); b.graph()->SetEnd(ret); } CallDescriptor* cdesc = Linkage::GetSimplifiedCDescriptor(&zone, msig); return CompileGraph("wrapper", cdesc, caller.graph()); } template <typename CType> class ArgsBuffer { public: static const int kMaxParamCount = 64; explicit ArgsBuffer(int count, int seed = 1) : count_(count), seed_(seed) { // initialize the buffer with "seed 0" seed_ = 0; Mutate(); seed_ = seed; } class Sig : public MachineSignature { public: explicit Sig(int param_count) : MachineSignature(1, param_count, MachTypes()) { CHECK(param_count <= kMaxParamCount); } }; static MachineType* MachTypes() { MachineType t = MachineTypeForC<CType>(); static MachineType kTypes[kMaxParamCount + 1] = { t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t}; return kTypes; } Node* MakeConstant(RawMachineAssembler& raw, int32_t value) { return raw.Int32Constant(value); } Node* MakeConstant(RawMachineAssembler& raw, int64_t value) { return raw.Int64Constant(value); } Node* MakeConstant(RawMachineAssembler& raw, float32 value) { return raw.Float32Constant(value); } Node* MakeConstant(RawMachineAssembler& raw, float64 value) { return raw.Float64Constant(value); } Node* LoadInput(RawMachineAssembler& raw, Node* base, int index) { Node* offset = raw.Int32Constant(index * sizeof(CType)); return raw.Load(MachineTypeForC<CType>(), base, offset); } Node* StoreOutput(RawMachineAssembler& raw, Node* value) { Node* base = raw.PointerConstant(&output); Node* offset = raw.Int32Constant(0); return raw.Store(MachineTypeForC<CType>().representation(), base, offset, value, kNoWriteBarrier); } // Computes the next set of inputs by updating the {input} array. void Mutate(); void Reset() { memset(input, 0, sizeof(input)); } int count_; int seed_; CType input[kMaxParamCount]; CType output; }; template <> void ArgsBuffer<int32_t>::Mutate() { uint32_t base = 1111111111u * seed_; for (int j = 0; j < count_ && j < kMaxParamCount; j++) { input[j] = static_cast<int32_t>(256 + base + j + seed_ * 13); } output = -1; seed_++; } template <> void ArgsBuffer<int64_t>::Mutate() { uint64_t base = 11111111111111111ull * seed_; for (int j = 0; j < count_ && j < kMaxParamCount; j++) { input[j] = static_cast<int64_t>(256 + base + j + seed_ * 13); } output = -1; seed_++; } template <> void ArgsBuffer<float32>::Mutate() { float64 base = -33.25 * seed_; for (int j = 0; j < count_ && j < kMaxParamCount; j++) { input[j] = 256 + base + j + seed_ * 13; } output = std::numeric_limits<float32>::quiet_NaN(); seed_++; } template <> void ArgsBuffer<float64>::Mutate() { float64 base = -111.25 * seed_; for (int j = 0; j < count_ && j < kMaxParamCount; j++) { input[j] = 256 + base + j + seed_ * 13; } output = std::numeric_limits<float64>::quiet_NaN(); seed_++; } int ParamCount(CallDescriptor* desc) { return static_cast<int>(desc->GetMachineSignature()->parameter_count()); } template <typename CType> class Computer { public: static void Run(CallDescriptor* desc, void (*build)(CallDescriptor*, RawMachineAssembler&), CType (*compute)(CallDescriptor*, CType* inputs), int seed = 1) { int num_params = ParamCount(desc); CHECK_LE(num_params, kMaxParamCount); Isolate* isolate = CcTest::InitIsolateOnce(); HandleScope scope(isolate); Handle<Code> inner = Handle<Code>::null(); { // Build the graph for the computation. Zone zone(isolate->allocator()); Graph graph(&zone); RawMachineAssembler raw(isolate, &graph, desc); build(desc, raw); inner = CompileGraph("Compute", desc, &graph, raw.Export()); } CSignature0<int32_t> csig; ArgsBuffer<CType> io(num_params, seed); { // constant mode. Handle<Code> wrapper = Handle<Code>::null(); { // Wrap the above code with a callable function that passes constants. Zone zone(isolate->allocator()); Graph graph(&zone); CallDescriptor* cdesc = Linkage::GetSimplifiedCDescriptor(&zone, &csig); RawMachineAssembler raw(isolate, &graph, cdesc); Node* target = raw.HeapConstant(inner); Node** args = zone.NewArray<Node*>(num_params); for (int i = 0; i < num_params; i++) { args[i] = io.MakeConstant(raw, io.input[i]); } Node* call = raw.CallN(desc, target, args); Node* store = io.StoreOutput(raw, call); USE(store); raw.Return(raw.Int32Constant(seed)); wrapper = CompileGraph("Compute-wrapper-const", cdesc, &graph, raw.Export()); } CodeRunner<int32_t> runnable(isolate, wrapper, &csig); // Run the code, checking it against the reference. CType expected = compute(desc, io.input); int32_t check_seed = runnable.Call(); CHECK_EQ(seed, check_seed); CHECK_EQ(expected, io.output); } { // buffer mode. Handle<Code> wrapper = Handle<Code>::null(); { // Wrap the above code with a callable function that loads from {input}. Zone zone(isolate->allocator()); Graph graph(&zone); CallDescriptor* cdesc = Linkage::GetSimplifiedCDescriptor(&zone, &csig); RawMachineAssembler raw(isolate, &graph, cdesc); Node* base = raw.PointerConstant(io.input); Node* target = raw.HeapConstant(inner); Node** args = zone.NewArray<Node*>(kMaxParamCount); for (int i = 0; i < num_params; i++) { args[i] = io.LoadInput(raw, base, i); } Node* call = raw.CallN(desc, target, args); Node* store = io.StoreOutput(raw, call); USE(store); raw.Return(raw.Int32Constant(seed)); wrapper = CompileGraph("Compute-wrapper", cdesc, &graph, raw.Export()); } CodeRunner<int32_t> runnable(isolate, wrapper, &csig); // Run the code, checking it against the reference. for (int i = 0; i < 5; i++) { CType expected = compute(desc, io.input); int32_t check_seed = runnable.Call(); CHECK_EQ(seed, check_seed); CHECK_EQ(expected, io.output); io.Mutate(); } } } }; } // namespace static void TestInt32Sub(CallDescriptor* desc) { Isolate* isolate = CcTest::InitIsolateOnce(); HandleScope scope(isolate); Zone zone(isolate->allocator()); GraphAndBuilders inner(&zone); { // Build the add function. GraphAndBuilders& b = inner; Node* start = b.graph()->NewNode(b.common()->Start(5)); b.graph()->SetStart(start); Node* p0 = b.graph()->NewNode(b.common()->Parameter(0), start); Node* p1 = b.graph()->NewNode(b.common()->Parameter(1), start); Node* add = b.graph()->NewNode(b.machine()->Int32Sub(), p0, p1); Node* ret = b.graph()->NewNode(b.common()->Return(), add, start, start); b.graph()->SetEnd(ret); } Handle<Code> inner_code = CompileGraph("Int32Sub", desc, inner.graph()); Handle<Code> wrapper = WrapWithCFunction(inner_code, desc); MachineSignature* msig = const_cast<MachineSignature*>(desc->GetMachineSignature()); CodeRunner<int32_t> runnable(isolate, wrapper, CSignature::FromMachine(&zone, msig)); FOR_INT32_INPUTS(i) { FOR_INT32_INPUTS(j) { int32_t expected = static_cast<int32_t>(static_cast<uint32_t>(*i) - static_cast<uint32_t>(*j)); int32_t result = runnable.Call(*i, *j); CHECK_EQ(expected, result); } } } static void CopyTwentyInt32(CallDescriptor* desc) { const int kNumParams = 20; int32_t input[kNumParams]; int32_t output[kNumParams]; Isolate* isolate = CcTest::InitIsolateOnce(); HandleScope scope(isolate); Handle<Code> inner = Handle<Code>::null(); { // Writes all parameters into the output buffer. Zone zone(isolate->allocator()); Graph graph(&zone); RawMachineAssembler raw(isolate, &graph, desc); Node* base = raw.PointerConstant(output); for (int i = 0; i < kNumParams; i++) { Node* offset = raw.Int32Constant(i * sizeof(int32_t)); raw.Store(MachineRepresentation::kWord32, base, offset, raw.Parameter(i), kNoWriteBarrier); } raw.Return(raw.Int32Constant(42)); inner = CompileGraph("CopyTwentyInt32", desc, &graph, raw.Export()); } CSignature0<int32_t> csig; Handle<Code> wrapper = Handle<Code>::null(); { // Loads parameters from the input buffer and calls the above code. Zone zone(isolate->allocator()); Graph graph(&zone); CallDescriptor* cdesc = Linkage::GetSimplifiedCDescriptor(&zone, &csig); RawMachineAssembler raw(isolate, &graph, cdesc); Node* base = raw.PointerConstant(input); Node* target = raw.HeapConstant(inner); Node** args = zone.NewArray<Node*>(kNumParams); for (int i = 0; i < kNumParams; i++) { Node* offset = raw.Int32Constant(i * sizeof(int32_t)); args[i] = raw.Load(MachineType::Int32(), base, offset); } Node* call = raw.CallN(desc, target, args); raw.Return(call); wrapper = CompileGraph("CopyTwentyInt32-wrapper", cdesc, &graph, raw.Export()); } CodeRunner<int32_t> runnable(isolate, wrapper, &csig); // Run the code, checking it correctly implements the memcpy. for (int i = 0; i < 5; i++) { uint32_t base = 1111111111u * i; for (int j = 0; j < kNumParams; j++) { input[j] = static_cast<int32_t>(base + 13 * j); } memset(output, 0, sizeof(output)); CHECK_EQ(42, runnable.Call()); for (int j = 0; j < kNumParams; j++) { CHECK_EQ(input[j], output[j]); } } } static void Test_RunInt32SubWithRet(int retreg) { Int32Signature sig(2); base::AccountingAllocator allocator; Zone zone(&allocator); RegisterPairs pairs; while (pairs.More()) { int parray[2]; int rarray[] = {retreg}; pairs.Next(&parray[0], &parray[1], false); Allocator params(parray, 2, nullptr, 0); Allocator rets(rarray, 1, nullptr, 0); RegisterConfig config(params, rets); CallDescriptor* desc = config.Create(&zone, &sig); TestInt32Sub(desc); } } // Separate tests for parallelization. #define TEST_INT32_SUB_WITH_RET(x) \ TEST(Run_Int32Sub_all_allocatable_pairs_##x) { \ if (x < Register::kNumRegisters && \ GetRegConfig()->IsAllocatableGeneralCode(x)) { \ Test_RunInt32SubWithRet(x); \ } \ } TEST_INT32_SUB_WITH_RET(0) TEST_INT32_SUB_WITH_RET(1) TEST_INT32_SUB_WITH_RET(2) TEST_INT32_SUB_WITH_RET(3) TEST_INT32_SUB_WITH_RET(4) TEST_INT32_SUB_WITH_RET(5) TEST_INT32_SUB_WITH_RET(6) TEST_INT32_SUB_WITH_RET(7) TEST_INT32_SUB_WITH_RET(8) TEST_INT32_SUB_WITH_RET(9) TEST_INT32_SUB_WITH_RET(10) TEST_INT32_SUB_WITH_RET(11) TEST_INT32_SUB_WITH_RET(12) TEST_INT32_SUB_WITH_RET(13) TEST_INT32_SUB_WITH_RET(14) TEST_INT32_SUB_WITH_RET(15) TEST_INT32_SUB_WITH_RET(16) TEST_INT32_SUB_WITH_RET(17) TEST_INT32_SUB_WITH_RET(18) TEST_INT32_SUB_WITH_RET(19) TEST(Run_Int32Sub_all_allocatable_single) { Int32Signature sig(2); RegisterPairs pairs; while (pairs.More()) { base::AccountingAllocator allocator; Zone zone(&allocator); int parray[1]; int rarray[1]; pairs.Next(&rarray[0], &parray[0], true); Allocator params(parray, 1, nullptr, 0); Allocator rets(rarray, 1, nullptr, 0); RegisterConfig config(params, rets); CallDescriptor* desc = config.Create(&zone, &sig); TestInt32Sub(desc); } } TEST(Run_CopyTwentyInt32_all_allocatable_pairs) { Int32Signature sig(20); RegisterPairs pairs; while (pairs.More()) { base::AccountingAllocator allocator; Zone zone(&allocator); int parray[2]; int rarray[] = {GetRegConfig()->GetAllocatableGeneralCode(0)}; pairs.Next(&parray[0], &parray[1], false); Allocator params(parray, 2, nullptr, 0); Allocator rets(rarray, 1, nullptr, 0); RegisterConfig config(params, rets); CallDescriptor* desc = config.Create(&zone, &sig); CopyTwentyInt32(desc); } } template <typename CType> static void Run_Computation( CallDescriptor* desc, void (*build)(CallDescriptor*, RawMachineAssembler&), CType (*compute)(CallDescriptor*, CType* inputs), int seed = 1) { Computer<CType>::Run(desc, build, compute, seed); } static uint32_t coeff[] = {1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113}; static void Build_Int32_WeightedSum(CallDescriptor* desc, RawMachineAssembler& raw) { Node* result = raw.Int32Constant(0); for (int i = 0; i < ParamCount(desc); i++) { Node* term = raw.Int32Mul(raw.Parameter(i), raw.Int32Constant(coeff[i])); result = raw.Int32Add(result, term); } raw.Return(result); } static int32_t Compute_Int32_WeightedSum(CallDescriptor* desc, int32_t* input) { uint32_t result = 0; for (int i = 0; i < ParamCount(desc); i++) { result += static_cast<uint32_t>(input[i]) * coeff[i]; } return static_cast<int32_t>(result); } static void Test_Int32_WeightedSum_of_size(int count) { Int32Signature sig(count); for (int p0 = 0; p0 < Register::kNumRegisters; p0++) { if (GetRegConfig()->IsAllocatableGeneralCode(p0)) { base::AccountingAllocator allocator; Zone zone(&allocator); int parray[] = {p0}; int rarray[] = {GetRegConfig()->GetAllocatableGeneralCode(0)}; Allocator params(parray, 1, nullptr, 0); Allocator rets(rarray, 1, nullptr, 0); RegisterConfig config(params, rets); CallDescriptor* desc = config.Create(&zone, &sig); Run_Computation<int32_t>(desc, Build_Int32_WeightedSum, Compute_Int32_WeightedSum, 257 + count); } } } // Separate tests for parallelization. #define TEST_INT32_WEIGHTEDSUM(x) \ TEST(Run_Int32_WeightedSum_##x) { Test_Int32_WeightedSum_of_size(x); } TEST_INT32_WEIGHTEDSUM(1) TEST_INT32_WEIGHTEDSUM(2) TEST_INT32_WEIGHTEDSUM(3) TEST_INT32_WEIGHTEDSUM(4) TEST_INT32_WEIGHTEDSUM(5) TEST_INT32_WEIGHTEDSUM(7) TEST_INT32_WEIGHTEDSUM(9) TEST_INT32_WEIGHTEDSUM(11) TEST_INT32_WEIGHTEDSUM(17) TEST_INT32_WEIGHTEDSUM(19) template <int which> static void Build_Select(CallDescriptor* desc, RawMachineAssembler& raw) { raw.Return(raw.Parameter(which)); } template <typename CType, int which> static CType Compute_Select(CallDescriptor* desc, CType* inputs) { return inputs[which]; } template <typename CType, int which> static void RunSelect(CallDescriptor* desc) { int count = ParamCount(desc); if (count <= which) return; Run_Computation<CType>(desc, Build_Select<which>, Compute_Select<CType, which>, 1044 + which + 3 * sizeof(CType)); } template <int which> void Test_Int32_Select() { int parray[] = {GetRegConfig()->GetAllocatableGeneralCode(0)}; int rarray[] = {GetRegConfig()->GetAllocatableGeneralCode(0)}; Allocator params(parray, 1, nullptr, 0); Allocator rets(rarray, 1, nullptr, 0); RegisterConfig config(params, rets); base::AccountingAllocator allocator; Zone zone(&allocator); for (int i = which + 1; i <= 64; i++) { Int32Signature sig(i); CallDescriptor* desc = config.Create(&zone, &sig); RunSelect<int32_t, which>(desc); } } // Separate tests for parallelization. #define TEST_INT32_SELECT(x) \ TEST(Run_Int32_Select_##x) { Test_Int32_Select<x>(); } TEST_INT32_SELECT(0) TEST_INT32_SELECT(1) TEST_INT32_SELECT(2) TEST_INT32_SELECT(3) TEST_INT32_SELECT(4) TEST_INT32_SELECT(5) TEST_INT32_SELECT(6) TEST_INT32_SELECT(11) TEST_INT32_SELECT(15) TEST_INT32_SELECT(19) TEST_INT32_SELECT(45) TEST_INT32_SELECT(62) TEST_INT32_SELECT(63) TEST(Int64Select_registers) { if (GetRegConfig()->num_allocatable_general_registers() < 2) return; if (kPointerSize < 8) return; // TODO(titzer): int64 on 32-bit platforms int rarray[] = {GetRegConfig()->GetAllocatableGeneralCode(0)}; ArgsBuffer<int64_t>::Sig sig(2); RegisterPairs pairs; base::AccountingAllocator allocator; Zone zone(&allocator); while (pairs.More()) { int parray[2]; pairs.Next(&parray[0], &parray[1], false); Allocator params(parray, 2, nullptr, 0); Allocator rets(rarray, 1, nullptr, 0); RegisterConfig config(params, rets); CallDescriptor* desc = config.Create(&zone, &sig); RunSelect<int64_t, 0>(desc); RunSelect<int64_t, 1>(desc); } } TEST(Float32Select_registers) { if (GetRegConfig()->num_allocatable_double_registers() < 2) { return; } int rarray[] = {GetRegConfig()->GetAllocatableDoubleCode(0)}; ArgsBuffer<float32>::Sig sig(2); Float32RegisterPairs pairs; base::AccountingAllocator allocator; Zone zone(&allocator); while (pairs.More()) { int parray[2]; pairs.Next(&parray[0], &parray[1], false); Allocator params(nullptr, 0, parray, 2); Allocator rets(nullptr, 0, rarray, 1); RegisterConfig config(params, rets); CallDescriptor* desc = config.Create(&zone, &sig); RunSelect<float32, 0>(desc); RunSelect<float32, 1>(desc); } } TEST(Float64Select_registers) { if (GetRegConfig()->num_allocatable_double_registers() < 2) return; if (GetRegConfig()->num_allocatable_general_registers() < 2) return; int rarray[] = {GetRegConfig()->GetAllocatableDoubleCode(0)}; ArgsBuffer<float64>::Sig sig(2); Float64RegisterPairs pairs; base::AccountingAllocator allocator; Zone zone(&allocator); while (pairs.More()) { int parray[2]; pairs.Next(&parray[0], &parray[1], false); Allocator params(nullptr, 0, parray, 2); Allocator rets(nullptr, 0, rarray, 1); RegisterConfig config(params, rets); CallDescriptor* desc = config.Create(&zone, &sig); RunSelect<float64, 0>(desc); RunSelect<float64, 1>(desc); } } TEST(Float32Select_stack_params_return_reg) { int rarray[] = {GetRegConfig()->GetAllocatableDoubleCode(0)}; Allocator params(nullptr, 0, nullptr, 0); Allocator rets(nullptr, 0, rarray, 1); RegisterConfig config(params, rets); base::AccountingAllocator allocator; Zone zone(&allocator); for (int count = 1; count < 6; count++) { ArgsBuffer<float32>::Sig sig(count); CallDescriptor* desc = config.Create(&zone, &sig); RunSelect<float32, 0>(desc); RunSelect<float32, 1>(desc); RunSelect<float32, 2>(desc); RunSelect<float32, 3>(desc); RunSelect<float32, 4>(desc); RunSelect<float32, 5>(desc); } } TEST(Float64Select_stack_params_return_reg) { int rarray[] = {GetRegConfig()->GetAllocatableDoubleCode(0)}; Allocator params(nullptr, 0, nullptr, 0); Allocator rets(nullptr, 0, rarray, 1); RegisterConfig config(params, rets); base::AccountingAllocator allocator; Zone zone(&allocator); for (int count = 1; count < 6; count++) { ArgsBuffer<float64>::Sig sig(count); CallDescriptor* desc = config.Create(&zone, &sig); RunSelect<float64, 0>(desc); RunSelect<float64, 1>(desc); RunSelect<float64, 2>(desc); RunSelect<float64, 3>(desc); RunSelect<float64, 4>(desc); RunSelect<float64, 5>(desc); } } template <typename CType, int which> static void Build_Select_With_Call(CallDescriptor* desc, RawMachineAssembler& raw) { Handle<Code> inner = Handle<Code>::null(); int num_params = ParamCount(desc); CHECK_LE(num_params, kMaxParamCount); { Isolate* isolate = CcTest::InitIsolateOnce(); // Build the actual select. Zone zone(isolate->allocator()); Graph graph(&zone); RawMachineAssembler raw(isolate, &graph, desc); raw.Return(raw.Parameter(which)); inner = CompileGraph("Select-indirection", desc, &graph, raw.Export()); CHECK(!inner.is_null()); CHECK(inner->IsCode()); } { // Build a call to the function that does the select. Node* target = raw.HeapConstant(inner); Node** args = raw.zone()->NewArray<Node*>(num_params); for (int i = 0; i < num_params; i++) { args[i] = raw.Parameter(i); } Node* call = raw.CallN(desc, target, args); raw.Return(call); } } TEST(Float64StackParamsToStackParams) { int rarray[] = {GetRegConfig()->GetAllocatableDoubleCode(0)}; Allocator params(nullptr, 0, nullptr, 0); Allocator rets(nullptr, 0, rarray, 1); base::AccountingAllocator allocator; Zone zone(&allocator); ArgsBuffer<float64>::Sig sig(2); RegisterConfig config(params, rets); CallDescriptor* desc = config.Create(&zone, &sig); Run_Computation<float64>(desc, Build_Select_With_Call<float64, 0>, Compute_Select<float64, 0>, 1098); Run_Computation<float64>(desc, Build_Select_With_Call<float64, 1>, Compute_Select<float64, 1>, 1099); } void MixedParamTest(int start) { if (GetRegConfig()->num_double_registers() < 2) return; // TODO(titzer): mix in 64-bit types on all platforms when supported. #if V8_TARGET_ARCH_32_BIT static MachineType types[] = { MachineType::Int32(), MachineType::Float32(), MachineType::Float64(), MachineType::Int32(), MachineType::Float64(), MachineType::Float32(), MachineType::Float32(), MachineType::Float64(), MachineType::Int32(), MachineType::Float32(), MachineType::Int32(), MachineType::Float64(), MachineType::Float64(), MachineType::Float32(), MachineType::Int32(), MachineType::Float64(), MachineType::Int32(), MachineType::Float32()}; #else static MachineType types[] = { MachineType::Int32(), MachineType::Int64(), MachineType::Float32(), MachineType::Float64(), MachineType::Int32(), MachineType::Float64(), MachineType::Float32(), MachineType::Int64(), MachineType::Int64(), MachineType::Float32(), MachineType::Float32(), MachineType::Int32(), MachineType::Float64(), MachineType::Float64(), MachineType::Int64(), MachineType::Int32(), MachineType::Float64(), MachineType::Int32(), MachineType::Float32()}; #endif Isolate* isolate = CcTest::InitIsolateOnce(); // Build machine signature MachineType* params = &types[start]; const int num_params = static_cast<int>(arraysize(types) - start); // Build call descriptor int parray_gp[] = {GetRegConfig()->GetAllocatableGeneralCode(0), GetRegConfig()->GetAllocatableGeneralCode(1)}; int rarray_gp[] = {GetRegConfig()->GetAllocatableGeneralCode(0)}; int parray_fp[] = {GetRegConfig()->GetAllocatableDoubleCode(0), GetRegConfig()->GetAllocatableDoubleCode(1)}; int rarray_fp[] = {GetRegConfig()->GetAllocatableDoubleCode(0)}; Allocator palloc(parray_gp, 2, parray_fp, 2); Allocator ralloc(rarray_gp, 1, rarray_fp, 1); RegisterConfig config(palloc, ralloc); for (int which = 0; which < num_params; which++) { base::AccountingAllocator allocator; Zone zone(&allocator); HandleScope scope(isolate); MachineSignature::Builder builder(&zone, 1, num_params); builder.AddReturn(params[which]); for (int j = 0; j < num_params; j++) builder.AddParam(params[j]); MachineSignature* sig = builder.Build(); CallDescriptor* desc = config.Create(&zone, sig); Handle<Code> select; { // build the select. Zone zone(&allocator); Graph graph(&zone); RawMachineAssembler raw(isolate, &graph, desc); raw.Return(raw.Parameter(which)); select = CompileGraph("Compute", desc, &graph, raw.Export()); } { // call the select. Handle<Code> wrapper = Handle<Code>::null(); int32_t expected_ret; char bytes[kDoubleSize]; V8_ALIGNED(8) char output[kDoubleSize]; int expected_size = 0; CSignature0<int32_t> csig; { // Wrap the select code with a callable function that passes constants. Zone zone(&allocator); Graph graph(&zone); CallDescriptor* cdesc = Linkage::GetSimplifiedCDescriptor(&zone, &csig); RawMachineAssembler raw(isolate, &graph, cdesc); Node* target = raw.HeapConstant(select); Node** args = zone.NewArray<Node*>(num_params); int64_t constant = 0x0102030405060708; for (int i = 0; i < num_params; i++) { MachineType param_type = sig->GetParam(i); Node* konst = nullptr; if (param_type == MachineType::Int32()) { int32_t value[] = {static_cast<int32_t>(constant)}; konst = raw.Int32Constant(value[0]); if (i == which) memcpy(bytes, value, expected_size = 4); } if (param_type == MachineType::Int64()) { int64_t value[] = {static_cast<int64_t>(constant)}; konst = raw.Int64Constant(value[0]); if (i == which) memcpy(bytes, value, expected_size = 8); } if (param_type == MachineType::Float32()) { float32 value[] = {static_cast<float32>(constant)}; konst = raw.Float32Constant(value[0]); if (i == which) memcpy(bytes, value, expected_size = 4); } if (param_type == MachineType::Float64()) { float64 value[] = {static_cast<float64>(constant)}; konst = raw.Float64Constant(value[0]); if (i == which) memcpy(bytes, value, expected_size = 8); } CHECK_NOT_NULL(konst); args[i] = konst; constant += 0x1010101010101010; } Node* call = raw.CallN(desc, target, args); Node* store = raw.StoreToPointer(output, sig->GetReturn().representation(), call); USE(store); expected_ret = static_cast<int32_t>(constant); raw.Return(raw.Int32Constant(expected_ret)); wrapper = CompileGraph("Select-mixed-wrapper-const", cdesc, &graph, raw.Export()); } CodeRunner<int32_t> runnable(isolate, wrapper, &csig); CHECK_EQ(expected_ret, runnable.Call()); for (int i = 0; i < expected_size; i++) { CHECK_EQ(static_cast<int>(bytes[i]), static_cast<int>(output[i])); } } } } TEST(MixedParams_0) { MixedParamTest(0); } TEST(MixedParams_1) { MixedParamTest(1); } TEST(MixedParams_2) { MixedParamTest(2); } TEST(MixedParams_3) { MixedParamTest(3); } template <typename T> void TestStackSlot(MachineType slot_type, T expected) { // Test: Generate with a function f which reserves a stack slot, call an inner // function g from f which writes into the stack slot of f. if (GetRegConfig()->num_allocatable_double_registers() < 2) return; Isolate* isolate = CcTest::InitIsolateOnce(); // Lots of code to generate the build descriptor for the inner function. int parray_gp[] = {GetRegConfig()->GetAllocatableGeneralCode(0), GetRegConfig()->GetAllocatableGeneralCode(1)}; int rarray_gp[] = {GetRegConfig()->GetAllocatableGeneralCode(0)}; int parray_fp[] = {GetRegConfig()->GetAllocatableDoubleCode(0), GetRegConfig()->GetAllocatableDoubleCode(1)}; int rarray_fp[] = {GetRegConfig()->GetAllocatableDoubleCode(0)}; Allocator palloc(parray_gp, 2, parray_fp, 2); Allocator ralloc(rarray_gp, 1, rarray_fp, 1); RegisterConfig config(palloc, ralloc); Zone zone(isolate->allocator()); HandleScope scope(isolate); MachineSignature::Builder builder(&zone, 1, 12); builder.AddReturn(MachineType::Int32()); for (int i = 0; i < 10; i++) { builder.AddParam(MachineType::Int32()); } builder.AddParam(slot_type); builder.AddParam(MachineType::Pointer()); MachineSignature* sig = builder.Build(); CallDescriptor* desc = config.Create(&zone, sig); // Create inner function g. g has lots of parameters so that they are passed // over the stack. Handle<Code> inner; Graph graph(&zone); RawMachineAssembler g(isolate, &graph, desc); g.Store(slot_type.representation(), g.Parameter(11), g.Parameter(10), WriteBarrierKind::kNoWriteBarrier); g.Return(g.Parameter(9)); inner = CompileGraph("Compute", desc, &graph, g.Export()); // Create function f with a stack slot which calls the inner function g. BufferedRawMachineAssemblerTester<T> f(slot_type); Node* target = f.HeapConstant(inner); Node* stack_slot = f.StackSlot(slot_type.representation()); Node* args[12]; for (int i = 0; i < 10; i++) { args[i] = f.Int32Constant(i); } args[10] = f.Parameter(0); args[11] = stack_slot; f.CallN(desc, target, args); f.Return(f.Load(slot_type, stack_slot, f.IntPtrConstant(0))); CHECK_EQ(expected, f.Call(expected)); } TEST(RunStackSlotInt32) { int32_t magic = 0x12345678; TestStackSlot(MachineType::Int32(), magic); } #if !V8_TARGET_ARCH_32_BIT TEST(RunStackSlotInt64) { int64_t magic = 0x123456789abcdef0; TestStackSlot(MachineType::Int64(), magic); } #endif TEST(RunStackSlotFloat32) { float magic = 1234.125f; TestStackSlot(MachineType::Float32(), magic); } TEST(RunStackSlotFloat64) { double magic = 3456.375; TestStackSlot(MachineType::Float64(), magic); } } // namespace compiler } // namespace internal } // namespace v8