Commit f00b4e94 authored by dusan.milosavljevic's avatar dusan.milosavljevic Committed by Commit bot

MIPS: Refactor simulator and add selection instructions for r6.

TEST=
BUG=

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

Cr-Commit-Position: refs/heads/master@{#27530}
parent 00477a5d
......@@ -1920,6 +1920,34 @@ void Assembler::movf(Register rd, Register rs, uint16_t cc) {
}
void Assembler::seleqz(Register rs, Register rt, Register rd) {
DCHECK(IsMipsArchVariant(kMips32r6));
GenInstrRegister(SPECIAL, rs, rt, rd, 0, SELEQZ_S);
}
void Assembler::seleqz(SecondaryField fmt, FPURegister fd, FPURegister ft,
FPURegister fs) {
DCHECK(IsMipsArchVariant(kMips32r6));
DCHECK((fmt == D) || (fmt == S));
GenInstrRegister(COP1, fmt, ft, fs, fd, SELEQZ_C);
}
void Assembler::selnez(Register rs, Register rt, Register rd) {
DCHECK(IsMipsArchVariant(kMips32r6));
GenInstrRegister(SPECIAL, rs, rt, rd, 0, SELNEZ_S);
}
void Assembler::selnez(SecondaryField fmt, FPURegister fd, FPURegister ft,
FPURegister fs) {
DCHECK(IsMipsArchVariant(kMips32r6));
DCHECK((fmt == D) || (fmt == S));
GenInstrRegister(COP1, fmt, ft, fs, fd, SELNEZ_C);
}
// Bit twiddling.
void Assembler::clz(Register rd, Register rs) {
if (!IsMipsArchVariant(kMips32r6)) {
......
......@@ -252,6 +252,8 @@ Instruction::Type Instruction::InstructionType() const {
case MOVZ:
case MOVN:
case MOVCI:
case SELEQZ_S:
case SELNEZ_S:
return kRegisterType;
default:
return kUnsupported;
......@@ -280,6 +282,8 @@ Instruction::Type Instruction::InstructionType() const {
case BC1: // Branch on coprocessor condition.
case BC1EQZ:
case BC1NEZ:
case SELEQZ_C:
case SELNEZ_C:
return kImmediateType;
default:
return kRegisterType;
......
......@@ -99,7 +99,13 @@ class Decoder {
void Format(Instruction* instr, const char* format);
void Unknown(Instruction* instr);
// Each of these functions decodes one particular instruction type.
void DecodeTypeRegisterDRsType(Instruction* instr);
void DecodeTypeRegisterLRsType(Instruction* instr);
void DecodeTypeRegisterSPECIAL(Instruction* instr);
void DecodeTypeRegisterSPECIAL2(Instruction* instr);
void DecodeTypeRegisterSPECIAL3(Instruction* instr);
void DecodeTypeRegister(Instruction* instr);
void DecodeTypeImmediate(Instruction* instr);
void DecodeTypeJump(Instruction* instr);
......@@ -449,33 +455,7 @@ void Decoder::Unknown(Instruction* instr) {
}
void Decoder::DecodeTypeRegister(Instruction* instr) {
switch (instr->OpcodeFieldRaw()) {
case COP1: // Coprocessor instructions.
switch (instr->RsFieldRaw()) {
case BC1: // bc1 handled in DecodeTypeImmediate.
UNREACHABLE();
break;
case MFC1:
Format(instr, "mfc1 'rt, 'fs");
break;
case MFHC1:
Format(instr, "mfhc1 'rt, 'fs");
break;
case MTC1:
Format(instr, "mtc1 'rt, 'fs");
break;
// These are called "fs" too, although they are not FPU registers.
case CTC1:
Format(instr, "ctc1 'rt, 'fs");
break;
case CFC1:
Format(instr, "cfc1 'rt, 'fs");
break;
case MTHC1:
Format(instr, "mthc1 'rt, 'fs");
break;
case D:
void Decoder::DecodeTypeRegisterDRsType(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case ADD_D:
Format(instr, "add.d 'fd, 'fs, 'ft");
......@@ -553,29 +533,10 @@ void Decoder::DecodeTypeRegister(Instruction* instr) {
Format(instr, "unknown.cop1.d");
break;
}
break;
case S:
switch (instr->FunctionFieldRaw()) {
case CVT_D_S:
Format(instr, "cvt.d.s 'fd, 'fs");
break;
default:
UNIMPLEMENTED_MIPS();
}
break;
case W:
switch (instr->FunctionFieldRaw()) {
case CVT_S_W: // Convert word to float (single).
Format(instr, "cvt.s.w 'fd, 'fs");
break;
case CVT_D_W: // Convert word to double.
Format(instr, "cvt.d.w 'fd, 'fs");
break;
default:
UNREACHABLE();
}
break;
case L:
}
void Decoder::DecodeTypeRegisterLRsType(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case CVT_D_L:
Format(instr, "cvt.d.l 'fd, 'fs");
......@@ -616,24 +577,10 @@ void Decoder::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case PS:
UNIMPLEMENTED_MIPS();
break;
default:
UNREACHABLE();
}
break;
case COP1X:
switch (instr->FunctionFieldRaw()) {
case MADD_D:
Format(instr, "madd.d 'fd, 'fr, 'fs, 'ft");
break;
default:
UNREACHABLE();
}
break;
case SPECIAL:
}
void Decoder::DecodeTypeRegisterSPECIAL(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case JR:
Format(instr, "jr 'rs");
......@@ -642,7 +589,7 @@ void Decoder::DecodeTypeRegister(Instruction* instr) {
Format(instr, "jalr 'rs");
break;
case SLL:
if ( 0x0 == static_cast<int>(instr->InstructionBits()))
if (0x0 == static_cast<int>(instr->InstructionBits()))
Format(instr, "nop");
else
Format(instr, "sll 'rd, 'rt, 'sa");
......@@ -682,11 +629,10 @@ void Decoder::DecodeTypeRegister(Instruction* instr) {
if (instr->Bits(25, 16) == 0) {
Format(instr, "mfhi 'rd");
} else {
if ((instr->FunctionFieldRaw() == CLZ_R6)
&& (instr->FdValue() == 1)) {
if ((instr->FunctionFieldRaw() == CLZ_R6) && (instr->FdValue() == 1)) {
Format(instr, "clz 'rd, 'rs");
} else if ((instr->FunctionFieldRaw() == CLO_R6)
&& (instr->FdValue() == 1)) {
} else if ((instr->FunctionFieldRaw() == CLO_R6) &&
(instr->FdValue() == 1)) {
Format(instr, "clo 'rd, 'rs");
}
}
......@@ -809,16 +755,18 @@ void Decoder::DecodeTypeRegister(Instruction* instr) {
}
break;
case SELEQZ_S:
Format(instr, "seleqz 'rd, 'rs, 'rt");
Format(instr, "seleqz 'rs, 'rt, 'rd");
break;
case SELNEZ_S:
Format(instr, "selnez 'rd, 'rs, 'rt");
Format(instr, "selnez 'rs, 'rt, 'rd");
break;
default:
UNREACHABLE();
}
break;
case SPECIAL2:
}
void Decoder::DecodeTypeRegisterSPECIAL2(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case MUL:
Format(instr, "mul 'rd, 'rs, 'rt");
......@@ -831,8 +779,10 @@ void Decoder::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case SPECIAL3:
}
void Decoder::DecodeTypeRegisterSPECIAL3(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case INS: {
if (IsMipsArchVariant(kMips32r2)) {
......@@ -853,6 +803,86 @@ void Decoder::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
}
void Decoder::DecodeTypeRegister(Instruction* instr) {
switch (instr->OpcodeFieldRaw()) {
case COP1: // Coprocessor instructions.
switch (instr->RsFieldRaw()) {
case BC1: // bc1 handled in DecodeTypeImmediate.
UNREACHABLE();
break;
case MFC1:
Format(instr, "mfc1 'rt, 'fs");
break;
case MFHC1:
Format(instr, "mfhc1 'rt, 'fs");
break;
case MTC1:
Format(instr, "mtc1 'rt, 'fs");
break;
// These are called "fs" too, although they are not FPU registers.
case CTC1:
Format(instr, "ctc1 'rt, 'fs");
break;
case CFC1:
Format(instr, "cfc1 'rt, 'fs");
break;
case MTHC1:
Format(instr, "mthc1 'rt, 'fs");
break;
case D:
DecodeTypeRegisterDRsType(instr);
break;
case S:
switch (instr->FunctionFieldRaw()) {
case CVT_D_S:
Format(instr, "cvt.d.s 'fd, 'fs");
break;
default:
UNIMPLEMENTED_MIPS();
}
break;
case W:
switch (instr->FunctionFieldRaw()) {
case CVT_S_W: // Convert word to float (single).
Format(instr, "cvt.s.w 'fd, 'fs");
break;
case CVT_D_W: // Convert word to double.
Format(instr, "cvt.d.w 'fd, 'fs");
break;
default:
UNREACHABLE();
}
break;
case L:
DecodeTypeRegisterLRsType(instr);
break;
case PS:
UNIMPLEMENTED_MIPS();
break;
default:
UNREACHABLE();
}
break;
case COP1X:
switch (instr->FunctionFieldRaw()) {
case MADD_D:
Format(instr, "madd.d 'fd, 'fr, 'fs, 'ft");
break;
default:
UNREACHABLE();
}
break;
case SPECIAL:
DecodeTypeRegisterSPECIAL(instr);
break;
case SPECIAL2:
DecodeTypeRegisterSPECIAL2(instr);
break;
case SPECIAL3:
DecodeTypeRegisterSPECIAL3(instr);
break;
default:
UNREACHABLE();
......
......@@ -2079,6 +2079,8 @@ void Simulator::ConfigureTypeRegister(Instruction* instr,
case DIV:
case DIVU:
// div and divu never raise exceptions.
case SELEQZ_S:
case SELNEZ_S:
break;
default:
UNREACHABLE();
......@@ -2130,99 +2132,28 @@ void Simulator::ConfigureTypeRegister(Instruction* instr,
}
void Simulator::DecodeTypeRegister(Instruction* instr) {
// Instruction fields.
const Opcode op = instr->OpcodeFieldRaw();
const int32_t rs_reg = instr->RsValue();
const int32_t rs = get_register(rs_reg);
const uint32_t rs_u = static_cast<uint32_t>(rs);
const int32_t rt_reg = instr->RtValue();
const int32_t rt = get_register(rt_reg);
const uint32_t rt_u = static_cast<uint32_t>(rt);
const int32_t rd_reg = instr->RdValue();
const int32_t fr_reg = instr->FrValue();
const int32_t fs_reg = instr->FsValue();
const int32_t ft_reg = instr->FtValue();
const int32_t fd_reg = instr->FdValue();
int64_t i64hilo = 0;
uint64_t u64hilo = 0;
// ALU output.
// It should not be used as is. Instructions using it should always
// initialize it first.
int32_t alu_out = 0x12345678;
// For break and trap instructions.
bool do_interrupt = false;
// For jr and jalr.
// Get current pc.
int32_t current_pc = get_pc();
// Next pc
int32_t next_pc = 0;
int32_t return_addr_reg = 31;
// Set up the variables if needed before executing the instruction.
ConfigureTypeRegister(instr,
&alu_out,
&i64hilo,
&u64hilo,
&next_pc,
&return_addr_reg,
&do_interrupt);
// ---------- Raise exceptions triggered.
SignalExceptions();
// ---------- Execution.
switch (op) {
case COP1:
switch (instr->RsFieldRaw()) {
case CFC1:
set_register(rt_reg, alu_out);
break;
case MFC1:
set_register(rt_reg, alu_out);
break;
case MFHC1:
set_register(rt_reg, alu_out);
break;
case CTC1:
// At the moment only FCSR is supported.
DCHECK(fs_reg == kFCSRRegister);
FCSR_ = registers_[rt_reg];
break;
case MTC1:
// Hardware writes upper 32-bits to zero on mtc1.
set_fpu_register_hi_word(fs_reg, 0);
set_fpu_register_word(fs_reg, registers_[rt_reg]);
break;
case MTHC1:
set_fpu_register_hi_word(fs_reg, registers_[rt_reg]);
break;
case S:
float f;
switch (instr->FunctionFieldRaw()) {
case CVT_D_S:
f = get_fpu_register_float(fs_reg);
set_fpu_register_double(fd_reg, static_cast<double>(f));
break;
default:
// CVT_W_S CVT_L_S TRUNC_W_S ROUND_W_S ROUND_L_S FLOOR_W_S FLOOR_L_S
// CEIL_W_S CEIL_L_S CVT_PS_S are unimplemented.
UNREACHABLE();
}
break;
case D:
void Simulator::DecodeTypeRegisterDRsType(Instruction* instr,
const int32_t& fr_reg,
const int32_t& fs_reg,
const int32_t& ft_reg,
const int32_t& fd_reg) {
double ft, fs;
uint32_t cc, fcsr_cc;
int64_t i64;
fs = get_fpu_register_double(fs_reg);
ft = get_fpu_register_double(ft_reg);
int64_t ft_int = static_cast<int64_t>(ft);
cc = instr->FCccValue();
fcsr_cc = get_fcsr_condition_bit(cc);
switch (instr->FunctionFieldRaw()) {
case SELEQZ_C:
DCHECK(IsMipsArchVariant(kMips32r6));
set_fpu_register_double(fd_reg, (ft_int & 0x1) == 0 ? fs : 0.0);
break;
case SELNEZ_C:
DCHECK(IsMipsArchVariant(kMips32r6));
set_fpu_register_double(fd_reg, (ft_int & 0x1) != 0 ? fs : 0.0);
break;
case ADD_D:
set_fpu_register_double(fd_reg, fs + ft);
break;
......@@ -2254,22 +2185,19 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
set_fcsr_bit(fcsr_cc, (fs == ft));
break;
case C_UEQ_D:
set_fcsr_bit(fcsr_cc,
(fs == ft) || (std::isnan(fs) || std::isnan(ft)));
set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft)));
break;
case C_OLT_D:
set_fcsr_bit(fcsr_cc, (fs < ft));
break;
case C_ULT_D:
set_fcsr_bit(fcsr_cc,
(fs < ft) || (std::isnan(fs) || std::isnan(ft)));
set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft)));
break;
case C_OLE_D:
set_fcsr_bit(fcsr_cc, (fs <= ft));
break;
case C_ULE_D:
set_fcsr_bit(fcsr_cc,
(fs <= ft) || (std::isnan(fs) || std::isnan(ft)));
set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft)));
break;
case CVT_W_D: // Convert double to word.
// Rounding modes are not yet supported.
......@@ -2288,8 +2216,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
if (set_fcsr_round_error(fs, rounded)) {
set_fpu_register_word(fd_reg, kFPUInvalidResult);
}
}
break;
} break;
case TRUNC_W_D: // Truncate double to word (round towards 0).
{
double rounded = trunc(fs);
......@@ -2298,8 +2225,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
if (set_fcsr_round_error(fs, rounded)) {
set_fpu_register_word(fd_reg, kFPUInvalidResult);
}
}
break;
} break;
case FLOOR_W_D: // Round double to word towards negative infinity.
{
double rounded = std::floor(fs);
......@@ -2308,8 +2234,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
if (set_fcsr_round_error(fs, rounded)) {
set_fpu_register_word(fd_reg, kFPUInvalidResult);
}
}
break;
} break;
case CEIL_W_D: // Round double to word towards positive infinity.
{
double rounded = std::ceil(fs);
......@@ -2318,8 +2243,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
if (set_fcsr_round_error(fs, rounded)) {
set_fpu_register_word(fd_reg, kFPUInvalidResult);
}
}
break;
} break;
case CVT_S_D: // Convert double to float (single).
set_fpu_register_float(fd_reg, static_cast<float>(fs));
break;
......@@ -2346,8 +2270,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
break;
}
case ROUND_L_D: { // Mips32r2 instruction.
double rounded =
fs > 0 ? std::floor(fs + 0.5) : std::ceil(fs - 0.5);
double rounded = fs > 0 ? std::floor(fs + 0.5) : std::ceil(fs - 0.5);
i64 = static_cast<int64_t>(rounded);
if (IsFp64Mode()) {
set_fpu_register(fd_reg, i64);
......@@ -2381,8 +2304,12 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case W:
}
void Simulator::DecodeTypeRegisterWRsType(Instruction* instr, int32_t& alu_out,
const int32_t& fd_reg,
const int32_t& fs_reg) {
switch (instr->FunctionFieldRaw()) {
case CVT_S_W: // Convert word to float (single).
alu_out = get_fpu_register_signed_word(fs_reg);
......@@ -2395,20 +2322,55 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default: // Mips64r6 CMP.S instructions unimplemented.
UNREACHABLE();
}
}
void Simulator::DecodeTypeRegisterSRsType(Instruction* instr,
const int32_t& ft_reg,
const int32_t& fs_reg,
const int32_t& fd_reg) {
float f;
double ft = get_fpu_register_double(ft_reg);
int64_t ft_int = static_cast<int64_t>(ft);
switch (instr->FunctionFieldRaw()) {
case CVT_D_S:
f = get_fpu_register_float(fs_reg);
set_fpu_register_double(fd_reg, static_cast<double>(f));
break;
case L:
fs = get_fpu_register_double(fs_reg);
ft = get_fpu_register_double(ft_reg);
case SELEQZ_C:
DCHECK(IsMipsArchVariant(kMips32r6));
set_fpu_register_double(
fd_reg, (ft_int & 0x1) == 0 ? get_fpu_register_double(fs_reg) : 0.0);
break;
case SELNEZ_C:
DCHECK(IsMipsArchVariant(kMips32r6));
set_fpu_register_double(
fd_reg, (ft_int & 0x1) != 0 ? get_fpu_register_double(fs_reg) : 0.0);
break;
default:
// CVT_W_S CVT_L_S TRUNC_W_S ROUND_W_S ROUND_L_S FLOOR_W_S FLOOR_L_S
// CEIL_W_S CEIL_L_S CVT_PS_S are unimplemented.
UNREACHABLE();
}
}
void Simulator::DecodeTypeRegisterLRsType(Instruction* instr,
const int32_t& ft_reg,
const int32_t& fs_reg,
const int32_t& fd_reg) {
double fs = get_fpu_register_double(fs_reg);
double ft = get_fpu_register_double(ft_reg);
switch (instr->FunctionFieldRaw()) {
case CVT_D_L: // Mips32r2 instruction.
// Watch the signs here, we want 2 32-bit vals
// to make a sign-64.
int64_t i64;
if (IsFp64Mode()) {
i64 = get_fpu_register(fs_reg);
} else {
i64 = static_cast<uint32_t>(get_fpu_register_word(fs_reg));
i64 |= static_cast<int64_t>(
get_fpu_register_word(fs_reg + 1)) << 32;
i64 |= static_cast<int64_t>(get_fpu_register_word(fs_reg + 1)) << 32;
}
set_fpu_register_double(fd_reg, static_cast<double>(i64));
break;
......@@ -2470,12 +2432,63 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default: // CMP_OR CMP_UNE CMP_NE UNIMPLEMENTED.
UNREACHABLE();
}
}
void Simulator::DecodeTypeRegisterCOP1(
Instruction* instr, const int32_t& rs_reg, const int32_t& rs,
const uint32_t& rs_u, const int32_t& rt_reg, const int32_t& rt,
const uint32_t& rt_u, const int32_t& rd_reg, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg, const int32_t& fd_reg,
int64_t& i64hilo, uint64_t& u64hilo, int32_t& alu_out, bool& do_interrupt,
int32_t& current_pc, int32_t& next_pc, int32_t& return_addr_reg) {
switch (instr->RsFieldRaw()) {
case CFC1:
set_register(rt_reg, alu_out);
break;
case MFC1:
set_register(rt_reg, alu_out);
break;
case MFHC1:
set_register(rt_reg, alu_out);
break;
case CTC1:
// At the moment only FCSR is supported.
DCHECK(fs_reg == kFCSRRegister);
FCSR_ = registers_[rt_reg];
break;
case MTC1:
// Hardware writes upper 32-bits to zero on mtc1.
set_fpu_register_hi_word(fs_reg, 0);
set_fpu_register_word(fs_reg, registers_[rt_reg]);
break;
case MTHC1:
set_fpu_register_hi_word(fs_reg, registers_[rt_reg]);
break;
case S: {
DecodeTypeRegisterSRsType(instr, ft_reg, fs_reg, fd_reg);
break;
}
case D:
DecodeTypeRegisterDRsType(instr, fr_reg, fs_reg, ft_reg, fd_reg);
break;
case W:
DecodeTypeRegisterWRsType(instr, alu_out, fd_reg, fs_reg);
break;
case L:
DecodeTypeRegisterLRsType(instr, ft_reg, fs_reg, fd_reg);
break;
default:
UNREACHABLE();
}
break;
case COP1X:
}
void Simulator::DecodeTypeRegisterCOP1X(Instruction* instr,
const int32_t& fr_reg,
const int32_t& fs_reg,
const int32_t& ft_reg,
const int32_t& fd_reg) {
switch (instr->FunctionFieldRaw()) {
case MADD_D:
double fr, ft, fs;
......@@ -2487,9 +2500,25 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case SPECIAL:
}
void Simulator::DecodeTypeRegisterSPECIAL(
Instruction* instr, const int32_t& rs_reg, const int32_t& rs,
const uint32_t& rs_u, const int32_t& rt_reg, const int32_t& rt,
const uint32_t& rt_u, const int32_t& rd_reg, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg, const int32_t& fd_reg,
int64_t& i64hilo, uint64_t& u64hilo, int32_t& alu_out, bool& do_interrupt,
int32_t& current_pc, int32_t& next_pc, int32_t& return_addr_reg) {
switch (instr->FunctionFieldRaw()) {
case SELEQZ_S:
DCHECK(IsMipsArchVariant(kMips32r6));
set_register(rd_reg, rt == 0 ? rs : 0);
break;
case SELNEZ_S:
DCHECK(IsMipsArchVariant(kMips32r6));
set_register(rd_reg, rt != 0 ? rs : 0);
break;
case JR: {
Instruction* branch_delay_instr = reinterpret_cast<Instruction*>(
current_pc+Instruction::kInstrSize);
......@@ -2638,8 +2667,12 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default: // For other special opcodes we do the default operation.
set_register(rd_reg, alu_out);
}
break;
case SPECIAL2:
}
void Simulator::DecodeTypeRegisterSPECIAL2(Instruction* instr,
const int32_t& rd_reg,
int32_t& alu_out) {
switch (instr->FunctionFieldRaw()) {
case MUL:
set_register(rd_reg, alu_out);
......@@ -2650,8 +2683,12 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default: // For other special2 opcodes we do the default operation.
set_register(rd_reg, alu_out);
}
break;
case SPECIAL3:
}
void Simulator::DecodeTypeRegisterSPECIAL3(Instruction* instr,
const int32_t& rt_reg,
int32_t& alu_out) {
switch (instr->FunctionFieldRaw()) {
case INS:
// Ins instr leaves result in Rt, rather than Rd.
......@@ -2664,6 +2701,71 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
}
void Simulator::DecodeTypeRegister(Instruction* instr) {
// Instruction fields.
const Opcode op = instr->OpcodeFieldRaw();
const int32_t rs_reg = instr->RsValue();
const int32_t rs = get_register(rs_reg);
const uint32_t rs_u = static_cast<uint32_t>(rs);
const int32_t rt_reg = instr->RtValue();
const int32_t rt = get_register(rt_reg);
const uint32_t rt_u = static_cast<uint32_t>(rt);
const int32_t rd_reg = instr->RdValue();
const int32_t fr_reg = instr->FrValue();
const int32_t fs_reg = instr->FsValue();
const int32_t ft_reg = instr->FtValue();
const int32_t fd_reg = instr->FdValue();
int64_t i64hilo = 0;
uint64_t u64hilo = 0;
// ALU output.
// It should not be used as is. Instructions using it should always
// initialize it first.
int32_t alu_out = 0x12345678;
// For break and trap instructions.
bool do_interrupt = false;
// For jr and jalr.
// Get current pc.
int32_t current_pc = get_pc();
// Next pc
int32_t next_pc = 0;
int32_t return_addr_reg = 31;
// Set up the variables if needed before executing the instruction.
ConfigureTypeRegister(instr, &alu_out, &i64hilo, &u64hilo, &next_pc,
&return_addr_reg, &do_interrupt);
// ---------- Raise exceptions triggered.
SignalExceptions();
// ---------- Execution.
switch (op) {
case COP1:
DecodeTypeRegisterCOP1(instr, rs_reg, rs, rs_u, rt_reg, rt, rt_u, rd_reg,
fr_reg, fs_reg, ft_reg, fd_reg, i64hilo, u64hilo,
alu_out, do_interrupt, current_pc, next_pc,
return_addr_reg);
break;
case COP1X:
DecodeTypeRegisterCOP1X(instr, fr_reg, fs_reg, ft_reg, fd_reg);
break;
case SPECIAL:
DecodeTypeRegisterSPECIAL(instr, rs_reg, rs, rs_u, rt_reg, rt, rt_u,
rd_reg, fr_reg, fs_reg, ft_reg, fd_reg, i64hilo,
u64hilo, alu_out, do_interrupt, current_pc,
next_pc, return_addr_reg);
break;
case SPECIAL2:
DecodeTypeRegisterSPECIAL2(instr, rd_reg, alu_out);
break;
case SPECIAL3:
DecodeTypeRegisterSPECIAL3(instr, rt_reg, alu_out);
break;
// Unimplemented opcodes raised an error in the configuration step before,
// so we can use the default here to set the destination register in common
......
......@@ -265,6 +265,47 @@ class Simulator {
// Executing is handled based on the instruction type.
void DecodeTypeRegister(Instruction* instr);
// Called from DecodeTypeRegisterCOP1
void DecodeTypeRegisterDRsType(Instruction* instr, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg,
const int32_t& fd_reg);
void DecodeTypeRegisterWRsType(Instruction* instr, int32_t& alu_out,
const int32_t& fd_reg, const int32_t& fs_reg);
void DecodeTypeRegisterSRsType(Instruction* instr, const int32_t& ft_reg,
const int32_t& fs_reg, const int32_t& fd_reg);
void DecodeTypeRegisterLRsType(Instruction* instr, const int32_t& ft_reg,
const int32_t& fs_reg, const int32_t& fd_reg);
// Functions called from DeocodeTypeRegister
void DecodeTypeRegisterCOP1(
Instruction* instr, const int32_t& rs_reg, const int32_t& rs,
const uint32_t& rs_u, const int32_t& rt_reg, const int32_t& rt,
const uint32_t& rt_u, const int32_t& rd_reg, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg, const int32_t& fd_reg,
int64_t& i64hilo, uint64_t& u64hilo, int32_t& alu_out, bool& do_interrupt,
int32_t& current_pc, int32_t& next_pc, int32_t& return_addr_reg);
void DecodeTypeRegisterCOP1X(Instruction* instr, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg,
const int32_t& fd_reg);
void DecodeTypeRegisterSPECIAL(
Instruction* instr, const int32_t& rs_reg, const int32_t& rs,
const uint32_t& rs_u, const int32_t& rt_reg, const int32_t& rt,
const uint32_t& rt_u, const int32_t& rd_reg, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg, const int32_t& fd_reg,
int64_t& i64hilo, uint64_t& u64hilo, int32_t& alu_out, bool& do_interrupt,
int32_t& current_pc, int32_t& next_pc, int32_t& return_addr_reg);
void DecodeTypeRegisterSPECIAL2(Instruction* instr, const int32_t& rd_reg,
int32_t& alu_out);
void DecodeTypeRegisterSPECIAL3(Instruction* instr, const int32_t& rt_reg,
int32_t& alu_out);
// Helper function for DecodeTypeRegister.
void ConfigureTypeRegister(Instruction* instr,
int32_t* alu_out,
......
......@@ -2139,13 +2139,8 @@ void Assembler::seleqz(Register rs, Register rt, Register rd) {
// FPR.
void Assembler::seleqz(SecondaryField fmt, FPURegister fd,
FPURegister ft, FPURegister fs) {
DCHECK(kArchVariant == kMips64r6);
DCHECK(fmt == D);
DCHECK(fmt == S);
Instr instr = COP1 | fmt << kRsShift | ft.code() << kFtShift |
fs.code() << kFsShift | fd.code() << kFdShift | SELEQZ_C;
emit(instr);
DCHECK((fmt == D) || (fmt == S));
GenInstrRegister(COP1, fmt, ft, fs, fd, SELEQZ_C);
}
......@@ -2160,12 +2155,8 @@ void Assembler::selnez(Register rs, Register rt, Register rd) {
void Assembler::selnez(SecondaryField fmt, FPURegister fd,
FPURegister ft, FPURegister fs) {
DCHECK(kArchVariant == kMips64r6);
DCHECK(fmt == D);
DCHECK(fmt == S);
Instr instr = COP1 | fmt << kRsShift | ft.code() << kFtShift |
fs.code() << kFsShift | fd.code() << kFdShift | SELNEZ_C;
emit(instr);
DCHECK((fmt == D) || (fmt == S));
GenInstrRegister(COP1, fmt, ft, fs, fd, SELNEZ_C);
}
......
......@@ -269,6 +269,8 @@ Instruction::Type Instruction::InstructionType() const {
case MOVZ:
case MOVN:
case MOVCI:
case SELEQZ_S:
case SELNEZ_S:
return kRegisterType;
default:
return kUnsupported;
......
......@@ -101,8 +101,23 @@ class Decoder {
int DecodeBreakInstr(Instruction* instr);
// Each of these functions decodes one particular instruction type.
void DecodeTypeRegisterDRsType(Instruction* instr);
void DecodeTypeRegisterLRsType(Instruction* instr);
void DecodeTypeRegisterSPECIAL(Instruction* instr);
void DecodeTypeRegisterSPECIAL2(Instruction* instr);
void DecodeTypeRegisterSPECIAL3(Instruction* instr);
void DecodeTypeRegisterCOP1(Instruction* instr);
void DecodeTypeRegisterCOP1X(Instruction* instr);
int DecodeTypeRegister(Instruction* instr);
void DecodeTypeImmediateCOP1W(Instruction* instr);
void DecodeTypeImmediateCOP1L(Instruction* instr);
void DecodeTypeImmediateCOP1S(Instruction* instr);
void DecodeTypeImmediateCOP1D(Instruction* instr);
void DecodeTypeImmediateCOP1(Instruction* instr);
void DecodeTypeImmediateREGIMM(Instruction* instr);
void DecodeTypeImmediate(Instruction* instr);
void DecodeTypeJump(Instruction* instr);
const disasm::NameConverter& converter_;
......@@ -474,37 +489,14 @@ int Decoder::DecodeBreakInstr(Instruction* instr) {
}
int Decoder::DecodeTypeRegister(Instruction* instr) {
switch (instr->OpcodeFieldRaw()) {
case COP1: // Coprocessor instructions.
switch (instr->RsFieldRaw()) {
case MFC1:
Format(instr, "mfc1 'rt, 'fs");
break;
case DMFC1:
Format(instr, "dmfc1 'rt, 'fs");
break;
case MFHC1:
Format(instr, "mfhc1 'rt, 'fs");
break;
case MTC1:
Format(instr, "mtc1 'rt, 'fs");
break;
case DMTC1:
Format(instr, "dmtc1 'rt, 'fs");
break;
// These are called "fs" too, although they are not FPU registers.
case CTC1:
Format(instr, "ctc1 'rt, 'fs");
break;
case CFC1:
Format(instr, "cfc1 'rt, 'fs");
void Decoder::DecodeTypeRegisterDRsType(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case SELEQZ_C:
Format(instr, "seleqz.D 'ft, 'fs, 'fd");
break;
case MTHC1:
Format(instr, "mthc1 'rt, 'fs");
case SELNEZ_C:
Format(instr, "selnez.D 'ft, 'fs, 'fd");
break;
case D:
switch (instr->FunctionFieldRaw()) {
case ADD_D:
Format(instr, "add.d 'fd, 'fs, 'ft");
break;
......@@ -590,17 +582,10 @@ int Decoder::DecodeTypeRegister(Instruction* instr) {
Format(instr, "unknown.cop1.d");
break;
}
break;
case W:
switch (instr->FunctionFieldRaw()) {
case CVT_D_W: // Convert word to double.
Format(instr, "cvt.d.w 'fd, 'fs");
break;
default:
UNREACHABLE();
}
break;
case L:
}
void Decoder::DecodeTypeRegisterLRsType(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case CVT_D_L:
Format(instr, "cvt.d.l 'fd, 'fs");
......@@ -641,12 +626,58 @@ int Decoder::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
}
void Decoder::DecodeTypeRegisterCOP1(Instruction* instr) {
switch (instr->RsFieldRaw()) {
case MFC1:
Format(instr, "mfc1 'rt, 'fs");
break;
case DMFC1:
Format(instr, "dmfc1 'rt, 'fs");
break;
case MFHC1:
Format(instr, "mfhc1 'rt, 'fs");
break;
case MTC1:
Format(instr, "mtc1 'rt, 'fs");
break;
case DMTC1:
Format(instr, "dmtc1 'rt, 'fs");
break;
// These are called "fs" too, although they are not FPU registers.
case CTC1:
Format(instr, "ctc1 'rt, 'fs");
break;
case CFC1:
Format(instr, "cfc1 'rt, 'fs");
break;
case MTHC1:
Format(instr, "mthc1 'rt, 'fs");
break;
case D:
DecodeTypeRegisterDRsType(instr);
break;
case W:
switch (instr->FunctionFieldRaw()) {
case CVT_D_W: // Convert word to double.
Format(instr, "cvt.d.w 'fd, 'fs");
break;
default:
UNREACHABLE();
}
break;
case COP1X:
case L:
DecodeTypeRegisterLRsType(instr);
break;
default:
UNREACHABLE();
}
}
void Decoder::DecodeTypeRegisterCOP1X(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case MADD_D:
Format(instr, "madd.d 'fd, 'fr, 'fs, 'ft");
......@@ -654,8 +685,10 @@ int Decoder::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case SPECIAL:
}
void Decoder::DecodeTypeRegisterSPECIAL(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case JR:
Format(instr, "jr 'rs");
......@@ -758,11 +791,10 @@ int Decoder::DecodeTypeRegister(Instruction* instr) {
if (instr->Bits(25, 16) == 0) {
Format(instr, "mfhi 'rd");
} else {
if ((instr->FunctionFieldRaw() == CLZ_R6)
&& (instr->FdValue() == 1)) {
if ((instr->FunctionFieldRaw() == CLZ_R6) && (instr->FdValue() == 1)) {
Format(instr, "clz 'rd, 'rs");
} else if ((instr->FunctionFieldRaw() == CLO_R6)
&& (instr->FdValue() == 1)) {
} else if ((instr->FunctionFieldRaw() == CLO_R6) &&
(instr->FdValue() == 1)) {
Format(instr, "clo 'rd, 'rs");
}
}
......@@ -896,8 +928,6 @@ int Decoder::DecodeTypeRegister(Instruction* instr) {
case SLTU:
Format(instr, "sltu 'rd, 'rs, 'rt");
break;
case BREAK:
return DecodeBreakInstr(instr);
case TGE:
Format(instr, "tge 'rs, 'rt, code: 'code");
break;
......@@ -930,16 +960,18 @@ int Decoder::DecodeTypeRegister(Instruction* instr) {
}
break;
case SELEQZ_S:
Format(instr, "seleqz 'rd, 'rs, 'rt");
Format(instr, "seleqz 'rs, 'rt, 'rd");
break;
case SELNEZ_S:
Format(instr, "selnez 'rd, 'rs, 'rt");
Format(instr, "selnez 'rs, 'rt, 'rd");
break;
default:
UNREACHABLE();
}
break;
case SPECIAL2:
}
void Decoder::DecodeTypeRegisterSPECIAL2(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case MUL:
Format(instr, "mul 'rd, 'rs, 'rt");
......@@ -952,8 +984,10 @@ int Decoder::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case SPECIAL3:
}
void Decoder::DecodeTypeRegisterSPECIAL3(Instruction* instr) {
switch (instr->FunctionFieldRaw()) {
case INS: {
Format(instr, "ins 'rt, 'rs, 'sa, 'ss2");
......@@ -970,71 +1004,69 @@ int Decoder::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
break;
default:
UNREACHABLE();
}
return Instruction::kInstrSize;
}
void Decoder::DecodeTypeImmediate(Instruction* instr) {
int Decoder::DecodeTypeRegister(Instruction* instr) {
switch (instr->OpcodeFieldRaw()) {
case COP1:
switch (instr->RsFieldRaw()) {
case BC1:
if (instr->FBtrueValue()) {
Format(instr, "bc1t 'bc, 'imm16u");
} else {
Format(instr, "bc1f 'bc, 'imm16u");
}
break;
case BC1EQZ:
Format(instr, "bc1eqz 'ft, 'imm16u");
case COP1: // Coprocessor instructions.
DecodeTypeRegisterCOP1(instr);
break;
case BC1NEZ:
Format(instr, "bc1nez 'ft, 'imm16u");
case COP1X:
DecodeTypeRegisterCOP1X(instr);
break;
case W: // CMP.S instruction.
switch (instr->FunctionValue()) {
case CMP_AF:
Format(instr, "cmp.af.S 'ft, 'fs, 'fd");
case SPECIAL:
switch (instr->FunctionFieldRaw()) {
case BREAK:
return DecodeBreakInstr(instr);
default:
DecodeTypeRegisterSPECIAL(instr);
break;
case CMP_UN:
Format(instr, "cmp.un.S 'ft, 'fs, 'fd");
}
break;
case CMP_EQ:
Format(instr, "cmp.eq.S 'ft, 'fs, 'fd");
case SPECIAL2:
DecodeTypeRegisterSPECIAL2(instr);
break;
case CMP_UEQ:
Format(instr, "cmp.ueq.S 'ft, 'fs, 'fd");
case SPECIAL3:
DecodeTypeRegisterSPECIAL3(instr);
break;
case CMP_LT:
Format(instr, "cmp.lt.S 'ft, 'fs, 'fd");
default:
UNREACHABLE();
}
return Instruction::kInstrSize;
}
void Decoder::DecodeTypeImmediateCOP1D(Instruction* instr) {
switch (instr->FunctionValue()) {
case SEL:
Format(instr, "sel.D 'ft, 'fs, 'fd");
break;
case CMP_ULT:
Format(instr, "cmp.ult.S 'ft, 'fs, 'fd");
case SELEQZ_C:
Format(instr, "seleqz.D 'ft, 'fs, 'fd");
break;
case CMP_LE:
Format(instr, "cmp.le.S 'ft, 'fs, 'fd");
case SELNEZ_C:
Format(instr, "selnez.D 'ft, 'fs, 'fd");
break;
case CMP_ULE:
Format(instr, "cmp.ule.S 'ft, 'fs, 'fd");
case MIN:
Format(instr, "min.D 'ft, 'fs, 'fd");
break;
case CMP_OR:
Format(instr, "cmp.or.S 'ft, 'fs, 'fd");
case MINA:
Format(instr, "mina.D 'ft, 'fs, 'fd");
break;
case CMP_UNE:
Format(instr, "cmp.une.S 'ft, 'fs, 'fd");
case MAX:
Format(instr, "max.D 'ft, 'fs, 'fd");
break;
case CMP_NE:
Format(instr, "cmp.ne.S 'ft, 'fs, 'fd");
case MAXA:
Format(instr, "maxa.D 'ft, 'fs, 'fd");
break;
default:
UNREACHABLE();
}
break;
case L: // CMP.D instruction.
}
void Decoder::DecodeTypeImmediateCOP1L(Instruction* instr) {
switch (instr->FunctionValue()) {
case CMP_AF:
Format(instr, "cmp.af.D 'ft, 'fs, 'fd");
......@@ -1072,8 +1104,10 @@ void Decoder::DecodeTypeImmediate(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case S:
}
void Decoder::DecodeTypeImmediateCOP1S(Instruction* instr) {
switch (instr->FunctionValue()) {
case SEL:
Format(instr, "sel.S 'ft, 'fs, 'fd");
......@@ -1099,41 +1133,84 @@ void Decoder::DecodeTypeImmediate(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case D:
}
void Decoder::DecodeTypeImmediateCOP1W(Instruction* instr) {
switch (instr->FunctionValue()) {
case SEL:
Format(instr, "sel.D 'ft, 'fs, 'fd");
case CMP_AF:
Format(instr, "cmp.af.S 'ft, 'fs, 'fd");
break;
case SELEQZ_C:
Format(instr, "seleqz.D 'ft, 'fs, 'fd");
case CMP_UN:
Format(instr, "cmp.un.S 'ft, 'fs, 'fd");
break;
case SELNEZ_C:
Format(instr, "selnez.D 'ft, 'fs, 'fd");
case CMP_EQ:
Format(instr, "cmp.eq.S 'ft, 'fs, 'fd");
break;
case MIN:
Format(instr, "min.D 'ft, 'fs, 'fd");
case CMP_UEQ:
Format(instr, "cmp.ueq.S 'ft, 'fs, 'fd");
break;
case MINA:
Format(instr, "mina.D 'ft, 'fs, 'fd");
case CMP_LT:
Format(instr, "cmp.lt.S 'ft, 'fs, 'fd");
break;
case MAX:
Format(instr, "max.D 'ft, 'fs, 'fd");
case CMP_ULT:
Format(instr, "cmp.ult.S 'ft, 'fs, 'fd");
break;
case MAXA:
Format(instr, "maxa.D 'ft, 'fs, 'fd");
case CMP_LE:
Format(instr, "cmp.le.S 'ft, 'fs, 'fd");
break;
case CMP_ULE:
Format(instr, "cmp.ule.S 'ft, 'fs, 'fd");
break;
case CMP_OR:
Format(instr, "cmp.or.S 'ft, 'fs, 'fd");
break;
case CMP_UNE:
Format(instr, "cmp.une.S 'ft, 'fs, 'fd");
break;
case CMP_NE:
Format(instr, "cmp.ne.S 'ft, 'fs, 'fd");
break;
default:
UNREACHABLE();
}
}
void Decoder::DecodeTypeImmediateCOP1(Instruction* instr) {
switch (instr->RsFieldRaw()) {
case BC1:
if (instr->FBtrueValue()) {
Format(instr, "bc1t 'bc, 'imm16u");
} else {
Format(instr, "bc1f 'bc, 'imm16u");
}
break;
case BC1EQZ:
Format(instr, "bc1eqz 'ft, 'imm16u");
break;
case BC1NEZ:
Format(instr, "bc1nez 'ft, 'imm16u");
break;
case W: // CMP.S instruction.
DecodeTypeImmediateCOP1W(instr);
break;
case L: // CMP.D instruction.
DecodeTypeImmediateCOP1L(instr);
break;
case S:
DecodeTypeImmediateCOP1S(instr);
break;
case D:
DecodeTypeImmediateCOP1D(instr);
break;
default:
UNREACHABLE();
}
}
break; // Case COP1.
// ------------- REGIMM class.
case REGIMM:
void Decoder::DecodeTypeImmediateREGIMM(Instruction* instr) {
switch (instr->RtFieldRaw()) {
case BLTZ:
Format(instr, "bltz 'rs, 'imm16u");
......@@ -1159,6 +1236,17 @@ void Decoder::DecodeTypeImmediate(Instruction* instr) {
default:
UNREACHABLE();
}
}
void Decoder::DecodeTypeImmediate(Instruction* instr) {
switch (instr->OpcodeFieldRaw()) {
case COP1:
DecodeTypeImmediateCOP1(instr);
break; // Case COP1.
// ------------- REGIMM class.
case REGIMM:
break; // Case REGIMM.
// ------------- Branch instructions.
case BEQ:
......
......@@ -2206,6 +2206,9 @@ void Simulator::ConfigureTypeRegister(Instruction* instr,
case DDIVU:
// div and divu never raise exceptions.
break;
case SELEQZ_S:
case SELNEZ_S:
break;
default:
UNREACHABLE();
}
......@@ -2267,90 +2270,9 @@ void Simulator::ConfigureTypeRegister(Instruction* instr,
}
void Simulator::DecodeTypeRegister(Instruction* instr) {
// Instruction fields.
const Opcode op = instr->OpcodeFieldRaw();
const int64_t rs_reg = instr->RsValue();
const int64_t rs = get_register(rs_reg);
const uint64_t rs_u = static_cast<uint32_t>(rs);
const int64_t rt_reg = instr->RtValue();
const int64_t rt = get_register(rt_reg);
const uint64_t rt_u = static_cast<uint32_t>(rt);
const int64_t rd_reg = instr->RdValue();
const int32_t fr_reg = instr->FrValue();
const int32_t fs_reg = instr->FsValue();
const int32_t ft_reg = instr->FtValue();
const int64_t fd_reg = instr->FdValue();
int64_t i64hilo = 0;
uint64_t u64hilo = 0;
// ALU output.
// It should not be used as is. Instructions using it should always
// initialize it first.
int64_t alu_out = 0x12345678;
// For break and trap instructions.
bool do_interrupt = false;
// For jr and jalr.
// Get current pc.
int64_t current_pc = get_pc();
// Next pc
int64_t next_pc = 0;
int64_t return_addr_reg = 31;
int64_t i128resultH;
int64_t i128resultL;
// Set up the variables if needed before executing the instruction.
ConfigureTypeRegister(instr,
&alu_out,
&i64hilo,
&u64hilo,
&next_pc,
&return_addr_reg,
&do_interrupt,
&i128resultH,
&i128resultL);
// ---------- Raise exceptions triggered.
SignalExceptions();
// ---------- Execution.
switch (op) {
case COP1:
switch (instr->RsFieldRaw()) {
case BC1: // Branch on coprocessor condition.
case BC1EQZ:
case BC1NEZ:
UNREACHABLE();
break;
case CFC1:
set_register(rt_reg, alu_out);
break;
case MFC1:
case DMFC1:
case MFHC1:
set_register(rt_reg, alu_out);
break;
case CTC1:
// At the moment only FCSR is supported.
DCHECK(fs_reg == kFCSRRegister);
FCSR_ = registers_[rt_reg];
break;
case MTC1:
// Hardware writes upper 32-bits to zero on mtc1.
set_fpu_register_hi_word(fs_reg, 0);
set_fpu_register_word(fs_reg, registers_[rt_reg]);
break;
case DMTC1:
set_fpu_register(fs_reg, registers_[rt_reg]);
break;
case MTHC1:
set_fpu_register_hi_word(fs_reg, registers_[rt_reg]);
break;
case S:
void Simulator::DecodeTypeRegisterSRsType(Instruction* instr,
const int32_t& fs_reg,
const int64_t& fd_reg) {
float f;
switch (instr->FunctionFieldRaw()) {
case CVT_D_S:
......@@ -2362,16 +2284,29 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
// CEIL_W_S CEIL_L_S CVT_PS_S are unimplemented.
UNREACHABLE();
}
break;
case D:
}
void Simulator::DecodeTypeRegisterDRsType(Instruction* instr,
const int32_t& fs_reg,
const int64_t& ft_reg,
const int32_t& fd_reg) {
double ft, fs;
uint32_t cc, fcsr_cc;
int64_t i64;
fs = get_fpu_register_double(fs_reg);
ft = get_fpu_register_double(ft_reg);
cc = instr->FCccValue();
fcsr_cc = get_fcsr_condition_bit(cc);
int64_t ft_int = static_cast<int64_t>(ft);
switch (instr->FunctionFieldRaw()) {
case SELEQZ_C:
DCHECK(kArchVariant == kMips64r6);
set_fpu_register_double(fd_reg, (ft_int & 0x1) == 0 ? fs : 0.0);
break;
case SELNEZ_C:
DCHECK(kArchVariant == kMips64r6);
set_fpu_register_double(fd_reg, (ft_int & 0x1) != 0 ? fs : 0.0);
break;
case ADD_D:
set_fpu_register_double(fd_reg, fs + ft);
break;
......@@ -2403,22 +2338,19 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
set_fcsr_bit(fcsr_cc, (fs == ft));
break;
case C_UEQ_D:
set_fcsr_bit(fcsr_cc,
(fs == ft) || (std::isnan(fs) || std::isnan(ft)));
set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft)));
break;
case C_OLT_D:
set_fcsr_bit(fcsr_cc, (fs < ft));
break;
case C_ULT_D:
set_fcsr_bit(fcsr_cc,
(fs < ft) || (std::isnan(fs) || std::isnan(ft)));
set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft)));
break;
case C_OLE_D:
set_fcsr_bit(fcsr_cc, (fs <= ft));
break;
case C_ULE_D:
set_fcsr_bit(fcsr_cc,
(fs <= ft) || (std::isnan(fs) || std::isnan(ft)));
set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft)));
break;
case CVT_W_D: // Convert double to word.
// Rounding modes are not yet supported.
......@@ -2438,8 +2370,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
if (set_fcsr_round_error(fs, rounded)) {
set_fpu_register(fd_reg, kFPUInvalidResult);
}
}
break;
} break;
case TRUNC_W_D: // Truncate double to word (round towards 0).
{
double rounded = trunc(fs);
......@@ -2448,8 +2379,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
if (set_fcsr_round_error(fs, rounded)) {
set_fpu_register(fd_reg, kFPUInvalidResult);
}
}
break;
} break;
case FLOOR_W_D: // Round double to word towards negative infinity.
{
double rounded = std::floor(fs);
......@@ -2458,8 +2388,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
if (set_fcsr_round_error(fs, rounded)) {
set_fpu_register(fd_reg, kFPUInvalidResult);
}
}
break;
} break;
case CEIL_W_D: // Round double to word towards positive infinity.
{
double rounded = std::ceil(fs);
......@@ -2468,8 +2397,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
if (set_fcsr_round_error(fs, rounded)) {
set_fpu_register(fd_reg, kFPUInvalidResult);
}
}
break;
} break;
case CVT_S_D: // Convert double to float (single).
set_fpu_register_float(fd_reg, static_cast<float>(fs));
break;
......@@ -2521,8 +2449,13 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case W:
}
void Simulator::DecodeTypeRegisterWRsType(Instruction* instr,
const int32_t& fs_reg,
const int32_t& fd_reg,
int64_t& alu_out) {
switch (instr->FunctionFieldRaw()) {
case CVT_S_W: // Convert word to float (single).
alu_out = get_fpu_register_signed_word(fs_reg);
......@@ -2535,10 +2468,16 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default: // Mips64r6 CMP.S instructions unimplemented.
UNREACHABLE();
}
break;
case L:
fs = get_fpu_register_double(fs_reg);
ft = get_fpu_register_double(ft_reg);
}
void Simulator::DecodeTypeRegisterLRsType(Instruction* instr,
const int32_t& fs_reg,
const int32_t& fd_reg,
const int32_t& ft_reg) {
double fs = get_fpu_register_double(fs_reg);
double ft = get_fpu_register_double(ft_reg);
int64_t i64;
switch (instr->FunctionFieldRaw()) {
case CVT_D_L: // Mips32r2 instruction.
i64 = get_fpu_register(fs_reg);
......@@ -2602,12 +2541,68 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default: // CMP_OR CMP_UNE CMP_NE UNIMPLEMENTED
UNREACHABLE();
}
}
void Simulator::DecodeTypeRegisterCOP1(
Instruction* instr, const int64_t& rs_reg, const int64_t& rs,
const uint64_t& rs_u, const int64_t& rt_reg, const int64_t& rt,
const uint64_t& rt_u, const int64_t& rd_reg, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg, const int64_t& fd_reg,
int64_t& alu_out) {
switch (instr->RsFieldRaw()) {
case BC1: // Branch on coprocessor condition.
case BC1EQZ:
case BC1NEZ:
UNREACHABLE();
break;
case CFC1:
set_register(rt_reg, alu_out);
break;
case MFC1:
case DMFC1:
case MFHC1:
set_register(rt_reg, alu_out);
break;
case CTC1:
// At the moment only FCSR is supported.
DCHECK(fs_reg == kFCSRRegister);
FCSR_ = registers_[rt_reg];
break;
case MTC1:
// Hardware writes upper 32-bits to zero on mtc1.
set_fpu_register_hi_word(fs_reg, 0);
set_fpu_register_word(fs_reg, registers_[rt_reg]);
break;
case DMTC1:
set_fpu_register(fs_reg, registers_[rt_reg]);
break;
case MTHC1:
set_fpu_register_hi_word(fs_reg, registers_[rt_reg]);
break;
case S:
DecodeTypeRegisterSRsType(instr, fs_reg, fd_reg);
break;
case D:
DecodeTypeRegisterDRsType(instr, fs_reg, ft_reg, fd_reg);
break;
case W:
DecodeTypeRegisterWRsType(instr, fs_reg, fd_reg, alu_out);
break;
case L:
DecodeTypeRegisterLRsType(instr, fs_reg, fd_reg, ft_reg);
break;
default:
UNREACHABLE();
}
break;
case COP1X:
}
void Simulator::DecodeTypeRegisterCOP1X(Instruction* instr,
const int32_t& fr_reg,
const int32_t& fs_reg,
const int32_t& ft_reg,
const int64_t& fd_reg) {
switch (instr->FunctionFieldRaw()) {
case MADD_D:
double fr, ft, fs;
......@@ -2619,23 +2614,39 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
break;
case SPECIAL:
}
void Simulator::DecodeTypeRegisterSPECIAL(
Instruction* instr, const int64_t& rs_reg, const int64_t& rs,
const uint64_t& rs_u, const int64_t& rt_reg, const int64_t& rt,
const uint64_t& rt_u, const int64_t& rd_reg, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg, const int64_t& fd_reg,
int64_t& i64hilo, uint64_t& u64hilo, int64_t& alu_out, bool& do_interrupt,
int64_t& current_pc, int64_t& next_pc, int64_t& return_addr_reg,
int64_t& i128resultH, int64_t& i128resultL) {
switch (instr->FunctionFieldRaw()) {
case SELEQZ_S:
DCHECK(kArchVariant == kMips64r6);
set_register(rd_reg, rt == 0 ? rs : 0);
break;
case SELNEZ_S:
DCHECK(kArchVariant == kMips64r6);
set_register(rd_reg, rt != 0 ? rs : 0);
break;
case JR: {
Instruction* branch_delay_instr = reinterpret_cast<Instruction*>(
current_pc+Instruction::kInstrSize);
Instruction* branch_delay_instr =
reinterpret_cast<Instruction*>(current_pc + Instruction::kInstrSize);
BranchDelayInstructionDecode(branch_delay_instr);
set_pc(next_pc);
pc_modified_ = true;
break;
}
case JALR: {
Instruction* branch_delay_instr = reinterpret_cast<Instruction*>(
current_pc+Instruction::kInstrSize);
Instruction* branch_delay_instr =
reinterpret_cast<Instruction*>(current_pc + Instruction::kInstrSize);
BranchDelayInstructionDecode(branch_delay_instr);
set_register(return_addr_reg,
current_pc + 2 * Instruction::kInstrSize);
set_register(return_addr_reg, current_pc + 2 * Instruction::kInstrSize);
set_pc(next_pc);
pc_modified_ = true;
break;
......@@ -2648,8 +2659,7 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
} else {
switch (instr->SaValue()) {
case MUL_OP:
set_register(rd_reg,
static_cast<int32_t>(i64hilo & 0xffffffff));
set_register(rd_reg, static_cast<int32_t>(i64hilo & 0xffffffff));
break;
case MUH_OP:
set_register(rd_reg, static_cast<int32_t>(i64hilo >> 32));
......@@ -2774,8 +2784,12 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
set_register(rd_reg, alu_out);
TraceRegWr(alu_out);
}
break;
case SPECIAL2:
}
void Simulator::DecodeTypeRegisterSPECIAL2(Instruction* instr,
const int64_t& rd_reg,
int64_t& alu_out) {
switch (instr->FunctionFieldRaw()) {
case MUL:
set_register(rd_reg, alu_out);
......@@ -2787,8 +2801,12 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default: // For other special2 opcodes we do the default operation.
set_register(rd_reg, alu_out);
}
break;
case SPECIAL3:
}
void Simulator::DecodeTypeRegisterSPECIAL3(Instruction* instr,
const int64_t& rt_reg,
int64_t& alu_out) {
switch (instr->FunctionFieldRaw()) {
case INS:
// Ins instr leaves result in Rt, rather than Rd.
......@@ -2804,6 +2822,79 @@ void Simulator::DecodeTypeRegister(Instruction* instr) {
default:
UNREACHABLE();
}
}
void Simulator::DecodeTypeRegister(Instruction* instr) {
// Instruction fields.
const Opcode op = instr->OpcodeFieldRaw();
const int64_t rs_reg = instr->RsValue();
const int64_t rs = get_register(rs_reg);
const uint64_t rs_u = static_cast<uint32_t>(rs);
const int64_t rt_reg = instr->RtValue();
const int64_t rt = get_register(rt_reg);
const uint64_t rt_u = static_cast<uint32_t>(rt);
const int64_t rd_reg = instr->RdValue();
const int32_t fr_reg = instr->FrValue();
const int32_t fs_reg = instr->FsValue();
const int32_t ft_reg = instr->FtValue();
const int64_t fd_reg = instr->FdValue();
int64_t i64hilo = 0;
uint64_t u64hilo = 0;
// ALU output.
// It should not be used as is. Instructions using it should always
// initialize it first.
int64_t alu_out = 0x12345678;
// For break and trap instructions.
bool do_interrupt = false;
// For jr and jalr.
// Get current pc.
int64_t current_pc = get_pc();
// Next pc
int64_t next_pc = 0;
int64_t return_addr_reg = 31;
int64_t i128resultH;
int64_t i128resultL;
// Set up the variables if needed before executing the instruction.
ConfigureTypeRegister(instr,
&alu_out,
&i64hilo,
&u64hilo,
&next_pc,
&return_addr_reg,
&do_interrupt,
&i128resultH,
&i128resultL);
// ---------- Raise exceptions triggered.
SignalExceptions();
// ---------- Execution.
switch (op) {
case COP1:
DecodeTypeRegisterCOP1(instr, rs_reg, rs, rs_u, rt_reg, rt, rt_u, rd_reg,
fr_reg, fs_reg, ft_reg, fd_reg, alu_out);
break;
case COP1X:
DecodeTypeRegisterCOP1X(instr, fr_reg, fs_reg, ft_reg, fd_reg);
break;
case SPECIAL:
DecodeTypeRegisterSPECIAL(
instr, rs_reg, rs, rs_u, rt_reg, rt, rt_u, rd_reg, fr_reg, fs_reg,
ft_reg, fd_reg, i64hilo, u64hilo, alu_out, do_interrupt, current_pc,
next_pc, return_addr_reg, i128resultH, i128resultL);
break;
case SPECIAL2:
DecodeTypeRegisterSPECIAL2(instr, rd_reg, alu_out);
break;
case SPECIAL3:
DecodeTypeRegisterSPECIAL3(instr, rt_reg, alu_out);
break;
// Unimplemented opcodes raised an error in the configuration step before,
// so we can use the default here to set the destination register in common
......
......@@ -312,6 +312,45 @@ class Simulator {
inline int32_t SetDoubleHIW(double* addr);
inline int32_t SetDoubleLOW(double* addr);
// functions called from DecodeTypeRegister
void DecodeTypeRegisterCOP1(Instruction* instr, const int64_t& rs_reg,
const int64_t& rs, const uint64_t& rs_u,
const int64_t& rt_reg, const int64_t& rt,
const uint64_t& rt_u, const int64_t& rd_reg,
const int32_t& fr_reg, const int32_t& fs_reg,
const int32_t& ft_reg, const int64_t& fd_reg,
int64_t& alu_out);
void DecodeTypeRegisterCOP1X(Instruction* instr, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg,
const int64_t& fd_reg);
void DecodeTypeRegisterSPECIAL(
Instruction* instr, const int64_t& rs_reg, const int64_t& rs,
const uint64_t& rs_u, const int64_t& rt_reg, const int64_t& rt,
const uint64_t& rt_u, const int64_t& rd_reg, const int32_t& fr_reg,
const int32_t& fs_reg, const int32_t& ft_reg, const int64_t& fd_reg,
int64_t& i64hilo, uint64_t& u64hilo, int64_t& alu_out, bool& do_interrupt,
int64_t& current_pc, int64_t& next_pc, int64_t& return_addr_reg,
int64_t& i128resultH, int64_t& i128resultL);
void DecodeTypeRegisterSPECIAL2(Instruction* instr, const int64_t& rd_reg,
int64_t& alu_out);
void DecodeTypeRegisterSPECIAL3(Instruction* instr, const int64_t& rt_reg,
int64_t& alu_out);
void DecodeTypeRegisterSRsType(Instruction* instr, const int32_t& fs_reg,
const int64_t& fd_reg);
void DecodeTypeRegisterDRsType(Instruction* instr, const int32_t& fs_reg,
const int64_t& ft_reg, const int32_t& fd_reg);
void DecodeTypeRegisterWRsType(Instruction* instr, const int32_t& fs_reg,
const int32_t& fd_reg, int64_t& alu_out);
void DecodeTypeRegisterLRsType(Instruction* instr, const int32_t& fs_reg,
const int32_t& fd_reg, const int32_t& ft_reg);
// Executing is handled based on the instruction type.
void DecodeTypeRegister(Instruction* instr);
......
......@@ -48,6 +48,93 @@ typedef Object* (*F3)(void* p, int p1, int p2, int p3, int p4);
#define __ assm.
TEST(MIPS16) {
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
MacroAssembler assm(isolate, NULL, 0);
typedef struct test {
int a;
int b;
int c;
int d;
double e;
double f;
double g;
double h;
double i;
double j;
double k;
double l;
} Test;
Test test;
// integer part of test
__ addiu(t1, zero_reg, 1); // t1=1
__ seleqz(t1, zero_reg, t3); // t3=1
__ sw(t3, MemOperand(a0, OFFSET_OF(Test, a))); // a=1
__ seleqz(t1, t1, t2); // t2=0
__ sw(t2, MemOperand(a0, OFFSET_OF(Test, b))); // b=0
__ selnez(t1, zero_reg, t3); // t3=1;
__ sw(t3, MemOperand(a0, OFFSET_OF(Test, c))); // c=0
__ selnez(t1, t1, t3); // t3=1
__ sw(t3, MemOperand(a0, OFFSET_OF(Test, d))); // d=1
// floating point part of test S format
__ li(t0, 0x80);
__ mtc1(t0, f4);
__ cvt_d_w(f4, f4); // f4=0x80
__ li(t0, 0xf3);
__ mtc1(t0, f6);
__ cvt_d_w(f6, f6); // f6=0xf3
__ seleqz(S, f8, f4, f6); // f8=0xf3
__ seleqz(S, f10, f6, f6); // f10=0
__ sdc1(f8, MemOperand(a0, OFFSET_OF(Test, e))); // e=0xf3
__ sdc1(f10, MemOperand(a0, OFFSET_OF(Test, f))); // f=0
__ selnez(S, f8, f4, f6); // f8=0
__ selnez(S, f10, f6, f6); // f10=0xf3*/
__ sdc1(f8, MemOperand(a0, OFFSET_OF(Test, g))); // g=0
__ sdc1(f10, MemOperand(a0, OFFSET_OF(Test, h))); // h=0xf3
__ li(t0, 0x80);
__ mtc1(t0, f4);
__ cvt_d_w(f4, f4); // f4=0x80
__ li(t0, 0xf3);
__ mtc1(t0, f6);
__ cvt_d_w(f6, f6); // f6=0xf3
__ seleqz(D, f8, f4, f6); // f8=0xf3
__ seleqz(D, f10, f6, f6); // f10=0
__ sdc1(f8, MemOperand(a0, OFFSET_OF(Test, i))); // i=0xf3
__ sdc1(f10, MemOperand(a0, OFFSET_OF(Test, j))); // j=0
__ selnez(S, f8, f4, f6); // f8=0
__ selnez(S, f10, f6, f6); // f10=0xf3*/
__ sdc1(f8, MemOperand(a0, OFFSET_OF(Test, k))); // k=0
__ sdc1(f10, MemOperand(a0, OFFSET_OF(Test, l))); // l=0xf3
__ jr(ra);
__ nop();
CodeDesc desc;
assm.GetCode(&desc);
Handle<Code> code = isolate->factory()->NewCode(
desc, Code::ComputeFlags(Code::STUB), Handle<Code>());
F3 f = FUNCTION_CAST<F3>(code->entry());
(CALL_GENERATED_CODE(f, &test, 0, 0, 0, 0));
CHECK_EQ(test.a, 1);
CHECK_EQ(test.b, 0);
CHECK_EQ(test.c, 0);
CHECK_EQ(test.d, 1);
CHECK_EQ(test.e, 0xf3);
CHECK_EQ(test.f, 0x0);
CHECK_EQ(test.g, 0);
CHECK_EQ(test.h, 0xf3);
CHECK_EQ(test.i, 0xf3);
CHECK_EQ(test.j, 0x0);
CHECK_EQ(test.k, 0);
CHECK_EQ(test.l, 0xf3);
}
TEST(MIPS0) {
......
......@@ -48,6 +48,72 @@ typedef Object* (*F3)(void* p, int p1, int p2, int p3, int p4);
#define __ assm.
TEST(MIPS17) {
if (kArchVariant == kMips64r6) {
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
MacroAssembler assm(isolate, NULL, 0);
typedef struct test {
int a;
int b;
int c;
int d;
double i;
double j;
double k;
double l;
} Test;
Test test;
// integer part of test
__ addiu(t1, zero_reg, 1); // t1=1
__ seleqz(t1, zero_reg, t3); // t3=1
__ sw(t3, MemOperand(a0, OFFSET_OF(Test, a))); // a=1
__ seleqz(t1, t1, t2); // t2=0
__ sw(t2, MemOperand(a0, OFFSET_OF(Test, b))); // b=0
__ selnez(t1, zero_reg, t3); // t3=1;
__ sw(t3, MemOperand(a0, OFFSET_OF(Test, c))); // c=0
__ selnez(t1, t1, t3); // t3=1
__ sw(t3, MemOperand(a0, OFFSET_OF(Test, d))); // d=1
// floating point part of test
__ li(t0, 0x80);
__ mtc1(t0, f4);
__ cvt_d_w(f4, f4); // f4=0x80
__ li(t0, 0xf3);
__ mtc1(t0, f6);
__ cvt_d_w(f6, f6); // f6=0xf3
__ seleqz(D, f8, f4, f6); // f8=0xf3
__ seleqz(D, f10, f6, f6); // f10=0
__ sdc1(f8, MemOperand(a0, OFFSET_OF(Test, i))); // i=0xf3
__ sdc1(f10, MemOperand(a0, OFFSET_OF(Test, j))); // j=0
__ selnez(D, f8, f4, f6); // f8=0
__ selnez(D, f10, f6, f6); // f10=0xf3*/
__ sdc1(f8, MemOperand(a0, OFFSET_OF(Test, k))); // k=0
__ sdc1(f10, MemOperand(a0, OFFSET_OF(Test, l))); // l=0xf3
__ jr(ra);
__ nop();
CodeDesc desc;
assm.GetCode(&desc);
Handle<Code> code = isolate->factory()->NewCode(
desc, Code::ComputeFlags(Code::STUB), Handle<Code>());
F3 f = FUNCTION_CAST<F3>(code->entry());
(CALL_GENERATED_CODE(f, &test, 0, 0, 0, 0));
CHECK_EQ(test.a, 1);
CHECK_EQ(test.b, 0);
CHECK_EQ(test.c, 0);
CHECK_EQ(test.d, 1);
CHECK_EQ(test.i, 0xf3);
CHECK_EQ(test.j, 0x0);
CHECK_EQ(test.k, 0);
CHECK_EQ(test.l, 0xf3);
}
}
TEST(MIPS0) {
CcTest::InitializeVM();
......
......@@ -90,6 +90,20 @@ bool DisassembleAndCompare(byte* pc, const char* compare_string) {
if (failure) { \
V8_Fatal(__FILE__, __LINE__, "MIPS Disassembler tests failed.\n"); \
}
// tests only seleqz, selnez, seleqz.fmt and selnez.fmt
TEST(Type1) {
SET_UP();
if (IsMipsArchVariant(kMips32r6)) {
COMPARE(seleqz(a0, a1, a2), "00853035 seleqz a0, a1, a2");
COMPARE(selnez(a0, a1, a2), "00853037 selnez a0, a1, a2");
COMPARE(seleqz(S, f0, f1, f2), "45000894 seleqz.S f0, f1, f2");
COMPARE(selnez(S, f0, f1, f2), "45000897 selnez.S f0, f1, f2");
COMPARE(seleqz(D, f3, f4, f5), "00853035 seleqz.D f3, f4, f5");
COMPARE(selnez(D, f3, f4, f5), "00853037 selnez.D f3, f4, f5");
}
VERIFY_RUN();
}
TEST(Type0) {
......
......@@ -92,6 +92,25 @@ if (failure) { \
}
TEST(Type1) {
if (kArchVariant == kMips64r6) {
SET_UP();
COMPARE(seleqz(a0, a1, a2), "00853035 seleqz a0, a1, a2");
COMPARE(selnez(a0, a1, a2), "00853037 selnez a0, a1, a2");
COMPARE(seleqz(D, f3, f4, f5), "462428d4 seleqz.D f4, f5, f3");
COMPARE(selnez(D, f3, f4, f5), "462428d7 selnez.D f4, f5, f3");
/*COMPARE(min(D, f3, f4, f5),
"462428dc min.D f4, f5, f3");
COMPARE(max(D, f3, f4, f5),
"462428de max.D f4, f5, f3");*/
VERIFY_RUN();
}
}
TEST(Type0) {
SET_UP();
......
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