Commit cf4fb916 authored by Pierre Langlois's avatar Pierre Langlois Committed by Commit Bot

Reland "[cctest] Add fuzz tests for generating parallel moves."

This is a reland of c6b153fd
Original change's description:
> [cctest] Add fuzz tests for generating parallel moves.
>
> These new tests are somewhat similar to the existing gap resolver tests except
> we use the code generator and eventually run the generated code. The main idea
> is to cover cases that are difficult to hit, such as move from/to slots which
> are out of range of loads and stores, but may happen nonetheless.
>
> At this time, the tests only make sure the code generator actually generated
> some code, and that this code runs. In the future, it would be great to also
> check that the moves were actually performed.
>
> Bug: v8:6553
> Change-Id: I089a25fa05b3a20649658bb8952926ab11f91d68
> Reviewed-on: https://chromium-review.googlesource.com/574850
> Commit-Queue: Pierre Langlois <pierre.langlois@arm.com>
> Reviewed-by: Bill Budge <bbudge@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#47733}

Bug: v8:6553
Change-Id: Ia3eac9d7e6a23e2f6fea839b71d460cb7ad6ff6e
Reviewed-on: https://chromium-review.googlesource.com/645868Reviewed-by: 's avatarBill Budge <bbudge@chromium.org>
Commit-Queue: Pierre Langlois <pierre.langlois@arm.com>
Cr-Commit-Position: refs/heads/master@{#48115}
parent 4de8feba
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/assembler-inl.h"
#include "src/base/utils/random-number-generator.h"
#include "src/codegen.h"
#include "src/compilation-info.h"
#include "src/compiler/code-generator.h"
......@@ -11,25 +13,145 @@
#include "src/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/function-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
class CodeGeneratorTester : public InitializedHandleScope {
namespace {
int GetSlotSizeInBytes(MachineRepresentation rep) {
switch (rep) {
case MachineRepresentation::kTagged:
case MachineRepresentation::kFloat32:
return kPointerSize;
case MachineRepresentation::kFloat64:
return kDoubleSize;
case MachineRepresentation::kSimd128:
return kSimd128Size;
default:
break;
}
UNREACHABLE();
}
} // namespace
// Wrapper around the CodeGenerator with the ability to randomly generate moves
// and swaps which can then be executed. The `slots` map represents how many
// slots should be allocated per representation. Parallel moves will then be
// generated by randomly picking slots. Constants can be provided so that
// parallel moves may use them.
//
// At the moment, only the following representations are tested:
// - kTagged
// - kFloat32
// - kFloat64
// - kSimd128
// There is no need to test using Word32 or Word64 as they are the same as
// Tagged as far as the code generator is concerned.
class CodeGeneratorTester : public HandleAndZoneScope {
public:
CodeGeneratorTester()
: zone_(main_isolate()->allocator(), ZONE_NAME),
info_(ArrayVector("test"), main_isolate(), &zone_,
CodeGeneratorTester(std::map<MachineRepresentation, int> slots =
std::map<MachineRepresentation, int>{},
std::initializer_list<Constant> constants = {})
: info_(ArrayVector("test"), main_isolate(), main_zone(),
Code::ComputeFlags(Code::STUB)),
descriptor_(Linkage::GetJSCallDescriptor(&zone_, false, 0,
CallDescriptor::kNoFlags)),
descriptor_(Linkage::GetStubCallDescriptor(
main_isolate(), main_zone(), VoidDescriptor(main_isolate()), 0,
CallDescriptor::kNoFlags, Operator::kNoProperties,
MachineType::AnyTagged(), 0)),
linkage_(descriptor_),
blocks_(&zone_),
sequence_(main_isolate(), &zone_, &blocks_),
blocks_(main_zone()),
sequence_(main_isolate(), main_zone(), &blocks_),
rng_(CcTest::random_number_generator()),
frame_(descriptor_->CalculateFixedFrameSize()),
generator_(&zone_, &frame_, &linkage_, &sequence_, &info_,
generator_(main_zone(), &frame_, &linkage_, &sequence_, &info_,
base::Optional<OsrHelper>(), kNoSourcePosition, nullptr) {
// Keep track of all supported representations depending on what kind of
// stack slots are supported.
for (const auto& slot : slots) {
supported_reps_.push_back(slot.first);
}
// Allocate new slots until we run out of them.
while (std::any_of(slots.cbegin(), slots.cend(),
[](const std::pair<MachineRepresentation, int>& entry) {
// True if there are slots left to allocate for this
// representation.
return entry.second > 0;
})) {
// Pick a random MachineRepresentation from supported_reps_.
MachineRepresentation rep = CreateRandomMachineRepresentation();
auto entry = slots.find(rep);
DCHECK(entry != slots.end());
// We may have picked a representation for which all slots have already
// been allocated.
if (entry->second > 0) {
// Keep a map of (MachineRepresentation . std::vector<int>) with
// allocated slots to pick from for each representation.
RegisterSlot(rep, frame_.AllocateSpillSlot(GetSlotSizeInBytes(rep)));
entry->second--;
}
}
for (auto constant : constants) {
int virtual_register = AllocateConstant(constant);
// Associate constants with their compatible representations.
// TODO(all): Test all types of constants.
switch (constant.type()) {
// Integer constants are always moved to a tagged location, whatever
// their sizes.
case Constant::kInt32:
case Constant::kInt64:
RegisterConstant(MachineRepresentation::kTagged, virtual_register);
break;
// FP constants may be moved to a tagged location using a heap number,
// or directly to a location of the same size.
case Constant::kFloat32:
RegisterConstant(MachineRepresentation::kTagged, virtual_register);
RegisterConstant(MachineRepresentation::kFloat32, virtual_register);
break;
case Constant::kFloat64:
RegisterConstant(MachineRepresentation::kTagged, virtual_register);
RegisterConstant(MachineRepresentation::kFloat64, virtual_register);
break;
default:
break;
}
}
// Force a frame to be created.
generator_.frame_access_state()->MarkHasFrame(true);
generator_.AssembleConstructFrame();
// TODO(all): Generate a stack check here so that we fail gracefully if the
// frame is too big.
}
int AllocateConstant(Constant constant) {
int virtual_register = sequence_.NextVirtualRegister();
sequence_.AddConstant(virtual_register, constant);
return virtual_register;
}
// Register a constant referenced by `virtual_register` as compatible with
// `rep`.
void RegisterConstant(MachineRepresentation rep, int virtual_register) {
auto entry = constants_.find(rep);
if (entry == constants_.end()) {
std::vector<int> vregs = {virtual_register};
constants_.emplace(rep, vregs);
} else {
entry->second.push_back(virtual_register);
}
}
void RegisterSlot(MachineRepresentation rep, int slot) {
auto entry = allocated_slots_.find(rep);
if (entry == allocated_slots_.end()) {
std::vector<int> slots = {slot};
allocated_slots_.emplace(rep, slots);
} else {
entry->second.push_back(slot);
}
}
enum PushTypeFlag {
......@@ -38,6 +160,124 @@ class CodeGeneratorTester : public InitializedHandleScope {
kScalarPush = CodeGenerator::kScalarPush
};
enum OperandConstraint {
kNone,
// Restrict operands to non-constants. This is useful when generating a
// destination.
kCannotBeConstant
};
// Generate parallel moves at random. Note that they may not be compatible
// between each other as this doesn't matter to the code generator.
ParallelMove* GenerateRandomMoves(int size) {
ParallelMove* parallel_move = new (main_zone()) ParallelMove(main_zone());
for (int i = 0; i < size;) {
MachineRepresentation rep = CreateRandomMachineRepresentation();
MoveOperands mo(CreateRandomOperand(kNone, rep),
CreateRandomOperand(kCannotBeConstant, rep));
// It isn't valid to call `AssembleMove` and `AssembleSwap` with redundant
// moves.
if (mo.IsRedundant()) continue;
parallel_move->AddMove(mo.source(), mo.destination());
// Iterate only when a move was created.
i++;
}
return parallel_move;
}
ParallelMove* GenerateRandomSwaps(int size) {
ParallelMove* parallel_move = new (main_zone()) ParallelMove(main_zone());
for (int i = 0; i < size;) {
MachineRepresentation rep = CreateRandomMachineRepresentation();
InstructionOperand lhs = CreateRandomOperand(kCannotBeConstant, rep);
InstructionOperand rhs = CreateRandomOperand(kCannotBeConstant, rep);
MoveOperands mo(lhs, rhs);
// It isn't valid to call `AssembleMove` and `AssembleSwap` with redundant
// moves.
if (mo.IsRedundant()) continue;
// Canonicalize the swap: the register operand has to be the left hand
// side.
if (lhs.IsStackSlot() || lhs.IsFPStackSlot()) {
std::swap(lhs, rhs);
}
parallel_move->AddMove(lhs, rhs);
// Iterate only when a swap was created.
i++;
}
return parallel_move;
}
MachineRepresentation CreateRandomMachineRepresentation() {
int index = rng_->NextInt(static_cast<int>(supported_reps_.size()));
return supported_reps_[index];
}
InstructionOperand CreateRandomOperand(OperandConstraint constraint,
MachineRepresentation rep) {
// Only generate a Constant if the operand is a source and we have a
// constant with a compatible representation in stock.
bool generate_constant = (constraint != kCannotBeConstant) &&
(constants_.find(rep) != constants_.end());
switch (rng_->NextInt(generate_constant ? 3 : 2)) {
case 0:
return CreateRandomStackSlotOperand(rep);
case 1:
return CreateRandomRegisterOperand(rep);
case 2:
return CreateRandomConstant(rep);
}
UNREACHABLE();
}
InstructionOperand CreateRandomRegisterOperand(MachineRepresentation rep) {
int code;
const RegisterConfiguration* conf = RegisterConfiguration::Default();
switch (rep) {
case MachineRepresentation::kFloat32: {
int index = rng_->NextInt(conf->num_allocatable_float_registers());
code = conf->RegisterConfiguration::GetAllocatableFloatCode(index);
break;
}
case MachineRepresentation::kFloat64: {
int index = rng_->NextInt(conf->num_allocatable_double_registers());
code = conf->RegisterConfiguration::GetAllocatableDoubleCode(index);
break;
}
case MachineRepresentation::kSimd128: {
int index = rng_->NextInt(conf->num_allocatable_simd128_registers());
code = conf->RegisterConfiguration::GetAllocatableSimd128Code(index);
break;
}
case MachineRepresentation::kTagged: {
// Pick an allocatable register that is not the return register.
do {
int index = rng_->NextInt(conf->num_allocatable_general_registers());
code = conf->RegisterConfiguration::GetAllocatableGeneralCode(index);
} while (code == kReturnRegister0.code());
break;
}
default:
UNREACHABLE();
break;
}
return AllocatedOperand(LocationOperand::REGISTER, rep, code);
}
InstructionOperand CreateRandomStackSlotOperand(MachineRepresentation rep) {
int index = rng_->NextInt(static_cast<int>(allocated_slots_[rep].size()));
return AllocatedOperand(LocationOperand::STACK_SLOT, rep,
allocated_slots_[rep][index]);
}
InstructionOperand CreateRandomConstant(MachineRepresentation rep) {
int index = rng_->NextInt(static_cast<int>(constants_[rep].size()));
return ConstantOperand(constants_[rep][index]);
}
void CheckAssembleTailCallGaps(Instruction* instr,
int first_unused_stack_slot,
CodeGeneratorTester::PushTypeFlag push_type) {
......@@ -64,7 +304,24 @@ class CodeGeneratorTester : public InitializedHandleScope {
generator_.AssembleTailCallAfterGap(instr, first_unused_stack_slot);
}
void CheckAssembleMove(InstructionOperand* source,
InstructionOperand* destination) {
int start = generator_.tasm()->pc_offset();
generator_.AssembleMove(source, destination);
CHECK(generator_.tasm()->pc_offset() > start);
}
void CheckAssembleSwap(InstructionOperand* source,
InstructionOperand* destination) {
int start = generator_.tasm()->pc_offset();
generator_.AssembleSwap(source, destination);
CHECK(generator_.tasm()->pc_offset() > start);
}
Handle<Code> Finalize() {
InstructionOperand zero = ImmediateOperand(ImmediateOperand::INLINE, 0);
generator_.AssembleReturn(&zero);
generator_.FinishCode();
generator_.safepoints()->Emit(generator_.tasm(),
frame_.GetTotalFrameSlotCount());
......@@ -79,19 +336,133 @@ class CodeGeneratorTester : public InitializedHandleScope {
}
}
Zone* zone() { return &zone_; }
void Run() {
HandleScope scope(main_isolate());
Handle<Code> code = Finalize();
if (FLAG_print_code) {
code->Print();
}
FunctionTester ft(code);
ft.Call();
}
v8::base::RandomNumberGenerator* rng() const { return rng_; }
private:
Zone zone_;
CompilationInfo info_;
CallDescriptor* descriptor_;
Linkage linkage_;
ZoneVector<InstructionBlock*> blocks_;
InstructionSequence sequence_;
std::vector<MachineRepresentation> supported_reps_;
std::map<MachineRepresentation, std::vector<int>> allocated_slots_;
std::map<MachineRepresentation, std::vector<int>> constants_;
v8::base::RandomNumberGenerator* rng_;
Frame frame_;
CodeGenerator generator_;
};
// The following fuzz tests will assemble a lot of moves, wrap them in
// executable native code and run them. At this time, we only check that
// something is actually generated, and that it runs on hardware or the
// simulator.
// TODO(all): It would be great to record the data on the stack after all moves
// are executed so that we could test the functionality in an architecture
// independent way. We would also have to make sure we generate moves compatible
// with each other as the gap-resolver tests do.
TEST(FuzzAssembleMove) {
// Test small and potentially large ranges separately. Note that the number of
// slots affects how much stack is allocated when running the generated code.
// This means we have to be careful not to exceed the stack limit, which is
// lower on Windows.
for (auto n : {64, 500}) {
std::map<MachineRepresentation, int> slots = {
{MachineRepresentation::kTagged, n},
{MachineRepresentation::kFloat32, n},
{MachineRepresentation::kFloat64, n}};
if (CpuFeatures::SupportsWasmSimd128()) {
// Generate fewer 128-bit slots.
slots.emplace(MachineRepresentation::kSimd128, n / 4);
}
CodeGeneratorTester c(
slots,
{Constant(0), Constant(1), Constant(2), Constant(3), Constant(4),
Constant(5), Constant(6), Constant(7),
Constant(static_cast<float>(0.1)), Constant(static_cast<float>(0.2)),
Constant(static_cast<float>(0.3)), Constant(static_cast<float>(0.4)),
Constant(static_cast<double>(0.5)), Constant(static_cast<double>(0.6)),
Constant(static_cast<double>(0.7)),
Constant(static_cast<double>(0.8))});
ParallelMove* moves = c.GenerateRandomMoves(1000);
for (const auto m : *moves) {
c.CheckAssembleMove(&m->source(), &m->destination());
}
c.Run();
}
}
TEST(FuzzAssembleSwap) {
// Test small and potentially large ranges separately. Note that the number of
// slots affects how much stack is allocated when running the generated code.
// This means we have to be careful not to exceed the stack limit, which is
// lower on Windows.
for (auto n : {64, 500}) {
std::map<MachineRepresentation, int> slots = {
{MachineRepresentation::kTagged, n},
{MachineRepresentation::kFloat32, n},
{MachineRepresentation::kFloat64, n}};
if (CpuFeatures::SupportsWasmSimd128()) {
// Generate fewer 128-bit slots.
slots.emplace(MachineRepresentation::kSimd128, n / 4);
}
CodeGeneratorTester c(slots);
ParallelMove* moves = c.GenerateRandomSwaps(1000);
for (const auto m : *moves) {
c.CheckAssembleSwap(&m->source(), &m->destination());
}
c.Run();
}
}
TEST(FuzzAssembleMoveAndSwap) {
// Test small and potentially large ranges separately. Note that the number of
// slots affects how much stack is allocated when running the generated code.
// This means we have to be careful not to exceed the stack limit, which is
// lower on Windows.
for (auto n : {64, 500}) {
std::map<MachineRepresentation, int> slots = {
{MachineRepresentation::kTagged, n},
{MachineRepresentation::kFloat32, n},
{MachineRepresentation::kFloat64, n}};
if (CpuFeatures::SupportsWasmSimd128()) {
// Generate fewer 128-bit slots.
slots.emplace(MachineRepresentation::kSimd128, n / 4);
}
CodeGeneratorTester c(
slots,
{Constant(0), Constant(1), Constant(2), Constant(3), Constant(4),
Constant(5), Constant(6), Constant(7),
Constant(static_cast<float>(0.1)), Constant(static_cast<float>(0.2)),
Constant(static_cast<float>(0.3)), Constant(static_cast<float>(0.4)),
Constant(static_cast<double>(0.5)), Constant(static_cast<double>(0.6)),
Constant(static_cast<double>(0.7)),
Constant(static_cast<double>(0.8))});
for (int i = 0; i < 1000; i++) {
// Randomly alternate between swaps and moves.
if (c.rng()->NextInt(2) == 0) {
MoveOperands* move = c.GenerateRandomMoves(1)->at(0);
c.CheckAssembleMove(&move->source(), &move->destination());
} else {
MoveOperands* move = c.GenerateRandomSwaps(1)->at(0);
c.CheckAssembleSwap(&move->source(), &move->destination());
}
}
c.Run();
}
}
TEST(AssembleTailCallGap) {
const RegisterConfiguration* conf = RegisterConfiguration::Default();
......@@ -142,14 +513,22 @@ TEST(AssembleTailCallGap) {
{
// Generate a series of register pushes only.
CodeGeneratorTester c;
Instruction* instr = Instruction::New(c.zone(), kArchNop);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
Instruction* instr = Instruction::New(c.main_zone(), kArchNop);
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(r3, slot_0);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(r2, slot_1);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(r1, slot_2);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(r0, slot_3);
c.CheckAssembleTailCallGaps(instr, first_slot + 4,
......@@ -160,14 +539,22 @@ TEST(AssembleTailCallGap) {
{
// Generate a series of stack pushes only.
CodeGeneratorTester c;
Instruction* instr = Instruction::New(c.zone(), kArchNop);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
Instruction* instr = Instruction::New(c.main_zone(), kArchNop);
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(slot_minus_4, slot_0);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(slot_minus_3, slot_1);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(slot_minus_2, slot_2);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(slot_minus_1, slot_3);
c.CheckAssembleTailCallGaps(instr, first_slot + 4,
......@@ -178,14 +565,22 @@ TEST(AssembleTailCallGap) {
{
// Generate a mix of stack and register pushes.
CodeGeneratorTester c;
Instruction* instr = Instruction::New(c.zone(), kArchNop);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
Instruction* instr = Instruction::New(c.main_zone(), kArchNop);
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(slot_minus_2, slot_0);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(r1, slot_1);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(slot_minus_1, slot_2);
instr->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION, c.zone())
instr
->GetOrCreateParallelMove(Instruction::FIRST_GAP_POSITION,
c.main_zone())
->AddMove(r0, slot_3);
c.CheckAssembleTailCallGaps(instr, first_slot + 4,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment