gdb/record: Speeding up recording in RISC-V

I measured that removing saving mem chunks and regs to std::vector before
calling API functions speeds up stepping up to 15%, so I added this
optimization (as Guinevere Larsen <guinevere@redhat.com> recommended in
initial support). It turns out that after this, the m_record_type and
m_error_occured no longer needed, so I removed them too.

Approved-By: Guinevere Larsen <guinevere@redhat.com>
This commit is contained in:
timurgol007
2025-10-08 18:44:19 +03:00
parent 46eeb5132a
commit bef948d551

View File

@@ -4887,72 +4887,32 @@ equivalent change in the disassembler output."),
&showriscvcmdlist);
}
/* A wrapper to read register under number regnum to address addr.
Returns false if error happened and makes warning. */
static bool
try_read (struct regcache *regcache, int regnum, ULONGEST &addr)
{
gdb_assert (regcache != nullptr);
if (regcache->raw_read (regnum, &addr)
!= register_status::REG_VALID)
{
warning (_("Can not read at address %s"), hex_string (addr));
return false;
}
return true;
}
/* Helper class to record instruction. */
class riscv_recorded_insn final
{
public:
/* Type for saved register. */
using regnum_type = int;
/* Type for saved memory. First is address, second is length. */
using memory_type = std::pair<CORE_ADDR, int>;
/* Enum class that represents which type does recording belong to. */
enum class record_type
{
UNKNOWN,
ORDINARY,
/* Type for memory address. */
using mem_addr = CORE_ADDR;
/* Corner cases. */
ECALL,
EBREAK,
SRET,
MRET,
};
/* Type for memory length. */
using mem_len = int;
private:
/* Type for set of registers that need to be saved. */
using recorded_regs = std::vector<regnum_type>;
/* Type for set of memory records that need to be saved. */
using recorded_mems = std::vector<memory_type>;
/* Current regcache. */
struct regcache *m_regcache = nullptr;
/* Type for memory address, extracted from memory_type. */
using mem_addr = decltype (std::declval<memory_type> ().first);
/* Type for memory length, extracted from memory_type. */
using mem_len = decltype (std::declval<memory_type> ().second);
/* Record type of current instruction. */
record_type m_record_type = record_type::UNKNOWN;
/* Flag that represents was there an error in current recording. */
bool m_error_occured = false;
/* Set of registers that need to be recorded. */
recorded_regs m_regs;
/* Set of memory chunks that need to be recorded. */
recorded_mems m_mems;
/* Current riscv gdbarch. */
struct riscv_gdbarch_tdep *m_gdbarch = nullptr;
/* Width in bytes of the general purpose registers for GDBARCH,
where recording is happening. */
int m_xlen = 0;
/* Flag that says whether we are in baremetal mode. */
bool m_in_baremetal_mode = false;
/* Helper for decode 16-bit instruction RS1. */
static regnum_type
decode_crs1_short (ULONGEST opcode) noexcept
@@ -5009,68 +4969,34 @@ private:
return (ival >> OP_SH_CSR) & OP_MASK_CSR;
}
/* Set any record type. Always returns true. */
/* Reads register. Returns false if error happened. */
bool
set_record_type (record_type type) noexcept
read_reg (regnum_type regnum, ULONGEST &addr) noexcept
{
m_record_type = type;
return true;
}
if (m_regcache->raw_read (regnum, &addr) == register_status::REG_VALID)
return true;
/* Set ordinary record type. Always returns true. */
bool
set_ordinary_record_type () noexcept
{
m_record_type = record_type::ORDINARY;
return true;
}
/* Set error happened. Always returns false. */
bool
set_error () noexcept
{
m_error_occured = true;
warning (_("Can not read at address %s"), hex_string (addr));
return false;
}
/* Check if current recording has an error. */
bool
has_error () const noexcept
{
return m_error_occured;
}
/* Reads register. Sets error and returns false if error happened. */
bool
read_reg (struct regcache *regcache, regnum_type reg,
ULONGEST &addr) noexcept
{
gdb_assert (regcache != nullptr);
if (!try_read (regcache, reg, addr))
return set_error ();
return true;
}
/* Save register. Returns true or aborts if exception happened. */
/* Save register. Returns false if error happened. */
bool
save_reg (regnum_type regnum) noexcept
{
m_regs.emplace_back (regnum);
return true;
return (record_full_arch_list_add_reg (m_regcache, regnum) == 0);
}
/* Save memory chunk. Returns true or aborts if exception happened. */
/* Save memory chunk. Returns false if error happened. */
bool
save_mem (mem_addr addr, mem_len len) noexcept
{
m_mems.emplace_back (addr, len);
return true;
return (record_full_arch_list_add_mem (addr, len) == 0);
}
/* Returns true if instruction needs only saving pc. */
static bool
need_save_pc (ULONGEST ival) noexcept
need_save_only_pc (ULONGEST ival) noexcept
{
return (is_beq_insn (ival) || is_bne_insn (ival) || is_blt_insn (ival)
|| is_bge_insn (ival) || is_bltu_insn (ival) || is_bgeu_insn (ival)
@@ -5079,19 +5005,9 @@ private:
|| is_sfence_vma_insn (ival));
}
/* Returns true if instruction is classified. */
bool
try_save_pc (ULONGEST ival) noexcept
{
if (!need_save_pc (ival))
return false;
return set_ordinary_record_type ();
}
/* Returns true if instruction needs only saving pc and rd. */
static bool
need_save_pc_rd (ULONGEST ival) noexcept
need_save_rd (ULONGEST ival) noexcept
{
return (is_lui_insn (ival) || is_auipc_insn (ival) || is_jal_insn (ival)
|| is_jalr_insn (ival) || is_lb_insn (ival) || is_lh_insn (ival)
@@ -5126,21 +5042,17 @@ private:
|| is_fcvt_lu_d_insn (ival) || is_fmv_x_d_insn (ival));
}
/* Returns true if instruction is classified. This function can set
m_error_occured. */
/* Returns true if instruction successfully saved rd. */
bool
try_save_pc_rd (ULONGEST ival) noexcept
try_save_rd (ULONGEST ival) noexcept
{
if (!need_save_pc_rd (ival))
return false;
return (!save_reg (decode_rd (ival)) || set_ordinary_record_type ());
return save_reg (decode_rd (ival));;
}
/* Returns true if instruction needs only saving pc and
floating point rd. */
static bool
need_save_pc_fprd (ULONGEST ival) noexcept
need_save_fprd (ULONGEST ival) noexcept
{
return (is_flw_insn (ival) || is_fmadd_s_insn (ival)
|| is_fmsub_s_insn (ival) || is_fnmsub_s_insn (ival)
@@ -5165,45 +5077,35 @@ private:
|| is_fcvt_d_lu_insn (ival) || is_fmv_d_x_insn (ival));
}
/* Returns true if instruction is classified. This function can set
m_error_occured. */
/* Returns true if instruction successfully saved floating point rd. */
bool
try_save_pc_fprd (ULONGEST ival) noexcept
try_save_fprd (ULONGEST ival) noexcept
{
if (!need_save_pc_fprd (ival))
return false;
return (!save_reg (RISCV_FIRST_FP_REGNUM + decode_rd (ival))
|| set_ordinary_record_type ());
return save_reg (RISCV_FIRST_FP_REGNUM + decode_rd (ival));
}
/* Returns true if instruction needs only saving pc, rd and csr. */
static bool
need_save_pc_rd_csr (ULONGEST ival) noexcept
need_save_rd_csr (ULONGEST ival) noexcept
{
return (is_csrrw_insn (ival) || is_csrrs_insn (ival) || is_csrrc_insn (ival)
|| is_csrrwi_insn (ival) || is_csrrsi_insn (ival)
|| is_csrrci_insn (ival));
}
/* Returns true if instruction is classified. This function can set
m_error_occured. */
/* Returns true if instruction successfully saved rd and csr. */
bool
try_save_pc_rd_csr (ULONGEST ival) noexcept
try_save_rd_csr (ULONGEST ival) noexcept
{
if (!need_save_pc_rd_csr (ival))
return false;
return (!save_reg (decode_rd (ival))
|| !save_reg (RISCV_FIRST_CSR_REGNUM + decode_csr (ival))
|| set_ordinary_record_type ());
return (save_reg (decode_rd (ival))
&& save_reg (RISCV_FIRST_CSR_REGNUM + decode_csr (ival)));
}
/* Returns the size of the memory chunk that needs to be saved if the
instruction belongs to the group that needs only saving pc and memory.
Otherwise returns 0. */
static mem_len
need_save_pc_mem (ULONGEST ival) noexcept
need_save_mem (ULONGEST ival) noexcept
{
if (is_sb_insn (ival))
return 1;
@@ -5216,28 +5118,22 @@ private:
return 0;
}
/* Returns true if instruction is classified. This function can set
m_error_occured. */
/* Returns true if instruction successfully saved memory. */
bool
try_save_pc_mem (ULONGEST ival, struct regcache *regcache) noexcept
try_save_mem (ULONGEST ival, mem_len len) noexcept
{
gdb_assert (regcache != nullptr);
mem_addr addr = 0;
ULONGEST offset = EXTRACT_STYPE_IMM (ival);
mem_addr addr = mem_addr{};
mem_len len = need_save_pc_mem (ival);
if (len <= 0)
return false;
mem_len offset = EXTRACT_STYPE_IMM (ival);
return (!read_reg (regcache, decode_rs1 (ival), addr)
|| !save_mem (addr + offset, len) || set_ordinary_record_type ());
return (read_reg (decode_rs1 (ival), addr)
&& save_mem (addr + offset, len));
}
/* Returns the size of the memory chunk that needs to be saved if the
instruction belongs to the group that needs only saving pc, rd and memory.
Otherwise returns 0. */
static mem_len
need_save_pc_rd_mem (ULONGEST ival) noexcept
need_save_rd_mem (ULONGEST ival) noexcept
{
if (is_sc_w_insn (ival) || is_amoswap_w_insn (ival)
|| is_amoadd_w_insn (ival) || is_amoxor_w_insn (ival)
@@ -5254,58 +5150,68 @@ private:
return 0;
}
/* Returns true if instruction is classified. This function can set
m_error_occured. */
/* Returns true if instruction successfully saved rd and memory. */
bool
try_save_pc_rd_mem (ULONGEST ival, struct regcache *regcache) noexcept
try_save_rd_mem (ULONGEST ival, mem_len len) noexcept
{
gdb_assert (regcache != nullptr);
mem_len len = need_save_pc_rd_mem (ival);
mem_addr addr = 0;
if (len <= 0)
return false;
return (!read_reg (regcache, decode_rs1 (ival), addr)
|| !save_mem (addr, len) || !save_reg (decode_rd (ival))
|| set_ordinary_record_type ());
return (read_reg (decode_rs1 (ival), addr) && save_mem (addr, len)
&& save_reg (decode_rd (ival)));
}
/* Returns true if instruction is successfully recordered. The length of
the instruction must be equal 4 bytes. */
bool
record_insn_len4 (ULONGEST ival, struct regcache *regcache) noexcept
record_insn_len4 (ULONGEST ival) noexcept
{
gdb_assert (regcache != nullptr);
mem_len len = 0;
ULONGEST reg_val = 0;
if (is_ecall_insn (ival))
{
return set_record_type (record_type::ECALL);
/* We are in baremetal mode. */
if (m_in_baremetal_mode)
{
warning (_("Syscall record is not supported"));
return false;
}
/* We are in linux mode. */
return (read_reg (RISCV_A7_REGNUM, reg_val)
&& m_gdbarch->riscv_syscall_record (m_regcache, reg_val) == 0);
}
if (is_ebreak_insn (ival))
{
return set_record_type (record_type::EBREAK);
}
return true;
if (is_sret_insn (ival))
{
return (!save_reg (RISCV_CSR_SSTATUS_REGNUM)
|| !save_reg (RISCV_CSR_MEPC_REGNUM)
|| set_record_type (record_type::SRET));
}
return (save_reg (RISCV_CSR_SSTATUS_REGNUM)
&& save_reg (RISCV_CSR_MEPC_REGNUM));
if (is_mret_insn (ival))
{
return (!save_reg (RISCV_CSR_MSTATUS_REGNUM)
|| !save_reg (RISCV_CSR_MEPC_REGNUM)
|| set_record_type (record_type::MRET));
}
return (save_reg (RISCV_CSR_MSTATUS_REGNUM)
&& save_reg (RISCV_CSR_MEPC_REGNUM));
if (try_save_pc (ival) || try_save_pc_rd (ival) || try_save_pc_fprd (ival)
|| try_save_pc_rd_csr (ival) || try_save_pc_mem (ival, regcache)
|| try_save_pc_rd_mem (ival, regcache))
return !has_error ();
if (need_save_only_pc (ival))
return true;
if (need_save_rd (ival))
return try_save_rd (ival);
if (need_save_fprd (ival))
return try_save_fprd (ival);
if (need_save_rd_csr (ival))
return try_save_rd_csr (ival);
len = need_save_mem (ival);
if (len > 0)
return try_save_mem (ival, len);
len = need_save_rd_mem (ival);
if (len > 0)
return try_save_rd_mem (ival, len);
warning (_("Currently this instruction with len 4(%s) is unsupported"),
hex_string (ival));
@@ -5315,105 +5221,98 @@ private:
/* Returns true if instruction is successfully recordered. The length of
the instruction must be equal 2 bytes. */
bool
record_insn_len2 (ULONGEST ival, struct regcache *regcache) noexcept
record_insn_len2 (ULONGEST ival) noexcept
{
gdb_assert (regcache != nullptr);
mem_addr addr = mem_addr{};
mem_addr addr = 0;
ULONGEST offset = 0;
/* The order here is very important, because
opcodes of some instructions may be the same. */
if (is_c_addi4spn_insn (ival) || is_c_lw_insn (ival)
|| (m_xlen == 8 && is_c_ld_insn (ival)))
return (!save_reg (decode_crs2_short (ival))
|| set_ordinary_record_type ());
return save_reg (decode_crs2_short (ival));
if (is_c_fld_insn (ival) || (m_xlen == 4 && is_c_flw_insn (ival)))
return (!save_reg (RISCV_FIRST_FP_REGNUM + decode_crs2_short (ival))
|| set_ordinary_record_type ());
return save_reg (RISCV_FIRST_FP_REGNUM + decode_crs2_short (ival));
if (is_c_fsd_insn (ival) || (m_xlen == 8 && is_c_sd_insn (ival)))
{
ULONGEST offset = ULONGEST{EXTRACT_CLTYPE_LD_IMM (ival)};
return (!read_reg (regcache, decode_crs1_short (ival), addr)
|| !save_mem (addr + offset, 8) || set_ordinary_record_type ());
offset = ULONGEST{EXTRACT_CLTYPE_LD_IMM (ival)};
return (read_reg (decode_crs1_short (ival), addr)
&& save_mem (addr + offset, 8));
}
if ((m_xlen == 4 && is_c_fsw_insn (ival)) || is_c_sw_insn (ival))
{
ULONGEST offset = ULONGEST{EXTRACT_CLTYPE_LW_IMM (ival)};
return (!read_reg (regcache, decode_crs1_short (ival), addr)
|| !save_mem (addr + offset, 4) || set_ordinary_record_type ());
offset = ULONGEST{EXTRACT_CLTYPE_LW_IMM (ival)};
return (read_reg (decode_crs1_short (ival), addr)
&& save_mem (addr + offset, 4));
}
if (is_c_nop_insn (ival))
return set_ordinary_record_type ();
return true;
if (is_c_addi_insn (ival))
return (!save_reg (decode_crs1 (ival)) || set_ordinary_record_type ());
return save_reg (decode_crs1 (ival));
if (m_xlen == 4 && is_c_jal_insn (ival))
return (!save_reg (RISCV_RA_REGNUM) || set_ordinary_record_type ());
return save_reg (RISCV_RA_REGNUM);
if ((m_xlen == 8 && is_c_addiw_insn (ival)) || is_c_li_insn (ival))
return (!save_reg (decode_crs1 (ival)) || set_ordinary_record_type ());
return save_reg (decode_crs1 (ival));
if (is_c_addi16sp_insn (ival))
return (!save_reg (RISCV_SP_REGNUM) || set_ordinary_record_type ());
return save_reg (RISCV_SP_REGNUM);
if (is_c_lui_insn (ival))
return (!save_reg (decode_crs1 (ival)) || set_ordinary_record_type ());
return save_reg (decode_crs1 (ival));
if (is_c_srli_insn (ival) || is_c_srai_insn (ival) || is_c_andi_insn (ival)
|| is_c_sub_insn (ival) || is_c_xor_insn (ival) || is_c_or_insn (ival)
|| is_c_and_insn (ival) || (m_xlen == 8 && is_c_subw_insn (ival))
|| (m_xlen == 8 && is_c_addw_insn (ival)))
return (!save_reg (decode_crs1_short (ival))
|| set_ordinary_record_type ());
return save_reg (decode_crs1_short (ival));
if (is_c_j_insn (ival) || is_c_beqz_insn (ival) || is_c_bnez_insn (ival))
return set_ordinary_record_type ();
return true;
if (is_c_slli_insn (ival))
return (!save_reg (decode_crs1 (ival)) || set_ordinary_record_type ());
return save_reg (decode_crs1 (ival));
if (is_c_fldsp_insn (ival) || (m_xlen == 4 && is_c_flwsp_insn (ival)))
return (!save_reg (RISCV_FIRST_FP_REGNUM + decode_crs1 (ival))
|| set_ordinary_record_type ());
return save_reg (RISCV_FIRST_FP_REGNUM + decode_crs1 (ival));
if (is_c_lwsp_insn (ival) || (m_xlen == 8 && is_c_ldsp_insn (ival)))
return (!save_reg (decode_crs1 (ival)) || set_ordinary_record_type ());
return save_reg (decode_crs1 (ival));
if (is_c_jr_insn (ival))
return set_ordinary_record_type ();
return true;
if (is_c_mv_insn (ival))
return (!save_reg (decode_crs1 (ival)) || set_ordinary_record_type ());
return save_reg (decode_crs1 (ival));
if (is_c_ebreak_insn (ival))
{
return set_record_type (record_type::EBREAK);
}
return true;
if (is_c_jalr_insn (ival))
return (!save_reg (RISCV_RA_REGNUM) || set_ordinary_record_type ());
return save_reg (RISCV_RA_REGNUM);
if (is_c_add_insn (ival))
return (!save_reg (decode_crs1 (ival)) || set_ordinary_record_type ());
return save_reg (decode_crs1 (ival));
if (is_c_fsdsp_insn (ival) || (m_xlen == 8 && is_c_sdsp_insn (ival)))
{
ULONGEST offset = ULONGEST{EXTRACT_CSSTYPE_SDSP_IMM (ival)};
return (!read_reg (regcache, RISCV_SP_REGNUM, addr)
|| !save_mem (addr + offset, 8) || set_ordinary_record_type ());
offset = ULONGEST{EXTRACT_CSSTYPE_SDSP_IMM (ival)};
return (read_reg (RISCV_SP_REGNUM, addr)
&& save_mem (addr + offset, 8));
}
if (is_c_swsp_insn (ival) || (m_xlen == 4 && is_c_fswsp_insn (ival)))
{
ULONGEST offset = ULONGEST{EXTRACT_CSSTYPE_SWSP_IMM (ival)};
return (!read_reg (regcache, RISCV_SP_REGNUM, addr)
|| !save_mem (addr + offset, 4) || set_ordinary_record_type ());
offset = ULONGEST{EXTRACT_CSSTYPE_SWSP_IMM (ival)};
return (read_reg (RISCV_SP_REGNUM, addr)
&& save_mem (addr + offset, 4));
}
warning (_("Currently this instruction with len 2(%s) is unsupported"),
@@ -5422,11 +5321,6 @@ private:
}
public:
/* Iterator for registers that need to be recorded. */
using regs_iter = recorded_regs::const_iterator;
/* Iterator for memory chunks that need to be recorded. */
using mems_iter = recorded_mems::const_iterator;
/* Record instruction at address addr. Returns false if error happened. */
bool
record (gdbarch *gdbarch, struct regcache *regcache, CORE_ADDR addr) noexcept
@@ -5434,138 +5328,46 @@ public:
gdb_assert (gdbarch != nullptr);
gdb_assert (regcache != nullptr);
int m_length = 0;
ULONGEST ival = 0;
m_gdbarch = gdbarch_tdep<riscv_gdbarch_tdep> (gdbarch);
m_regcache = regcache;
m_xlen = riscv_isa_xlen (gdbarch);
m_in_baremetal_mode = (m_gdbarch->riscv_syscall_record == nullptr);
int insn_length = 0;
ULONGEST ival = 0;
/* Since fetch_instruction can throw an exception,
it must be wrapped in a try-catch block. */
try
{
ival = riscv_insn::fetch_instruction (gdbarch, addr, &m_length);
ival = riscv_insn::fetch_instruction (gdbarch, addr, &insn_length);
}
catch (const gdb_exception_error &ex)
{
warning ("%s", ex.what ());
return false;
}
if (!save_reg (RISCV_PC_REGNUM))
return false;
if (m_length == 4)
return record_insn_len4 (ival, regcache);
if (insn_length == 2)
return record_insn_len2 (ival);
if (m_length == 2)
return record_insn_len2 (ival, regcache);
if (insn_length == 4)
return record_insn_len4 (ival);
/* 6 bytes or more. If the instruction is longer than 8 bytes, we don't
have full instruction bits in ival. At least, such long instructions
are not defined yet, so just ignore it. */
gdb_assert (m_length > 0 && m_length % 2 == 0);
gdb_assert (insn_length > 0 && insn_length % 2 == 0);
warning (_("Can not record unknown instruction (opcode = %s)"),
hex_string (ival));
return false;
}
/* Get record type of instruction. */
record_type
get_record_type () const noexcept
{
return m_record_type;
}
/* Returns an iterator to the beginning of the registers that need
to be saved. */
regs_iter
regs_begin () const noexcept
{
return m_regs.begin ();
}
/* Returns an iterator to the end of the registers that need
to be saved. */
regs_iter
regs_end () const noexcept
{
return m_regs.end ();
}
/* Returns an iterator to the beginning of the memory chunks that need
to be saved. */
mems_iter
mems_begin () const noexcept
{
return m_mems.begin ();
}
/* Returns an iterator to the end of the memory chunks that need
to be saved. */
mems_iter
mems_end () const noexcept
{
return m_mems.end ();
}
};
/* A helper function to record instruction using record API. */
static int
riscv_record_insn_details (struct gdbarch *gdbarch, struct regcache *regcache,
const riscv_recorded_insn &insn)
{
gdb_assert (gdbarch != nullptr);
gdb_assert (regcache != nullptr);
riscv_gdbarch_tdep *tdep = gdbarch_tdep<riscv_gdbarch_tdep> (gdbarch);
auto regs_begin = insn.regs_begin ();
auto regs_end = insn.regs_end ();
if (std::any_of (regs_begin,
regs_end,
[&regcache] (auto &&reg_it)
{
return record_full_arch_list_add_reg (regcache, reg_it);
}))
return -1;
auto mems_begin = insn.mems_begin ();
auto mems_end = insn.mems_end ();
if (std::any_of (mems_begin,
mems_end,
[] (auto &&mem_it)
{
return record_full_arch_list_add_mem (mem_it.first,
mem_it.second);
}))
return -1;
switch (insn.get_record_type ())
{
case riscv_recorded_insn::record_type::ORDINARY:
case riscv_recorded_insn::record_type::EBREAK:
case riscv_recorded_insn::record_type::SRET:
case riscv_recorded_insn::record_type::MRET:
break;
case riscv_recorded_insn::record_type::ECALL:
{
if (!tdep->riscv_syscall_record)
{
warning (_("Syscall record is not supported"));
return -1;
}
ULONGEST reg_val = ULONGEST{};
if (!try_read (regcache, RISCV_A7_REGNUM, reg_val))
return -1;
return tdep->riscv_syscall_record (regcache, reg_val);
}
default:
return -1;
}
return 0;
}
/* Parse the current instruction and record the values of the registers and
memory that will be changed in current instruction to record_arch_list.
Return -1 if something is wrong. */
@@ -5581,10 +5383,8 @@ riscv_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
if (!insn.record (gdbarch, regcache, addr))
return -1;
int ret_val = riscv_record_insn_details (gdbarch, regcache, insn);
if (record_full_arch_list_add_end ())
return -1;
return ret_val;
return 0;
}