rexcode/arm64: MRS/MSR printed the system register as a number

`mrs x0, cntvct_el0` came back out of the disassembler as
`mrs x0, #24322`. The 230 sysreg constants were already there and the
encoding was byte-exact against llvm-mc -- the printer simply had no
SYS_REG handling, so the packed 15-bit field printed as an immediate.
Nothing an assembler would take back.

Adds a value -> name table (sorted, binary searched) and prints that
operand by name for MRS, and for the MSR form that takes one. MSR's
other form holds a PSTATE field selector in the same slot, which is a
different namespace; the two are told apart by whether the second
operand is a register or an immediate.

Six encodings carry two names, so a round-trip can come back spelled as
the sibling; the first by source order wins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UmHLRF11EoWwNWCJ7JGaA
This commit is contained in:
Brendan Punsky
2026-08-27 08:59:47 -04:00
committed by Flāvius
parent d951ee9963
commit 01bdf7522c
2 changed files with 298 additions and 0 deletions

View File

@@ -118,6 +118,20 @@ sbprint :: proc(
// MOVZ/MOVN/MOVK store the shift as an hw index (0..3 = LSL #0/16/32/48),
// which assemblers write as `lsl #16` and omit entirely when it is zero.
// MRS/MSR carry the system register as a packed field, so it has to be
// printed by name -- a bare number is not something an assembler takes.
// MSR's other form takes a PSTATE field selector in the same slot,
// which is a different namespace; it is the one whose second operand
// is an immediate rather than a register.
sysreg_slot, sysreg_ok := -1, false
#partial switch inst.mnemonic {
case .MRS:
sysreg_slot, sysreg_ok = 1, inst.operand_count >= 2
case .MSR:
sysreg_slot = 0
sysreg_ok = inst.operand_count >= 2 && inst.ops[1].kind == .REGISTER
}
mov_wide := inst.mnemonic == .MOVZ || inst.mnemonic == .MOVN || inst.mnemonic == .MOVK
end_slot := int(inst.operand_count)
if mov_wide && end_slot == 3 && inst.ops[2].kind == .IMMEDIATE && inst.ops[2].immediate == 0 {
@@ -134,6 +148,8 @@ sbprint :: proc(
if mov_wide && slot == 2 {
strings.write_string(sb, opts.uppercase ? "LSL #" : "lsl #")
write_decimal_u32(sb, u32(inst.ops[slot].immediate) * 16)
} else if slot == sysreg_slot && sysreg_ok {
write_sysreg(sb, inst.ops[slot].immediate, opts.uppercase)
} else {
write_operand(sb, &inst.ops[slot], &display, opts)
}
@@ -340,6 +356,26 @@ write_mnemonic :: proc(sb: ^strings.Builder, m: Mnemonic, uppercase: bool) {
// NOTE: an element-indexed operand still prints as `v0.s` -- the lane index
// rides in a separate immediate operand, so `v0.s[2]` needs the printer to
// fold that operand into this one, which it does not yet do.
// A system register by name (`cntvct_el0`), falling back to the raw field
// when it is not one we know.
@(private="file")
write_sysreg :: proc(sb: ^strings.Builder, value: i64, uppercase: bool) {
name, ok := sysreg_name(value)
if !ok {
strings.write_byte(sb, '#')
write_signed_decimal(sb, value)
return
}
for i in 0 ..< len(name) {
c := name[i]
if uppercase && c >= 'a' && c <= 'z' {
strings.write_byte(sb, c - 'a' + 'A')
} else {
strings.write_byte(sb, c)
}
}
}
@(private="file")
write_vector_shape :: proc(sb: ^strings.Builder, r: Register, size: u8, uppercase: bool) {
shape := ""

View File

@@ -405,3 +405,265 @@ MFAR_EL3 :: i64(0x7305) // 3 6 6 0 5 (Multiple FAR)
// ---- Performance Monitor extras ----
PMCCFILTR_EL0 :: i64(0x5F7F) // 3 3 14 15 7
PMUSERENR_EL0_REPEAT :: PMUSERENR_EL0 // re-export alias placeholder
// -----------------------------------------------------------------------------
// Value -> name, for printing
// -----------------------------------------------------------------------------
//
// MRS/MSR carry the system register as a packed 15-bit field, so a disassembly
// has a number where an assembler wants a name. Sorted by value; binary search.
//
// Six encodings have two names (a read view and a write view, or an alias
// added by a later extension); the first by source order wins, so a round-trip
// can come back spelled as the sibling.
Sysreg_Name :: struct {
value: u16,
name: string,
}
@(rodata)
SYSREG_NAMES := [?]Sysreg_Name{
{0x0010, "mdccint_el1"},
{0x0012, "mdscr_el1"},
{0x0084, "oslar_el1"},
{0x008C, "oslsr_el1"},
{0x1080, "mdrar_el1"},
{0x1A20, "dbgdtr_el0"},
{0x1A28, "dbgdtrrx_el0"},
{0x1BC6, "dbgclaimset_el1"},
{0x1BCE, "dbgclaimclr_el1"},
{0x1BF6, "dbgauthstatus_el1"},
{0x4000, "midr_el1"},
{0x4005, "mpidr_el1"},
{0x4008, "id_pfr0_el1"},
{0x4009, "id_pfr1_el1"},
{0x400A, "id_dfr0_el1"},
{0x400B, "id_afr0_el1"},
{0x400C, "id_mmfr0_el1"},
{0x400D, "id_mmfr1_el1"},
{0x400E, "id_mmfr2_el1"},
{0x400F, "id_mmfr3_el1"},
{0x4010, "id_isar0_el1"},
{0x4011, "id_isar1_el1"},
{0x4012, "id_isar2_el1"},
{0x4013, "id_isar3_el1"},
{0x4014, "id_isar4_el1"},
{0x4015, "id_isar5_el1"},
{0x4017, "id_isar6_el1"},
{0x4018, "mvfr0_el1"},
{0x4019, "mvfr1_el1"},
{0x401A, "mvfr2_el1"},
{0x4020, "id_aa64pfr0_el1"},
{0x4021, "id_aa64pfr1_el1"},
{0x4024, "id_aa64zfr0_el1"},
{0x4025, "id_aa64smfr0_el1"},
{0x4028, "id_aa64dfr0_el1"},
{0x4029, "id_aa64dfr1_el1"},
{0x402A, "id_aa64dfr2_el1"},
{0x402C, "id_aa64afr0_el1"},
{0x402D, "id_aa64afr1_el1"},
{0x402E, "id_mmfr5_el1"},
{0x4030, "id_aa64isar0_el1"},
{0x4031, "id_aa64isar1_el1"},
{0x4032, "id_aa64isar2_el1"},
{0x4033, "id_aa64isar3_el1"},
{0x4036, "id_mmfr4_el1"},
{0x4038, "id_aa64mmfr0_el1"},
{0x4039, "id_aa64mmfr1_el1"},
{0x403A, "id_aa64mmfr2_el1"},
{0x4080, "sctlr_el1"},
{0x4081, "actlr_el1"},
{0x4082, "cpacr_el1"},
{0x4100, "ttbr0_el1"},
{0x4101, "ttbr1_el1"},
{0x4200, "spsr_el1"},
{0x4201, "elr_el1"},
{0x4208, "sp_el0"},
{0x4212, "current_el"},
{0x4282, "tcr_el1"},
{0x4288, "afsr0_el1"},
{0x4289, "afsr1_el1"},
{0x4290, "zcr_el1"},
{0x4296, "smcr_el1"},
{0x4298, "erridr_el1"},
{0x4299, "errselr_el1"},
{0x42A0, "erxfr_el1"},
{0x42A1, "erxctlr_el1"},
{0x42A2, "erxstatus_el1"},
{0x42A3, "erxaddr_el1"},
{0x42A8, "erxmisc0_el1"},
{0x42A9, "erxmisc1_el1"},
{0x42AA, "erxmisc2_el1"},
{0x42AB, "erxmisc3_el1"},
{0x4300, "tfsr_el1"},
{0x4301, "tfsre0_el1"},
{0x4318, "apiakeylo_el1"},
{0x4319, "apiakeyhi_el1"},
{0x431A, "apibkeylo_el1"},
{0x431B, "apibkeyhi_el1"},
{0x4320, "apdakeylo_el1"},
{0x4321, "apdakeyhi_el1"},
{0x4322, "apdbkeylo_el1"},
{0x4323, "apdbkeyhi_el1"},
{0x4328, "apgakeylo_el1"},
{0x4329, "apgakeyhi_el1"},
{0x4380, "par_el1"},
{0x4510, "mair_el1"},
{0x4518, "amair_el1"},
{0x4520, "lorsa_el1"},
{0x4521, "lorea_el1"},
{0x4522, "lorn_el1"},
{0x4523, "lorc_el1"},
{0x4527, "lorid_el1"},
{0x4600, "vbar_el1"},
{0x4608, "isr_el1"},
{0x4609, "disr_el1"},
{0x4630, "icc_pmr_el1"},
{0x4681, "contextidr_el1"},
{0x4684, "tpidr_el1"},
{0x4708, "cntkctl_el1"},
{0x4800, "ccsidr_el1"},
{0x4801, "clidr_el1"},
{0x4804, "gmid_el1"},
{0x4948, "pmscr_el1"},
{0x494A, "pmsicr_el1"},
{0x494B, "pmsirr_el1"},
{0x494C, "pmsfcr_el1"},
{0x494D, "pmsevfr_el1"},
{0x494E, "pmslatfr_el1"},
{0x494F, "pmsidr_el1"},
{0x4950, "pmblimitr_el1"},
{0x4951, "pmbptr_el1"},
{0x4953, "pmbsr_el1"},
{0x4957, "pmbidr_el1"},
{0x4B90, "trblimitr_el1"},
{0x4B91, "trbptr_el1"},
{0x4B92, "trbbaser_el1"},
{0x4B93, "trbsr_el1"},
{0x4B94, "trbmar_el1"},
{0x4B96, "trbtrg_el1"},
{0x4B97, "trbidr_el1"},
{0x4C40, "icc_iar0_el1"},
{0x4C41, "icc_eoir0_el1"},
{0x4C42, "icc_hppir0_el1"},
{0x4C43, "icc_bpr0_el1"},
{0x4C59, "icc_dir_el1"},
{0x4C5B, "icc_rpr_el1"},
{0x4C60, "icc_iar1_el1"},
{0x4C61, "icc_eoir1_el1"},
{0x4C62, "icc_hppir1_el1"},
{0x4C63, "icc_bpr1_el1"},
{0x4C64, "icc_ctlr_el1"},
{0x4C65, "icc_sre_el1"},
{0x4C66, "icc_igrpen0_el1"},
{0x4C67, "icc_igrpen1_el1"},
{0x4CE1, "pmintenset_el1"},
{0x4CE2, "pmintenclr_el1"},
{0x5000, "csselr_el1"},
{0x5290, "esr_el1"},
{0x5300, "far_el1"},
{0x5801, "ctr_el0"},
{0x5807, "dczid_el0"},
{0x5920, "rndr"},
{0x5921, "rndrrs"},
{0x5A10, "nzcv"},
{0x5A11, "daif"},
{0x5A20, "fpcr"},
{0x5A21, "fpsr"},
{0x5A22, "svcr"},
{0x5A28, "dspsr_el0"},
{0x5A29, "dlr_el0"},
{0x5CDD, "icc_sgi1r_el1"},
{0x5CDE, "icc_asgi1r_el1"},
{0x5CDF, "icc_sgi0r_el1"},
{0x5CE0, "pmcr_el0"},
{0x5CE1, "pmcntenset_el0"},
{0x5CE2, "pmcntenclr_el0"},
{0x5CE3, "pmovsclr_el0"},
{0x5CE4, "pmswinc_el0"},
{0x5CE5, "pmselr_el0"},
{0x5CE6, "pmceid0_el0"},
{0x5CE7, "pmceid1_el0"},
{0x5CE8, "pmccntr_el0"},
{0x5CF0, "pmuserenr_el0"},
{0x5E82, "tpidr_el0"},
{0x5E83, "tpidrro_el0"},
{0x5E85, "tpidr2_el0"},
{0x5F00, "cntfrq_el0"},
{0x5F01, "cntpct_el0"},
{0x5F02, "cntvct_el0"},
{0x5F10, "cntp_tval_el0"},
{0x5F11, "cntp_ctl_el0"},
{0x5F12, "cntp_cval_el0"},
{0x5F18, "cntv_tval_el0"},
{0x5F19, "cntv_ctl_el0"},
{0x5F1A, "cntv_cval_el0"},
{0x5F7F, "pmccfiltr_el0"},
{0x6080, "sctlr_el2"},
{0x6088, "hcr_el2"},
{0x6089, "mdcr_el2"},
{0x608B, "hstr_el2"},
{0x6108, "vttbr_el2"},
{0x610A, "vtcr_el2"},
{0x6180, "dacr32_el2"},
{0x6200, "spsr_el2"},
{0x6201, "elr_el2"},
{0x6208, "sp_el1"},
{0x628B, "vsesr_el2"},
{0x6290, "esr_el2"},
{0x6296, "smcr_el2"},
{0x6298, "fpexc32_el2"},
{0x6300, "far_el2"},
{0x6600, "vbar_el2"},
{0x6609, "vdisr_el2"},
{0x6708, "cnthctl_el2"},
{0x6710, "cnthp_tval_el2"},
{0x6711, "cnthp_ctl_el2"},
{0x6712, "cnthp_cval_el2"},
{0x6718, "cnthv_tval_el2"},
{0x6719, "cnthv_ctl_el2"},
{0x671A, "cnthv_cval_el2"},
{0x671B, "cntvoff_el2"},
{0x6C65, "icc_sre_el2"},
{0x6CD8, "ich_hcr_el2"},
{0x6CD9, "ich_vtr_el2"},
{0x6CDA, "ich_misr_el2"},
{0x6CDB, "ich_eisr_el2"},
{0x6CDD, "ich_elrsr_el2"},
{0x6CDF, "ich_vmcr_el2"},
{0x6E82, "tpidr_el2"},
{0x7080, "sctlr_el3"},
{0x70B4, "gptbr_el3"},
{0x70B6, "gpccr_el3"},
{0x7200, "spsr_el3"},
{0x7201, "elr_el3"},
{0x7290, "zcr_el3"},
{0x7305, "mfar_el3"},
{0x7600, "vbar_el3"},
{0x7682, "tpidr_el3"},
{0x7C64, "icc_ctlr_el3"},
{0x7C65, "icc_sre_el3"},
{0x7C67, "icc_igrpen1_el3"},
{0x7F10, "cntps_tval_el1"},
{0x7F11, "cntps_ctl_el1"},
{0x7F12, "cntps_cval_el1"},
}
// Name for a packed system-register field, or ok=false when it is not one we
// know -- callers should fall back to printing the raw immediate.
@(require_results)
sysreg_name :: proc "contextless" (value: i64) -> (name: string, ok: bool) {
v := u16(value & 0x7FFF)
lo, hi := 0, len(SYSREG_NAMES) - 1
for lo <= hi {
mid := (lo + hi) / 2
e := SYSREG_NAMES[mid]
switch {
case e.value == v: return e.name, true
case e.value < v: lo = mid + 1
case: hi = mid - 1
}
}
return "", false
}