kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 5fec69282f697c6a701638e7da969c3f60fd52b0
parent 84863072e412579624a0d6c9f304344ebc4b1289
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed,  3 Jun 2026 14:53:11 -0700

Add AA64 system register asm support

Diffstat:
Msrc/arch/aa64/asm.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/aa64/isa.c | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/aa64/isa.h | 41+++++++++++++++++++++++++++++++++++++++++
Mtest/arch/aa64_inline_test.c | 17+++++++++++++++++
4 files changed, 247 insertions(+), 0 deletions(-)

diff --git a/src/arch/aa64/asm.c b/src/arch/aa64/asm.c @@ -669,6 +669,53 @@ static void p_clrex(AsmDriver* d) { emit32(d, aa64_clrex(opt)); } +/* System-register access (MRS/MSR register form). + * + * mrs Xt, <sysreg> ; read system register into Xt + * msr <sysreg>, Xt ; write Xt into the system register + * + * A system register is named (resolved against the shared isa.c name table + * via aa64_sysreg_by_name) or given by the architectural generic spelling + * S<op0>_<op1>_C<crn>_C<crm>_<op2> (e.g. s3_3_c13_c0_2 == tpidr_el0), which + * covers any encodable register. The immediate PSTATE forms (msr daifset, + * #imm / spsel, #imm) are not handled. */ + +/* Consume the next token as a system-register name into the 5 fields. */ +static void parse_sysreg(AsmDriver* d, const char* what, u32* op0, u32* op1, + u32* crn, u32* crm, u32* op2) { + AsmTok t = asm_driver_peek(d); + if (t.kind != ASM_TOK_IDENT) + asm_driver_panic(d, "asm: expected system register"); + (void)asm_driver_next(d); + Slice sl = pool_slice(asm_driver_pool(d), t.v.ident); + if (!aa64_sysreg_by_name(sl.s, sl.len, op0, op1, crn, crm, op2)) + asm_driver_panic(d, what); +} + +static void p_mrs_(AsmDriver* d) { + AA64Reg rt = parse_reg(d); + if (!rt.is64 || rt.is_sp) + asm_driver_panic(d, "asm: mrs: destination must be a 64-bit GPR"); + expect_comma(d, "mrs"); + u32 op0, op1, crn, crm, op2; + parse_sysreg(d, "asm: mrs: unknown system register", &op0, &op1, &crn, &crm, + &op2); + emit32(d, aa64_sysreg_move(/*is_read=*/1, op0, op1, crn, crm, op2, rt.num)); +} + +static void p_msr_(AsmDriver* d) { + u32 op0, op1, crn, crm, op2; + parse_sysreg(d, + "asm: msr: unknown system register (immediate PSTATE forms " + "like daifset are unsupported)", + &op0, &op1, &crn, &crm, &op2); + expect_comma(d, "msr"); + AA64Reg rt = parse_reg(d); + if (!rt.is64 || rt.is_sp) + asm_driver_panic(d, "asm: msr: source must be a 64-bit GPR"); + emit32(d, aa64_sysreg_move(/*is_read=*/0, op0, op1, crn, crm, op2, rt.num)); +} + /* mov: * mov Rd, Rm → ORR Rd, ZR, Rm * mov Rd, #imm → MOVZ (if imm fits in a single halfword unshifted) @@ -2254,6 +2301,8 @@ static const AA64Mn kTable[] = { {"svc", p_svc_, 0}, {"brk", p_brk_, 0}, {"hlt", p_hlt_, 0}, + {"mrs", p_mrs_, 0}, + {"msr", p_msr_, 0}, {"ldr", p_ldr_, 0}, {"str", p_str_, 0}, {"ldrb", p_ldrb, 0}, diff --git a/src/arch/aa64/isa.c b/src/arch/aa64/isa.c @@ -33,6 +33,89 @@ #define AA64_LDST_OPC_LDRS_X 2u #define AA64_LDST_OPC_LDRS_W 3u +static const AA64SysRegName aa64_sysreg_names[] = { + {"tpidr_el0", 3, 3, 13, 0, 2}, {"tpidrro_el0", 3, 3, 13, 0, 3}, + {"tpidr_el1", 3, 0, 13, 0, 4}, {"midr_el1", 3, 0, 0, 0, 0}, + {"mpidr_el1", 3, 0, 0, 0, 5}, {"ctr_el0", 3, 3, 0, 0, 1}, + {"dczid_el0", 3, 3, 0, 0, 7}, {"nzcv", 3, 3, 4, 2, 0}, + {"daif", 3, 3, 4, 2, 1}, {"fpcr", 3, 3, 4, 4, 0}, + {"fpsr", 3, 3, 4, 4, 1}, +}; + +static char aa64_lc(char c) { + return (c >= 'A' && c <= 'Z') ? (char)(c + ('a' - 'A')) : c; +} + +static int aa64_icase_eq(const char* a, size_t an, const char* b) { + size_t i = 0; + for (; i < an && b[i]; ++i) { + if (aa64_lc(a[i]) != aa64_lc(b[i])) return 0; + } + return i == an && b[i] == '\0'; +} + +static int aa64_sysreg_rd_field(const char* s, size_t n, size_t* i, u32* out) { + u32 v = 0; + if (*i >= n || s[*i] < '0' || s[*i] > '9') return 0; + while (*i < n && s[*i] >= '0' && s[*i] <= '9') { + v = v * 10u + (u32)(s[*i] - '0'); + if (v > 15u) return 0; + ++(*i); + } + *out = v; + return 1; +} + +static int aa64_parse_generic_sysreg(const char* s, size_t n, u32* op0, + u32* op1, u32* crn, u32* crm, u32* op2) { + size_t i = 0; + if (i >= n || aa64_lc(s[i]) != 's') return 0; + ++i; + if (!aa64_sysreg_rd_field(s, n, &i, op0)) return 0; + if (i >= n || s[i] != '_') return 0; + ++i; + if (!aa64_sysreg_rd_field(s, n, &i, op1)) return 0; + if (i + 1 >= n || s[i] != '_' || aa64_lc(s[i + 1]) != 'c') return 0; + i += 2; + if (!aa64_sysreg_rd_field(s, n, &i, crn)) return 0; + if (i + 1 >= n || s[i] != '_' || aa64_lc(s[i + 1]) != 'c') return 0; + i += 2; + if (!aa64_sysreg_rd_field(s, n, &i, crm)) return 0; + if (i >= n || s[i] != '_') return 0; + ++i; + if (!aa64_sysreg_rd_field(s, n, &i, op2)) return 0; + return i == n && *op0 <= 3u && *op1 <= 7u && *crn <= 15u && *crm <= 15u && + *op2 <= 7u; +} + +int aa64_sysreg_by_name(const char* s, size_t n, u32* op0, u32* op1, u32* crn, + u32* crm, u32* op2) { + size_t k; + if (!s || !n) return 0; + for (k = 0; k < sizeof aa64_sysreg_names / sizeof aa64_sysreg_names[0]; ++k) { + if (aa64_icase_eq(s, n, aa64_sysreg_names[k].name)) { + *op0 = aa64_sysreg_names[k].op0; + *op1 = aa64_sysreg_names[k].op1; + *crn = aa64_sysreg_names[k].crn; + *crm = aa64_sysreg_names[k].crm; + *op2 = aa64_sysreg_names[k].op2; + return 1; + } + } + return aa64_parse_generic_sysreg(s, n, op0, op1, crn, crm, op2); +} + +const char* aa64_sysreg_name(u32 op0, u32 op1, u32 crn, u32 crm, u32 op2) { + size_t k; + for (k = 0; k < sizeof aa64_sysreg_names / sizeof aa64_sysreg_names[0]; ++k) { + const AA64SysRegName* r = &aa64_sysreg_names[k]; + if (r->op0 == op0 && r->op1 == op1 && r->crn == crn && r->crm == crm && + r->op2 == op2) + return r->name; + } + return NULL; +} + const AA64InsnDesc aa64_insn_table[] = { /* ----- Move-wide immediate (MOVN / MOVZ / MOVK) ----- */ {MN("movn"), 0x12800000u, 0x7F800000u, AA64_FMT_MOVEWIDE, 0, {0, 0}}, @@ -433,6 +516,22 @@ const AA64InsnDesc aa64_insn_table[] = { {MN("isb"), 0xD50330DFu, 0xFFFFF0FFu, AA64_FMT_BARRIER, 0, {0, 0}}, {MN("clrex"), 0xD503305Fu, 0xFFFFF0FFu, AA64_FMT_BARRIER, 0, {0, 0}}, + /* ----- System-register move (MRS read / MSR write, register form) ----- + * The selector op0:op1:CRn:CRm:op2 and Rt decode from the word; the mask + * pins bits[31:20] (fixes L and op0's high bit), leaving the rest. */ + {MN("mrs"), + AA64_MRS_MATCH, + AA64_SYSREG_MOVE_MASK, + AA64_FMT_SYSREG, + 0, + {0, 0}}, + {MN("msr"), + AA64_MSR_MATCH, + AA64_SYSREG_MOVE_MASK, + AA64_FMT_SYSREG, + 0, + {0, 0}}, + /* ----- Data-processing (1 source): RBIT / REV16 / REV / CLZ ----- * sf (bit31) free; opcode2 in bits[15:10] selects the operation. REV has * a 32-bit (opcode2=000010) and 64-bit (000011) encoding, both "rev". */ @@ -974,6 +1073,44 @@ static void print_barrier(StrBuf* sb, u32 w, const AA64InsnDesc* desc) { } } +static void print_sysreg_name(StrBuf* sb, u32 op0, u32 op1, u32 crn, u32 crm, + u32 op2) { + const char* name = aa64_sysreg_name(op0, op1, crn, crm, op2); + if (name) { + strbuf_puts(sb, name); + return; + } + strbuf_putc(sb, 's'); + strbuf_put_u64(sb, (u64)op0); + strbuf_putc(sb, '_'); + strbuf_put_u64(sb, (u64)op1); + strbuf_puts(sb, "_c"); + strbuf_put_u64(sb, (u64)crn); + strbuf_puts(sb, "_c"); + strbuf_put_u64(sb, (u64)crm); + strbuf_putc(sb, '_'); + strbuf_put_u64(sb, (u64)op2); +} + +static void print_sysreg(StrBuf* sb, u32 w) { + int is_read = (int)((w >> 21) & 1u); + u32 op0 = 2u | ((w >> 19) & 1u); + u32 op1 = (w >> 16) & 7u; + u32 crn = (w >> 12) & 0xfu; + u32 crm = (w >> 8) & 0xfu; + u32 op2 = (w >> 5) & 7u; + u32 rt = w & 0x1fu; + if (is_read) { + emit_reg(sb, rt, /*sf=*/1, /*sp_means_sp=*/0); + strbuf_puts(sb, ", "); + print_sysreg_name(sb, op0, op1, crn, crm, op2); + } else { + print_sysreg_name(sb, op0, op1, crn, crm, op2); + strbuf_puts(sb, ", "); + emit_reg(sb, rt, /*sf=*/1, /*sp_means_sp=*/0); + } +} + /* FP scalar size from the 2-bit ftype field: 00=single, 01=double, 11=half. */ static char aa64_ftype_prefix(u32 ftype) { return ftype == 0u ? 's' : (ftype == 1u ? 'd' : 'h'); @@ -1245,6 +1382,9 @@ void aa64_print_operands(StrBuf* sb, const AA64InsnDesc* desc, u32 word, case AA64_FMT_LOG_IMM: print_logimm(sb, word); break; + case AA64_FMT_SYSREG: + print_sysreg(sb, word); + break; case AA64_FMT_LDST_REGOFF: print_ldst_regoff(sb, word); break; diff --git a/src/arch/aa64/isa.h b/src/arch/aa64/isa.h @@ -69,6 +69,7 @@ typedef enum AA64Format { AA64_FMT_LDST_EXCL, /* load/store exclusive + acquire/release ordered * (LDXR/LDAXR/STXR/STLXR/LDAR/STLR + b/h) */ AA64_FMT_LOG_IMM, /* logical, immediate (AND/ORR/EOR/ANDS #bitmask) */ + AA64_FMT_SYSREG, /* system-register move (MRS Xt,<reg> / MSR <reg>,Xt) */ } AA64Format; /* ---- AsmFlags column on AA64InsnDesc ---- @@ -1081,6 +1082,46 @@ static inline u32 aa64_msr_daifclr(u32 imm4) { } /* ==================================================================== + * Generic system-register move (MRS/MSR register form). A named system + * register is the 15-bit selector op0:op1:CRn:CRm:op2 (op0's high bit is + * fixed by the encoding, so only its low bit is a field). + * MRS Xt, <sysreg> : 1101 0101 0 0 1 op0lo op1 CRn CRm op2 Rt (read, L=1) + * MSR <sysreg>, Xt : 1101 0101 0 0 0 op0lo op1 CRn CRm op2 Rt (write, L=0) + * e.g. TPIDR_EL0 = (op0=3,op1=3,CRn=13,CRm=0,op2=2): + * MSR TPIDR_EL0, X0 -> 0xd51bd040 ; MRS X0, TPIDR_EL0 -> 0xd53bd040. */ +static inline u32 aa64_sysreg_move(int is_read, u32 op0, u32 op1, u32 crn, + u32 crm, u32 op2, u32 rt) { + return 0xd5000000u | (is_read ? (1u << 21) : 0u) | ((op0 & 3u) << 19) | + ((op1 & 7u) << 16) | ((crn & 0xfu) << 12) | ((crm & 0xfu) << 8) | + ((op2 & 7u) << 5) | (rt & 0x1fu); +} + +/* System-register move encoding family: MRS (read, L=1) and MSR (write, + * L=0). The disassembler matches these; the register selector op0:op1:CRn: + * CRm:op2 and Rt are decoded from the word. op0's high bit is fixed (bit + * 20), so the mask pins bits[31:20] and leaves op0lo/op1/CRn/CRm/op2/Rt. */ +#define AA64_MRS_MATCH 0xd5300000u +#define AA64_MSR_MATCH 0xd5100000u +#define AA64_SYSREG_MOVE_MASK 0xfff00000u + +/* Shared system-register name table (single source for the assembler's + * name->selector parse and the disassembler's selector->name print). */ +typedef struct AA64SysRegName { + const char* name; + u8 op0, op1, crn, crm, op2; +} AA64SysRegName; + +/* Resolve a system-register name (case-insensitive, length n) to its five + * selector fields. Returns 1 on a hit, 0 otherwise. */ +int aa64_sysreg_by_name(const char* s, size_t n, u32* op0, u32* op1, u32* crn, + u32* crm, u32* op2); + +/* Reverse lookup: canonical lowercase name for a selector, or NULL when the + * selector is not in the table (the caller prints the generic Sx_x_Cx_Cx_x + * spelling instead). */ +const char* aa64_sysreg_name(u32 op0, u32 op1, u32 crn, u32 crm, u32 op2); + +/* ==================================================================== * Load/store pair, signed-offset (STP / LDP, no pre/post-increment). * opc(2) 101 V(1) 010 L(1) imm7 Rt2 Rn Rt (bit 23 = 0) * diff --git a/test/arch/aa64_inline_test.c b/test/arch/aa64_inline_test.c @@ -7,6 +7,11 @@ #define EXPECTED_MOV_X1_X5 0xaa0503e1u #define EXPECTED_NOP 0xd503201fu #define EXPECTED_ADD_W0_WSP_7 0x11001fe0u +/* System-register move (MRS/MSR register form). TPIDR_EL0 is S3_3_C13_C0_2. + * MSR TPIDR_EL0, X0 : 0xd51bd040 | Rt (write, L=0) + * MRS X1, TPIDR_EL0 : 0xd53bd040 | Rt (read, L=1) */ +#define EXPECTED_MSR_TPIDR_X0 0xd51bd040u +#define EXPECTED_MRS_X1_TPIDR 0xd53bd041u static void put32le(uint8_t out[4], uint32_t v) { out[0] = (uint8_t)v; @@ -22,6 +27,10 @@ static void aa64_body(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { it_inline_asm(c, cg, "mov x1, x5", NULL, 0, NULL, 0, NULL, 0); it_inline_asm(c, cg, "nop ; nop", NULL, 0, NULL, 0, NULL, 0); + /* System-register thread-pointer access (used by the freestanding cross + * startup stub to seed TPIDR_EL0). */ + it_inline_asm(c, cg, "msr tpidr_el0, x0", NULL, 0, NULL, 0, NULL, 0); + it_inline_asm(c, cg, "mrs x1, tpidr_el0", NULL, 0, NULL, 0, NULL, 0); kit_cg_push_int(cg, 7, i64_ty); imm = it_asm_op(c, "i", "imm", i64_ty, KIT_CG_ASM_IN); @@ -62,6 +71,14 @@ int main(void) { put32le(pat, EXPECTED_ADD_W0_WSP_7); IT_EXPECT(&env, it_contains(text.data, text.len, pat, 4), "missing add w0, wsp, #7 immediate-substitution encoding"); + + put32le(pat, EXPECTED_MSR_TPIDR_X0); + IT_EXPECT(&env, it_contains(text.data, text.len, pat, 4), + "missing msr tpidr_el0, x0 encoding"); + + put32le(pat, EXPECTED_MRS_X1_TPIDR); + IT_EXPECT(&env, it_contains(text.data, text.len, pat, 4), + "missing mrs x1, tpidr_el0 encoding"); } it_text_close(&text);