[cctest] Support testing Simd128 moves and swaps

Extend the code-generator tests to cover AssembleMove and AssembleSwap with
Simd128 registers and stack slots, for targets that support them.

For this to work however, we need support for passing Simd128 stack parameters
in TurboFan which this patch implements for Arm and x86. PPC and S390 both do
not support the Simd128 representation and it appears MIPS and MIPS64's
implementation of AssembleMove and AssembleSwap do not support it either.

As per the design of the tests, the set of values to perform moves on are
represented in a FixedArray of Smis (for kTagged) and HeapNumbers (for kFloat32
and kFloat64). They are converted to raw values for the moves to be performed
on, to be then converted back into a FixedArray. For the kSimd128
representation, we represent values as a FixedArray of 4 Smis, each representing
a lane. They are converted to a raw Simd128 vector using the `I32x4ReplaceLane`
and `I32x4ExtractLane` operations.

Finally, these tests need Simd128 variables mixed with the CodeStubAssembler
which is not a use-case officially supported. And as a result, the `RecordWrite`
stub does not guarantee to preserve Simd128 registers. To get around this, we
have to be careful to skip write barriers when dealing with Simd128 parameters
inside the "teardown" function, and we've had to move all allocations to the
"setup" function.

Thanks to this, we are able to catch bugs such as this one
https://bugs.chromium.org/p/v8/issues/detail?id=6843.

Bug: v8:6848
Change-Id: I8787d6339cdbfcd9356c5e8995925f0b45c562fa
Reviewed-on: https://chromium-review.googlesource.com/728599
Commit-Queue: Pierre Langlois <pierre.langlois@arm.com>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Bill Budge <bbudge@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50326}
This commit is contained in:
Pierre Langlois 2018-01-02 11:27:15 +00:00 committed by Commit Bot
parent 78ac640554
commit 0761b55d21
12 changed files with 359 additions and 96 deletions

View File

@ -1345,6 +1345,10 @@ class Assembler : public AssemblerBase {
void pop(); void pop();
void vpush(QwNeonRegister src, Condition cond = al) {
vstm(db_w, sp, src.low(), src.high(), cond);
}
void vpush(DwVfpRegister src, Condition cond = al) { void vpush(DwVfpRegister src, Condition cond = al) {
vstm(db_w, sp, src, src, cond); vstm(db_w, sp, src, src, cond);
} }

View File

@ -1623,13 +1623,23 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
case kArmPush: case kArmPush:
if (instr->InputAt(0)->IsFPRegister()) { if (instr->InputAt(0)->IsFPRegister()) {
LocationOperand* op = LocationOperand::cast(instr->InputAt(0)); LocationOperand* op = LocationOperand::cast(instr->InputAt(0));
if (op->representation() == MachineRepresentation::kFloat64) { switch (op->representation()) {
__ vpush(i.InputDoubleRegister(0)); case MachineRepresentation::kFloat32:
frame_access_state()->IncreaseSPDelta(kDoubleSize / kPointerSize); __ vpush(i.InputFloatRegister(0));
} else { frame_access_state()->IncreaseSPDelta(1);
DCHECK_EQ(MachineRepresentation::kFloat32, op->representation()); break;
__ vpush(i.InputFloatRegister(0)); case MachineRepresentation::kFloat64:
frame_access_state()->IncreaseSPDelta(1); __ vpush(i.InputDoubleRegister(0));
frame_access_state()->IncreaseSPDelta(kDoubleSize / kPointerSize);
break;
case MachineRepresentation::kSimd128: {
__ vpush(i.InputSimd128Register(0));
frame_access_state()->IncreaseSPDelta(kSimd128Size / kPointerSize);
break;
}
default:
UNREACHABLE();
break;
} }
} else { } else {
__ push(i.InputRegister(0)); __ push(i.InputRegister(0));

View File

@ -1326,7 +1326,9 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
Register prev = __ StackPointer(); Register prev = __ StackPointer();
__ SetStackPointer(arch_opcode == kArm64PokeCSP ? csp : jssp); __ SetStackPointer(arch_opcode == kArm64PokeCSP ? csp : jssp);
Operand operand(i.InputInt32(1) * kPointerSize); Operand operand(i.InputInt32(1) * kPointerSize);
if (instr->InputAt(0)->IsFPRegister()) { if (instr->InputAt(0)->IsSimd128Register()) {
__ Poke(i.InputSimd128Register(0), operand);
} else if (instr->InputAt(0)->IsFPRegister()) {
__ Poke(i.InputFloat64Register(0), operand); __ Poke(i.InputFloat64Register(0), operand);
} else { } else {
__ Poke(i.InputOrZeroRegister64(0), operand); __ Poke(i.InputOrZeroRegister64(0), operand);

View File

@ -1687,6 +1687,8 @@ void InstructionSelector::EmitPrepareArguments(
bool always_claim = to_native_stack != from_native_stack; bool always_claim = to_native_stack != from_native_stack;
// `arguments` includes alignment "holes". This means that slots bigger than
// kPointerSize, e.g. Simd128, will span across multiple arguments.
int claim_count = static_cast<int>(arguments->size()); int claim_count = static_cast<int>(arguments->size());
int slot = claim_count - 1; int slot = claim_count - 1;
claim_count = RoundUp(claim_count, 2); claim_count = RoundUp(claim_count, 2);
@ -1710,8 +1712,12 @@ void InstructionSelector::EmitPrepareArguments(
// Poke the arguments into the stack. // Poke the arguments into the stack.
while (slot >= 0) { while (slot >= 0) {
Emit(poke, g.NoOutput(), g.UseRegister((*arguments)[slot].node), Node* input_node = (*arguments)[slot].node;
g.TempImmediate(slot)); // Skip any alignment holes in pushed nodes.
if (input_node != nullptr) {
Emit(poke, g.NoOutput(), g.UseRegister(input_node),
g.TempImmediate(slot));
}
slot--; slot--;
// TODO(ahaas): Poke arguments in pairs if two subsequent arguments have the // TODO(ahaas): Poke arguments in pairs if two subsequent arguments have the
// same type. // same type.

View File

@ -1568,6 +1568,17 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
frame_access_state()->IncreaseSPDelta(kDoubleSize / kPointerSize); frame_access_state()->IncreaseSPDelta(kDoubleSize / kPointerSize);
} }
break; break;
case kIA32PushSimd128:
if (instr->InputAt(0)->IsFPRegister()) {
__ sub(esp, Immediate(kSimd128Size));
__ movups(Operand(esp, 0), i.InputSimd128Register(0));
} else {
__ movups(kScratchDoubleReg, i.InputOperand(0));
__ sub(esp, Immediate(kSimd128Size));
__ movups(Operand(esp, 0), kScratchDoubleReg);
}
frame_access_state()->IncreaseSPDelta(kSimd128Size / kPointerSize);
break;
case kIA32Push: case kIA32Push:
if (AddressingModeField::decode(instr->opcode()) != kMode_None) { if (AddressingModeField::decode(instr->opcode()) != kMode_None) {
size_t index = 0; size_t index = 0;

View File

@ -110,6 +110,7 @@ namespace compiler {
V(IA32Push) \ V(IA32Push) \
V(IA32PushFloat32) \ V(IA32PushFloat32) \
V(IA32PushFloat64) \ V(IA32PushFloat64) \
V(IA32PushSimd128) \
V(IA32Poke) \ V(IA32Poke) \
V(IA32Peek) \ V(IA32Peek) \
V(IA32StackCheck) \ V(IA32StackCheck) \

View File

@ -269,6 +269,7 @@ int InstructionScheduler::GetTargetInstructionFlags(
case kIA32Push: case kIA32Push:
case kIA32PushFloat32: case kIA32PushFloat32:
case kIA32PushFloat64: case kIA32PushFloat64:
case kIA32PushSimd128:
case kIA32Poke: case kIA32Poke:
return kHasSideEffect; return kHasSideEffect;

View File

@ -1000,6 +1000,8 @@ void InstructionSelector::EmitPrepareArguments(
Emit(kIA32PushFloat32, g.NoOutput(), value); Emit(kIA32PushFloat32, g.NoOutput(), value);
} else if (input.location.GetType() == MachineType::Float64()) { } else if (input.location.GetType() == MachineType::Float64()) {
Emit(kIA32PushFloat64, g.NoOutput(), value); Emit(kIA32PushFloat64, g.NoOutput(), value);
} else if (input.location.GetType() == MachineType::Simd128()) {
Emit(kIA32PushSimd128, g.NoOutput(), value);
} else { } else {
Emit(kIA32Push, g.NoOutput(), value); Emit(kIA32Push, g.NoOutput(), value);
} }

View File

@ -265,6 +265,11 @@ class MachineRepresentationInferrer {
MachineRepresentation::kFloat64; MachineRepresentation::kFloat64;
} }
break; break;
case IrOpcode::kI32x4ReplaceLane:
case IrOpcode::kI32x4Splat:
representation_vector_[node->id()] =
MachineRepresentation::kSimd128;
break;
#undef LABEL #undef LABEL
default: default:
break; break;
@ -369,6 +374,14 @@ class MachineRepresentationChecker {
CheckValueInputRepresentationIs(node, 0, CheckValueInputRepresentationIs(node, 0,
MachineRepresentation::kSimd128); MachineRepresentation::kSimd128);
break; break;
case IrOpcode::kI32x4ReplaceLane:
CheckValueInputRepresentationIs(node, 0,
MachineRepresentation::kSimd128);
CheckValueInputForInt32Op(node, 1);
break;
case IrOpcode::kI32x4Splat:
CheckValueInputForInt32Op(node, 0);
break;
#define LABEL(opcode) case IrOpcode::k##opcode: #define LABEL(opcode) case IrOpcode::k##opcode:
case IrOpcode::kChangeInt32ToTagged: case IrOpcode::kChangeInt32ToTagged:
case IrOpcode::kChangeUint32ToTagged: case IrOpcode::kChangeUint32ToTagged:

View File

@ -1981,18 +1981,37 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
frame_access_state()->IncreaseSPDelta(1); frame_access_state()->IncreaseSPDelta(1);
unwinding_info_writer_.MaybeIncreaseBaseOffsetAt(__ pc_offset(), unwinding_info_writer_.MaybeIncreaseBaseOffsetAt(__ pc_offset(),
kPointerSize); kPointerSize);
} else if (instr->InputAt(0)->IsFPRegister()) { } else if (instr->InputAt(0)->IsFloatRegister() ||
instr->InputAt(0)->IsDoubleRegister()) {
// TODO(titzer): use another machine instruction? // TODO(titzer): use another machine instruction?
__ subq(rsp, Immediate(kDoubleSize)); __ subq(rsp, Immediate(kDoubleSize));
frame_access_state()->IncreaseSPDelta(kDoubleSize / kPointerSize); frame_access_state()->IncreaseSPDelta(kDoubleSize / kPointerSize);
unwinding_info_writer_.MaybeIncreaseBaseOffsetAt(__ pc_offset(), unwinding_info_writer_.MaybeIncreaseBaseOffsetAt(__ pc_offset(),
kDoubleSize); kDoubleSize);
__ Movsd(Operand(rsp, 0), i.InputDoubleRegister(0)); __ Movsd(Operand(rsp, 0), i.InputDoubleRegister(0));
} else { } else if (instr->InputAt(0)->IsSimd128Register()) {
// TODO(titzer): use another machine instruction?
__ subq(rsp, Immediate(kSimd128Size));
frame_access_state()->IncreaseSPDelta(kSimd128Size / kPointerSize);
unwinding_info_writer_.MaybeIncreaseBaseOffsetAt(__ pc_offset(),
kSimd128Size);
__ Movups(Operand(rsp, 0), i.InputSimd128Register(0));
} else if (instr->InputAt(0)->IsStackSlot() ||
instr->InputAt(0)->IsFloatStackSlot() ||
instr->InputAt(0)->IsDoubleStackSlot()) {
__ pushq(i.InputOperand(0)); __ pushq(i.InputOperand(0));
frame_access_state()->IncreaseSPDelta(1); frame_access_state()->IncreaseSPDelta(1);
unwinding_info_writer_.MaybeIncreaseBaseOffsetAt(__ pc_offset(), unwinding_info_writer_.MaybeIncreaseBaseOffsetAt(__ pc_offset(),
kPointerSize); kPointerSize);
} else {
DCHECK(instr->InputAt(0)->IsSimd128StackSlot());
__ Movups(kScratchDoubleReg, i.InputOperand(0));
// TODO(titzer): use another machine instruction?
__ subq(rsp, Immediate(kSimd128Size));
frame_access_state()->IncreaseSPDelta(kSimd128Size / kPointerSize);
unwinding_info_writer_.MaybeIncreaseBaseOffsetAt(__ pc_offset(),
kSimd128Size);
__ Movups(Operand(rsp, 0), kScratchDoubleReg);
} }
break; break;
case kX64Poke: { case kX64Poke: {

View File

@ -1441,6 +1441,9 @@ void InstructionSelector::EmitPrepareArguments(
// Push any stack arguments. // Push any stack arguments.
int effect_level = GetEffectLevel(node); int effect_level = GetEffectLevel(node);
for (PushParameter input : base::Reversed(*arguments)) { for (PushParameter input : base::Reversed(*arguments)) {
// Skip any alignment holes in pushed nodes. We may have one in case of a
// Simd128 stack argument.
if (input.node == nullptr) continue;
if (g.CanBeImmediate(input.node)) { if (g.CanBeImmediate(input.node)) {
Emit(kX64Push, g.NoOutput(), g.UseImmediate(input.node)); Emit(kX64Push, g.NoOutput(), g.UseImmediate(input.node));
} else if (IsSupported(ATOM) || } else if (IsSupported(ATOM) ||

View File

@ -49,8 +49,10 @@ Handle<Code> BuildTeardownFunction(Isolate* isolate, CallDescriptor* descriptor,
// arguments: // arguments:
// ~~~ // ~~~
// FixedArray setup(CodeObject* test, FixedArray state_in) { // FixedArray setup(CodeObject* test, FixedArray state_in) {
// FixedArray state_out = AllocateFixedArray(state_in.length());
// // `test` will tail-call to its first parameter which will be `teardown`. // // `test` will tail-call to its first parameter which will be `teardown`.
// return test(teardown, state_in[0], state_in[1], state_in[2], ...); // return test(teardown, state_out, state_in[0], state_in[1],
// state_in[2], ...);
// } // }
// ~~~ // ~~~
// //
@ -58,11 +60,14 @@ Handle<Code> BuildTeardownFunction(Isolate* isolate, CallDescriptor* descriptor,
// values to pass to the `test` function. The array will have been created using // values to pass to the `test` function. The array will have been created using
// `GenerateInitialState()` and needs to be converted in the following way: // `GenerateInitialState()` and needs to be converted in the following way:
// //
// | Parameter type | FixedArray element | Conversion | // | Parameter type | FixedArray element | Conversion |
// |----------------+--------------------+------------------------------------| // |----------------+---------------------+------------------------------------|
// | kTagged | Smi | None. | // | kTagged | Smi | None. |
// | kFloat32 | HeapNumber | Load value and convert to Float32. | // | kFloat32 | HeapNumber | Load value and convert to Float32. |
// | kFloat64 | HeapNumber | Load value. | // | kFloat64 | HeapNumber | Load value. |
// | kSimd128 | FixedArray<Smi>[4] | Untag each Smi and write the |
// | | | results into lanes of a new |
// | | | 128-bit vector. |
// //
Handle<Code> BuildSetupFunction(Isolate* isolate, CallDescriptor* descriptor, Handle<Code> BuildSetupFunction(Isolate* isolate, CallDescriptor* descriptor,
std::vector<AllocatedOperand> parameters) { std::vector<AllocatedOperand> parameters) {
@ -73,6 +78,32 @@ Handle<Code> BuildSetupFunction(Isolate* isolate, CallDescriptor* descriptor,
params.push_back(__ Parameter(0)); params.push_back(__ Parameter(0));
params.push_back( params.push_back(
__ HeapConstant(BuildTeardownFunction(isolate, descriptor, parameters))); __ HeapConstant(BuildTeardownFunction(isolate, descriptor, parameters)));
// First allocate the FixedArray which will hold the final results. Here we
// should take care of all allocations, meaning we allocate HeapNumbers and
// FixedArrays representing Simd128 values.
Node* state_out = __ AllocateFixedArray(PACKED_ELEMENTS,
__ IntPtrConstant(parameters.size()));
for (int i = 0; i < static_cast<int>(parameters.size()); i++) {
switch (parameters[i].representation()) {
case MachineRepresentation::kTagged:
break;
case MachineRepresentation::kFloat32:
case MachineRepresentation::kFloat64:
__ StoreFixedArrayElement(state_out, i, __ AllocateHeapNumber());
break;
case MachineRepresentation::kSimd128: {
__ StoreFixedArrayElement(
state_out, i,
__ AllocateFixedArray(PACKED_SMI_ELEMENTS, __ IntPtrConstant(4)));
break;
}
default:
UNREACHABLE();
break;
}
}
params.push_back(state_out);
// Then take each element of the initial state and pass them as arguments.
Node* state_in = __ Parameter(1); Node* state_in = __ Parameter(1);
for (int i = 0; i < static_cast<int>(parameters.size()); i++) { for (int i = 0; i < static_cast<int>(parameters.size()); i++) {
Node* element = __ LoadFixedArrayElement(state_in, __ IntPtrConstant(i)); Node* element = __ LoadFixedArrayElement(state_in, __ IntPtrConstant(i));
@ -87,6 +118,21 @@ Handle<Code> BuildSetupFunction(Isolate* isolate, CallDescriptor* descriptor,
case MachineRepresentation::kFloat64: case MachineRepresentation::kFloat64:
element = __ LoadHeapNumberValue(element); element = __ LoadHeapNumberValue(element);
break; break;
case MachineRepresentation::kSimd128: {
Node* vector = tester.raw_assembler_for_testing()->AddNode(
tester.raw_assembler_for_testing()->machine()->I32x4Splat(),
__ Int32Constant(0));
for (int lane = 0; lane < 4; lane++) {
Node* lane_value = __ SmiToWord32(
__ LoadFixedArrayElement(element, __ IntPtrConstant(lane)));
vector = tester.raw_assembler_for_testing()->AddNode(
tester.raw_assembler_for_testing()->machine()->I32x4ReplaceLane(
lane),
vector, lane_value);
}
element = vector;
break;
}
default: default:
UNREACHABLE(); UNREACHABLE();
break; break;
@ -99,45 +145,59 @@ Handle<Code> BuildSetupFunction(Isolate* isolate, CallDescriptor* descriptor,
return tester.GenerateCodeCloseAndEscape(); return tester.GenerateCodeCloseAndEscape();
} }
// Build the `teardown` function. It allocates and fills a FixedArray with all // Build the `teardown` function. It takes a FixedArray as argument, fills it
// its parameters. The parameters need to be consistent with `parameters`. // with the rest of its parameters and returns it. The parameters need to be
// consistent with `parameters`.
// ~~~ // ~~~
// FixedArray teardown(CodeObject* /* unused */, // FixedArray teardown(CodeObject* /* unused */, FixedArray result,
// // Tagged registers. // // Tagged registers.
// Object* r0, Object* r1, ..., // Object* r0, Object* r1, ...,
// // FP registers. // // FP registers.
// Float32 s0, Float64 d1, ..., // Float32 s0, Float64 d1, ...,
// // Mixed stack slots. // // Mixed stack slots.
// Float64 mem0, Object* mem1, Float32 mem2, ...) { // Float64 mem0, Object* mem1, Float32 mem2, ...) {
// return new FixedArray(r0, r1, ..., s0, d1, ..., mem0, mem1, mem2, ...); // result[0] = r0;
// result[1] = r1;
// ...
// result[..] = s0;
// ...
// result[..] = mem0;
// ...
// return result;
// } // }
// ~~~ // ~~~
// //
// This function needs to convert its parameters into values fit for a // This function needs to convert its parameters into values fit for a
// FixedArray, essentially reverting what the `setup` function did: // FixedArray, essentially reverting what the `setup` function did:
// //
// | Parameter type | Parameter value | Conversion | // | Parameter type | Parameter value | Conversion |
// |----------------+-------------------+----------------------------| // |----------------+-------------------+--------------------------------------|
// | kTagged | Smi or HeapNumber | None. | // | kTagged | Smi or HeapNumber | None. |
// | kFloat32 | Raw Float32 | Convert to Float64 and | // | kFloat32 | Raw Float32 | Convert to Float64. |
// | | | allocate a new HeapNumber. | // | kFloat64 | Raw Float64 | None. |
// | kFloat64 | Raw Float64 | Allocate a new HeapNumber. | // | kSimd128 | Raw Simd128 | Split into 4 Word32 values and tag |
// | | | them. |
// //
// Note that it is possible for a `kTagged` value to go from a Smi to a // Note that it is possible for a `kTagged` value to go from a Smi to a
// HeapNumber. This is because `AssembleMove` will allocate a new HeapNumber if // HeapNumber. This is because `AssembleMove` will allocate a new HeapNumber if
// it is asked to move a FP constant to a tagged register or slot. // it is asked to move a FP constant to a tagged register or slot.
// //
// Finally, it is important that this function does not call `RecordWrite` which
// is why "setup" is in charge of all allocations and we are using
// SKIP_WRITE_BARRIER. The reason for this is that `RecordWrite` may clobber the
// top 64 bits of Simd128 registers. This is the case on x64, ia32 and Arm64 for
// example.
Handle<Code> BuildTeardownFunction(Isolate* isolate, CallDescriptor* descriptor, Handle<Code> BuildTeardownFunction(Isolate* isolate, CallDescriptor* descriptor,
std::vector<AllocatedOperand> parameters) { std::vector<AllocatedOperand> parameters) {
CodeAssemblerTester tester(isolate, descriptor); CodeAssemblerTester tester(isolate, descriptor);
CodeStubAssembler assembler(tester.state()); CodeStubAssembler assembler(tester.state());
Node* result_array = __ AllocateFixedArray( Node* result_array = __ Parameter(1);
PACKED_ELEMENTS, __ IntPtrConstant(parameters.size()));
for (int i = 0; i < static_cast<int>(parameters.size()); i++) { for (int i = 0; i < static_cast<int>(parameters.size()); i++) {
// The first argument is not used. // The first argument is not used and the second is "result_array".
Node* param = __ Parameter(i + 1); Node* param = __ Parameter(i + 2);
switch (parameters[i].representation()) { switch (parameters[i].representation()) {
case MachineRepresentation::kTagged: case MachineRepresentation::kTagged:
__ StoreFixedArrayElement(result_array, i, param, SKIP_WRITE_BARRIER);
break; break;
// Box FP values into HeapNumbers. // Box FP values into HeapNumbers.
case MachineRepresentation::kFloat32: case MachineRepresentation::kFloat32:
@ -145,13 +205,28 @@ Handle<Code> BuildTeardownFunction(Isolate* isolate, CallDescriptor* descriptor,
tester.raw_assembler_for_testing()->ChangeFloat32ToFloat64(param); tester.raw_assembler_for_testing()->ChangeFloat32ToFloat64(param);
// Fallthrough // Fallthrough
case MachineRepresentation::kFloat64: case MachineRepresentation::kFloat64:
param = __ AllocateHeapNumberWithValue(param); __ StoreObjectFieldNoWriteBarrier(
__ LoadFixedArrayElement(result_array, i), HeapNumber::kValueOffset,
param, MachineRepresentation::kFloat64);
break; break;
case MachineRepresentation::kSimd128: {
Node* vector = __ LoadFixedArrayElement(result_array, i);
for (int lane = 0; lane < 4; lane++) {
Node* lane_value =
__ SmiFromWord32(tester.raw_assembler_for_testing()->AddNode(
tester.raw_assembler_for_testing()
->machine()
->I32x4ExtractLane(lane),
param));
__ StoreFixedArrayElement(vector, lane, lane_value,
SKIP_WRITE_BARRIER);
}
break;
}
default: default:
UNREACHABLE(); UNREACHABLE();
break; break;
} }
__ StoreFixedArrayElement(result_array, i, param);
} }
__ Return(result_array); __ Return(result_array);
return tester.GenerateCodeCloseAndEscape(); return tester.GenerateCodeCloseAndEscape();
@ -159,7 +234,7 @@ Handle<Code> BuildTeardownFunction(Isolate* isolate, CallDescriptor* descriptor,
// Print the content of `value`, representing the register or stack slot // Print the content of `value`, representing the register or stack slot
// described by `operand`. // described by `operand`.
void PrintStateValue(std::ostream& os, Handle<Object> value, void PrintStateValue(std::ostream& os, Isolate* isolate, Handle<Object> value,
AllocatedOperand operand) { AllocatedOperand operand) {
switch (operand.representation()) { switch (operand.representation()) {
case MachineRepresentation::kTagged: case MachineRepresentation::kTagged:
@ -173,6 +248,18 @@ void PrintStateValue(std::ostream& os, Handle<Object> value,
case MachineRepresentation::kFloat64: case MachineRepresentation::kFloat64:
os << value->Number(); os << value->Number();
break; break;
case MachineRepresentation::kSimd128: {
FixedArray* vector = FixedArray::cast(*value);
os << "[";
for (int lane = 0; lane < 4; lane++) {
os << Smi::cast(*vector->GetValueChecked<Smi>(isolate, lane))->value();
if (lane < 3) {
os << ", ";
}
}
os << "]";
break;
}
default: default:
UNREACHABLE(); UNREACHABLE();
break; break;
@ -187,6 +274,16 @@ void PrintStateValue(std::ostream& os, Handle<Object> value,
os << ")"; os << ")";
} }
bool TestSimd128Moves() {
#if defined(V8_TARGET_ARCH_MIPS) || defined(V8_TARGET_ARCH_MIPS64)
// TODO(mips): Implement support for the kSimd128 representation in
// AssembleMove and AssembleSwap on MIPS.
return false;
#else
return CpuFeatures::SupportsWasmSimd128();
#endif
}
} // namespace } // namespace
#undef __ #undef __
@ -196,11 +293,11 @@ void PrintStateValue(std::ostream& os, Handle<Object> value,
// with. It has the ability to randomly generate lists of moves and run the code // with. It has the ability to randomly generate lists of moves and run the code
// generated by the CodeGeneratorTester. // generated by the CodeGeneratorTester.
// //
// At the moment, only the following representations are tested: // The following representations are tested:
// - kTagged // - kTagged
// - kFloat32 // - kFloat32
// - kFloat64 // - kFloat64
// - TODO(planglois): Add support for kSimd128. // - kSimd128 (if supported)
// There is no need to test using Word32 or Word64 as they are the same as // 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. // Tagged as far as the code generator is concerned.
// //
@ -215,43 +312,49 @@ void PrintStateValue(std::ostream& os, Handle<Object> value,
// //
// - The `setup` function receives a FixedArray as the initial state. It // - The `setup` function receives a FixedArray as the initial state. It
// unpacks it and passes each element as arguments to the generated code // unpacks it and passes each element as arguments to the generated code
// `test`. We also pass the `teardown` function as a first argument. Thanks // `test`. We also pass the `teardown` function as a first argument as well
// to the custom CallDescriptor, registers and stack slots get initialised // as a newly allocated FixedArray as a second argument which will hold the
// according to the content of the FixedArray. // final results. Thanks to the custom CallDescriptor, registers and stack
// slots get initialised according to the content of the initial FixedArray.
// //
// - The `test` function performs the list of moves on its parameters and // - The `test` function performs the list of moves on its parameters and
// eventually tail-calls to its first parameter, which is the `teardown` // eventually tail-calls to its first parameter, which is the `teardown`
// function. // function.
// //
// - The `teardown` function allocates a new FixedArray and fills it with all // - The `teardown` function receives the final results as a FixedArray, fills
// its parameters. Thanks to the tail-call, this is as if the `setup` // it with the rest of its arguments and returns it. Thanks to the
// function called `teardown` directly, except now moves were performed! // tail-call, this is as if the `setup` function called `teardown` directly,
// except now moves were performed!
// //
// .----------------setup--------------------------. // .----------------setup--------------------------.
// | Take a FixedArray as parameters with | // | Take a FixedArray as parameters with |
// | all the initial values of registers | // | all the initial values of registers |
// | and stack slots. | <- CodeStubAssembler // | and stack slots. | <- CodeStubAssembler
// | | // | |
// | Call test(teardown, state[0], state[1], ...); | // | Allocate a new FixedArray `result` with |
// | initial values. |
// | |
// | Call test(teardown, result, state[0], |
// | state[1], state[2], ...); |
// '-----------------------------------------------' // '-----------------------------------------------'
// | // |
// V // V
// .----------------test-----------------------------. // .----------------test-------------------------------.
// | - Move(param3, param42); | // | - Move(param3, param42); |
// | - Swap(param64, param1); | // | - Swap(param64, param4); |
// | - Move(param2, param6); | <- CodeGeneratorTester // | - Move(param2, param6); | <- CodeGeneratorTester
// | ... | // | ... |
// | | // | |
// | // "teardown" is the first parameter as well as | // | // "teardown" is the first parameter as well as |
// | // the callee. | // | // the callee. |
// | TailCall param0(param0, param1, param2, ...); | // | TailCall teardown(teardown, result, param2, ...); |
// '-------------------------------------------------' // '---------------------------------------------------'
// | // |
// V // V
// .----------------teardown--------------. // .----------------teardown---------------------------.
// | Create a FixedArray with all | // | Fill in the incoming `result` FixedArray with all |
// | parameters and return it. | <- CodeStubAssembler // | parameters and return it. | <- CodeStubAssembler
// '--------------------------------------' // '---------------------------------------------------'
class TestEnvironment : public HandleAndZoneScope { class TestEnvironment : public HandleAndZoneScope {
public: public:
@ -263,8 +366,7 @@ class TestEnvironment : public HandleAndZoneScope {
static constexpr int kTaggedSlotCount = 64; static constexpr int kTaggedSlotCount = 64;
static constexpr int kFloat32SlotCount = 64; static constexpr int kFloat32SlotCount = 64;
static constexpr int kFloat64SlotCount = 64; static constexpr int kFloat64SlotCount = 64;
static constexpr int kStackParameterCount = static constexpr int kSimd128SlotCount = 16;
kTaggedSlotCount + kFloat32SlotCount + kFloat64SlotCount;
// TODO(all): Test all types of constants (e.g. ExternalReference and // TODO(all): Test all types of constants (e.g. ExternalReference and
// HeapObject). // HeapObject).
@ -276,7 +378,6 @@ class TestEnvironment : public HandleAndZoneScope {
: blocks_(1, main_zone()), : blocks_(1, main_zone()),
code_(main_isolate(), main_zone(), &blocks_), code_(main_isolate(), main_zone(), &blocks_),
rng_(CcTest::random_number_generator()), rng_(CcTest::random_number_generator()),
// TODO(planglois): Support kSimd128.
supported_reps_({MachineRepresentation::kTagged, supported_reps_({MachineRepresentation::kTagged,
MachineRepresentation::kFloat32, MachineRepresentation::kFloat32,
MachineRepresentation::kFloat64}) { MachineRepresentation::kFloat64}) {
@ -287,72 +388,125 @@ class TestEnvironment : public HandleAndZoneScope {
block->set_ao_number(RpoNumber::FromInt(0)); block->set_ao_number(RpoNumber::FromInt(0));
blocks_[0] = block; blocks_[0] = block;
int stack_slot_count =
kTaggedSlotCount + kFloat32SlotCount + kFloat64SlotCount;
if (TestSimd128Moves()) {
stack_slot_count += kSimd128SlotCount;
supported_reps_.push_back(MachineRepresentation::kSimd128);
}
// The "teardown" and "test" functions share the same descriptor with the // The "teardown" and "test" functions share the same descriptor with the
// following signature: // following signature:
// ~~~ // ~~~
// FixedArray f(CodeObject* teardown, // FixedArray f(CodeObject* teardown, FixedArray preallocated_result,
// // Tagged registers. // // Tagged registers.
// Object*, Object*, ..., // Object*, Object*, ...,
// // FP registers. // // FP registers.
// Float32, Float64, ..., // Float32, Float64, Simd128, ...,
// // Mixed stack slots. // // Mixed stack slots.
// Float64, Object*, Float32, ...); // Float64, Object*, Float32, Simd128, ...);
// ~~~ // ~~~
LocationSignature::Builder test_signature(main_zone(), 1, LocationSignature::Builder test_signature(
1 + kGeneralRegisterCount + main_zone(), 1,
kDoubleRegisterCount + 2 + kGeneralRegisterCount + kDoubleRegisterCount + stack_slot_count);
kStackParameterCount);
// The first parameter will be the code object of the "teardown" // The first parameter will be the code object of the "teardown"
// function. This way, the "test" function can tail-call to it. // function. This way, the "test" function can tail-call to it.
test_signature.AddParam(LinkageLocation::ForRegister( test_signature.AddParam(LinkageLocation::ForRegister(
kReturnRegister0.code(), MachineType::AnyTagged())); kReturnRegister0.code(), MachineType::AnyTagged()));
// The second parameter will be a pre-allocated FixedArray that the
// "teardown" function will fill with result and then return. We place this
// parameter on the first stack argument slot which is always -1. And
// therefore slots to perform moves on start at -2.
test_signature.AddParam(
LinkageLocation::ForCallerFrameSlot(-1, MachineType::AnyTagged()));
int slot_parameter_n = -2;
const int kTotalStackParameterCount = stack_slot_count + 1;
// Initialise registers. // Initialise registers.
// Make sure that the target has enough general purpose registers to
// generate a call to a CodeObject using this descriptor. We have reserved
// kReturnRegister0 as the first parameter, and the call will need a
// register to hold the CodeObject address. So the maximum number of
// registers left to test with is the number of available registers minus 2.
DCHECK_LE(
kGeneralRegisterCount,
RegisterConfiguration::Default()->num_allocatable_general_registers() -
2);
int32_t general_mask = int32_t general_mask =
RegisterConfiguration::Default()->allocatable_general_codes_mask(); RegisterConfiguration::Default()->allocatable_general_codes_mask();
// kReturnRegister0 is used to hold the "teardown" code object, do not // kReturnRegister0 is used to hold the "teardown" code object, do not
// generate moves using it. // generate moves using it.
std::unique_ptr<const RegisterConfiguration> registers( std::unique_ptr<const RegisterConfiguration> registers(
RegisterConfiguration::RestrictGeneralRegisters( RegisterConfiguration::RestrictGeneralRegisters(
general_mask & ~(1 << kReturnRegister0.code()))); general_mask & ~kReturnRegister0.bit()));
for (int i = 0; i < kGeneralRegisterCount; i++) { for (int i = 0; i < kGeneralRegisterCount; i++) {
int code = registers->GetAllocatableGeneralCode(i); int code = registers->GetAllocatableGeneralCode(i);
AddRegister(&test_signature, MachineRepresentation::kTagged, code); AddRegister(&test_signature, MachineRepresentation::kTagged, code);
} }
// We assume that Double and Float registers alias, depending on // We assume that Double, Float and Simd128 registers alias, depending on
// kSimpleFPAliasing. For this reason, we allocate a Float and a Double in // kSimpleFPAliasing. For this reason, we allocate a Float, Double and
// pairs. // Simd128 together, hence the reason why `kDoubleRegisterCount` should be a
static_assert((kDoubleRegisterCount % 2) == 0, // multiple of 3 and 2 in case Simd128 is not supported.
"kDoubleRegisterCount should be a multiple of two."); static_assert(
((kDoubleRegisterCount % 2) == 0) && ((kDoubleRegisterCount % 3) == 0),
"kDoubleRegisterCount should be a multiple of two and three.");
for (int i = 0; i < kDoubleRegisterCount; i += 2) { for (int i = 0; i < kDoubleRegisterCount; i += 2) {
// Make sure we do not allocate FP registers which alias. We double the if (kSimpleFPAliasing) {
// index for Float registers if the aliasing is not "Simple": // Allocate three registers at once if kSimd128 is supported, else
// Simple -> s0, d1, s2, d3, s4, d5, ... // allocate in pairs.
// Arm32-style -> s0, d1, s4, d3, s8, d5, ... AddRegister(&test_signature, MachineRepresentation::kFloat32,
// This isn't space-efficient at all but suits our need. registers->GetAllocatableFloatCode(i));
static_assert(kDoubleRegisterCount < 16, AddRegister(&test_signature, MachineRepresentation::kFloat64,
"Arm has a d16 register but no overlapping s32 register."); registers->GetAllocatableDoubleCode(i + 1));
int float_code = if (TestSimd128Moves()) {
registers->GetAllocatableFloatCode(kSimpleFPAliasing ? i : i * 2); AddRegister(&test_signature, MachineRepresentation::kSimd128,
int double_code = registers->GetAllocatableDoubleCode(i + 1); registers->GetAllocatableSimd128Code(i + 2));
AddRegister(&test_signature, MachineRepresentation::kFloat32, float_code); i++;
AddRegister(&test_signature, MachineRepresentation::kFloat64, }
double_code); } else {
// Make sure we do not allocate FP registers which alias. To do this, we
// allocate three 128-bit registers and then convert two of them to a
// float and a double. With this aliasing scheme, a Simd128 register
// aliases two Double registers and four Float registers, so we need to
// scale indexes accordingly:
//
// Simd128 register: q0, q1, q2, q3, q4, q5
// | | | |
// V V V V
// Aliases: s0, d2, q2, s12, d8, q5
//
// This isn't space efficient at all but suits our need.
static_assert(
kDoubleRegisterCount < 8,
"Arm has a q8 and a d16 register but no overlapping s32 register.");
int first_simd128 = registers->GetAllocatableSimd128Code(i);
int second_simd128 = registers->GetAllocatableSimd128Code(i + 1);
AddRegister(&test_signature, MachineRepresentation::kFloat32,
first_simd128 * 4);
AddRegister(&test_signature, MachineRepresentation::kFloat64,
second_simd128 * 2);
if (TestSimd128Moves()) {
int third_simd128 = registers->GetAllocatableSimd128Code(i + 2);
AddRegister(&test_signature, MachineRepresentation::kSimd128,
third_simd128);
i++;
}
}
} }
// Initialise stack slots. // Initialise stack slots.
// Stack parameters start at -1.
int slot_parameter_n = -1;
// TODO(planglois): Support kSimd128 stack slots.
std::map<MachineRepresentation, int> slots = { std::map<MachineRepresentation, int> slots = {
{MachineRepresentation::kTagged, kTaggedSlotCount}, {MachineRepresentation::kTagged, kTaggedSlotCount},
{MachineRepresentation::kFloat32, kFloat32SlotCount}, {MachineRepresentation::kFloat32, kFloat32SlotCount},
{MachineRepresentation::kFloat64, kFloat64SlotCount}}; {MachineRepresentation::kFloat64, kFloat64SlotCount}};
if (TestSimd128Moves()) {
slots.emplace(MachineRepresentation::kSimd128, kSimd128SlotCount);
}
// Allocate new slots until we run out of them. // Allocate new slots until we run out of them.
while (std::any_of(slots.cbegin(), slots.cend(), while (std::any_of(slots.cbegin(), slots.cend(),
@ -415,7 +569,7 @@ class TestEnvironment : public HandleAndZoneScope {
LinkageLocation::ForAnyRegister( LinkageLocation::ForAnyRegister(
MachineType::AnyTagged()), // target location MachineType::AnyTagged()), // target location
test_signature.Build(), // location_sig test_signature.Build(), // location_sig
kStackParameterCount, // stack_parameter_count kTotalStackParameterCount, // stack_parameter_count
Operator::kNoProperties, // properties Operator::kNoProperties, // properties
kNoCalleeSaved, // callee-saved registers kNoCalleeSaved, // callee-saved registers
kNoCalleeSaved, // callee-saved fp kNoCalleeSaved, // callee-saved fp
@ -496,6 +650,15 @@ class TestEnvironment : public HandleAndZoneScope {
state->set( state->set(
i, *main_isolate()->factory()->NewHeapNumber(rng_->NextDouble())); i, *main_isolate()->factory()->NewHeapNumber(rng_->NextDouble()));
break; break;
case MachineRepresentation::kSimd128: {
Handle<FixedArray> vector =
main_isolate()->factory()->NewFixedArray(4);
for (int lane = 0; lane < 4; lane++) {
vector->set(lane, Smi::FromInt(rng_->NextInt(Smi::kMaxValue)));
}
state->set(i, *vector);
break;
}
default: default:
UNREACHABLE(); UNREACHABLE();
break; break;
@ -618,17 +781,45 @@ class TestEnvironment : public HandleAndZoneScope {
actual->GetValueChecked<Object>(main_isolate(), i); actual->GetValueChecked<Object>(main_isolate(), i);
Handle<Object> expected_value = Handle<Object> expected_value =
expected->GetValueChecked<Object>(main_isolate(), i); expected->GetValueChecked<Object>(main_isolate(), i);
if (!actual_value->StrictEquals(*expected_value)) { if (!CompareValues(actual_value, expected_value,
layout_[i].representation())) {
std::ostringstream expected_str; std::ostringstream expected_str;
PrintStateValue(expected_str, expected_value, layout_[i]); PrintStateValue(expected_str, main_isolate(), expected_value,
layout_[i]);
std::ostringstream actual_str; std::ostringstream actual_str;
PrintStateValue(actual_str, actual_value, layout_[i]); PrintStateValue(actual_str, main_isolate(), actual_value, layout_[i]);
V8_Fatal(__FILE__, __LINE__, "Expected: '%s' but got '%s'", V8_Fatal(__FILE__, __LINE__, "Expected: '%s' but got '%s'",
expected_str.str().c_str(), actual_str.str().c_str()); expected_str.str().c_str(), actual_str.str().c_str());
} }
} }
} }
bool CompareValues(Handle<Object> actual, Handle<Object> expected,
MachineRepresentation rep) {
switch (rep) {
case MachineRepresentation::kTagged:
case MachineRepresentation::kFloat32:
case MachineRepresentation::kFloat64:
return actual->StrictEquals(*expected);
case MachineRepresentation::kSimd128:
for (int lane = 0; lane < 4; lane++) {
Handle<Smi> actual_lane =
FixedArray::cast(*actual)->GetValueChecked<Smi>(main_isolate(),
lane);
Handle<Smi> expected_lane =
FixedArray::cast(*expected)->GetValueChecked<Smi>(main_isolate(),
lane);
if (!actual_lane->StrictEquals(*expected_lane)) {
return false;
}
}
return true;
default:
UNREACHABLE();
break;
}
}
enum OperandConstraint { enum OperandConstraint {
kNone, kNone,
// Restrict operands to non-constants. This is useful when generating a // Restrict operands to non-constants. This is useful when generating a
@ -748,7 +939,7 @@ constexpr int TestEnvironment::kDoubleRegisterCount;
constexpr int TestEnvironment::kTaggedSlotCount; constexpr int TestEnvironment::kTaggedSlotCount;
constexpr int TestEnvironment::kFloat32SlotCount; constexpr int TestEnvironment::kFloat32SlotCount;
constexpr int TestEnvironment::kFloat64SlotCount; constexpr int TestEnvironment::kFloat64SlotCount;
constexpr int TestEnvironment::kStackParameterCount; constexpr int TestEnvironment::kSimd128SlotCount;
constexpr int TestEnvironment::kSmiConstantCount; constexpr int TestEnvironment::kSmiConstantCount;
constexpr int TestEnvironment::kFloatConstantCount; constexpr int TestEnvironment::kFloatConstantCount;
constexpr int TestEnvironment::kDoubleConstantCount; constexpr int TestEnvironment::kDoubleConstantCount;