commit dba8886b09e7bd057a9bcdc43c9588d458cbcf02
parent 37e9c50a8723354b323469550c4a38943eace07f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 13 May 2026 07:44:31 -0700
Validate AArch64 SP and ZR asm operands
Diffstat:
2 files changed, 73 insertions(+), 0 deletions(-)
diff --git a/src/arch/aa64_asm.c b/src/arch/aa64_asm.c
@@ -223,6 +223,16 @@ static AA64Reg parse_ldstp_reg(AsmDriver* d) {
return r;
}
+static void reject_sp_reg(AsmDriver* d, AA64Reg r, const char* what) {
+ if (r.is_sp) asm_driver_panic(d, "asm: %s: SP register not allowed", what);
+}
+
+static void require_sp_spelling(AsmDriver* d, AA64Reg r, const char* what) {
+ if (r.num == 31u && !r.is_sp)
+ asm_driver_panic(d, "asm: %s: zero register not allowed in SP operand",
+ what);
+}
+
/* Parse "#imm" (with optional + / -) or a bare expression — GNU as is
* lenient about the leading hash. Returns an i64. */
static i64 parse_imm_const(AsmDriver* d) {
@@ -387,6 +397,8 @@ static void p_mov(AsmDriver* d) {
/* mov involving SP encodes as `ADD Rd, Rsp, #0` per AArch64;
* approximate with that exact form. */
if (rd.is_sp || src.is_sp) {
+ require_sp_spelling(d, rd, "mov sp");
+ require_sp_spelling(d, src, "mov sp");
emit32(d, aa64_add_imm(rd.is64, rd.num, src.num, 0, 0));
return;
}
@@ -515,6 +527,14 @@ static void p_addsub(AsmDriver* d, int is_sub, int set_flags) {
if (tok_punct(t, '#') || t.kind == TOK_NUM || tok_punct(t, '-') ||
tok_punct(t, '+')) {
/* immediate form */
+ if (rd.is64 != rn.is64)
+ asm_driver_panic(d, "asm: add/sub imm: width mismatch");
+ require_sp_spelling(d, rn, "add/sub imm");
+ if (set_flags) {
+ reject_sp_reg(d, rd, "add/sub imm");
+ } else {
+ require_sp_spelling(d, rd, "add/sub imm");
+ }
i64 imm = parse_imm_const(d);
u32 sh = 0;
if (asm_driver_eat_comma(d)) {
@@ -540,6 +560,9 @@ static void p_addsub(AsmDriver* d, int is_sub, int set_flags) {
}
/* register form */
AA64Reg rm = parse_reg(d);
+ reject_sp_reg(d, rd, "add/sub reg");
+ reject_sp_reg(d, rn, "add/sub reg");
+ reject_sp_reg(d, rm, "add/sub reg");
if (rd.is64 != rm.is64 || rd.is64 != rn.is64)
asm_driver_panic(d, "asm: add/sub reg: width mismatch");
u32 shift = 0, imm6 = 0;
@@ -561,6 +584,7 @@ static void p_cmp(AsmDriver* d, int is_neg /* cmn flips op */) {
Tok t = asm_driver_peek(d);
if (tok_punct(t, '#') || t.kind == TOK_NUM || tok_punct(t, '-') ||
tok_punct(t, '+')) {
+ require_sp_spelling(d, rn, "cmp imm");
i64 imm = parse_imm_const(d);
u32 sh = 0;
if (asm_driver_eat_comma(d)) {
@@ -587,6 +611,8 @@ static void p_cmp(AsmDriver* d, int is_neg /* cmn flips op */) {
return;
}
AA64Reg rm = parse_reg(d);
+ reject_sp_reg(d, rn, "cmp reg");
+ reject_sp_reg(d, rm, "cmp reg");
if (rm.is64 != rn.is64) asm_driver_panic(d, "asm: cmp: width mismatch");
u32 shift = 0, imm6 = 0;
if (asm_driver_eat_comma(d)) parse_shift_mod(d, &shift, &imm6);
@@ -619,6 +645,8 @@ static void p_neg(AsmDriver* d, int set_flags) {
AA64Reg rd = parse_reg(d);
expect_comma(d, "neg");
AA64Reg rm = parse_reg(d);
+ reject_sp_reg(d, rd, "neg");
+ reject_sp_reg(d, rm, "neg");
if (rd.is64 != rm.is64) asm_driver_panic(d, "asm: neg: width mismatch");
u32 shift = 0, imm6 = 0;
if (asm_driver_eat_comma(d)) parse_shift_mod(d, &shift, &imm6);
@@ -777,6 +805,7 @@ static AA64Mem parse_mem(AsmDriver* d) {
m.base = parse_reg(d);
if (!m.base.is64)
asm_driver_panic(d, "asm: ldr/str: base register must be 64-bit");
+ require_sp_spelling(d, m.base, "ldr/str base");
if (asm_driver_eat_comma(d)) {
m.imm = parse_imm_const(d);
m.has_offset = 1;
@@ -791,6 +820,7 @@ static AA64Mem parse_mem(AsmDriver* d) {
* alignment of imm. */
static void p_ldr_str(AsmDriver* d, int is_load) {
AA64Reg rt = parse_reg(d);
+ reject_sp_reg(d, rt, "ldr/str");
expect_comma(d, "ldr/str");
AA64Mem m = parse_mem(d);
u32 size = rt.is64 ? 3u : 2u;
@@ -824,6 +854,7 @@ static void p_ldr_str(AsmDriver* d, int is_load) {
/* ldur/stur — unscaled signed-imm9. */
static void p_ldur_stur(AsmDriver* d, int is_load) {
AA64Reg rt = parse_reg(d);
+ reject_sp_reg(d, rt, "ldur/stur");
expect_comma(d, "ldur/stur");
AA64Mem m = parse_mem(d);
u32 size = rt.is64 ? 3u : 2u;
@@ -843,6 +874,8 @@ static void p_ldp_stp(AsmDriver* d, int is_load) {
expect_comma(d, "ldp/stp");
AA64Reg rt2 = parse_ldstp_reg(d);
expect_comma(d, "ldp/stp");
+ reject_sp_reg(d, rt, "ldp/stp");
+ reject_sp_reg(d, rt2, "ldp/stp");
if (rt.is64 != rt2.is64 || rt.is_fp != rt2.is_fp)
asm_driver_panic(d, "asm: ldp/stp: width mismatch");
AA64Mem m = parse_mem(d);
diff --git a/test/arch/aa64_inline_test.c b/test/arch/aa64_inline_test.c
@@ -81,6 +81,7 @@ static int g_fail = 0;
* (no bare hex literals as load-bearing values). */
#define EXPECTED_MOV_W0_W9 0x2a0903e0u /* mov w0, w9 ≡ orr w0, wzr, w9 */
#define EXPECTED_SVC_0 0xd4000001u /* svc #0 */
+#define EXPECTED_ADD_W0_WSP_7 0x11001fe0u /* add w0, wsp, #7 */
static u32 read_word_le(const Section* s, u32 ofs) {
u8 b[4];
@@ -245,6 +246,45 @@ int main(void) {
}
}
+ /* ---- smoke case 5: ADD/SUB immediate treats reg31 as SP, not ZR ---- */
+ {
+ u32 start = mc->pos(mc);
+ target->asm_block(target, "add w0, wsp, #7",
+ NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
+ u32 end = mc->pos(mc);
+ EXPECT(end - start == 4u, "smoke5: expected 4 bytes, got %u",
+ (end - start));
+ if (end - start == 4u) {
+ const Section* sec = obj_section_get(ob, text_sec);
+ u32 w = read_word_le(sec, start);
+ EXPECT(w == EXPECTED_ADD_W0_WSP_7,
+ "smoke5: add w0, wsp, #7 = 0x%08x, want 0x%08x", w,
+ EXPECTED_ADD_W0_WSP_7);
+ }
+ }
+
+ /* ---- smoke case 6: ZR spelling is rejected in SP-only slots ---- */
+ {
+ int saw_panic = 0;
+ if (setjmp(c->panic) == 0) {
+ target->asm_block(target, "add w0, wzr, #7",
+ NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
+ } else {
+ saw_panic = 1;
+ }
+ EXPECT(saw_panic, "smoke6: expected add w0, wzr, #7 to panic");
+ }
+ {
+ int saw_panic = 0;
+ if (setjmp(c->panic) == 0) {
+ target->asm_block(target, "ldr w0, [xzr]",
+ NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
+ } else {
+ saw_panic = 1;
+ }
+ EXPECT(saw_panic, "smoke6: expected ldr w0, [xzr] to panic");
+ }
+
cfree_compiler_free(cc);
if (g_fail) {