commit 8fc2220a22d992bfc53306be9c34ffa9ef6c5601
parent 6ddf35a70674161ac4cccecf126763cc566e8c50
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 29 May 2026 14:24:30 -0700
aa64 asm: encode byte/half and signed sub-word loads/stores
p_ldr_str hardwired the access size to the register width (word/dword), so the
assembler had no ldrb/strb/ldrh/strh/ldrsb/ldrsh/ldrsw. Generalize it to
p_ldst_core(is_load, fixed_size, sign_ext): byte/half use a fixed size; signed
loads pick opc 10/11 (sign-extend to 64-/32-bit) by destination width.
Byte-identical to clang; adds aa64_ldst_subword corpus case.
Diffstat:
4 files changed, 48 insertions(+), 3 deletions(-)
diff --git a/src/arch/aa64/asm.c b/src/arch/aa64/asm.c
@@ -935,13 +935,21 @@ static AA64Mem parse_mem(AsmDriver* d) {
/* ldr/str Rt, [Xn, #imm] — chooses scaled or unscaled form based on
* alignment of imm. */
-static void p_ldr_str(AsmDriver* d, int is_load) {
+/* Core load/store. `fixed_size` is the access log2-size (0=byte..3=dword) for
+ * ldrb/ldrh/ldrsw etc., or -1 to derive it from the register width (ldr/str).
+ * `sign_ext` selects the signed-load opc (10 = sign-extend to 64-bit, 11 = to
+ * 32-bit), keyed on the destination register width. */
+static void p_ldst_core(AsmDriver* d, int is_load, int fixed_size,
+ int sign_ext) {
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;
- u32 opc = is_load ? AA64_LDST_OPC_LDR : AA64_LDST_OPC_STR;
+ u32 size = (fixed_size >= 0) ? (u32)fixed_size : (rt.is64 ? 3u : 2u);
+ u32 opc = !is_load ? AA64_LDST_OPC_STR
+ : !sign_ext ? AA64_LDST_OPC_LDR
+ : rt.is64 ? 2u /* LDRS*, 64-bit dst */
+ : 3u; /* LDRS*, 32-bit dst */
if (!m.pre_index) {
/* Try scaled unsigned-imm12 first. */
u32 scale = 1u << size;
@@ -974,6 +982,19 @@ static void p_ldr_str(AsmDriver* d, int is_load) {
asm_driver_panic(d, "asm: ldr/str: pre-indexed form not yet supported");
}
+/* ldr/str: access width follows the register (Wt=word, Xt=dword). */
+static void p_ldr_str(AsmDriver* d, int is_load) {
+ p_ldst_core(d, is_load, /*fixed_size=*/-1, /*sign_ext=*/0);
+}
+/* Byte/half + signed sub-word loads/stores (fixed access width). */
+static void p_ldrb(AsmDriver* d) { p_ldst_core(d, 1, 0, 0); }
+static void p_strb(AsmDriver* d) { p_ldst_core(d, 0, 0, 0); }
+static void p_ldrh(AsmDriver* d) { p_ldst_core(d, 1, 1, 0); }
+static void p_strh(AsmDriver* d) { p_ldst_core(d, 0, 1, 0); }
+static void p_ldrsb(AsmDriver* d) { p_ldst_core(d, 1, 0, 1); }
+static void p_ldrsh(AsmDriver* d) { p_ldst_core(d, 1, 1, 1); }
+static void p_ldrsw(AsmDriver* d) { p_ldst_core(d, 1, 2, 1); }
+
/* ldur/stur — unscaled signed-imm9. */
static void p_ldur_stur(AsmDriver* d, int is_load) {
AA64Reg rt = parse_reg(d);
@@ -1399,6 +1420,13 @@ static const AA64Mn kTable[] = {
{"hlt", p_hlt_, 0},
{"ldr", p_ldr_, 0},
{"str", p_str_, 0},
+ {"ldrb", p_ldrb, 0},
+ {"strb", p_strb, 0},
+ {"ldrh", p_ldrh, 0},
+ {"strh", p_strh, 0},
+ {"ldrsb", p_ldrsb, 0},
+ {"ldrsh", p_ldrsh, 0},
+ {"ldrsw", p_ldrsw, 0},
{"ldur", p_ldur_, 0},
{"stur", p_stur_, 0},
{"ldp", p_ldp_, 0},
diff --git a/test/asm/encode/aa64_ldst_subword.expected.hex b/test/asm/encode/aa64_ldst_subword.expected.hex
@@ -0,0 +1 @@
+2000403962104039a4040039e6044079280d00796a018039ac0dc039ee118079300680b9200840b9620840f9c0035fd6
diff --git a/test/asm/encode/aa64_ldst_subword.s b/test/asm/encode/aa64_ldst_subword.s
@@ -0,0 +1,14 @@
+.text
+t:
+ ldrb w0, [x1]
+ ldrb w2, [x3, #4]
+ strb w4, [x5, #1]
+ ldrh w6, [x7, #2]
+ strh w8, [x9, #6]
+ ldrsb x10, [x11]
+ ldrsb w12, [x13, #3]
+ ldrsh x14, [x15, #8]
+ ldrsw x16, [x17, #4]
+ ldr w0, [x1, #8]
+ ldr x2, [x3, #16]
+ ret
diff --git a/test/asm/encode/aa64_ldst_subword.targets b/test/asm/encode/aa64_ldst_subword.targets
@@ -0,0 +1 @@
+aa64
+\ No newline at end of file