[arm] Implement Float(32|64)(Min|Max) using vsel.

BUG=

Review URL: https://codereview.chromium.org/1862993002

Cr-Commit-Position: refs/heads/master@{#35292}
This commit is contained in:
jacob.bramley 2016-04-06 03:17:14 -07:00 committed by Commit bot
parent d2eb555ee1
commit 141324cfdc
11 changed files with 493 additions and 10 deletions

View File

@ -3371,6 +3371,69 @@ void Assembler::vcmp(const SwVfpRegister src1, const float src2,
0x5 * B9 | B6);
}
void Assembler::vsel(Condition cond, const DwVfpRegister dst,
const DwVfpRegister src1, const DwVfpRegister src2) {
// cond=kSpecialCondition(31-28) | 11100(27-23) | D(22) |
// vsel_cond=XX(21-20) | Vn(19-16) | Vd(15-12) | 101(11-9) | sz=1(8) | N(7) |
// 0(6) | M(5) | 0(4) | Vm(3-0)
DCHECK(CpuFeatures::IsSupported(ARMv8));
int vd, d;
dst.split_code(&vd, &d);
int vn, n;
src1.split_code(&vn, &n);
int vm, m;
src2.split_code(&vm, &m);
int sz = 1;
// VSEL has a special (restricted) condition encoding.
// eq(0b0000)... -> 0b00
// ge(0b1010)... -> 0b10
// gt(0b1100)... -> 0b11
// vs(0b0110)... -> 0b01
// No other conditions are supported.
int vsel_cond = (cond >> 30) & 0x3;
if ((cond != eq) && (cond != ge) && (cond != gt) && (cond != vs)) {
// We can implement some other conditions by swapping the inputs.
DCHECK((cond == ne) | (cond == lt) | (cond == le) | (cond == vc));
std::swap(vn, vm);
std::swap(n, m);
}
emit(kSpecialCondition | 0x1C * B23 | d * B22 | vsel_cond * B20 | vn * B16 |
vd * B12 | 0x5 * B9 | sz * B8 | n * B7 | m * B5 | vm);
}
void Assembler::vsel(Condition cond, const SwVfpRegister dst,
const SwVfpRegister src1, const SwVfpRegister src2) {
// cond=kSpecialCondition(31-28) | 11100(27-23) | D(22) |
// vsel_cond=XX(21-20) | Vn(19-16) | Vd(15-12) | 101(11-9) | sz=0(8) | N(7) |
// 0(6) | M(5) | 0(4) | Vm(3-0)
DCHECK(CpuFeatures::IsSupported(ARMv8));
int vd, d;
dst.split_code(&vd, &d);
int vn, n;
src1.split_code(&vn, &n);
int vm, m;
src2.split_code(&vm, &m);
int sz = 0;
// VSEL has a special (restricted) condition encoding.
// eq(0b0000)... -> 0b00
// ge(0b1010)... -> 0b10
// gt(0b1100)... -> 0b11
// vs(0b0110)... -> 0b01
// No other conditions are supported.
int vsel_cond = (cond >> 30) & 0x3;
if ((cond != eq) && (cond != ge) && (cond != gt) && (cond != vs)) {
// We can implement some other conditions by swapping the inputs.
DCHECK((cond == ne) | (cond == lt) | (cond == le) | (cond == vc));
std::swap(vn, vm);
std::swap(n, m);
}
emit(kSpecialCondition | 0x1C * B23 | d * B22 | vsel_cond * B20 | vn * B16 |
vd * B12 | 0x5 * B9 | sz * B8 | n * B7 | m * B5 | vm);
}
void Assembler::vsqrt(const DwVfpRegister dst,
const DwVfpRegister src,

View File

@ -1225,6 +1225,17 @@ class Assembler : public AssemblerBase {
const Condition cond = al);
void vcmp(const SwVfpRegister src1, const float src2,
const Condition cond = al);
// VSEL supports cond in {eq, ne, ge, lt, gt, le, vs, vc}.
void vsel(const Condition cond,
const DwVfpRegister dst,
const DwVfpRegister src1,
const DwVfpRegister src2);
void vsel(const Condition cond,
const SwVfpRegister dst,
const SwVfpRegister src1,
const SwVfpRegister src2);
void vsqrt(const DwVfpRegister dst,
const DwVfpRegister src,
const Condition cond = al);

View File

@ -1869,6 +1869,48 @@ void Decoder::DecodeSpecialCondition(Instruction* instr) {
Unknown(instr);
}
break;
case 0x1C:
if ((instr->Bits(11, 9) == 0x5) && (instr->Bit(6) == 0) &&
(instr->Bit(4) == 0)) {
// VSEL* (floating-point)
bool dp_operation = (instr->SzValue() == 1);
switch (instr->Bits(21, 20)) {
case 0x0:
if (dp_operation) {
Format(instr, "vseleq.f64 'Dd, 'Dn, 'Dm");
} else {
Format(instr, "vseleq.f32 'Sd, 'Sn, 'Sm");
}
break;
case 0x1:
if (dp_operation) {
Format(instr, "vselvs.f64 'Dd, 'Dn, 'Dm");
} else {
Format(instr, "vselvs.f32 'Sd, 'Sn, 'Sm");
}
break;
case 0x2:
if (dp_operation) {
Format(instr, "vselge.f64 'Dd, 'Dn, 'Dm");
} else {
Format(instr, "vselge.f32 'Sd, 'Sn, 'Sm");
}
break;
case 0x3:
if (dp_operation) {
Format(instr, "vselgt.f64 'Dd, 'Dn, 'Dm");
} else {
Format(instr, "vselgt.f32 'Sd, 'Sn, 'Sm");
}
break;
default:
UNREACHABLE(); // Case analysis is exhaustive.
break;
}
} else {
Unknown(instr);
}
break;
default:
Unknown(instr);
break;

View File

@ -4028,6 +4028,45 @@ void Simulator::DecodeSpecialCondition(Instruction* instr) {
UNIMPLEMENTED();
}
break;
case 0x1C:
if ((instr->Bits(11, 9) == 0x5) && (instr->Bit(6) == 0) &&
(instr->Bit(4) == 0)) {
// VSEL* (floating-point)
bool condition_holds;
switch (instr->Bits(21, 20)) {
case 0x0: // VSELEQ
condition_holds = (z_flag_ == 1);
break;
case 0x1: // VSELVS
condition_holds = (v_flag_ == 1);
break;
case 0x2: // VSELGE
condition_holds = (n_flag_ == v_flag_);
break;
case 0x3: // VSELGT
condition_holds = ((z_flag_ == 0) && (n_flag_ == v_flag_));
break;
default:
UNREACHABLE(); // Case analysis is exhaustive.
break;
}
if (instr->SzValue() == 0x1) {
int n = instr->VFPNRegValue(kDoublePrecision);
int m = instr->VFPMRegValue(kDoublePrecision);
int d = instr->VFPDRegValue(kDoublePrecision);
double result = get_double_from_d_register(condition_holds ? n : m);
set_d_register_from_double(d, result);
} else {
int n = instr->VFPNRegValue(kSinglePrecision);
int m = instr->VFPMRegValue(kSinglePrecision);
int d = instr->VFPDRegValue(kSinglePrecision);
float result = get_float_from_s_register(condition_holds ? n : m);
set_s_register_from_float(d, result);
}
} else {
UNIMPLEMENTED();
}
break;
default:
UNIMPLEMENTED();
break;

View File

@ -1146,6 +1146,46 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
DCHECK_EQ(LeaveCC, i.OutputSBit());
break;
}
case kArmFloat32Max: {
CpuFeatureScope scope(masm(), ARMv8);
// (b < a) ? a : b
SwVfpRegister a = i.InputFloat32Register(0);
SwVfpRegister b = i.InputFloat32Register(1);
SwVfpRegister result = i.OutputFloat32Register(0);
__ VFPCompareAndSetFlags(a, b);
__ vsel(gt, result, a, b);
break;
}
case kArmFloat32Min: {
CpuFeatureScope scope(masm(), ARMv8);
// (a < b) ? a : b
SwVfpRegister a = i.InputFloat32Register(0);
SwVfpRegister b = i.InputFloat32Register(1);
SwVfpRegister result = i.OutputFloat32Register(0);
__ VFPCompareAndSetFlags(b, a);
__ vsel(gt, result, a, b);
break;
}
case kArmFloat64Max: {
CpuFeatureScope scope(masm(), ARMv8);
// (b < a) ? a : b
DwVfpRegister a = i.InputFloat64Register(0);
DwVfpRegister b = i.InputFloat64Register(1);
DwVfpRegister result = i.OutputFloat64Register(0);
__ VFPCompareAndSetFlags(a, b);
__ vsel(gt, result, a, b);
break;
}
case kArmFloat64Min: {
CpuFeatureScope scope(masm(), ARMv8);
// (a < b) ? a : b
DwVfpRegister a = i.InputFloat64Register(0);
DwVfpRegister b = i.InputFloat64Register(1);
DwVfpRegister result = i.OutputFloat64Register(0);
__ VFPCompareAndSetFlags(b, a);
__ vsel(gt, result, a, b);
break;
}
case kArmPush:
if (instr->InputAt(0)->IsDoubleRegister()) {
__ vpush(i.InputDoubleRegister(0));

View File

@ -101,6 +101,10 @@ namespace compiler {
V(ArmVstrF32) \
V(ArmVldrF64) \
V(ArmVstrF64) \
V(ArmFloat32Max) \
V(ArmFloat32Min) \
V(ArmFloat64Max) \
V(ArmFloat64Min) \
V(ArmLdrb) \
V(ArmLdrsb) \
V(ArmStrb) \

View File

@ -99,6 +99,10 @@ int InstructionScheduler::GetTargetInstructionFlags(
case kArmVmovHighU32F64:
case kArmVmovHighF64U32:
case kArmVmovF64U32U32:
case kArmFloat64Max:
case kArmFloat64Min:
case kArmFloat32Max:
case kArmFloat32Min:
return kNoOpcodeFlags;
case kArmVldrF32:

View File

@ -1285,18 +1285,25 @@ void InstructionSelector::VisitFloat64Mod(Node* node) {
g.UseFixed(node->InputAt(1), d1))->MarkAsCall();
}
void InstructionSelector::VisitFloat32Max(Node* node) {
DCHECK(IsSupported(ARMv8));
VisitRRR(this, kArmFloat32Max, node);
}
void InstructionSelector::VisitFloat32Max(Node* node) { UNREACHABLE(); }
void InstructionSelector::VisitFloat64Max(Node* node) {
DCHECK(IsSupported(ARMv8));
VisitRRR(this, kArmFloat64Max, node);
}
void InstructionSelector::VisitFloat32Min(Node* node) {
DCHECK(IsSupported(ARMv8));
VisitRRR(this, kArmFloat32Min, node);
}
void InstructionSelector::VisitFloat64Max(Node* node) { UNREACHABLE(); }
void InstructionSelector::VisitFloat32Min(Node* node) { UNREACHABLE(); }
void InstructionSelector::VisitFloat64Min(Node* node) { UNREACHABLE(); }
void InstructionSelector::VisitFloat64Min(Node* node) {
DCHECK(IsSupported(ARMv8));
VisitRRR(this, kArmFloat64Min, node);
}
void InstructionSelector::VisitFloat32Abs(Node* node) {
VisitRR(this, kArmVabsF32, node);
@ -1826,7 +1833,11 @@ InstructionSelector::SupportedMachineOperatorFlags() {
MachineOperatorBuilder::kFloat64RoundTruncate |
MachineOperatorBuilder::kFloat64RoundTiesAway |
MachineOperatorBuilder::kFloat32RoundTiesEven |
MachineOperatorBuilder::kFloat64RoundTiesEven;
MachineOperatorBuilder::kFloat64RoundTiesEven |
MachineOperatorBuilder::kFloat32Min |
MachineOperatorBuilder::kFloat32Max |
MachineOperatorBuilder::kFloat64Min |
MachineOperatorBuilder::kFloat64Max;
}
return flags;
}

View File

@ -2231,6 +2231,158 @@ TEST(ARMv8_vrintX) {
}
}
TEST(ARMv8_vsel) {
// Test the vsel floating point instructions.
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
Assembler assm(isolate, NULL, 0);
// Used to indicate whether a condition passed or failed.
static constexpr float kResultPass = 1.0f;
static constexpr float kResultFail = -kResultPass;
struct ResultsF32 {
float vseleq_;
float vselge_;
float vselgt_;
float vselvs_;
// The following conditions aren't architecturally supported, but the
// assembler implements them by swapping the inputs.
float vselne_;
float vsellt_;
float vselle_;
float vselvc_;
};
struct ResultsF64 {
double vseleq_;
double vselge_;
double vselgt_;
double vselvs_;
// The following conditions aren't architecturally supported, but the
// assembler implements them by swapping the inputs.
double vselne_;
double vsellt_;
double vselle_;
double vselvc_;
};
if (CpuFeatures::IsSupported(ARMv8)) {
CpuFeatureScope scope(&assm, ARMv8);
// Create a helper function:
// void TestVsel(uint32_t nzcv,
// ResultsF32* results_f32,
// ResultsF64* results_f64);
__ msr(CPSR_f, Operand(r0));
__ vmov(s1, kResultPass);
__ vmov(s2, kResultFail);
__ vsel(eq, s0, s1, s2);
__ vstr(s0, r1, offsetof(ResultsF32, vseleq_));
__ vsel(ge, s0, s1, s2);
__ vstr(s0, r1, offsetof(ResultsF32, vselge_));
__ vsel(gt, s0, s1, s2);
__ vstr(s0, r1, offsetof(ResultsF32, vselgt_));
__ vsel(vs, s0, s1, s2);
__ vstr(s0, r1, offsetof(ResultsF32, vselvs_));
__ vsel(ne, s0, s1, s2);
__ vstr(s0, r1, offsetof(ResultsF32, vselne_));
__ vsel(lt, s0, s1, s2);
__ vstr(s0, r1, offsetof(ResultsF32, vsellt_));
__ vsel(le, s0, s1, s2);
__ vstr(s0, r1, offsetof(ResultsF32, vselle_));
__ vsel(vc, s0, s1, s2);
__ vstr(s0, r1, offsetof(ResultsF32, vselvc_));
__ vmov(d1, kResultPass);
__ vmov(d2, kResultFail);
__ vsel(eq, d0, d1, d2);
__ vstr(d0, r2, offsetof(ResultsF64, vseleq_));
__ vsel(ge, d0, d1, d2);
__ vstr(d0, r2, offsetof(ResultsF64, vselge_));
__ vsel(gt, d0, d1, d2);
__ vstr(d0, r2, offsetof(ResultsF64, vselgt_));
__ vsel(vs, d0, d1, d2);
__ vstr(d0, r2, offsetof(ResultsF64, vselvs_));
__ vsel(ne, d0, d1, d2);
__ vstr(d0, r2, offsetof(ResultsF64, vselne_));
__ vsel(lt, d0, d1, d2);
__ vstr(d0, r2, offsetof(ResultsF64, vsellt_));
__ vsel(le, d0, d1, d2);
__ vstr(d0, r2, offsetof(ResultsF64, vselle_));
__ vsel(vc, d0, d1, d2);
__ vstr(d0, r2, offsetof(ResultsF64, vselvc_));
__ bx(lr);
CodeDesc desc;
assm.GetCode(&desc);
Handle<Code> code = isolate->factory()->NewCode(
desc, Code::ComputeFlags(Code::STUB), Handle<Code>());
#ifdef DEBUG
OFStream os(stdout);
code->Print(os);
#endif
F5 f = FUNCTION_CAST<F5>(code->entry());
Object* dummy = nullptr;
USE(dummy);
STATIC_ASSERT(kResultPass == -kResultFail);
#define CHECK_VSEL(n, z, c, v, vseleq, vselge, vselgt, vselvs) \
do { \
ResultsF32 results_f32; \
ResultsF64 results_f64; \
uint32_t nzcv = (n << 31) | (z << 30) | (c << 29) | (v << 28); \
dummy = CALL_GENERATED_CODE(isolate, f, nzcv, &results_f32, &results_f64, \
0, 0); \
CHECK_EQ(vseleq, results_f32.vseleq_); \
CHECK_EQ(vselge, results_f32.vselge_); \
CHECK_EQ(vselgt, results_f32.vselgt_); \
CHECK_EQ(vselvs, results_f32.vselvs_); \
CHECK_EQ(-vseleq, results_f32.vselne_); \
CHECK_EQ(-vselge, results_f32.vsellt_); \
CHECK_EQ(-vselgt, results_f32.vselle_); \
CHECK_EQ(-vselvs, results_f32.vselvc_); \
CHECK_EQ(vseleq, results_f64.vseleq_); \
CHECK_EQ(vselge, results_f64.vselge_); \
CHECK_EQ(vselgt, results_f64.vselgt_); \
CHECK_EQ(vselvs, results_f64.vselvs_); \
CHECK_EQ(-vseleq, results_f64.vselne_); \
CHECK_EQ(-vselge, results_f64.vsellt_); \
CHECK_EQ(-vselgt, results_f64.vselle_); \
CHECK_EQ(-vselvs, results_f64.vselvc_); \
} while (0);
// N Z C V vseleq vselge vselgt vselvs
CHECK_VSEL(0, 0, 0, 0, kResultFail, kResultPass, kResultPass, kResultFail);
CHECK_VSEL(0, 0, 0, 1, kResultFail, kResultFail, kResultFail, kResultPass);
CHECK_VSEL(0, 0, 1, 0, kResultFail, kResultPass, kResultPass, kResultFail);
CHECK_VSEL(0, 0, 1, 1, kResultFail, kResultFail, kResultFail, kResultPass);
CHECK_VSEL(0, 1, 0, 0, kResultPass, kResultPass, kResultFail, kResultFail);
CHECK_VSEL(0, 1, 0, 1, kResultPass, kResultFail, kResultFail, kResultPass);
CHECK_VSEL(0, 1, 1, 0, kResultPass, kResultPass, kResultFail, kResultFail);
CHECK_VSEL(0, 1, 1, 1, kResultPass, kResultFail, kResultFail, kResultPass);
CHECK_VSEL(1, 0, 0, 0, kResultFail, kResultFail, kResultFail, kResultFail);
CHECK_VSEL(1, 0, 0, 1, kResultFail, kResultPass, kResultPass, kResultPass);
CHECK_VSEL(1, 0, 1, 0, kResultFail, kResultFail, kResultFail, kResultFail);
CHECK_VSEL(1, 0, 1, 1, kResultFail, kResultPass, kResultPass, kResultPass);
CHECK_VSEL(1, 1, 0, 0, kResultPass, kResultFail, kResultFail, kResultFail);
CHECK_VSEL(1, 1, 0, 1, kResultPass, kResultPass, kResultFail, kResultPass);
CHECK_VSEL(1, 1, 1, 0, kResultPass, kResultFail, kResultFail, kResultFail);
CHECK_VSEL(1, 1, 1, 1, kResultPass, kResultPass, kResultFail, kResultPass);
#undef CHECK_VSEL
}
}
TEST(regress4292_b) {
CcTest::InitializeVM();

View File

@ -860,6 +860,51 @@ TEST(ARMv8_vrintX_disasm) {
}
TEST(ARMv8_vselX_disasm) {
SET_UP();
if (CpuFeatures::IsSupported(ARMv8)) {
// Native instructions.
COMPARE(vsel(eq, d0, d1, d2),
"fe010b02 vseleq.f64 d0, d1, d2");
COMPARE(vsel(eq, s0, s1, s2),
"fe000a81 vseleq.f32 s0, s1, s2");
COMPARE(vsel(ge, d0, d1, d2),
"fe210b02 vselge.f64 d0, d1, d2");
COMPARE(vsel(ge, s0, s1, s2),
"fe200a81 vselge.f32 s0, s1, s2");
COMPARE(vsel(gt, d0, d1, d2),
"fe310b02 vselgt.f64 d0, d1, d2");
COMPARE(vsel(gt, s0, s1, s2),
"fe300a81 vselgt.f32 s0, s1, s2");
COMPARE(vsel(vs, d0, d1, d2),
"fe110b02 vselvs.f64 d0, d1, d2");
COMPARE(vsel(vs, s0, s1, s2),
"fe100a81 vselvs.f32 s0, s1, s2");
// Inverted conditions (and swapped inputs).
COMPARE(vsel(ne, d0, d1, d2),
"fe020b01 vseleq.f64 d0, d2, d1");
COMPARE(vsel(ne, s0, s1, s2),
"fe010a20 vseleq.f32 s0, s2, s1");
COMPARE(vsel(lt, d0, d1, d2),
"fe220b01 vselge.f64 d0, d2, d1");
COMPARE(vsel(lt, s0, s1, s2),
"fe210a20 vselge.f32 s0, s2, s1");
COMPARE(vsel(le, d0, d1, d2),
"fe320b01 vselgt.f64 d0, d2, d1");
COMPARE(vsel(le, s0, s1, s2),
"fe310a20 vselgt.f32 s0, s2, s1");
COMPARE(vsel(vc, d0, d1, d2),
"fe120b01 vselvs.f64 d0, d2, d1");
COMPARE(vsel(vc, s0, s1, s2),
"fe110a20 vselvs.f32 s0, s2, s1");
}
VERIFY_RUN();
}
TEST(Neon) {
SET_UP();

View File

@ -2954,6 +2954,78 @@ TEST_F(InstructionSelectorTest, Word32Clz) {
EXPECT_EQ(s.ToVreg(n), s.ToVreg(s[0]->Output()));
}
TEST_F(InstructionSelectorTest, Float32Max) {
StreamBuilder m(this, MachineType::Float32(), MachineType::Float32(),
MachineType::Float32());
Node* const p0 = m.Parameter(0);
Node* const p1 = m.Parameter(1);
Node* const n = m.Float32Max(p0, p1);
m.Return(n);
Stream s = m.Build(ARMv8);
// Float32Max is `(b < a) ? a : b`.
ASSERT_EQ(1U, s.size());
EXPECT_EQ(kArmFloat32Max, s[0]->arch_opcode());
ASSERT_EQ(2U, s[0]->InputCount());
EXPECT_EQ(s.ToVreg(p0), s.ToVreg(s[0]->InputAt(0)));
EXPECT_EQ(s.ToVreg(p1), s.ToVreg(s[0]->InputAt(1)));
ASSERT_EQ(1U, s[0]->OutputCount());
EXPECT_EQ(s.ToVreg(n), s.ToVreg(s[0]->Output()));
}
TEST_F(InstructionSelectorTest, Float32Min) {
StreamBuilder m(this, MachineType::Float32(), MachineType::Float32(),
MachineType::Float32());
Node* const p0 = m.Parameter(0);
Node* const p1 = m.Parameter(1);
Node* const n = m.Float32Min(p0, p1);
m.Return(n);
Stream s = m.Build(ARMv8);
// Float32Min is `(a < b) ? a : b`.
ASSERT_EQ(1U, s.size());
EXPECT_EQ(kArmFloat32Min, s[0]->arch_opcode());
ASSERT_EQ(2U, s[0]->InputCount());
EXPECT_EQ(s.ToVreg(p0), s.ToVreg(s[0]->InputAt(0)));
EXPECT_EQ(s.ToVreg(p1), s.ToVreg(s[0]->InputAt(1)));
ASSERT_EQ(1U, s[0]->OutputCount());
EXPECT_EQ(s.ToVreg(n), s.ToVreg(s[0]->Output()));
}
TEST_F(InstructionSelectorTest, Float64Max) {
StreamBuilder m(this, MachineType::Float64(), MachineType::Float64(),
MachineType::Float64());
Node* const p0 = m.Parameter(0);
Node* const p1 = m.Parameter(1);
Node* const n = m.Float64Max(p0, p1);
m.Return(n);
Stream s = m.Build(ARMv8);
// Float64Max is `(b < a) ? a : b`.
ASSERT_EQ(1U, s.size());
EXPECT_EQ(kArmFloat64Max, s[0]->arch_opcode());
ASSERT_EQ(2U, s[0]->InputCount());
EXPECT_EQ(s.ToVreg(p0), s.ToVreg(s[0]->InputAt(0)));
EXPECT_EQ(s.ToVreg(p1), s.ToVreg(s[0]->InputAt(1)));
ASSERT_EQ(1U, s[0]->OutputCount());
EXPECT_EQ(s.ToVreg(n), s.ToVreg(s[0]->Output()));
}
TEST_F(InstructionSelectorTest, Float64Min) {
StreamBuilder m(this, MachineType::Float64(), MachineType::Float64(),
MachineType::Float64());
Node* const p0 = m.Parameter(0);
Node* const p1 = m.Parameter(1);
Node* const n = m.Float64Min(p0, p1);
m.Return(n);
Stream s = m.Build(ARMv8);
// Float64Min is `(a < b) ? a : b`.
ASSERT_EQ(1U, s.size());
EXPECT_EQ(kArmFloat64Min, s[0]->arch_opcode());
ASSERT_EQ(2U, s[0]->InputCount());
EXPECT_EQ(s.ToVreg(p0), s.ToVreg(s[0]->InputAt(0)));
EXPECT_EQ(s.ToVreg(p1), s.ToVreg(s[0]->InputAt(1)));
ASSERT_EQ(1U, s[0]->OutputCount());
EXPECT_EQ(s.ToVreg(n), s.ToVreg(s[0]->Output()));
}
} // namespace compiler
} // namespace internal
} // namespace v8