diff --git a/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.h b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.h --- a/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.h +++ b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.h @@ -92,6 +92,9 @@ return WriteMemoryUnsigned(ctx, addr, value, sizeof(T)); } + llvm::RoundingMode GetRoundingMode(); + bool SetAccruedExceptions(llvm::APFloatBase::opStatus); + private: /// Last decoded instruction from m_opcode DecodeResult m_decoded; diff --git a/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp --- a/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp +++ b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp @@ -101,6 +101,12 @@ return LLDB_INVALID_REGNUM; } +static uint32_t FPREncodingToLLDB(uint32_t reg_encode) { + if (reg_encode >= 0 && reg_encode <= 31) + return fpr_f0_riscv + reg_encode; + return LLDB_INVALID_REGNUM; +} + bool Rd::Write(EmulateInstructionRISCV &emulator, uint64_t value) { uint32_t lldb_reg = GPREncodingToLLDB(rd); EmulateInstruction::Context ctx; @@ -112,6 +118,17 @@ registerValue); } +bool Rd::WriteAPFloat(EmulateInstructionRISCV &emulator, llvm::APFloat value) { + uint32_t lldb_reg = FPREncodingToLLDB(rd); + EmulateInstruction::Context ctx; + ctx.type = EmulateInstruction::eContextRegisterStore; + ctx.SetNoArgs(); + RegisterValue registerValue; + registerValue.SetUInt64(value.bitcastToAPInt().getZExtValue()); + return emulator.WriteRegister(ctx, eRegisterKindLLDB, lldb_reg, + registerValue); +} + llvm::Optional Rs::Read(EmulateInstructionRISCV &emulator) { uint32_t lldbReg = GPREncodingToLLDB(rs); RegisterValue value; @@ -135,6 +152,18 @@ [](uint64_t value) { return uint32_t(value); }); } +llvm::Optional Rs::ReadAPFloat(EmulateInstructionRISCV &emulator, + bool isDouble) { + uint32_t lldbReg = FPREncodingToLLDB(rs); + RegisterValue value; + if (!emulator.ReadRegister(eRegisterKindLLDB, lldbReg, value)) + return llvm::None; + uint64_t bits = value.GetAsUInt64(); + llvm::APInt api(64, bits, false); + return llvm::APFloat(isDouble ? llvm::APFloat(api.bitsToDouble()) + : llvm::APFloat(api.bitsToFloat())); +} + static bool CompareB(uint64_t rs1, uint64_t rs2, uint32_t funct3) { switch (funct3) { case BEQ: @@ -356,7 +385,7 @@ template static RISCVInst DecodeBType(uint32_t inst) { return T{Rs{DecodeRS1(inst)}, Rs{DecodeRS2(inst)}, DecodeBImm(inst), - (inst & 0x7000) >> 12}; + DecodeFunct3(inst)}; } template static RISCVInst DecodeSType(uint32_t inst) { @@ -375,6 +404,11 @@ return T{Rd{DecodeRD(inst)}, Rs{DecodeRS1(inst)}}; } +template static RISCVInst DecodeR4Type(uint32_t inst) { + return T{Rd{DecodeRD(inst)}, Rs{DecodeRS1(inst)}, Rs{DecodeRS2(inst)}, + Rs{DecodeRS3(inst)}, DecodeRM(inst)}; +} + static const InstrPattern PATTERNS[] = { // RV32I & RV64I (The base integer ISA) // {"LUI", 0x7F, 0x37, DecodeUType}, @@ -492,6 +526,34 @@ {"C_SUB", 0xFC63, 0x8C01, DecodeC_SUB}, {"C_SUBW", 0xFC63, 0x9C01, DecodeC_SUBW}, {"C_ADDW", 0xFC63, 0x9C21, DecodeC_ADDW}, + + // RV32F (Extension for Single-Precision Floating-Point) // + {"FLW", 0x707F, 0x2007, DecodeIType}, + {"FSW", 0x707F, 0x2027, DecodeSType}, + {"FMADD_S", 0x600007F, 0x43, DecodeR4Type}, + {"FMSUB_S", 0x600007F, 0x47, DecodeR4Type}, + {"FNMSUB_S", 0x600007F, 0x4B, DecodeR4Type}, + {"FNMADD_S", 0x600007F, 0x4F, DecodeR4Type}, + {"FADD_S", 0xFE00007F, 0x53, DecodeRType}, + {"FSUB_S", 0xFE00007F, 0x8000053, DecodeRType}, + {"FMUL_S", 0xFE00007F, 0x10000053, DecodeRType}, + {"FDIV_S", 0xFE00007F, 0x18000053, DecodeRType}, + {"FSQRT_S", 0xFFF0007F, 0x58000053, DecodeRType}, + {"FSGNJ_S", 0xFE00707F, 0x20000053, DecodeRType}, + {"FSGNJN_S", 0xFE00707F, 0x20001053, DecodeRType}, + {"FSGNJX_S", 0xFE00707F, 0x20002053, DecodeRType}, + {"FMIN_S", 0xFE00707F, 0x28000053, DecodeRType}, + {"FMAX_S", 0xFE00707F, 0x28001053, DecodeRType}, + {"FCVT_W_S", 0xFFF0007F, 0xC0000053, DecodeRType}, + {"FCVT_WU_S", 0xFFF0007F, 0xC0100053, DecodeRType}, + {"FMV_X_W", 0xFFF0707F, 0xE0000053, DecodeRType}, + {"FEQ_S", 0xFE00707F, 0xA2002053, DecodeRType}, + {"FLT_S", 0xFE00707F, 0xA2001053, DecodeRType}, + {"FLE_S", 0xFE00707F, 0xA2000053, DecodeRType}, + {"FCLASS_S", 0xFFF0707F, 0xE0001053, DecodeRType}, + {"FCVT_S_W", 0xFFF0007F, 0xD0000053, DecodeRType}, + {"FCVT_S_WU", 0xFFF0007F, 0xD0100053, DecodeRType}, + {"FMV_W_X", 0xFFF0707F, 0xF0000053, DecodeRType}, }; llvm::Optional EmulateInstructionRISCV::Decode(uint32_t inst) { @@ -504,10 +566,9 @@ for (const InstrPattern &pat : PATTERNS) { if ((inst & pat.type_mask) == pat.eigen) { - LLDB_LOGF(log, - "EmulateInstructionRISCV::%s: inst(%x at %" PRIx64 - ") was decoded to %s", - __FUNCTION__, inst, m_addr, pat.name); + LLDB_LOGF( + log, "EmulateInstructionRISCV::%s: inst(%x at %lx) was decoded to %s", + __FUNCTION__, inst, m_addr, pat.name); auto decoded = is_rvc ? pat.decode(try_rvc) : pat.decode(inst); return DecodeResult{decoded, inst, is_rvc, pat}; } @@ -1050,6 +1111,323 @@ m_emu, inst, 8, ZextD, [](uint64_t a, uint64_t b) { return std::max(a, b); }); } + bool operator()(FLW inst) { + return inst.rs1.Read(m_emu) + .transform([&](auto &&rs1) { + uint64_t addr = rs1 + uint64_t(inst.imm); + uint64_t bits = m_emu.ReadMem(addr).value(); + llvm::APFloat f(llvm::APFloat::IEEEsingle(), llvm::APInt(32, bits)); + return inst.rd.WriteAPFloat(m_emu, f); + }) + .value_or(false); + } + bool operator()(FSW inst) { + return zipOpt(inst.rs1.Read(m_emu), inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + uint64_t addr = rs1 + uint64_t(inst.imm); + uint64_t bits = rs2.bitcastToAPInt().getZExtValue(); + return m_emu.WriteMem(addr, bits); + }) + .value_or(false); + } + bool operator()(FMADD_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false), + inst.rs3.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2, rs3] = tup; + auto res = rs1.fusedMultiplyAdd(rs2, rs3, m_emu.GetRoundingMode()); + inst.rd.WriteAPFloat(m_emu, rs1); + return m_emu.SetAccruedExceptions(res); + }) + .value_or(false); + } + bool operator()(FMSUB_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false), + inst.rs3.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2, rs3] = tup; + auto res = rs1.fusedMultiplyAdd(rs2, -rs3, m_emu.GetRoundingMode()); + inst.rd.WriteAPFloat(m_emu, rs1); + return m_emu.SetAccruedExceptions(res); + }) + .value_or(false); + } + bool operator()(FNMSUB_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false), + inst.rs3.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2, rs3] = tup; + auto res = rs1.fusedMultiplyAdd(-rs2, rs3, m_emu.GetRoundingMode()); + inst.rd.WriteAPFloat(m_emu, rs1); + return m_emu.SetAccruedExceptions(res); + }) + .value_or(false); + } + bool operator()(FNMADD_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false), + inst.rs3.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2, rs3] = tup; + auto res = rs1.fusedMultiplyAdd(-rs2, -rs3, m_emu.GetRoundingMode()); + inst.rd.WriteAPFloat(m_emu, rs1); + return m_emu.SetAccruedExceptions(res); + }) + .value_or(false); + } + bool operator()(FADD_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + auto res = rs1.add(rs2, m_emu.GetRoundingMode()); + inst.rd.WriteAPFloat(m_emu, rs1); + return m_emu.SetAccruedExceptions(res); + }) + .value_or(false); + } + bool operator()(FSUB_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + auto res = rs1.subtract(rs2, m_emu.GetRoundingMode()); + inst.rd.WriteAPFloat(m_emu, rs1); + return m_emu.SetAccruedExceptions(res); + }) + .value_or(false); + } + bool operator()(FMUL_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + auto res = rs1.multiply(rs2, m_emu.GetRoundingMode()); + inst.rd.WriteAPFloat(m_emu, rs1); + return m_emu.SetAccruedExceptions(res); + }) + .value_or(false); + } + bool operator()(FDIV_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + auto res = rs1.divide(rs2, m_emu.GetRoundingMode()); + inst.rd.WriteAPFloat(m_emu, rs1); + return m_emu.SetAccruedExceptions(res); + }) + .value_or(false); + } + bool operator()(FSQRT_S inst) { + // TODO: APFloat doesn't have a sqrt function. + return false; + } + bool operator()(FSGNJ_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + rs1.copySign(rs2); + return inst.rd.WriteAPFloat(m_emu, rs1); + }) + .value_or(false); + } + bool operator()(FSGNJN_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + rs1.copySign(-rs2); + return inst.rd.WriteAPFloat(m_emu, rs1); + }) + .value_or(false); + } + bool operator()(FSGNJX_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + // spec: the sign bit is the XOR of the sign bits of rs1 and rs2. + // if rs1 and rs2 have the same signs + // set rs1 to positive + // else set rs1 to negative + if (rs1.isNegative() == rs2.isNegative()) { + rs1.clearSign(); + } else { + rs1.clearSign(); + rs1.changeSign(); + } + return inst.rd.WriteAPFloat(m_emu, rs1); + }) + .value_or(false); + } + bool operator()(FMIN_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + // If both inputs are NaNs, the result is the canonical NaN. + // If only one operand is a NaN, the result is the non-NaN operand. + // Signaling NaN inputs set the invalid operation exception flag, even + // when the result is not NaN. + if (rs1.isNaN() || rs2.isNaN()) + m_emu.SetAccruedExceptions(llvm::APFloat::opInvalidOp); + if (rs1.isNaN() && rs2.isNaN()) { + auto canonicalNaN = llvm::APFloat::getQNaN(rs1.getSemantics()); + return inst.rd.WriteAPFloat(m_emu, canonicalNaN); + } + return inst.rd.WriteAPFloat(m_emu, minnum(rs1, rs2)); + }) + .value_or(false); + } + bool operator()(FMAX_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + if (rs1.isNaN() || rs2.isNaN()) + m_emu.SetAccruedExceptions(llvm::APFloat::opInvalidOp); + if (rs1.isNaN() && rs2.isNaN()) { + auto canonicalNaN = llvm::APFloat::getQNaN(rs1.getSemantics()); + return inst.rd.WriteAPFloat(m_emu, canonicalNaN); + } + return inst.rd.WriteAPFloat(m_emu, maxnum(rs1, rs2)); + }) + .value_or(false); + } + bool operator()(FCVT_W_S inst) { + return inst.rs1.ReadAPFloat(m_emu, false) + .transform([&](auto &&rs1) { + int32_t res = rs1.convertToFloat(); + return inst.rd.Write(m_emu, uint64_t(res)); + }) + .value_or(false); + } + bool operator()(FCVT_WU_S inst) { + return inst.rs1.ReadAPFloat(m_emu, false) + .transform([&](auto &&rs1) { + uint32_t res = rs1.convertToFloat(); + return inst.rd.Write(m_emu, uint64_t(res)); + }) + .value_or(false); + } + bool operator()(FMV_X_W inst) { + return inst.rs1.ReadAPFloat(m_emu, false) + .transform([&](auto &&rs1) { + if (rs1.isNaN()) + return inst.rd.Write(m_emu, 0x7fc00000); + auto bits = rs1.bitcastToAPInt(); + return inst.rd.Write(m_emu, NanBoxing(uint64_t(bits.getSExtValue()))); + }) + .value_or(false); + } + bool operator()(FEQ_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + if (rs1.isNaN() || rs2.isNaN()) { + if (rs1.isSignaling() || rs2.isSignaling()) + m_emu.SetAccruedExceptions(llvm::APFloat::opInvalidOp); + return inst.rd.Write(m_emu, 0); + } + return inst.rd.Write(m_emu, + rs1.compare(rs2) == llvm::APFloat::cmpEqual); + }) + .value_or(false); + } + bool operator()(FLT_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + if (rs1.isNaN() || rs2.isNaN()) { + m_emu.SetAccruedExceptions(llvm::APFloat::opInvalidOp); + return inst.rd.Write(m_emu, 0); + } + return inst.rd.Write(m_emu, + rs1.compare(rs2) == llvm::APFloat::cmpLessThan); + }) + .value_or(false); + } + bool operator()(FLE_S inst) { + return zipOpt(inst.rs1.ReadAPFloat(m_emu, false), + inst.rs2.ReadAPFloat(m_emu, false)) + .transform([&](auto &&tup) { + auto [rs1, rs2] = tup; + if (rs1.isNaN() || rs2.isNaN()) { + m_emu.SetAccruedExceptions(llvm::APFloat::opInvalidOp); + return inst.rd.Write(m_emu, 0); + } + return inst.rd.Write(m_emu, rs1.compare(rs2) != + llvm::APFloat::cmpGreaterThan); + }) + .value_or(false); + } + bool operator()(FCLASS_S inst) { + return inst.rs1.ReadAPFloat(m_emu, false) + .transform([&](auto &&rs1) { + uint64_t result = 0; + if (rs1.isInfinity() && rs1.isNegative()) + result |= 1 << 0; + // neg normal + if (rs1.isNormal() && rs1.isNegative()) + result |= 1 << 1; + // neg subnormal + if (rs1.isDenormal() && rs1.isNegative()) + result |= 1 << 2; + if (rs1.isNegZero()) + result |= 1 << 3; + if (rs1.isPosZero()) + result |= 1 << 4; + // pos normal + if (rs1.isNormal() && !rs1.isNegative()) + result |= 1 << 5; + // pos subnormal + if (rs1.isDenormal() && !rs1.isNegative()) + result |= 1 << 6; + if (rs1.isInfinity() && !rs1.isNegative()) + result |= 1 << 7; + if (rs1.isNaN()) { + if (rs1.isSignaling()) + result |= 1 << 8; + else + result |= 1 << 9; + } + return inst.rd.Write(m_emu, result); + }) + .value_or(false); + } + bool operator()(FCVT_S_W inst) { + return inst.rs1.ReadI32(m_emu) + .transform([&](auto &&rs1) { + llvm::APFloat apf(llvm::APFloat::IEEEsingle(), rs1); + return inst.rd.WriteAPFloat(m_emu, apf); + }) + .value_or(false); + } + bool operator()(FCVT_S_WU inst) { + return inst.rs1.ReadU32(m_emu) + .transform([&](auto &&rs1) { + llvm::APFloat apf(llvm::APFloat::IEEEsingle(), rs1); + return inst.rd.WriteAPFloat(m_emu, apf); + }) + .value_or(false); + } + bool operator()(FMV_W_X inst) { + return inst.rs1.Read(m_emu) + .transform([&](auto &&rs1) { + llvm::APInt apInt(32, NanUnBoxing(rs1)); + llvm::APFloat apf(apInt.bitsToFloat()); + return inst.rd.WriteAPFloat(m_emu, apf); + }) + .value_or(false); + } bool operator()(INVALID inst) { return false; } bool operator()(RESERVED inst) { return false; } bool operator()(EBREAK inst) { return false; } @@ -1123,6 +1501,62 @@ LLDB_REGNUM_GENERIC_PC, pc); } +llvm::RoundingMode EmulateInstructionRISCV::GetRoundingMode() { + bool success = false; + auto fcsr = ReadRegisterUnsigned(eRegisterKindLLDB, fpr_fcsr_riscv, + LLDB_INVALID_ADDRESS, &success); + if (!success) + return llvm::RoundingMode::Invalid; + auto frm = (fcsr >> 5) & 0x7; + switch (frm) { + case 0b000: + return llvm::RoundingMode::NearestTiesToEven; + case 0b001: + return llvm::RoundingMode::TowardZero; + case 0b010: + return llvm::RoundingMode::TowardNegative; + case 0b011: + return llvm::RoundingMode::TowardPositive; + case 0b111: + return llvm::RoundingMode::Dynamic; + default: + // Reserved for future use. + return llvm::RoundingMode::Invalid; + } +} + +bool EmulateInstructionRISCV::SetAccruedExceptions( + llvm::APFloatBase::opStatus opStatus) { + bool success = false; + auto fcsr = ReadRegisterUnsigned(eRegisterKindLLDB, fpr_fcsr_riscv, + LLDB_INVALID_ADDRESS, &success); + if (!success) + return false; + switch (opStatus) { + case llvm::APFloatBase::opInvalidOp: + fcsr |= 1 << 4; + break; + case llvm::APFloatBase::opDivByZero: + fcsr |= 1 << 3; + break; + case llvm::APFloatBase::opOverflow: + fcsr |= 1 << 2; + break; + case llvm::APFloatBase::opUnderflow: + fcsr |= 1 << 1; + break; + case llvm::APFloatBase::opInexact: + fcsr |= 1 << 0; + break; + case llvm::APFloatBase::opOK: + break; + } + EmulateInstruction::Context ctx; + ctx.type = eContextRegisterStore; + ctx.SetNoArgs(); + return WriteRegisterUnsigned(ctx, eRegisterKindLLDB, fpr_fcsr_riscv, fcsr); +} + llvm::Optional EmulateInstructionRISCV::GetRegisterInfo(lldb::RegisterKind reg_kind, uint32_t reg_index) { diff --git a/lldb/source/Plugins/Instruction/RISCV/RISCVInstructions.h b/lldb/source/Plugins/Instruction/RISCV/RISCVInstructions.h --- a/lldb/source/Plugins/Instruction/RISCV/RISCVInstructions.h +++ b/lldb/source/Plugins/Instruction/RISCV/RISCVInstructions.h @@ -13,6 +13,7 @@ #include #include "EmulateInstructionRISCV.h" +#include "llvm/ADT/APFloat.h" #include "llvm/ADT/Optional.h" namespace lldb_private { @@ -22,6 +23,7 @@ struct Rd { uint32_t rd; bool Write(EmulateInstructionRISCV &emulator, uint64_t value); + bool WriteAPFloat(EmulateInstructionRISCV &emulator, llvm::APFloat value); }; struct Rs { @@ -30,6 +32,8 @@ llvm::Optional ReadI32(EmulateInstructionRISCV &emulator); llvm::Optional ReadI64(EmulateInstructionRISCV &emulator); llvm::Optional ReadU32(EmulateInstructionRISCV &emulator); + llvm::Optional ReadAPFloat(EmulateInstructionRISCV &emulator, + bool isDouble); }; #define I_TYPE_INST(NAME) \ @@ -68,6 +72,14 @@ Rd rd; \ Rs rs1; \ } +#define R4_TYPE_INST(NAME) \ + struct NAME { \ + Rd rd; \ + Rs rs1; \ + Rs rs2; \ + Rs rs3; \ + int32_t rm; \ + } /// The `inst` fields are used for debugging. #define INVALID_INST(NAME) \ struct NAME { \ @@ -170,6 +182,34 @@ R_TYPE_INST(AMOMINU_D); R_TYPE_INST(AMOMAXU_D); +// RV32F inst (The standard single-precision floating-point extension) +I_TYPE_INST(FLW); +S_TYPE_INST(FSW); +R4_TYPE_INST(FMADD_S); +R4_TYPE_INST(FMSUB_S); +R4_TYPE_INST(FNMADD_S); +R4_TYPE_INST(FNMSUB_S); +R_TYPE_INST(FADD_S); +R_TYPE_INST(FSUB_S); +R_TYPE_INST(FMUL_S); +R_TYPE_INST(FDIV_S); +R_TYPE_INST(FSQRT_S); +R_TYPE_INST(FSGNJ_S); +R_TYPE_INST(FSGNJN_S); +R_TYPE_INST(FSGNJX_S); +R_TYPE_INST(FMIN_S); +R_TYPE_INST(FMAX_S); +R_TYPE_INST(FCVT_W_S); +R_TYPE_INST(FCVT_WU_S); +R_TYPE_INST(FMV_X_W); +R_TYPE_INST(FEQ_S); +R_TYPE_INST(FLT_S); +R_TYPE_INST(FLE_S); +R_TYPE_INST(FCLASS_S); +R_TYPE_INST(FCVT_S_W); +R_TYPE_INST(FCVT_S_WU); +R_TYPE_INST(FMV_W_X); + /// Invalid and reserved instructions, the `inst` fields are used for debugging. INVALID_INST(INVALID); INVALID_INST(RESERVED); @@ -185,7 +225,10 @@ DIVUW, REMW, REMUW, LR_W, SC_W, AMOSWAP_W, AMOADD_W, AMOXOR_W, AMOAND_W, AMOOR_W, AMOMIN_W, AMOMAX_W, AMOMINU_W, AMOMAXU_W, LR_D, SC_D, AMOSWAP_D, AMOADD_D, AMOXOR_D, AMOAND_D, AMOOR_D, AMOMIN_D, AMOMAX_D, AMOMINU_D, - AMOMAXU_D, INVALID, EBREAK, RESERVED, HINT, NOP>; + AMOMAXU_D, FLW, FSW, FMADD_S, FMSUB_S, FNMADD_S, FNMSUB_S, FADD_S, FSUB_S, + FMUL_S, FDIV_S, FSQRT_S, FSGNJ_S, FSGNJN_S, FSGNJX_S, FMIN_S, FMAX_S, + FCVT_W_S, FCVT_WU_S, FMV_X_W, FEQ_S, FLT_S, FLE_S, FCLASS_S, FCVT_S_W, + FCVT_S_WU, FMV_W_X, INVALID, EBREAK, RESERVED, HINT, NOP>; struct InstrPattern { const char *name; @@ -206,6 +249,26 @@ constexpr uint32_t DecodeRD(uint32_t inst) { return (inst & 0xF80) >> 7; } constexpr uint32_t DecodeRS1(uint32_t inst) { return (inst & 0xF8000) >> 15; } constexpr uint32_t DecodeRS2(uint32_t inst) { return (inst & 0x1F00000) >> 20; } +constexpr uint32_t DecodeRS3(uint32_t inst) { + return (inst & 0xF0000000) >> 27; +} +constexpr uint32_t DecodeFunct3(uint32_t inst) { return (inst & 0x7000) >> 12; } +constexpr uint32_t DecodeFunct2(uint32_t inst) { + return (inst & 0xE000000) >> 25; +} +constexpr uint32_t DecodeFunct7(uint32_t inst) { + return (inst & 0xFE000000) >> 25; +} + +constexpr int32_t DecodeRM(uint32_t inst) { return DecodeFunct3(inst); } + +/// RISC-V spec: The upper bits of a valid NaN-boxed value must be all 1s. +constexpr uint64_t NanBoxing(uint64_t val) { + return val | 0xFFFF'FFFF'0000'0000; +} +constexpr uint32_t NanUnBoxing(uint64_t val) { + return val & (~0xFFFF'FFFF'0000'0000); +} } // namespace lldb_private #endif // LLDB_SOURCE_PLUGINS_INSTRUCTION_RISCV_RISCVINSTRUCTION_H diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_riscv64.h b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_riscv64.h --- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_riscv64.h +++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_riscv64.h @@ -31,7 +31,7 @@ }; struct FPR { - uint64_t f[32]; + uint64_t fpr[32]; uint32_t fcsr; }; diff --git a/lldb/unittests/Instruction/RISCV/TestRISCVEmulator.cpp b/lldb/unittests/Instruction/RISCV/TestRISCVEmulator.cpp --- a/lldb/unittests/Instruction/RISCV/TestRISCVEmulator.cpp +++ b/lldb/unittests/Instruction/RISCV/TestRISCVEmulator.cpp @@ -24,6 +24,7 @@ struct RISCVEmulatorTester : public EmulateInstructionRISCV, testing::Test { RegisterInfoPOSIX_riscv64::GPR gpr; + RegisterInfoPOSIX_riscv64::FPR fpr; uint8_t memory[1024] = {0}; RISCVEmulatorTester() @@ -32,6 +33,7 @@ EmulateInstruction::SetWriteRegCallback(WriteRegisterCallback); EmulateInstruction::SetReadMemCallback(ReadMemoryCallback); EmulateInstruction::SetWriteMemCallback(WriteMemoryCallback); + ClearAll(); } static bool ReadRegisterCallback(EmulateInstruction *instruction, void *baton, @@ -41,8 +43,13 @@ uint32_t reg = reg_info->kinds[eRegisterKindLLDB]; if (reg == gpr_x0_riscv) reg_value.SetUInt(0, reg_info->byte_size); - else + if (reg >= gpr_pc_riscv && reg <= gpr_x31_riscv) reg_value.SetUInt(tester->gpr.gpr[reg], reg_info->byte_size); + if (reg >= fpr_f0_riscv && reg <= fpr_f31_riscv) + reg_value.SetUInt(tester->fpr.fpr[reg - fpr_f0_riscv], + reg_info->byte_size); + if (reg == fpr_fcsr_riscv) + reg_value.SetUInt(tester->fpr.fcsr, reg_info->byte_size); return true; } @@ -52,8 +59,12 @@ const RegisterValue ®_value) { RISCVEmulatorTester *tester = (RISCVEmulatorTester *)instruction; uint32_t reg = reg_info->kinds[eRegisterKindLLDB]; - if (reg != gpr_x0_riscv) + if (reg >= gpr_pc_riscv && reg <= gpr_x31_riscv) tester->gpr.gpr[reg] = reg_value.GetAsUInt64(); + if (reg >= fpr_f0_riscv && reg <= fpr_f31_riscv) + tester->fpr.fpr[reg - fpr_f0_riscv] = reg_value.GetAsUInt64(); + if (reg == fpr_fcsr_riscv) + tester->fpr.fcsr = reg_value.GetAsUInt32(); return true; } @@ -81,6 +92,12 @@ .transform([&](DecodeResult res) { return Execute(res, ignore_cond); }) .value_or(false); } + + void ClearAll() { + memset(&gpr, 0, sizeof(gpr)); + memset(&fpr, 0, sizeof(fpr)); + memset(memory, 0, sizeof(memory)); + } }; TEST_F(RISCVEmulatorTester, testJAL) { @@ -155,8 +172,8 @@ using EncoderB = uint32_t (*)(uint32_t rs1, uint32_t rs2, int32_t offset); -void testBranch(RISCVEmulatorTester *tester, EncoderB encoder, bool branched, - uint64_t rs1, uint64_t rs2) { +static void testBranch(RISCVEmulatorTester *tester, EncoderB encoder, + bool branched, uint64_t rs1, uint64_t rs2) { // prepare test registers lldb::addr_t old_pc = 0x114514; tester->WritePC(old_pc); @@ -178,12 +195,13 @@ testBranch(this, name, false, rs1, rs2_continued); \ } -void CheckRD(RISCVEmulatorTester *tester, uint64_t rd, uint64_t value) { +static void CheckRD(RISCVEmulatorTester *tester, uint64_t rd, uint64_t value) { ASSERT_EQ(tester->gpr.gpr[rd], value); } template -void CheckMem(RISCVEmulatorTester *tester, uint64_t addr, uint64_t value) { +static void CheckMem(RISCVEmulatorTester *tester, uint64_t addr, + uint64_t value) { auto mem = tester->ReadMem(addr); ASSERT_TRUE(mem.has_value()); ASSERT_EQ(*mem, value); @@ -194,8 +212,8 @@ using PC = uint64_t; using RDComputer = std::function; -void TestInst(RISCVEmulatorTester *tester, DecodeResult inst, bool has_rs2, - RDComputer rd_val) { +static void TestInst(RISCVEmulatorTester *tester, DecodeResult inst, + bool has_rs2, RDComputer rd_val) { lldb::addr_t old_pc = 0x114514; tester->WritePC(old_pc); @@ -223,8 +241,8 @@ } template -void TestAtomic(RISCVEmulatorTester *tester, uint64_t inst, T rs1_val, - T rs2_val, T rd_expected, T mem_expected) { +static void TestAtomic(RISCVEmulatorTester *tester, uint64_t inst, T rs1_val, + T rs2_val, T rd_expected, T mem_expected) { // Atomic inst must have rs1 and rs2 uint32_t rd = DecodeRD(inst); @@ -261,7 +279,7 @@ RISCVInst inst_type; }; -bool compareInst(const RISCVInst &lhs, const RISCVInst &rhs) { +static bool compareInst(const RISCVInst &lhs, const RISCVInst &rhs) { if (lhs.index() != rhs.index()) return false; return std::visit( @@ -446,3 +464,188 @@ TestAtomic(this, 0xE0F7282F, 0x1, 0x2, 0x1, 0x2); TestAtomic(this, 0xE0F7382F, 0x1, 0x2, 0x1, 0x2); } + +struct FloatCalInst { + uint32_t inst; + std::string name; + float rs1_val; + float rs2_val; + float rd_val; +}; + +static void TestFloatCalInst(RISCVEmulatorTester *tester, DecodeResult inst, + float rs1_val, float rs2_val, float rd_exp) { + std::vector FloatCMP = {"FEQ_S", "FLT_S", "FLE_S"}; + std::vector FloatCal3 = {"FMADD_S", "FMSUB_S", "FNMSUB_S", + "FNMADD_S"}; + + uint32_t rd = DecodeRD(inst.inst); + uint32_t rs1 = DecodeRS1(inst.inst); + uint32_t rs2 = DecodeRS2(inst.inst); + + llvm::APFloat ap_rs1_val(rs1_val); + llvm::APFloat ap_rs2_val(rs2_val); + llvm::APFloat ap_rs3_val(0.5f); + + if (rs1) + tester->fpr.fpr[rs1] = ap_rs1_val.bitcastToAPInt().getZExtValue(); + if (rs2) + tester->fpr.fpr[rs2] = ap_rs2_val.bitcastToAPInt().getZExtValue(); + for (auto i : FloatCal3) { + if (inst.pattern.name == i) { + uint32_t rs3 = DecodeRS3(inst.inst); + tester->fpr.fpr[rs3] = ap_rs3_val.bitcastToAPInt().getZExtValue(); + } + } + ASSERT_TRUE(tester->Execute(inst, false)); + for (auto i : FloatCMP) { + if (inst.pattern.name == i) { + ASSERT_EQ(tester->gpr.gpr[rd], rd_exp); + return; + } + } + + llvm::APInt apInt(32, tester->fpr.fpr[rd]); + llvm::APFloat rd_val(apInt.bitsToFloat()); + ASSERT_EQ(rd_val.convertToFloat(), rd_exp); +} + +TEST_F(RISCVEmulatorTester, TestFloatInst) { + std::vector tests = { + {0x21F253, "FADD_S", 0.5f, 0.5f, 1.0f}, + {0x821F253, "FSUB_S", 1.0f, 0.5f, 0.5f}, + {0x1021F253, "FMUL_S", 0.5f, 0.5f, 0.25f}, + {0x1821F253, "FDIV_S", 0.1f, 0.1f, 1.0f}, + {0x20218253, "FSGNJ_S", 0.5f, 0.2f, 0.5f}, + {0x20219253, "FSGNJN_S", 0.5f, -1.0f, 0.5f}, + {0x2021A253, "FSGNJX_S", -0.5f, -0.5f, 0.5f}, + {0x2021A253, "FSGNJX_S", -0.5f, 0.5f, -0.5f}, + {0x28218253, "FMIN_S", -0.5f, 0.5f, -0.5f}, + {0x28218253, "FMIN_S", -0.5f, -0.6f, -0.6f}, + {0x28218253, "FMIN_S", 0.5f, 0.6f, 0.5f}, + {0x28219253, "FMAX_S", -0.5f, -0.6f, -0.5f}, + {0x28219253, "FMAX_S", 0.5f, 0.6f, 0.6f}, + {0x28219253, "FMAX_S", 0.5f, -0.6f, 0.5f}, + {0xA221A253, "FEQ_S", 0.5f, 0.5f, 1}, + {0xA221A253, "FEQ_S", 0.5f, -0.5f, 0}, + {0xA221A253, "FEQ_S", -0.5f, 0.5f, 0}, + {0xA221A253, "FEQ_S", 0.4f, 0.5f, 0}, + {0xA2219253, "FLT_S", 0.4f, 0.5f, 1}, + {0xA2219253, "FLT_S", 0.5f, 0.5f, 0}, + {0xA2218253, "FLE_S", 0.4f, 0.5f, 1}, + {0xA2218253, "FLE_S", 0.5f, 0.5f, 1}, + {0x4021F243, "FMADD_S", 0.5f, 0.5f, 0.75f}, + {0x4021F247, "FMSUB_S", 0.5f, 0.5f, -0.25f}, + {0x4021F24B, "FNMSUB_S", 0.5f, 0.5f, 0.25f}, + {0x4021F24F, "FNMADD_S", 0.5f, 0.5f, -0.75f}, + }; + for (auto i : tests) { + auto decode = this->Decode(i.inst); + ASSERT_TRUE(decode.has_value()); + std::string name = decode->pattern.name; + ASSERT_EQ(name, i.name); + TestFloatCalInst(this, decode.value(), i.rs1_val, i.rs2_val, i.rd_val); + } +} + +static void TestFCVT(RISCVEmulatorTester *tester, DecodeResult inst) { + std::vector FloatToInt = {"FCVT_W_S", "FCVT_WU_S"}; + std::vector IntToFloat = {"FCVT_S_W", "FCVT_S_WU"}; + + uint32_t rd = DecodeRD(inst.inst); + uint32_t rs1 = DecodeRS1(inst.inst); + + for (auto i : FloatToInt) { + if (inst.pattern.name == i) { + llvm::APFloat apf_rs1_val(12.0f); + tester->fpr.fpr[rs1] = apf_rs1_val.bitcastToAPInt().getZExtValue(); + ASSERT_TRUE(tester->Execute(inst, false)); + ASSERT_EQ(tester->gpr.gpr[rd], uint64_t(12)); + return; + } + } + + for (auto i : IntToFloat) { + if (inst.pattern.name == i) { + tester->gpr.gpr[rs1] = 12; + ASSERT_TRUE(tester->Execute(inst, false)); + llvm::APInt apInt(32, tester->fpr.fpr[rd]); + llvm::APFloat rd_val(apInt.bitsToFloat()); + ASSERT_EQ(rd_val.convertToFloat(), 12.0f); + return; + } + } +} + +struct FCVTInst { + uint32_t inst; + std::string name; +}; + +TEST_F(RISCVEmulatorTester, TestFCVTInst) { + std::vector tests = { + {0xC001F253, "FCVT_W_S"}, + {0xC011F253, "FCVT_WU_S"}, + {0xD001F253, "FCVT_S_W"}, + {0xD011F253, "FCVT_S_WU"}, + }; + for (auto i : tests) { + auto decode = this->Decode(i.inst); + ASSERT_TRUE(decode.has_value()); + std::string name = decode->pattern.name; + ASSERT_EQ(name, i.name); + TestFCVT(this, decode.value()); + } +} + +TEST_F(RISCVEmulatorTester, TestFloatLSInst) { + uint32_t FLWInst = 0x1A207; // imm = 0 + uint32_t FSWInst = 0x21A827; // imm = 16 + + llvm::APFloat apf(12.0f); + uint64_t bits = apf.bitcastToAPInt().getZExtValue(); + + *(uint64_t *)this->memory = bits; + auto decode = this->Decode(FLWInst); + ASSERT_TRUE(decode.has_value()); + std::string name = decode->pattern.name; + ASSERT_EQ(name, "FLW"); + ASSERT_TRUE(this->Execute(decode.value(), false)); + ASSERT_EQ(this->fpr.fpr[DecodeRD(FLWInst)], bits); + + this->fpr.fpr[DecodeRS2(FSWInst)] = bits; + decode = this->Decode(FSWInst); + ASSERT_TRUE(decode.has_value()); + name = decode->pattern.name; + ASSERT_EQ(name, "FSW"); + ASSERT_TRUE(this->Execute(decode.value(), false)); + ASSERT_EQ(*(uint32_t *)(this->memory + 16), bits); +} + +TEST_F(RISCVEmulatorTester, TestFMV_X_WInst) { + auto FMV_X_WInst = 0xE0018253; + + llvm::APFloat apf(12.0f); + auto bits = NanBoxing(apf.bitcastToAPInt().getZExtValue()); + this->fpr.fpr[DecodeRS1(FMV_X_WInst)] = bits; + auto decode = this->Decode(FMV_X_WInst); + ASSERT_TRUE(decode.has_value()); + std::string name = decode->pattern.name; + ASSERT_EQ(name, "FMV_X_W"); + ASSERT_TRUE(this->Execute(decode.value(), false)); + ASSERT_EQ(this->gpr.gpr[DecodeRD(FMV_X_WInst)], bits); +} + +TEST_F(RISCVEmulatorTester, TestFMV_W_XInst) { + auto FMV_W_XInst = 0xF0018253; + + llvm::APFloat apf(12.0f); + uint64_t bits = NanUnBoxing(apf.bitcastToAPInt().getZExtValue()); + this->gpr.gpr[DecodeRS1(FMV_W_XInst)] = bits; + auto decode = this->Decode(FMV_W_XInst); + ASSERT_TRUE(decode.has_value()); + std::string name = decode->pattern.name; + ASSERT_EQ(name, "FMV_W_X"); + ASSERT_TRUE(this->Execute(decode.value(), false)); + ASSERT_EQ(this->fpr.fpr[DecodeRD(FMV_W_XInst)], bits); +}