kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Msrc/arch/aa64/native.c | 13++++++++++++-
Atest/parse/cases/aggregate_copy_far_slot_misaligned.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Atest/parse/cases/aggregate_copy_far_slot_misaligned.expected | 1+
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