commit 261d004425ff5ab3be3242ace4927f15fe8f5ca6
parent b34c6beecd811a5aa7ac5bdbd2cc67c6ba99a032
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 15 Jun 2026 17:17:32 -0700
mc: give MCEmitter a per-function scratch arena
Generalizes the previous label-count reset: the MCEmitter now owns a
func_arena that backs all three per-function-transient allocations (the
label vector, forward-reference MCFixup nodes, and MCDataLabelRef nodes)
and is reset at every mc_begin_function. Previously these came from the
never-reset TU arena (c->tu), so the fixup/dataref nodes accumulated for
the whole TU and the label vector doubled-and-abandoned per function.
The whole-object CFI vectors (fdes/dirs) stay heap-realloc-backed (freed
in mc_free); only function-local scratch moves to func_arena. Safe because
labels/fixups/datarefs are all resolved within their producing function
(immediately for a backward ref, at mc_label_place for a forward one),
and cross-function references go through per-label SB_LOCAL object symbols
and emitted relocations in the ObjBuilder, not these structs.
Byte-identical (sqlite -O0 object bit-for-bit unchanged vs the prior
commit); cg-api, toy (1392/0), smoke-x64 green.
Diffstat:
1 file changed, 24 insertions(+), 14 deletions(-)
diff --git a/src/arch/mc.c b/src/arch/mc.c
@@ -111,7 +111,15 @@ typedef struct CfiFde {
typedef struct MCImpl {
MCEmitter base;
- Arena* arena;
+ /* Per-function scratch: labels, forward-reference fixups, and data-label
+ * refs. All are resolved within the function that produced them (immediately
+ * for a backward ref, at mc_label_place for a forward one); cross-function
+ * references go through per-label SB_LOCAL object symbols, and the emitted
+ * bytes/relocations live in the ObjBuilder, not here. So this arena is reset
+ * at every mc_begin_function — it grows only to the largest single function's
+ * scratch instead of accumulating the whole TU's in c->tu. The whole-object
+ * CFI vectors (fdes/dirs) are heap-realloc'd separately, not from here. */
+ Arena func_arena;
/* `loc` lives on MCEmitter base now (so per-arch emit hooks can read it
* to feed debug_emit_row). Use base.loc through impl_of(...)->base.loc
* or directly mc->base.loc. */
@@ -143,7 +151,7 @@ static void labels_grow(MCImpl* mc, u32 want) {
if (want <= mc->cap) return;
u32 ncap = mc->cap ? mc->cap * 2 : 16;
while (ncap < want) ncap *= 2;
- MCLabelInfo* nbuf = arena_array(mc->arena, MCLabelInfo, ncap);
+ MCLabelInfo* nbuf = arena_array(&mc->func_arena, MCLabelInfo, ncap);
if (mc->labels) memcpy(nbuf, mc->labels, sizeof(MCLabelInfo) * mc->nlabels);
/* The grown tail is left uninitialized: mc_label_new fully assigns every
* field of the one slot it hands out before any consumer indexes it, and
@@ -399,7 +407,7 @@ void mc_emit_label_ref(MCEmitter* m, MCLabel id, RelocKind kind,
compiler_panic(m->c, mc->base.loc, "MCEmitter: bad label %u", (unsigned)id);
}
MCLabelInfo* li = &mc->labels[id];
- MCFixup* fx = arena_new(mc->arena, MCFixup);
+ MCFixup* fx = arena_new(&mc->func_arena, MCFixup);
fx->sec_id = m->section_id;
fx->offset = obj_pos(m->obj, m->section_id) -
width; /* fixup site is the just-emitted insn */
@@ -436,7 +444,7 @@ void mc_emit_label_data_reloc(MCEmitter* m, u32 data_sec, u32 data_offset,
return;
}
{
- MCDataLabelRef* r = arena_new(mc->arena, MCDataLabelRef);
+ MCDataLabelRef* r = arena_new(&mc->func_arena, MCDataLabelRef);
r->data_sec = data_sec;
r->data_offset = data_offset;
r->kind = kind;
@@ -579,7 +587,7 @@ MCEmitter* mc_new(Compiler* c, ObjBuilder* o) {
base->cur_func_section = 0;
base->cur_func_start = 0;
- mc->arena = c->tu;
+ arena_init(&mc->func_arena, c->ctx->heap, 0);
mc->labels = NULL;
mc->nlabels = 0;
mc->cap = 0;
@@ -618,20 +626,22 @@ void mc_free(MCEmitter* m) {
mc->fdes_cap = 0;
mc->nfdes = 0;
}
+ arena_fini(&mc->func_arena);
}
void mc_begin_function(MCEmitter* m, ObjSymId sym, u32 section_id,
u32 start_offset) {
if (!m) return;
- /* Labels are function-local scratch: a reference is resolved immediately
- * (backward) or at mc_label_place (forward) before the function ends, and
- * cross-function references go through per-label SB_LOCAL object symbols, not
- * this array. Reset the count and reuse the buffer across functions, so it
- * grows only to the largest single function's label count. Without this it
- * accumulated every function's labels into one array in the (never-reset) TU
- * arena, doubling and abandoning each prior block (megabytes of dead arena +
- * large mmap-backed allocations on label-heavy TUs). */
- impl_of(m)->nlabels = 0;
+ /* Reclaim the previous function's label/fixup/dataref scratch (see the
+ * func_arena note on MCImpl). The labels vector is re-grown from the reset
+ * arena on demand, so drop the stale pointer/count here. */
+ {
+ MCImpl* mc = impl_of(m);
+ arena_reset(&mc->func_arena);
+ mc->labels = NULL;
+ mc->nlabels = 0;
+ mc->cap = 0;
+ }
m->cur_func_sym = sym;
m->cur_func_section = section_id;
m->cur_func_start = start_offset;