From 01bdf7522c6548ab0f59633b6ee206d722653923 Mon Sep 17 00:00:00 2001 From: Brendan Punsky Date: Thu, 27 Aug 2026 08:59:47 -0400 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_018UmHLRF11EoWwNWCJ7JGaA --- core/rexcode/isa/arm64/printer.odin | 36 ++++ core/rexcode/isa/arm64/sysregs.odin | 262 ++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+) diff --git a/core/rexcode/isa/arm64/printer.odin b/core/rexcode/isa/arm64/printer.odin index e0ddea32a..85d8c21c9 100644 --- a/core/rexcode/isa/arm64/printer.odin +++ b/core/rexcode/isa/arm64/printer.odin @@ -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 := "" diff --git a/core/rexcode/isa/arm64/sysregs.odin b/core/rexcode/isa/arm64/sysregs.odin index 32522a820..0e142edf4 100644 --- a/core/rexcode/isa/arm64/sysregs.odin +++ b/core/rexcode/isa/arm64/sysregs.odin @@ -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 +}