commit 6d7d79927123d94a9a05baaef05be308f89075a4
parent 261d004425ff5ab3be3242ace4927f15fe8f5ca6
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 15 Jun 2026 17:17:56 -0700
aa64: gate far-slot scaled fast path on natural alignment
The deferred far-slot load/store (Lever 1 / Fix B) emits a scaled
ldr/str [base,#imm<<sz] placeholder, whose immediate must be a multiple
of the access size. The fast path was taken for any frame-slot access
past the stur +-256 range without checking that the resolved offset
would actually be aligned to the access size.
An aggregate copy chunks an under-aligned record (e.g. SrcLoc: a
4-aligned 12-byte struct copied as an 8-byte + 4-byte access) into an
8-byte access at a 4-mod-8 frame offset. When such a record landed at a
far slot, the deferred patch resolved to a misaligned scaled offset that
the encoding can't represent, tripping the "far slot offset ...
misaligned" panic in aa_apply_patches (seen compiling src/asm/asm.c
during bootstrap).
The frame base (sp / AA_FRAME_BASE) is 16-aligned where this path is
live, so the resolved offset is a multiple of the access size exactly
when fp_off is. Gate the fast path on (fp_off & (size-1)) == 0; the
under-aligned case falls through to the fp-relative address-build path,
which encodes any offset. Byte-identical for every access that took the
fast path before (those were all aligned, else they would have panicked).
Regression test test/parse/cases/aggregate_copy_far_slot_misaligned.c
reproduces the panic on the pre-fix compiler; passes D/R/E on aa64 and
R/E on x64/rv64.
Diffstat:
3 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c
@@ -904,7 +904,18 @@ static void aa_emit_mem(AANativeTarget* a, int load, NativeLoc reg,
addr.index_kind == NATIVE_ADDR_INDEX_NONE && sz <= 3u && !sext_far) {
AANativeSlot* s = aa_slot(a, addr.base.frame);
i32 fp_off = aa_fp_off_slot(a, s->off) + addr.offset;
- if (fp_off < -256) {
+ /* The placeholder resolves to a scaled `ldr/str [base,#imm<<sz]`, whose
+ * immediate must be a multiple of the access size. The frame base
+ * (sp / AA_FRAME_BASE) is 16-aligned at this point (slot_sp_base is off
+ * whenever a Windows GP home area shifts the layout), so the resolved
+ * offset is a multiple of the access size exactly when fp_off is. An
+ * aggregate copy chunks an under-aligned record (e.g. a 4-aligned 12-byte
+ * struct copied as 8+4 bytes) into an access whose frame offset is not a
+ * multiple of the chunk size; such an access can't use the scaled form, so
+ * it falls through to the fp-relative address-build path below, which
+ * encodes any offset. */
+ u32 acc = 1u << sz;
+ if (fp_off < -256 && ((u32)fp_off & (acc - 1u)) == 0u) {
u32 vbit = native_loc_is_fp(reg) ? 1u : 0u;
AAPatch* p = aa_patch_alloc(a);
p->kind = AA_PATCH_SLOT;
diff --git a/test/parse/cases/aggregate_copy_far_slot_misaligned.c b/test/parse/cases/aggregate_copy_far_slot_misaligned.c
@@ -0,0 +1,46 @@
+/* Aggregate copy of a 4-aligned record living in a far (>256B) frame slot.
+ *
+ * A struct returned by value is homed into a local that the large `pad` frame
+ * pushes past the aarch64 stur/ldur +-256 range. The struct is 12 bytes with
+ * 4-byte alignment, so its slot can land at a frame offset that is 4 (mod 8).
+ * The store-into-local copy chunks the record as an 8-byte + a 4-byte access;
+ * the 8-byte chunk is then at a 4-mod-8 offset, which a scaled ldr/str cannot
+ * encode. The aarch64 far-slot fast path must detect the under-aligned access
+ * and fall back to the address-build form instead of emitting an unencodable
+ * scaled offset (which previously tripped a "far slot offset ... misaligned"
+ * codegen panic). The `perturb` array nudges the frame layout so `a` lands on
+ * the misaligned offset with the current allocator.
+ *
+ * x64/rv64 have no scaled-offset alignment constraint, so this just confirms
+ * the aggregate copy stays correct everywhere. */
+
+struct loc {
+ unsigned file, line, col;
+}; /* 12 bytes, align 4 */
+
+__attribute__((noinline)) static struct loc make_loc(unsigned x) {
+ struct loc r;
+ r.file = x;
+ r.line = x + 1u;
+ r.col = x + 2u;
+ return r;
+}
+
+__attribute__((noinline)) static unsigned work(unsigned seed) {
+ volatile long pad[40]; /* large frame: 'a' lands far from fp */
+ volatile unsigned perturb[3];
+ unsigned pt = 0;
+ for (int i = 0; i < 40; i++) pad[i] = i;
+ for (int i = 0; i < 3; i++) perturb[i] = seed + (unsigned)i;
+ struct loc a = make_loc(seed); /* struct-return -> 8+4 stack copy */
+ for (int i = 0; i < 3; i++) pt += perturb[i];
+ /* Keep pad live without folding its (large) sum into the checksum. */
+ for (int i = 0; i < 40; i++)
+ if (pad[i] < 0) pt += (unsigned)pad[i];
+ return a.file + a.line + a.col + pt;
+}
+
+int test_main(void) {
+ /* a = {5,6,7} = 18 ; perturb = 5+6+7 = 18 ; total 36 */
+ return (int)work(5u);
+}
diff --git a/test/parse/cases/aggregate_copy_far_slot_misaligned.expected b/test/parse/cases/aggregate_copy_far_slot_misaligned.expected
@@ -0,0 +1 @@
+36