commit 6cc5c5774c44412d402a94214e98a72bc8533eaa
parent 834999ce5a1086ff57a599be687e3c10fa619e2e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 09:19:39 -0700
Speed up O1 optimizer hot paths
Diffstat:
5 files changed, 165 insertions(+), 101 deletions(-)
diff --git a/doc/plan/PERF.md b/doc/plan/PERF.md
@@ -285,11 +285,28 @@ bitset/range helpers), while CGIR lowering plus record construction is another
**26 %**. Built-in metrics on the same TU report **2,522 functions**,
**121,075 blocks**, **98,516 PRegs**, **1,049,079 ranges**, and **29.1 M live
bitset words touched**, which explains why the liveness/range bitset helpers are
-visible. A cheap-looking cleanup also shows up: `metrics_scope_from_name` and
-`metrics_count` burn ~2.7 % of O1 finalization even with `KIT_METRICS` unset,
-because each hot scope/counter call string-decodes the name before discovering
-the profiler sink is null. The O1 hot path should use enum-specific wrappers or
-check the profiler before name decoding.
+visible. A cheap cleanup from this profile was the disabled metrics fast path:
+`metrics_scope_from_name` and `metrics_count` burned ~2.7 % of O1 finalization
+with `KIT_METRICS` unset because each hot scope/counter call string-decoded the
+name before discovering the profiler sink was null. `metrics_*` now checks the
+sink before name decoding; enum-specific wrappers would still make metrics-on
+collection cheaper.
+
+Follow-up cleanup on the same sqlite `-O1` workload made the hot shapes cheaper:
+CGIR lowering precomputes local address-use, param lookup, emit order, and
+fallthrough successors; regalloc builds coalesce-group member lists before
+candidate scoring and uses them in group scans; loop-tree construction
+precomputes backedges and uses generation-marked loop bodies. Bounded macOS
+release timing for the single amalgamation compile is now **0.93 s best / 0.96 s
+mean** (7 runs, 5 s per-run timeout); the ecosystem-style two-TU sqlite O1 build
+is **1.20 s best / 1.22 s mean** (5 runs, 8 s timeout), down from the previous
+**2.78 s** best row. `KIT_METRICS=1` ticks on the same single-amalgamation
+command: `opt.o1.total` **27.09 M → 16.78 M** (−38 %), `opt.regalloc`
+**13.81 M → 7.83 M** (−43 %), `opt.o1.cg_ir_lower` **4.94 M → 1.16 M** (−77 %),
+and `opt.build_loop_tree` **1.41 M → 0.20 M** (−86 %). Linux callgrind rerun was
+blocked in this pass because `podman machine` started then immediately stopped
+at 3 GiB, 2.5 GiB, and the restored 2 GiB setting; the VM was left configured at
+2 GiB/stopped.
### Code size is still locally spill-bound
diff --git a/src/core/metrics.h b/src/core/metrics.h
@@ -220,16 +220,19 @@ static inline KitProfileCounter metrics_counter_from_name(const char* name) {
}
static inline void metrics_scope_begin(Compiler* c, const char* name) {
+ if (!profile_sink(c)) return;
KitProfileScope scope = metrics_scope_from_name(name);
if (scope != KIT_PROFILE_SCOPE_NONE) profile_scope_begin(c, scope);
}
static inline void metrics_scope_end(Compiler* c, const char* name) {
+ if (!profile_sink(c)) return;
KitProfileScope scope = metrics_scope_from_name(name);
if (scope != KIT_PROFILE_SCOPE_NONE) profile_scope_end(c, scope);
}
static inline void metrics_count(Compiler* c, const char* name, u64 value) {
+ if (!profile_sink(c)) return;
KitProfileCounter counter = metrics_counter_from_name(name);
if (counter != KIT_PROFILE_COUNTER_NONE) profile_count(c, counter, value);
}
diff --git a/src/opt/cg_ir_lower.c b/src/opt/cg_ir_lower.c
@@ -32,9 +32,12 @@ typedef struct CgIrLower {
Func* f;
OptLocalMap* locals;
u32 nlocals;
+ u8* local_addr_used;
+ const CgIrParam** param_by_local;
u32* label_block;
u32 nlabels;
u32* inst_block;
+ u32* fallthrough_by_block;
u8* leader;
CGLocal mat_local[CG_IR_LOWER_MAX_MAT];
u8 mat_role[CG_IR_LOWER_MAX_MAT];
@@ -112,12 +115,6 @@ static int local_needs_home(const CgIrLocal* in) {
(in->desc.flags & (CG_LOCAL_ADDR_TAKEN | CG_LOCAL_MEMORY_REQUIRED));
}
-static int operand_uses_local_addr(const Operand* op, CGLocal local) {
- if (!op) return 0;
- if (op->kind == OPK_LOCAL) return op->v.local == local;
- return 0;
-}
-
/* AGG_COPY/AGG_SET take their dest/src as *pointer values* to the aggregate —
* the emitter derefs an OPK_LOCAL pointer operand via pointer_addr_from_operand
* (it loads the pointer; it does not address the local's own slot). So a
@@ -125,43 +122,44 @@ static int operand_uses_local_addr(const Operand* op, CGLocal local) {
* its address, and must not force the local to a frame home. Only a non-pointer
* operand (the aggregate-typed local itself) genuinely addresses its storage.
* (STORE/LOAD/ADDR_OF use addr_from_operand, where an OPK_LOCAL always
- * addresses the slot, so they keep operand_uses_local_addr.) */
-static int operand_uses_local_agg_addr(Compiler* c, const Operand* op,
- CGLocal local) {
- if (!op || op->kind != OPK_LOCAL || op->v.local != local) return 0;
- return !cg_type_is_ptr(c, op->type);
+ * addresses the slot, so they keep the direct-address marking below.) */
+static void note_local_addr_use(CgIrLower* l, const Operand* op) {
+ if (!op || op->kind != OPK_LOCAL) return;
+ CGLocal local = op->v.local;
+ if (local == CG_LOCAL_NONE || local > l->nlocals) return;
+ l->local_addr_used[local] = 1;
}
-static int local_address_used_in_cg_ir(Compiler* c, const CgIrFunc* f,
- CGLocal local) {
+static void note_local_agg_addr_use(CgIrLower* l, const Operand* op) {
+ if (!op || op->kind != OPK_LOCAL || cg_type_is_ptr(l->c, op->type)) return;
+ CGLocal local = op->v.local;
+ if (local == CG_LOCAL_NONE || local > l->nlocals) return;
+ l->local_addr_used[local] = 1;
+}
+
+static void mark_local_address_uses(CgIrLower* l) {
+ const CgIrFunc* f = l->src;
+ l->local_addr_used = arena_zarray(l->f->arena, u8, l->nlocals + 1u);
for (u32 i = 0; i < f->ninsts; ++i) {
const CgIrInst* in = &f->insts[i];
switch ((CgIrOp)in->op) {
case CG_IR_LOAD:
case CG_IR_BITFIELD_LOAD:
- if (in->nopnds > 1u && operand_uses_local_addr(&in->opnds[1], local))
- return 1;
+ if (in->nopnds > 1u) note_local_addr_use(l, &in->opnds[1]);
break;
case CG_IR_STORE:
case CG_IR_BITFIELD_STORE:
- if (in->nopnds > 0u && operand_uses_local_addr(&in->opnds[0], local))
- return 1;
+ if (in->nopnds > 0u) note_local_addr_use(l, &in->opnds[0]);
break;
case CG_IR_AGG_SET:
- if (in->nopnds > 0u &&
- operand_uses_local_agg_addr(c, &in->opnds[0], local))
- return 1;
+ if (in->nopnds > 0u) note_local_agg_addr_use(l, &in->opnds[0]);
break;
case CG_IR_ADDR_OF:
- if (in->nopnds > 1u && operand_uses_local_addr(&in->opnds[1], local))
- return 1;
+ if (in->nopnds > 1u) note_local_addr_use(l, &in->opnds[1]);
break;
case CG_IR_AGG_COPY:
- if ((in->nopnds > 0u &&
- operand_uses_local_agg_addr(c, &in->opnds[0], local)) ||
- (in->nopnds > 1u &&
- operand_uses_local_agg_addr(c, &in->opnds[1], local)))
- return 1;
+ if (in->nopnds > 0u) note_local_agg_addr_use(l, &in->opnds[0]);
+ if (in->nopnds > 1u) note_local_agg_addr_use(l, &in->opnds[1]);
break;
/* VA_START/VA_ARG/VA_END/VA_COPY consume a pointer *value* (the address
* of the va_list, produced by an earlier ADDR_OF); they do not take the
@@ -171,13 +169,13 @@ static int local_address_used_in_cg_ir(Compiler* c, const CgIrFunc* f,
break;
}
}
- return 0;
}
static void lower_locals(CgIrLower* l) {
l->nlocals = l->src->nlocals;
l->locals =
arena_zarray(l->f->arena, OptLocalMap, l->nlocals ? l->nlocals : 1u);
+ mark_local_address_uses(l);
for (u32 i = 0; i < l->src->nlocals; ++i) {
const CgIrLocal* in = &l->src->locals[i];
OptLocalMap* m;
@@ -196,7 +194,7 @@ static void lower_locals(CgIrLower* l) {
*/
m->address_taken =
local_needs_home(in) ||
- local_address_used_in_cg_ir(l->c, l->src, in->id) ||
+ (in->id <= l->nlocals && l->local_addr_used[in->id]) ||
cg_type_is_aggregate(l->c, in->desc.type) ||
cg_type_size(l->c, in->desc.type) > (u64)l->c->target.ptr_size;
@@ -222,10 +220,14 @@ static void lower_locals(CgIrLower* l) {
}
}
-static const CgIrParam* find_param(const CgIrFunc* f, CGLocal local) {
- for (u32 i = 0; i < f->nparams; ++i)
- if (f->params[i].local == local) return &f->params[i];
- return NULL;
+static void build_param_by_local(CgIrLower* l) {
+ l->param_by_local =
+ arena_zarray(l->f->arena, const CgIrParam*, l->nlocals + 1u);
+ for (u32 i = 0; i < l->src->nparams; ++i) {
+ const CgIrParam* p = &l->src->params[i];
+ if (p->local == CG_LOCAL_NONE || p->local > l->nlocals) continue;
+ l->param_by_local[p->local] = p;
+ }
}
static void lower_params(CgIrLower* l) {
@@ -239,10 +241,12 @@ static void lower_params(CgIrLower* l) {
const ABIFuncInfo* fi = NULL;
if (l->c && l->c->abi && l->f->desc.fn_type)
fi = abi_cg_func_info(l->c->abi, l->f->desc.fn_type);
+ build_param_by_local(l);
for (u32 i = 0; i < l->src->nlocals; ++i) {
const CgIrLocal* loc = &l->src->locals[i];
if (!loc->is_param) continue;
- const CgIrParam* p = find_param(l->src, loc->id);
+ const CgIrParam* p =
+ (loc->id <= l->nlocals) ? l->param_by_local[loc->id] : NULL;
OptLocalMap* m = local_map(l, loc->id, loc->desc.loc);
OptCGParamDesc d;
memset(&d, 0, sizeof d);
@@ -390,9 +394,6 @@ static void make_blocks(CgIrLower* l, const u32* label_place) {
l->f->entry = ir_block_new(l->f);
ir_note_emit(l->f, l->f->entry);
}
- l->f->emit_order_n = 0;
- for (u32 i = 0; i < f->ninsts; ++i) ir_note_emit(l->f, l->inst_block[i]);
- if (!f->ninsts) ir_note_emit(l->f, l->f->entry);
}
static void emit_param_decls(CgIrLower* l) {
@@ -1260,6 +1261,13 @@ static void lower_one_inst(CgIrLower* l, u32 idx) {
}
static void add_fallthrough_succs(CgIrLower* l) {
+ l->fallthrough_by_block =
+ arena_array(l->f->arena, u32, l->f->nblocks ? l->f->nblocks : 1u);
+ for (u32 b = 0; b < l->f->nblocks; ++b) l->fallthrough_by_block[b] = UINT32_MAX;
+ for (u32 i = 0; i + 1u < l->f->emit_order_n; ++i) {
+ u32 b = l->f->emit_order[i];
+ if (b < l->f->nblocks) l->fallthrough_by_block[b] = l->f->emit_order[i + 1u];
+ }
for (u32 b = 0; b < l->f->nblocks; ++b) {
Block* bl = &l->f->blocks[b];
if (bl->nsucc) continue;
@@ -1286,12 +1294,7 @@ static void add_fallthrough_succs(CgIrLower* l) {
break;
}
}
- for (u32 i = 0; i + 1u < l->f->emit_order_n; ++i) {
- if (l->f->emit_order[i] == b) {
- set_succ1(l, b, l->f->emit_order[i + 1u]);
- break;
- }
- }
+ set_succ1(l, b, l->fallthrough_by_block[b]);
}
}
diff --git a/src/opt/pass_loop.c b/src/opt/pass_loop.c
@@ -3,12 +3,20 @@
#include "core/arena.h"
#include "opt/opt_internal.h"
+static void loop_body_mark(u32* mark, u32 gen, u32* body_list, u32* nbody,
+ u32 b) {
+ if (mark[b] == gen) return;
+ mark[b] = gen;
+ body_list[(*nbody)++] = b;
+}
+
static void loop_mark_body(Func* f, const u8* visited, u32 header, u32 latch,
- u8* body, u32* stack) {
+ u32* mark, u32 gen, u32* body_list, u32* nbody,
+ u32* stack) {
u32 sp = 0;
- if (!body[header]) body[header] = 1;
- if (!body[latch]) {
- body[latch] = 1;
+ loop_body_mark(mark, gen, body_list, nbody, header);
+ if (mark[latch] != gen) {
+ loop_body_mark(mark, gen, body_list, nbody, latch);
stack[sp++] = latch;
}
@@ -18,8 +26,8 @@ static void loop_mark_body(Func* f, const u8* visited, u32 header, u32 latch,
Block* bl = &f->blocks[b];
for (u32 p = 0; p < bl->npreds; ++p) {
u32 pred = bl->preds[p];
- if (pred >= f->nblocks || !visited[pred] || body[pred]) continue;
- body[pred] = 1;
+ if (pred >= f->nblocks || !visited[pred] || mark[pred] == gen) continue;
+ loop_body_mark(mark, gen, body_list, nbody, pred);
stack[sp++] = pred;
}
}
@@ -50,28 +58,47 @@ void opt_build_loop_tree(Func* f) {
return;
}
- u8* body = arena_zarray(f->arena, u8, f->nblocks);
+ u32* first_edge = arena_array(f->arena, u32, f->nblocks);
+ for (u32 b = 0; b < f->nblocks; ++b) first_edge[b] = UINT32_MAX;
+ u32 max_edges = 0;
+ for (u32 b = 0; b < f->nblocks; ++b)
+ if (a.reachable[b]) max_edges += f->blocks[b].nsucc;
+ u32* edge_latch = arena_array(f->arena, u32, max_edges ? max_edges : 1u);
+ u32* edge_next = arena_array(f->arena, u32, max_edges ? max_edges : 1u);
+ u32 nedges = 0;
+
+ for (u32 latch = 0; latch < f->nblocks; ++latch) {
+ if (!a.reachable[latch]) continue;
+ Block* lb = &f->blocks[latch];
+ for (u32 s = 0; s < lb->nsucc; ++s) {
+ u32 header = lb->succ[s];
+ if (header >= f->nblocks || !a.reachable[header]) continue;
+ if (!opt_analysis_dominates(&a, header, latch)) continue;
+ edge_latch[nedges] = latch;
+ edge_next[nedges] = first_edge[header];
+ first_edge[header] = nedges++;
+ }
+ }
+
+ u32* mark = arena_zarray(f->arena, u32, f->nblocks);
+ u32* body_list = arena_array(f->arena, u32, f->nblocks);
u32* stack = arena_array(f->arena, u32, f->nblocks);
+ u32 gen = 1;
for (u32 header = 0; header < f->nblocks; ++header) {
- if (!a.reachable[header]) continue;
- memset(body, 0, f->nblocks * sizeof body[0]);
- int has_loop = 0;
-
- for (u32 latch = 0; latch < f->nblocks; ++latch) {
- if (!a.reachable[latch]) continue;
- Block* lb = &f->blocks[latch];
- for (u32 s = 0; s < lb->nsucc; ++s) {
- if (lb->succ[s] != header) continue;
- if (!opt_analysis_dominates(&a, header, latch)) continue;
- has_loop = 1;
- loop_mark_body(f, a.reachable, header, latch, body, stack);
- }
+ if (first_edge[header] == UINT32_MAX) continue;
+ u32 nbody = 0;
+ for (u32 e = first_edge[header]; e != UINT32_MAX; e = edge_next[e])
+ loop_mark_body(f, a.reachable, header, edge_latch[e], mark, gen,
+ body_list, &nbody, stack);
+ for (u32 i = 0; i < nbody; ++i) {
+ u32 b = body_list[i];
+ if (f->blocks[b].loop_depth < 31) ++f->blocks[b].loop_depth;
+ }
+ if (++gen == 0) {
+ memset(mark, 0, f->nblocks * sizeof mark[0]);
+ gen = 1;
}
-
- if (!has_loop) continue;
- for (u32 b = 0; b < f->nblocks; ++b)
- if (body[b] && f->blocks[b].loop_depth < 31) ++f->blocks[b].loop_depth;
}
for (u32 b = 0; b < f->nblocks; ++b)
diff --git a/src/opt/pass_lower.c b/src/opt/pass_lower.c
@@ -780,16 +780,42 @@ static int alloc_group_member(Func* f, PReg root, PReg v) {
return alloc_coalesce_root(f, v) == root;
}
-static void alloc_group_info(Func* f, const OptLiveRangeSet* ranges, PReg root,
+static void alloc_build_member_lists(Func* f, OptAllocator* a,
+ const OptLiveRangeSet* ranges) {
+ if (!f->opt_coalesce_parent) return;
+ u32 nr = opt_reg_count(f);
+ a->member_head = arena_zarray(f->arena, u32, nr ? nr : 1u);
+ a->member_next = arena_zarray(f->arena, u32, nr ? nr : 1u);
+ for (PReg v = 1; v < nr; ++v) {
+ if (ranges->first_range_by_preg[v] == OPT_RANGE_NONE) continue;
+ PReg root = alloc_coalesce_root(f, v);
+ a->member_next[v] = a->member_head[root];
+ a->member_head[root] = v;
+ }
+}
+
+static PReg alloc_group_first_member(const OptAllocator* a, PReg root) {
+ return a->member_head ? (PReg)a->member_head[root] : root;
+}
+
+static PReg alloc_group_next_member(const OptAllocator* a, PReg root,
+ PReg member) {
+ (void)root;
+ if (!a->member_head) return 0;
+ return (PReg)a->member_next[member];
+}
+
+static void alloc_group_info(Func* f, const OptAllocator* a,
+ const OptLiveRangeSet* ranges, PReg root,
OptAllocGroupInfo* out) {
memset(out, 0, sizeof *out);
out->root = root;
out->first = (u32)~0u;
out->tied_hard_reg = -1;
out->cls = f->preg_info[root].cls;
- for (PReg v = 1; v < opt_reg_count(f); ++v) {
+ for (PReg v = alloc_group_first_member(a, root); v != 0;
+ v = alloc_group_next_member(a, root, v)) {
if (ranges->first_range_by_preg[v] == OPT_RANGE_NONE) continue;
- if (!alloc_group_member(f, root, v)) continue;
OptPRegInfo* vi = &f->preg_info[v];
out->spill_cost += vi->frequency ? vi->frequency : vi->spill_cost;
out->live_length += vi->live_length;
@@ -952,10 +978,11 @@ static int spill_slot_compatible(Func* f, FrameSlot fs, PReg v) {
static void alloc_compute_group_conflicts(Func* f, OptAllocator* a,
const OptLiveRangeSet* ranges,
PReg root) {
+ (void)f;
for (u32 w = 0; w < a->loc_words; ++w) a->conflict_locs[w] = 0;
- for (PReg v = 1; v < opt_reg_count(f); ++v) {
+ for (PReg v = alloc_group_first_member(a, root); v != 0;
+ v = alloc_group_next_member(a, root, v)) {
if (ranges->first_range_by_preg[v] == OPT_RANGE_NONE) continue;
- if (!alloc_group_member(f, root, v)) continue;
for (u32 ri = ranges->first_range_by_preg[v]; ri != OPT_RANGE_NONE;
ri = ranges->ranges[ri].next) {
const OptLiveRange* lr = &ranges->ranges[ri];
@@ -977,11 +1004,12 @@ static void alloc_compute_group_conflicts(Func* f, OptAllocator* a,
static void alloc_mark_group_loc(Func* f, OptAllocator* a,
const OptLiveRangeSet* ranges, PReg root,
u32 loc_bit) {
+ (void)f;
u32 w = loc_bit / 64u;
u64 mask = 1ull << (loc_bit % 64u);
- for (PReg v = 1; v < opt_reg_count(f); ++v) {
+ for (PReg v = alloc_group_first_member(a, root); v != 0;
+ v = alloc_group_next_member(a, root, v)) {
if (ranges->first_range_by_preg[v] == OPT_RANGE_NONE) continue;
- if (!alloc_group_member(f, root, v)) continue;
for (u32 ri = ranges->first_range_by_preg[v]; ri != OPT_RANGE_NONE;
ri = ranges->ranges[ri].next) {
const OptLiveRange* lr = &ranges->ranges[ri];
@@ -999,9 +1027,9 @@ static void alloc_assign_group_hard(Func* f, OptAllocator* a,
Reg r) {
u8 cls = f->preg_info[root].cls;
u32 bit = hard_loc_bit(cls, r);
- for (PReg v = 1; v < opt_reg_count(f); ++v) {
+ for (PReg v = alloc_group_first_member(a, root); v != 0;
+ v = alloc_group_next_member(a, root, v)) {
if (ranges->first_range_by_preg[v] == OPT_RANGE_NONE) continue;
- if (!alloc_group_member(f, root, v)) continue;
OptPRegInfo* vi = &f->preg_info[v];
vi->alloc_kind = OPT_ALLOC_HARD;
vi->hard_reg = r;
@@ -1040,9 +1068,9 @@ static void alloc_assign_group_stack(Func* f, OptAllocator* a,
* groups sums all of their traffic — so the emitter's hot-slot-low ordering
* sees the slot's total demand, not just the first group that created it. */
spill_slot_add_priority(f, slot, group_priority);
- for (PReg v = 1; v < opt_reg_count(f); ++v) {
+ for (PReg v = alloc_group_first_member(a, root); v != 0;
+ v = alloc_group_next_member(a, root, v)) {
if (ranges->first_range_by_preg[v] == OPT_RANGE_NONE) continue;
- if (!alloc_group_member(f, root, v)) continue;
OptPRegInfo* vi = &f->preg_info[v];
vi->spill_slot = slot;
vi->alloc_kind = OPT_ALLOC_SPILL;
@@ -1054,9 +1082,9 @@ static void alloc_assign_group_stack(Func* f, OptAllocator* a,
u32 bit = a->hard_loc_bits + stack_idx;
u32 w = bit / 64u;
u64 mask = 1ull << (bit % 64u);
- for (PReg v = 1; v < opt_reg_count(f); ++v) {
+ for (PReg v = alloc_group_first_member(a, root); v != 0;
+ v = alloc_group_next_member(a, root, v)) {
if (ranges->first_range_by_preg[v] == OPT_RANGE_NONE) continue;
- if (!alloc_group_member(f, root, v)) continue;
for (u32 ri = ranges->first_range_by_preg[v]; ri != OPT_RANGE_NONE;
ri = ranges->ranges[ri].next) {
const OptLiveRange* lr = &ranges->ranges[ri];
@@ -1089,6 +1117,7 @@ static void opt_assign_ranges(Func* f, const OptLiveRangeSet* ranges,
a->stack_slots = NULL;
a->stack_slot_count = 0;
a->stack_slot_cap = 0;
+ alloc_build_member_lists(f, a, ranges);
/* Build candidate list: every coalesce-root PReg that has live ranges. */
u32 ncands = 0;
@@ -1105,26 +1134,11 @@ static void opt_assign_ranges(Func* f, const OptLiveRangeSet* ranges,
PReg root = alloc_coalesce_root(f, v);
if (root != v) continue;
cands[n].v = v;
- alloc_group_info(f, ranges, root, &cands[n].gi);
+ alloc_group_info(f, a, ranges, root, &cands[n].gi);
++n;
}
alloc_sort_candidates(cands, n);
- /* Build per-root member lists once (only meaningful with W3 coalescing). The
- * hint-path precise interference check uses these to enumerate a candidate
- * group's members cheaply instead of rescanning all PRegs per probe. */
- if (f->opt_coalesce_parent) {
- u32 nr = opt_reg_count(f);
- a->member_head = arena_zarray(f->arena, u32, nr ? nr : 1u);
- a->member_next = arena_zarray(f->arena, u32, nr ? nr : 1u);
- for (PReg v = 1; v < nr; ++v) {
- if (ranges->first_range_by_preg[v] == OPT_RANGE_NONE) continue;
- PReg root = alloc_coalesce_root(f, v);
- a->member_next[v] = a->member_head[root];
- a->member_head[root] = v;
- }
- }
-
for (u32 i = 0; i < n; ++i) {
PReg v = cands[i].v;
OptAllocGroupInfo gi = cands[i].gi;