commit 83dac6d332d09a32e1faa6bedcad0f7b23df8733
parent 87eb85cdc69c09fe71ad21e4d29c0f832bf05b77
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 21 May 2026 07:50:29 -0700
Improve AA64 compare and cset disassembly
Diffstat:
10 files changed, 198 insertions(+), 12 deletions(-)
diff --git a/src/arch/aa64/asm.c b/src/arch/aa64/asm.c
@@ -644,21 +644,42 @@ static void p_cmp(AsmDriver* d, int is_neg /* cmn flips op */) {
emit32(d, word);
}
-static void p_csinc(AsmDriver* d) {
+static void p_condsel(AsmDriver* d, u32 op, u32 op2, const char* what) {
AA64Reg rd = parse_reg(d);
- expect_comma(d, "csinc");
+ expect_comma(d, what);
AA64Reg rn = parse_reg(d);
- expect_comma(d, "csinc");
+ expect_comma(d, what);
AA64Reg rm = parse_reg(d);
- expect_comma(d, "csinc");
- u32 cond = parse_cond(d, "csinc");
+ expect_comma(d, what);
+ u32 cond = parse_cond(d, what);
if (rd.is_sp || rn.is_sp || rm.is_sp)
- asm_driver_panic(d, "asm: csinc: SP register not allowed");
+ asm_driver_panic(d, "asm: %s: SP register not allowed", what);
if (rd.is64 != rn.is64 || rd.is64 != rm.is64)
- asm_driver_panic(d, "asm: csinc: width mismatch");
- u32 word = 0x1A800400u | ((u32)rd.is64 << 31) | ((rm.num & 0x1fu) << 16) |
- ((cond & 0xfu) << 12) | ((rn.num & 0x1fu) << 5) |
- (rd.num & 0x1fu);
+ asm_driver_panic(d, "asm: %s: width mismatch", what);
+ u32 word = aa64_condsel_pack((AA64CondSel){.sf = (u32)rd.is64,
+ .op = op,
+ .S = 0,
+ .Rm = rm.num,
+ .cond = cond,
+ .op2 = op2,
+ .Rn = rn.num,
+ .Rd = rd.num});
+ emit32(d, word);
+}
+
+static void p_cset_like(AsmDriver* d, u32 op, u32 op2, const char* what) {
+ AA64Reg rd = parse_reg(d);
+ expect_comma(d, what);
+ u32 cond = parse_cond(d, what);
+ if (rd.is_sp) asm_driver_panic(d, "asm: %s: SP register not allowed", what);
+ u32 word = aa64_condsel_pack((AA64CondSel){.sf = (u32)rd.is64,
+ .op = op,
+ .S = 0,
+ .Rm = AA64_ZR,
+ .cond = cond ^ 1u,
+ .op2 = op2,
+ .Rn = AA64_ZR,
+ .Rd = rd.num});
emit32(d, word);
}
@@ -959,7 +980,12 @@ static void p_addsub_sub(AsmDriver* d) { p_addsub(d, 1, 0); }
static void p_addsub_subs(AsmDriver* d) { p_addsub(d, 1, 1); }
static void p_cmp_w(AsmDriver* d) { p_cmp(d, 0); }
static void p_cmn_w(AsmDriver* d) { p_cmp(d, 1); }
-static void p_csinc_(AsmDriver* d) { p_csinc(d); }
+static void p_csel_(AsmDriver* d) { p_condsel(d, 0, 0, "csel"); }
+static void p_csinc_(AsmDriver* d) { p_condsel(d, 0, 1, "csinc"); }
+static void p_csinv_(AsmDriver* d) { p_condsel(d, 1, 0, "csinv"); }
+static void p_csneg_(AsmDriver* d) { p_condsel(d, 1, 1, "csneg"); }
+static void p_cset_(AsmDriver* d) { p_cset_like(d, 0, 1, "cset"); }
+static void p_csetm_(AsmDriver* d) { p_cset_like(d, 1, 0, "csetm"); }
static void p_neg_w(AsmDriver* d) { p_neg(d, 0); }
static void p_negs_w(AsmDriver* d) { p_neg(d, 1); }
static void p_and_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_AND_OPC, 0); }
@@ -1038,7 +1064,12 @@ static const AA64Mn kTable[] = {
{"subs", p_addsub_subs, 0},
{"cmp", p_cmp_w, 0},
{"cmn", p_cmn_w, 0},
+ {"csel", p_csel_, 0},
{"csinc", p_csinc_, 0},
+ {"csinv", p_csinv_, 0},
+ {"csneg", p_csneg_, 0},
+ {"cset", p_cset_, 0},
+ {"csetm", p_csetm_, 0},
{"neg", p_neg_w, 0},
{"negs", p_negs_w, 0},
{"and", p_and_w, 0},
diff --git a/src/arch/aa64/isa.c b/src/arch/aa64/isa.c
@@ -79,6 +79,20 @@ const AA64InsnDesc aa64_insn_table[] = {
{"asrv", 0x1AC02800u, 0x5FE0FC00u, AA64_FMT_DP2, 0, {0, 0}},
{"rorv", 0x1AC02C00u, 0x5FE0FC00u, AA64_FMT_DP2, 0, {0, 0}},
+ /* ----- Conditional select -----
+ * CSET Rd, cond ≡ CSINC Rd, ZR, ZR, invert(cond).
+ * CSETM Rd, cond ≡ CSINV Rd, ZR, ZR, invert(cond).
+ * These aliases are expressible as fixed Rn/Rm=ZR masks, so put them
+ * before the canonical conditional-select rows. */
+ {"cset", 0x1A9F07E0u, 0x7FE00C00u | (0x1Fu << 16) | (0x1Fu << 5),
+ AA64_FMT_CONDSEL, AA64_ASMFL_ALIAS, {0, 0}},
+ {"csetm", 0x5A9F03E0u, 0x7FE00C00u | (0x1Fu << 16) | (0x1Fu << 5),
+ AA64_FMT_CONDSEL, AA64_ASMFL_ALIAS, {0, 0}},
+ {"csel", 0x1A800000u, 0x7FE00C00u, AA64_FMT_CONDSEL, 0, {0, 0}},
+ {"csinc", 0x1A800400u, 0x7FE00C00u, AA64_FMT_CONDSEL, 0, {0, 0}},
+ {"csinv", 0x5A800000u, 0x7FE00C00u, AA64_FMT_CONDSEL, 0, {0, 0}},
+ {"csneg", 0x5A800400u, 0x7FE00C00u, AA64_FMT_CONDSEL, 0, {0, 0}},
+
/* ----- Unconditional branch (register) -----
* RET aliases its no-operand spelling to RET X30 (Rn=11110). The
* tighter row matches when Rn=30 and prints "ret" without operands;
@@ -93,7 +107,12 @@ const AA64InsnDesc aa64_insn_table[] = {
{"adr", 0x10000000u, 0x9F000000u, AA64_FMT_PCREL_ADR, 0, {0, 0}},
{"adrp", 0x90000000u, 0x9F000000u, AA64_FMT_PCREL_ADR, 0, {0, 0}},
- /* ----- Add/Sub immediate ----- */
+ /* ----- Add/Sub immediate -----
+ * CMP/CMN immediate are the Rd=ZR aliases of SUBS/ADDS immediate. */
+ {"cmn", 0x3100001Fu, 0x7F00001Fu, AA64_FMT_ADDSUB_IMM, AA64_ASMFL_ALIAS,
+ {0, 0}},
+ {"cmp", 0x7100001Fu, 0x7F00001Fu, AA64_FMT_ADDSUB_IMM, AA64_ASMFL_ALIAS,
+ {0, 0}},
{"add", 0x11000000u, 0x7F000000u, AA64_FMT_ADDSUB_IMM, 0, {0, 0}},
{"adds", 0x31000000u, 0x7F000000u, AA64_FMT_ADDSUB_IMM, 0, {0, 0}},
{"sub", 0x51000000u, 0x7F000000u, AA64_FMT_ADDSUB_IMM, 0, {0, 0}},
@@ -352,6 +371,24 @@ static void print_dp2(StrBuf* sb, u32 w) {
emit_reg(sb, f.Rm, (int)f.sf, 0);
}
+static void print_condsel(StrBuf* sb, u32 w, const AA64InsnDesc* d) {
+ AA64CondSel f = aa64_condsel_unpack(w);
+ if (d->flags & AA64_ASMFL_ALIAS) {
+ /* CSET / CSETM: Rd, cond. Encoded condition is inverted. */
+ emit_reg(sb, f.Rd, (int)f.sf, 0);
+ strbuf_puts(sb, ", ");
+ emit_cond(sb, f.cond ^ 1u);
+ return;
+ }
+ emit_reg(sb, f.Rd, (int)f.sf, 0);
+ strbuf_puts(sb, ", ");
+ emit_reg(sb, f.Rn, (int)f.sf, 0);
+ strbuf_puts(sb, ", ");
+ emit_reg(sb, f.Rm, (int)f.sf, 0);
+ strbuf_puts(sb, ", ");
+ emit_cond(sb, f.cond);
+}
+
static void print_brreg(StrBuf* sb, u32 w, const AA64InsnDesc* d) {
AA64BrReg f = aa64_brreg_unpack(w);
if (d->flags & AA64_ASMFL_NORN) return; /* RET (with implicit X30) */
@@ -375,6 +412,14 @@ static void print_pcrel(StrBuf* sb, u32 w, u64 vaddr) {
static void print_addsubimm(StrBuf* sb, u32 w) {
AA64AddSubImm f = aa64_addsubimm_unpack(w);
+ if (f.S && f.Rd == AA64_ZR) {
+ /* CMP/CMN immediate aliases: Rn|SP, #imm. */
+ emit_reg(sb, f.Rn, (int)f.sf, 1);
+ strbuf_puts(sb, ", #");
+ strbuf_put_u64(sb, (u64)f.imm12);
+ if (f.sh) strbuf_puts(sb, ", lsl #12");
+ return;
+ }
/* For these encodings, Rd/Rn=31 means SP. */
emit_reg(sb, f.Rd, (int)f.sf, 1);
strbuf_puts(sb, ", ");
@@ -575,6 +620,7 @@ void aa64_print_operands(StrBuf* sb, const AA64InsnDesc* desc, u32 word,
case AA64_FMT_ADDSUB_SR: print_addsubsr(sb, word, desc); break;
case AA64_FMT_DP3: print_dp3(sb, word, desc); break;
case AA64_FMT_DP2: print_dp2(sb, word); break;
+ case AA64_FMT_CONDSEL: print_condsel(sb, word, desc); break;
case AA64_FMT_BR_REG: print_brreg(sb, word, desc); break;
case AA64_FMT_PCREL_ADR: print_pcrel(sb, word, vaddr); break;
case AA64_FMT_ADDSUB_IMM: print_addsubimm(sb, word); break;
diff --git a/src/arch/aa64/isa.h b/src/arch/aa64/isa.h
@@ -40,6 +40,7 @@ typedef enum AA64Format {
AA64_FMT_ADDSUB_SR, /* add/sub, shifted register */
AA64_FMT_DP3, /* data-processing, 3 source */
AA64_FMT_DP2, /* data-processing, 2 source */
+ AA64_FMT_CONDSEL, /* conditional select (CSEL / CSINC / aliases) */
AA64_FMT_BR_REG, /* unconditional branch (register) */
AA64_FMT_PCREL_ADR, /* PC-relative ADR / ADRP */
AA64_FMT_ADDSUB_IMM, /* add/sub, immediate */
@@ -472,6 +473,83 @@ static inline u32 aa64_rorv(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
}
/* ====================================================================
+ * Conditional select (CSEL / CSINC / CSINV / CSNEG)
+ * sf op S 11010100 Rm(5) cond(4) op2(2) Rn(5) Rd(5)
+ * 31 30 29 28..21 20..16 15..12 11..10 9..5 4..0
+ *
+ * The integer forms this backend emits keep S=0. Aliases such as CSET
+ * are descriptor-table rows over this same encoding family. */
+
+#define AA64_CONDSEL_FAMILY_MATCH 0x1A800000u
+#define AA64_CONDSEL_FAMILY_MASK 0x1FE00000u /* bits 28:21 fixed */
+
+typedef struct AA64CondSel {
+ u32 sf, op, S, Rm, cond, op2, Rn, Rd;
+} AA64CondSel;
+
+static inline u32 aa64_condsel_pack(AA64CondSel f) {
+ return ((f.sf & 1u) << 31) | ((f.op & 1u) << 30) |
+ ((f.S & 1u) << 29) | AA64_CONDSEL_FAMILY_MATCH |
+ ((f.Rm & 0x1fu) << 16) | ((f.cond & 0xfu) << 12) |
+ ((f.op2 & 3u) << 10) | ((f.Rn & 0x1fu) << 5) |
+ (f.Rd & 0x1fu);
+}
+
+static inline AA64CondSel aa64_condsel_unpack(u32 w) {
+ AA64CondSel f;
+ f.sf = (w >> 31) & 1u;
+ f.op = (w >> 30) & 1u;
+ f.S = (w >> 29) & 1u;
+ f.Rm = (w >> 16) & 0x1fu;
+ f.cond = (w >> 12) & 0xfu;
+ f.op2 = (w >> 10) & 3u;
+ f.Rn = (w >> 5) & 0x1fu;
+ f.Rd = w & 0x1fu;
+ return f;
+}
+
+static inline u32 aa64_csel_enc(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 cond) {
+ return aa64_condsel_pack((AA64CondSel){.sf = sf,
+ .op = 0,
+ .S = 0,
+ .Rm = Rm,
+ .cond = cond,
+ .op2 = 0,
+ .Rn = Rn,
+ .Rd = Rd});
+}
+static inline u32 aa64_csinc_enc(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 cond) {
+ return aa64_condsel_pack((AA64CondSel){.sf = sf,
+ .op = 0,
+ .S = 0,
+ .Rm = Rm,
+ .cond = cond,
+ .op2 = 1,
+ .Rn = Rn,
+ .Rd = Rd});
+}
+static inline u32 aa64_csinv_enc(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 cond) {
+ return aa64_condsel_pack((AA64CondSel){.sf = sf,
+ .op = 1,
+ .S = 0,
+ .Rm = Rm,
+ .cond = cond,
+ .op2 = 0,
+ .Rn = Rn,
+ .Rd = Rd});
+}
+static inline u32 aa64_csneg_enc(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 cond) {
+ return aa64_condsel_pack((AA64CondSel){.sf = sf,
+ .op = 1,
+ .S = 0,
+ .Rm = Rm,
+ .cond = cond,
+ .op2 = 1,
+ .Rn = Rn,
+ .Rd = Rd});
+}
+
+/* ====================================================================
* Unconditional branch (register) — BR / BLR / RET
* 1101011 opc(4) op2(5)=11111 op3(6)=000000 Rn(5) op4(5)=00000
* 31..25 24..21 20..16 15..10 9..5 4..0
diff --git a/test/arch/aa64_isa_test.c b/test/arch/aa64_isa_test.c
@@ -90,6 +90,11 @@ int main(void) {
check(aa64_udiv(1, 1, 2, 3), "udiv", "x1, x2, x3");
check(aa64_lslv(0, 1, 2, 3), "lslv", "w1, w2, w3");
+ /* CONDSEL: CSEL X1, X2, X3, EQ and CSET W4, NE. */
+ check(aa64_csel_enc(1, 1, 2, 3, 0), "csel", "x1, x2, x3, eq");
+ check(aa64_csinc_enc(0, 4, AA64_ZR, AA64_ZR, 0), "cset", "w4, ne");
+ check(aa64_csinv_enc(1, 5, AA64_ZR, AA64_ZR, 1), "csetm", "x5, eq");
+
/* BR_REG alias: RET (with implicit X30). */
check(aa64_ret(/*Rn=*/30), "ret", NULL);
/* BR_REG: BR X16 */
@@ -103,6 +108,15 @@ int main(void) {
/* ADDSUB_IMM: ADD X1, X2, #0x10 */
check(aa64_add_imm(1, 1, 2, 0x10, 0), "add", "x1, x2, #16");
check(aa64_sub_imm(1, 1, 2, 0x10, 0), "sub", "x1, x2, #16");
+ check(aa64_subs_imm12(1, AA64_ZR, 2, 0x10, 0), "cmp", "x2, #16");
+ check(aa64_addsubimm_pack((AA64AddSubImm){.sf = 0,
+ .op = 0,
+ .S = 1,
+ .sh = 1,
+ .imm12 = 1,
+ .Rn = AA64_SP,
+ .Rd = AA64_ZR}),
+ "cmn", "sp, #1, lsl #12");
/* LDST_UIMM (size=11, V=0): LDR X1, [X2, #8] (encoded imm12=1, scale=8) */
check(aa64_ldr64_uimm12(/*Rt=*/1, /*Rn=*/2, /*imm12_scaled=*/1), "ldr",
diff --git a/test/asm/decode/aa64_cmp_cset.expected.txt b/test/asm/decode/aa64_cmp_cset.expected.txt
@@ -0,0 +1,6 @@
+0: cmp x2, #16
+4: cmn sp, #1, lsl #12
+8: cset w4, ne
+c: csetm x5, eq
+10: csel x1, x2, x3, eq
+14: csinc x6, x7, x8, lt
diff --git a/test/asm/decode/aa64_cmp_cset.hex b/test/asm/decode/aa64_cmp_cset.hex
@@ -0,0 +1 @@
+5f4000f1ff0740b1e4079f1ae5139fda4100839ae6b4889a
diff --git a/test/asm/decode/aa64_cmp_cset.targets b/test/asm/decode/aa64_cmp_cset.targets
@@ -0,0 +1 @@
+aa64
diff --git a/test/asm/encode/aa64_cmp_cset.expected.hex b/test/asm/encode/aa64_cmp_cset.expected.hex
@@ -0,0 +1 @@
+5f4000f1ff0740b1e4079f1ae5139fda4100839ae6b4889a
diff --git a/test/asm/encode/aa64_cmp_cset.s b/test/asm/encode/aa64_cmp_cset.s
@@ -0,0 +1,7 @@
+.text
+ cmp x2, #16
+ cmn sp, #1, lsl #12
+ cset w4, ne
+ csetm x5, eq
+ csel x1, x2, x3, eq
+ csinc x6, x7, x8, lt
diff --git a/test/asm/encode/aa64_cmp_cset.targets b/test/asm/encode/aa64_cmp_cset.targets
@@ -0,0 +1 @@
+aa64