kit

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

commit 7f080a6f5976c89447a7c7b11e77ea44b71dde1b
parent bc5ef9d8e29b2fb214e596198202d19cc21c956d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 10 Jun 2026 22:24:09 -0700

perf(emit): tail-chunk buffer fast path + batch aa64 prologue patch/reserve

Three byte-identical emit-path wins, biggest on fn-count (many tiny functions):
- buf_walk (src/core/buf.c): emit-time patch-ups and reads overwhelmingly target
  the most-recently-written bytes (a function's own just-emitted prologue/
  branches), which live in the last indexed chunk. Check it before the
  directory binary search → O(1) common case instead of O(log n_chunks). Shared
  by every object emit + the DWARF patcher.
- aa_patch_prologue: patch the reserved prologue region in ONE obj_patch over a
  serialized byte buffer instead of up to 32 single-word aa_patch32 calls (each
  a chunk-directory lookup); mirrors the x64 backend.
- aa_func_begin: reserve the 32-word worst-case prologue as one bulk emit_bytes
  instead of 32 aa_emit32 calls. -g line rows replicated so debug output is
  unchanged.
- aa_emit32: skip the per-instruction obj_pos (section lookup + buf_pos) on the
  common no-debug compile; it is only needed for the -g line table.

fn-count 1049->787ms (-25%), body-size 2167->1917ms (-11%). Object output
byte-identical on aa64-macos/-linux including -g. Verified: test-isa/aa64-inline/
toy/debug/dwarf/link/elf/macho/smoke-x64/smoke-rv64 all green.

Diffstat:
Msrc/arch/aa64/native.c | 40+++++++++++++++++++++++++++++++++-------
Msrc/core/buf.c | 39++++++++++++++++++++++++---------------
2 files changed, 57 insertions(+), 22 deletions(-)

diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -316,11 +316,16 @@ extern void debug_func_pc_range(Debug*, ObjSecId text_section, u32 begin_ofs, static void aa_emit32(MCEmitter* mc, u32 word) { u8 b[4]; - u32 ofs = obj_pos(mc->obj, mc->section_id); wr_u32_le(b, word); - mc->emit_bytes(mc, b, sizeof b); - /* Record one line-table row per instruction start (no-op when not -g). */ - if (mc->debug) debug_emit_row(mc->debug, mc->section_id, ofs, mc->loc); + /* obj_pos is a section lookup + buf_pos; only the -g line table needs it, so + * skip it on the common no-debug compile (one fewer lookup per instruction). */ + if (mc->debug) { + u32 ofs = obj_pos(mc->obj, mc->section_id); + mc->emit_bytes(mc, b, sizeof b); + debug_emit_row(mc->debug, mc->section_id, ofs, mc->loc); + } else { + mc->emit_bytes(mc, b, sizeof b); + } } static void aa_patch32(ObjBuilder* obj, ObjSecId sec, u32 off, u32 word) { @@ -1143,7 +1148,22 @@ static void aa_func_begin(NativeTarget* t, const CGFuncDesc* fd) { AANativeTarget* a = aa_of(t); MCEmitter* mc = t->mc; aa_func_begin_common(t, fd); - for (u32 i = 0; i < AA_PROLOGUE_WORDS; ++i) aa_emit32(mc, 0xd503201fu); + /* Reserve the worst-case prologue as one bulk emit (32 NOP words) instead of + * 32 single-word aa_emit32 calls. Replicate the per-instruction -g line rows + * so debug output is unchanged. */ + { + u8 nops[AA_PROLOGUE_WORDS * 4u]; + for (u32 i = 0; i < AA_PROLOGUE_WORDS; ++i) + wr_u32_le(nops + i * 4u, 0xd503201fu); + if (mc->debug) { + u32 ofs = obj_pos(mc->obj, mc->section_id); + mc->emit_bytes(mc, nops, sizeof nops); + for (u32 i = 0; i < AA_PROLOGUE_WORDS; ++i) + debug_emit_row(mc->debug, mc->section_id, ofs + i * 4u, mc->loc); + } else { + mc->emit_bytes(mc, nops, sizeof nops); + } + } aa_emit_entry_saves(a); } @@ -1501,8 +1521,14 @@ static void aa_patch_prologue(AANativeTarget* a, const AAFrameLayout* L, words[n] = aa64_b(region - n); for (u32 i = n + 1u; i < region; ++i) words[i] = 0xd503201fu; } - for (u32 i = 0; i < region; ++i) - aa_patch32(a->base.obj, sec, a->prologue_pos + i * 4u, words[i]); + /* One contiguous patch over the reserved region rather than `region` + * separate obj_patch calls (each a chunk-directory lookup + small memcpy); + * mirrors the x64 backend. */ + { + u8 pbytes[AA_PROLOGUE_WORDS * 4u]; + for (u32 i = 0; i < region; ++i) wr_u32_le(pbytes + i * 4u, words[i]); + obj_patch(a->base.obj, sec, a->prologue_pos, pbytes, (size_t)region * 4u); + } } static void aa_emit_restore_frame(AANativeTarget* a, const AAFrameLayout* L) { diff --git a/src/core/buf.c b/src/core/buf.c @@ -116,22 +116,31 @@ static inline void buf_walk(const Buf* b, u32 ofs, void* external, size_t n, BufChunk* c; u32 chunk_start; u8* ext = (u8*)external; - /* Binary-search the directory for the last chunk whose start is <= ofs, then - * walk forward from there (the range may span into following chunks). */ - u32 lo = 0, hi = b->ndir; - while (lo < hi) { - u32 mid = lo + ((hi - lo) >> 1); - if (b->dir[mid].start <= ofs) - lo = mid + 1; - else - hi = mid; - } - if (lo == 0) { - c = b->head; - chunk_start = 0; + /* Fast path: emit-time patch-ups and reads overwhelmingly target the + * most-recently-written bytes (a function's own just-emitted prologue and + * branches), which live in the last indexed chunk. Check it before the binary + * search so the common case is O(1) instead of O(log n_chunks). */ + if (b->ndir > 0 && ofs >= b->dir[b->ndir - 1].start) { + c = b->dir[b->ndir - 1].chunk; + chunk_start = b->dir[b->ndir - 1].start; } else { - c = b->dir[lo - 1].chunk; - chunk_start = b->dir[lo - 1].start; + /* Binary-search the directory for the last chunk whose start is <= ofs, + * then walk forward (the range may span into following chunks). */ + u32 lo = 0, hi = b->ndir; + while (lo < hi) { + u32 mid = lo + ((hi - lo) >> 1); + if (b->dir[mid].start <= ofs) + lo = mid + 1; + else + hi = mid; + } + if (lo == 0) { + c = b->head; + chunk_start = 0; + } else { + c = b->dir[lo - 1].chunk; + chunk_start = b->dir[lo - 1].start; + } } while (c && n) { u32 chunk_end = chunk_start + c->used;