kit

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

commit b57f569929399c788ffc2078abd167689f73ba5d
parent 0cac2e5ced0729fe4cfe3359319142e8358a615e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 16:58:23 -0700

perf(cg): -O0 aa64 byte/half far frame slots positive-scaled off sp (Lever 3)

Extend Lever 1's deferred positive-scaled far-slot addressing (4/8-byte
slots resolved to ldr/str [sp,#scaled]) to 1/2-byte accesses. A byte/half
slot past stur's ±256 reach previously cost an address build (sub xN,x29,#off
[+movk] ; ldurb/sturb, 2-4 insns); it now resolves to a single scaled
ldrb/strb/ldrh/strh [base,#scaled] when the final offset fits.

The scaled byte/half reach (imm12*size = 4 KB / 8 KB) is below the largest
sqlite frame (VdbeExec ~6.5 KB), so a one-word patch cannot always fit. A
byte/half slot therefore reserves TWO placeholder words at body time
(scaled load/store + nop). When the resolved offset exceeds the scaled imm12,
aa_apply_patches fills both with an address-build fallback
'add x17,base,#(off & ~0xfff),lsl#12 ; ldrb/strb rt,[x17,#(off & 0xfff)]',
correct for any frame < 16 MB (the high part is a 4096-multiple that fits the
shift-12 addsub imm; the low part is <4096 and, for a half, even, so both
scale-divide cleanly). 4/8-byte slots keep the one-word patch (their
16 KB/32 KB reach covers any frame).

sqlite3.c -c (arm64-macOS): .text 1,495,408 -> 1,494,640 B
(373,852 -> 373,660 insns, -768 B / -192). Histogram: sub -1213, movk -144,
movz -48, sturb -659, sturh -363, ldurb -82, ldurh -157 (all flipped to the
scaled forms + reserved nops, +1210). Net is modest: moderate-far slots are
size-neutral (sub+ldurb -> strb+nop), the win is the big-frame overflow slots
that drop the multi-insn offset materialize. Deterministic, byte-identical
across recompiles.

Gates: test-toy 1392/0, test-cg-api, test-isa, test-opt, test-aa64-inline,
test-debug, test-dwarf all green. Differential probes vs /usr/bin/cc -O0
(kit -O0 and -O1) match, including a large-frame probe exercising the byte
overflow fallback (16 occurrences, offsets to 6854) and the alloca/x28-base
scaled path.

Diffstat:
Msrc/arch/aa64/native.c | 78+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 61 insertions(+), 17 deletions(-)

diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -209,8 +209,9 @@ typedef enum AAPatchKind { typedef struct AASlotPatch { u32 slot_off; /* aa_slot(...)->off */ i32 extra; /* addr.offset added to the slot base */ - u8 sz; /* size_idx: 2 (word) or 3 (dword) */ - u8 vbit; /* SIMD/FP register */ + u8 sz; /* size_idx: 0 (byte) / 1 (half) / 2 (word) / 3 (dword). sz<2 + * reserves a 2nd placeholder word (overflow fallback). */ + u8 vbit; /* SIMD/FP register (always 0 for sz<2) */ u8 load; /* 1 = ldr, 0 = str */ u8 rt; /* transfer register */ } AASlotPatch; @@ -875,15 +876,31 @@ static void aa_emit_mem(AANativeTarget* a, int load, NativeLoc reg, sz <= 1u) ? AA64_LDST_OPC_LDRS_X : AA64_LDST_OPC_LDR; - /* Far fixed-slot fast path (Lever 1 / Fix B). A plain 4/8-byte frame slot - * whose top-record fp offset is past stur's ±256 range would otherwise cost - * `sub xN,x29,#off (+movk) ; ldur` (2-3 insns). Instead emit a one-word - * positive scaled `ldr/str [sp,#0]` placeholder and defer the offset: once the - * frame is final, aa_apply_patches rewrites it to `[base, #frame_size-...]`, - * base = sp (stable) or AA_FRAME_BASE (alloca). Near slots keep their existing - * one-word ldur; byte/half stay on the fp path (scaled reach too small). */ + /* Far fixed-slot fast path (Lever 1/3 / Fix B). A plain frame slot whose + * top-record fp offset is past stur's ±256 range would otherwise cost `sub + * xN,x29,#off (+movk) ; ldur` (2-4 insns). Instead emit a positive scaled + * `ldr/str [sp,#0]` placeholder and defer the offset: once the frame is final, + * aa_apply_patches rewrites it to `[base, #frame_size-...]`, base = sp (stable) + * or AA_FRAME_BASE (alloca). + * + * A 4/8-byte slot (sz>=2) always fits the scaled imm12 (reach 16 KB/32 KB >= + * any frame), so it is a one-word patch. A 1/2-byte slot (sz<2, Lever 3) has a + * smaller scaled reach (4 KB/8 KB) that the largest frames (sqlite VdbeExec + * ~6.5 KB) exceed, so it reserves TWO words: when the resolved offset fits it + * is `ldrb/strb [base,#scaled] ; nop`, and when it overflows the resolver + * emits the address-build fallback `add x17,base,#hi ; ldrb/strb [x17,#lo]` + * (correct for any frame). byte/half are always integer (vbit=0; FP was + * widened to sz>=2 above). + * + * Lever 3/4 interaction: a far SIGNED narrow load (ld_opc==LDRS_X) is excluded + * from this scaled-slot path — the slot patch records only a plain LDR opcode, + * so it would zero-extend while the cg layer dropped the CV_SEXT. Such loads + * fall through to the general path below, which honors ld_opc (ldrsb/ldrsh with + * an address build). Rare (a far signed char/short slot); correctness over the + * one-insn win. */ + int sext_far = load && ld_opc == AA64_LDST_OPC_LDRS_X; if (a->slot_sp_base && addr.base_kind == NATIVE_ADDR_BASE_FRAME && - addr.index_kind == NATIVE_ADDR_INDEX_NONE && (sz == 2u || sz == 3u)) { + 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) { @@ -899,6 +916,8 @@ static void aa_emit_mem(AANativeTarget* a, int load, NativeLoc reg, p->u.slot.rt = (u8)rt; aa_emit32(mc, load ? aa_ldr_uimm_v(sz, vbit, rt, AA_SP, 0) : aa_str_uimm_v(sz, vbit, rt, AA_SP, 0)); + /* Reserve the fallback's second word for byte/half (filled at resolve). */ + if (sz < 2u) aa_emit32(mc, 0xd503201fu /* nop */); return; } } @@ -1786,14 +1805,39 @@ static void aa_apply_patches(AANativeTarget* a, const AAFrameLayout* L) { i32 fp_off = aa_fp_off_slot(a, sl->slot_off) + sl->extra; i32 base_off = (i32)aa_sp_off_saved_pair(L) + fp_off; u32 base = a->uses_frame_base ? AA_FRAME_BASE : AA_SP; - if (base_off < 0 || ((u32)base_off & ((1u << sl->sz) - 1u)) != 0u || - ((u32)base_off >> sl->sz) > 0xfffu) + u32 ubo = (u32)base_off; + /* Natural alignment is guaranteed by the slot allocator (and the byte case + * is unconditionally aligned); a misalignment would mean a layout bug. */ + if (base_off < 0 || (ubo & ((1u << sl->sz) - 1u)) != 0u) + aa_panic(a, "far slot offset negative or misaligned"); + if ((ubo >> sl->sz) <= 0xfffu) { + /* Scaled immediate fits: one-word ldr/str. For byte/half the reserved + * second placeholder word stays the nop emitted at body time. */ + aa_patch32(a->base.obj, sec, p->pos, + sl->load ? aa_ldr_uimm_v(sl->sz, sl->vbit, sl->rt, base, ubo) + : aa_str_uimm_v(sl->sz, sl->vbit, sl->rt, base, ubo)); + } else if (sl->sz < 2u) { + /* Lever 3 overflow fallback. byte/half scaled reach (4 KB/8 KB) is below + * the largest frames, so build the address into the reserved second word + * instead: `add x17,base,#(off & ~0xfff) ; ldrb/strb rt,[x17,#(off & + * 0xfff)]`. The high part is a multiple of 4096 (fits the shift-12 + * addsub imm for any frame < 16 MB); the low part is <4096 and, for a + * half, even (base_off is 2-aligned), so both scale-divide cleanly. */ + u32 hi = ubo & ~0xfffu; + u32 lo = ubo & 0xfffu; + if ((hi >> 12) > 0xfffu) + aa_panic(a, "far slot offset out of address-build range"); + aa_patch32(a->base.obj, sec, p->pos, + aa64_add_imm(1, AA_TMP1, base, hi >> 12, 1)); + aa_patch32(a->base.obj, sec, p->pos + 4u, + sl->load + ? aa_ldr_uimm_v(sl->sz, sl->vbit, sl->rt, AA_TMP1, lo) + : aa_str_uimm_v(sl->sz, sl->vbit, sl->rt, AA_TMP1, lo)); + } else { + /* 4/8-byte scaled reach (16 KB/32 KB) covers any frame, so this is + * unreachable; a one-word patch has no fallback room. */ aa_panic(a, "far slot offset out of positive scaled range"); - aa_patch32(a->base.obj, sec, p->pos, - sl->load ? aa_ldr_uimm_v(sl->sz, sl->vbit, sl->rt, base, - (u32)base_off) - : aa_str_uimm_v(sl->sz, sl->vbit, sl->rt, base, - (u32)base_off)); + } } else { /* AA_PATCH_TAIL */ NativeLoc callee = p->u.callee; u32 words[AA_TAIL_WORDS];