commit a8052633f6775e1cba26a4b7a6db19f237e98c22
parent f7fbbc9db78d92ef2796ccc4def521332e77e50e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 4 Jun 2026 10:45:21 -0700
aa64: support bitfield extension aliases
Diffstat:
10 files changed, 189 insertions(+), 12 deletions(-)
diff --git a/src/arch/aa64/asm.c b/src/arch/aa64/asm.c
@@ -1134,8 +1134,7 @@ static void p_log_sr(AsmDriver* d, u32 opc, u32 N) {
* ANDS uses ZR. Rn is always a GPR (caller's parse_reg already enforced
* GP for the two register operands). */
if (N) asm_driver_panic(d, "asm: logical: immediate form has no negation");
- if (rd.is64 != rn.is64)
- asm_driver_panic(d, "asm: logical: width mismatch");
+ if (rd.is64 != rn.is64) asm_driver_panic(d, "asm: logical: width mismatch");
u64 imm = (u64)parse_imm_const(d);
u32 bN = 0, immr = 0, imms = 0;
if (!aa64_logimm_encode(imm, rd.is64, &bN, &immr, &imms))
@@ -2089,6 +2088,74 @@ static void p_sbfm(AsmDriver* d) { p_bitfield(d, 0u); }
static void p_bfm(AsmDriver* d) { p_bitfield(d, 1u); }
static void p_ubfm(AsmDriver* d) { p_bitfield(d, 2u); }
+static void p_bfx(AsmDriver* d, u32 opc, const char* what) {
+ AA64Reg rd = parse_reg(d);
+ AA64Reg rn;
+ i64 lsb, width;
+ u32 reg_width;
+ expect_comma(d, what);
+ rn = parse_reg(d);
+ reject_sp_reg(d, rd, what);
+ reject_sp_reg(d, rn, what);
+ if (rd.is64 != rn.is64)
+ asm_driver_panic(d, "asm: %.*s: width mismatch",
+ SLICE_ARG(slice_from_cstr(what)));
+ expect_comma(d, what);
+ lsb = parse_imm_const(d);
+ expect_comma(d, what);
+ width = parse_imm_const(d);
+ reg_width = rd.is64 ? 64u : 32u;
+ if (lsb < 0 || width <= 0 || (u64)lsb >= reg_width ||
+ (u64)width > (u64)reg_width - (u64)lsb) {
+ asm_driver_panic(d, "asm: %.*s: bit range out of bounds",
+ SLICE_ARG(slice_from_cstr(what)));
+ }
+ emit32(d, aa64_bitfield(rd.is64, opc, (u32)lsb, (u32)(lsb + width - 1),
+ rd.num, rn.num));
+}
+
+static void p_sbfx(AsmDriver* d) { p_bfx(d, 0u, "sbfx"); }
+static void p_ubfx(AsmDriver* d) { p_bfx(d, 2u, "ubfx"); }
+
+static void p_sxt(AsmDriver* d, u32 bits, const char* what) {
+ AA64Reg rd = parse_reg(d);
+ AA64Reg rn;
+ expect_comma(d, what);
+ rn = parse_reg(d);
+ reject_sp_reg(d, rd, what);
+ reject_sp_reg(d, rn, what);
+ if (rn.is64)
+ asm_driver_panic(d, "asm: %.*s: source must be a W register",
+ SLICE_ARG(slice_from_cstr(what)));
+ if (bits == 32u && !rd.is64)
+ asm_driver_panic(d, "asm: sxtw: destination must be an X register");
+ emit32(d, aa64_bitfield(rd.is64, 0u, 0u, bits - 1u, rd.num, rn.num));
+}
+
+static void p_uxt(AsmDriver* d, u32 bits, const char* what) {
+ AA64Reg rd = parse_reg(d);
+ AA64Reg rn;
+ u32 sf;
+ expect_comma(d, what);
+ rn = parse_reg(d);
+ reject_sp_reg(d, rd, what);
+ reject_sp_reg(d, rn, what);
+ if (rn.is64)
+ asm_driver_panic(d, "asm: %.*s: source must be a W register",
+ SLICE_ARG(slice_from_cstr(what)));
+ if (bits == 32u && !rd.is64)
+ asm_driver_panic(d, "asm: uxtw: destination must be an X register");
+ sf = bits == 32u ? 1u : 0u;
+ emit32(d, aa64_bitfield(sf, 2u, 0u, bits - 1u, rd.num, rn.num));
+}
+
+static void p_sxtb(AsmDriver* d) { p_sxt(d, 8u, "sxtb"); }
+static void p_sxth(AsmDriver* d) { p_sxt(d, 16u, "sxth"); }
+static void p_sxtw(AsmDriver* d) { p_sxt(d, 32u, "sxtw"); }
+static void p_uxtb(AsmDriver* d) { p_uxt(d, 8u, "uxtb"); }
+static void p_uxth(AsmDriver* d) { p_uxt(d, 16u, "uxth"); }
+static void p_uxtw(AsmDriver* d) { p_uxt(d, 32u, "uxtw"); }
+
/* fmov: Vd,Vn (FP reg move) | Rd,Vn (fp->gpr) | Vd,Rn (gpr->fp). */
static void p_fmov(AsmDriver* d) {
FpOrGpr a = parse_fp_or_gpr(d);
@@ -2327,6 +2394,14 @@ static const AA64Mn kTable[] = {
{"sbfm", p_sbfm, 0},
{"ubfm", p_ubfm, 0},
{"bfm", p_bfm, 0},
+ {"sbfx", p_sbfx, 0},
+ {"ubfx", p_ubfx, 0},
+ {"sxtb", p_sxtb, 0},
+ {"sxth", p_sxth, 0},
+ {"sxtw", p_sxtw, 0},
+ {"uxtb", p_uxtb, 0},
+ {"uxth", p_uxth, 0},
+ {"uxtw", p_uxtw, 0},
{"nop", p_nop, 0},
{"dmb", p_dmb, 0},
{"dsb", p_dsb, 0},
diff --git a/src/arch/aa64/disasm.c b/src/arch/aa64/disasm.c
@@ -52,10 +52,14 @@ static void aa64_write_mnemonic(AA64Disasm* d, const AA64InsnDesc* desc,
return;
}
if (desc->fmt == AA64_FMT_BITFIELD) {
- /* SBFM/UBFM disassemble to their lsl/lsr/asr shift aliases when the
+ /* SBFM/UBFM disassemble to their preferred aliases when the
* fields match; aa64_print_operands renders the matching operands. */
- u32 shift;
+ u32 shift, lsb, width;
const char* alias = aa64_bitfield_shift_alias(word, &shift);
+ if (!alias) alias = aa64_bitfield_extend_alias(word);
+ if (!alias) alias = aa64_bitfield_extract_alias(word, &lsb, &width);
+ (void)lsb;
+ (void)width;
if (alias) {
strbuf_puts(&d->mnem, alias);
return;
diff --git a/src/arch/aa64/isa.c b/src/arch/aa64/isa.c
@@ -543,7 +543,7 @@ const AA64InsnDesc aa64_insn_table[] = {
/* ----- Bitfield move (SBFM / UBFM) -----
* sf/N free (read for W/X); opc (bits30:29) selects sbfm vs ubfm. Aliases
- * (lsl/lsr/asr/ubfx/sxtb/...) decode to the raw sbfm/ubfm form. */
+ * (lsl/lsr/asr/ubfx/sxtb/...) are selected by the bitfield printer. */
{MN("sbfm"), 0x13000000u, 0x7F800000u, AA64_FMT_BITFIELD, 0, {0, 0}},
{MN("ubfm"), 0x53000000u, 0x7F800000u, AA64_FMT_BITFIELD, 0, {0, 0}},
@@ -1183,16 +1183,24 @@ static void print_dp1(StrBuf* sb, u32 w) {
}
/* SBFM/UBFM: Rd, Rn, #immr, #imms. */
+static int bitfield_width(u32 word, u32* width) {
+ u32 sf = (word >> 31) & 1u;
+ u32 N = (word >> 22) & 1u;
+ if (N != sf) return 0;
+ *width = sf ? 64u : 32u;
+ return 1;
+}
+
/* SBFM/UBFM shift aliases. UBFM is LSR when imms is the top bit index and LSL
* when immr == imms+1 (the encoder's `lsl #s` form); SBFM is ASR when imms is
- * the top bit index. The other bitfield aliases (UBFX/SBFX/UXTB/SBFIZ/...) are
- * left as the raw mnemonic. */
+ * the top bit index. */
const char* aa64_bitfield_shift_alias(u32 word, u32* shift) {
- u32 sf = (word >> 31) & 1u;
u32 opc = (word >> 29) & 3u; /* 0 = SBFM, 2 = UBFM */
u32 immr = (word >> 16) & 0x3fu;
u32 imms = (word >> 10) & 0x3fu;
- u32 top = sf ? 63u : 31u;
+ u32 width, top;
+ if (!bitfield_width(word, &width)) return NULL;
+ top = width - 1u;
if (opc == 2u) { /* UBFM */
if (imms == top) {
*shift = immr;
@@ -1211,9 +1219,42 @@ const char* aa64_bitfield_shift_alias(u32 word, u32* shift) {
return NULL;
}
+const char* aa64_bitfield_extend_alias(u32 word) {
+ u32 sf = (word >> 31) & 1u;
+ u32 opc = (word >> 29) & 3u; /* 0 = SBFM, 2 = UBFM */
+ u32 immr = (word >> 16) & 0x3fu;
+ u32 imms = (word >> 10) & 0x3fu;
+ u32 width;
+ if (!bitfield_width(word, &width) || immr != 0u) return NULL;
+ (void)width;
+ if (opc == 0u) {
+ if (imms == 7u) return "sxtb";
+ if (imms == 15u) return "sxth";
+ if (sf && imms == 31u) return "sxtw";
+ } else if (opc == 2u && !sf) {
+ if (imms == 7u) return "uxtb";
+ if (imms == 15u) return "uxth";
+ }
+ return NULL;
+}
+
+const char* aa64_bitfield_extract_alias(u32 word, u32* lsb, u32* width_out) {
+ u32 opc = (word >> 29) & 3u; /* 0 = SBFM, 2 = UBFM */
+ u32 immr = (word >> 16) & 0x3fu;
+ u32 imms = (word >> 10) & 0x3fu;
+ u32 width;
+ if (!bitfield_width(word, &width)) return NULL;
+ (void)width;
+ if (opc != 0u && opc != 2u) return NULL;
+ if (immr > imms) return NULL;
+ *lsb = immr;
+ *width_out = imms - immr + 1u;
+ return opc == 0u ? "sbfx" : "ubfx";
+}
+
static void print_bitfield(StrBuf* sb, u32 w) {
int sf = (int)((w >> 31) & 1u);
- u32 shift;
+ u32 shift, lsb, width;
if (aa64_bitfield_shift_alias(w, &shift)) {
/* lsl/lsr/asr Rd, Rn, #shift (mnemonic chosen by the disasm mnemonic
* writer via the same helper). */
@@ -1224,6 +1265,24 @@ static void print_bitfield(StrBuf* sb, u32 w) {
strbuf_put_u64(sb, (u64)shift);
return;
}
+ if (aa64_bitfield_extend_alias(w)) {
+ /* sxtb/sxth/sxtw/uxtb/uxth Rd, Wn. The source is spelled Wn even when
+ * the underlying SBFM has sf=1 (sxtb/sxth/sxtw into Xd). */
+ emit_reg(sb, w & 0x1fu, sf, 0);
+ strbuf_puts(sb, ", ");
+ emit_reg(sb, (w >> 5) & 0x1fu, 0, 0);
+ return;
+ }
+ if (aa64_bitfield_extract_alias(w, &lsb, &width)) {
+ emit_reg(sb, w & 0x1fu, sf, 0);
+ strbuf_puts(sb, ", ");
+ emit_reg(sb, (w >> 5) & 0x1fu, sf, 0);
+ strbuf_puts(sb, ", #");
+ strbuf_put_u64(sb, (u64)lsb);
+ strbuf_puts(sb, ", #");
+ strbuf_put_u64(sb, (u64)width);
+ return;
+ }
emit_reg(sb, w & 0x1fu, sf, 0);
strbuf_puts(sb, ", ");
emit_reg(sb, (w >> 5) & 0x1fu, sf, 0);
diff --git a/src/arch/aa64/isa.h b/src/arch/aa64/isa.h
@@ -1591,6 +1591,14 @@ void aa64_print_operands(StrBuf* sb, const AA64InsnDesc* desc, u32 word,
* operand printers so the alias decision lives in one place. */
const char* aa64_bitfield_shift_alias(u32 word, u32* shift);
+/* Preferred SBFM/UBFM extension aliases: sxtb/sxth/sxtw/uxtb/uxth.
+ * Returns NULL when the bitfield does not match one of those forms. */
+const char* aa64_bitfield_extend_alias(u32 word);
+
+/* Preferred SBFM/UBFM extract aliases: sbfx/ubfx. Writes the least-significant
+ * source bit and extracted width when an alias is available. */
+const char* aa64_bitfield_extract_alias(u32 word, u32* lsb, u32* width);
+
/* Returns 1 on success, 0 on parse error. Phase 2 stub returns 0 for
* every format; phase 3 fills in the bodies. */
int aa64_parse_operands(struct AA64AsmTok* tok, const AA64InsnDesc* desc,
diff --git a/test/asm/decode/aa64_bitfield_aliases.expected.txt b/test/asm/decode/aa64_bitfield_aliases.expected.txt
@@ -0,0 +1,15 @@
+0: sxtb w6, w7
+4: sxtb x8, w9
+8: sxth w10, w11
+c: sxth x12, w13
+10: sxtw x14, w15
+14: uxtb w16, w17
+18: uxtb w18, w19
+1c: uxth w20, w21
+20: uxth w22, w23
+24: ubfx x24, x25, #0, #32
+28: sbfx w26, w27, #3, #5
+2c: sbfx x28, x29, #4, #20
+30: ubfx w0, w1, #5, #9
+34: ubfx x2, x3, #6, #33
+38: ret
diff --git a/test/asm/decode/aa64_bitfield_aliases.hex b/test/asm/decode/aa64_bitfield_aliases.hex
@@ -0,0 +1 @@
+e61c0013281d40936a3d0013ac3d4093ee7d4093301e0053721e0053b43e0053f63e0053387f40d37a1f0313bc5f449320340553629846d3c0035fd6
diff --git a/test/asm/decode/aa64_bitfield_aliases.targets b/test/asm/decode/aa64_bitfield_aliases.targets
@@ -0,0 +1 @@
+aa64
diff --git a/test/asm/decode/aa64_fp_bitfield_dp1.expected.txt b/test/asm/decode/aa64_fp_bitfield_dp1.expected.txt
@@ -17,5 +17,5 @@ c: rev x4, x5
40: scvtf s0, w1
44: ucvtf d2, x3
48: fcvtzu w4, d5
-4c: ubfm x0, x1, #4, #20
+4c: ubfx x0, x1, #4, #17
50: ret
diff --git a/test/asm/encode/aa64_bitfield_dp1.expected.hex b/test/asm/encode/aa64_bitfield_dp1.expected.hex
@@ -1 +1 @@
-2010c0da6210c05aa400c0dae60cc0da2809c05a6a05c0da2050449362280253a44048b3c0035fd6
+2010c0da6210c05aa400c0dae60cc0da2809c05a6a05c0da2050449362280253a44048b3e61c0013281d40936a3d0013ac3d4093ee7d4093301e0053721e0053b43e0053f63e0053387f40d37a1f0313bc5f449320340553629846d3c0035fd6
diff --git a/test/asm/encode/aa64_bitfield_dp1.s b/test/asm/encode/aa64_bitfield_dp1.s
@@ -9,4 +9,18 @@ t:
sbfm x0, x1, #4, #20
ubfm w2, w3, #2, #10
bfm x4, x5, #8, #16
+ sxtb w6, w7
+ sxtb x8, w9
+ sxth w10, w11
+ sxth x12, w13
+ sxtw x14, w15
+ uxtb w16, w17
+ uxtb x18, w19
+ uxth w20, w21
+ uxth x22, w23
+ uxtw x24, w25
+ sbfx w26, w27, #3, #5
+ sbfx x28, x29, #4, #20
+ ubfx w0, w1, #5, #9
+ ubfx x2, x3, #6, #33
ret