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:
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);