// Build Cranelift-style regalloc `Output` for emission.
//
// This pass does not rewrite MachV instructions. Instead, it produces:
// - Per-operand allocations for each instruction/terminator.
// - An edit stream (moves between regs and spill slots) that the emitter
// interleaves with instructions.
///|
fn assigned_preg_of(
vreg_id : Int,
assigned_pregs : Array[@abi.PReg?],
) -> @abi.PReg? {
if vreg_id >= 0 && vreg_id < assigned_pregs.length() {
assigned_pregs[vreg_id]
} else {
None
}
}
///|
fn spill_slot_of(vreg_id : Int, spill_slots : Array[Int]) -> Int {
if vreg_id >= 0 && vreg_id < spill_slots.length() {
spill_slots[vreg_id]
} else {
-1
}
}
///|
fn vreg_home_loc_dense(
vreg : @abi.VReg,
assigned_pregs : Array[@abi.PReg?],
spill_slots : Array[Int],
) -> Loc {
if assigned_preg_of(vreg.id, assigned_pregs) is Some(preg) {
Reg(preg)
} else {
let slot = spill_slot_of(vreg.id, spill_slots)
if slot >= 0 {
Spill(slot)
} else {
abort("missing allocation for vreg \{vreg.id}")
}
}
}
///|
fn reg_home_loc_dense(
reg : @abi.Reg,
assigned_pregs : Array[@abi.PReg?],
spill_slots : Array[Int],
) -> Loc {
match reg {
Physical(preg) => Reg(preg)
Virtual(vreg) => vreg_home_loc_dense(vreg, assigned_pregs, spill_slots)
}
}
///|
fn fixed_use_preg(inst : @instr.Inst, idx : Int) -> @abi.PReg? {
if idx < inst.use_constraints.length() {
match inst.use_constraints[idx] {
FixedReg(preg) => Some(preg)
_ => None
}
} else {
None
}
}
///|
fn fixed_def_preg(inst : @instr.Inst, idx : Int) -> @abi.PReg? {
if idx < inst.def_constraints.length() {
match inst.def_constraints[idx] {
FixedReg(preg) => Some(preg)
_ => None
}
} else {
None
}
}
///|
fn mark_used_reg(mask : Array[Bool], idx : Int) -> Unit {
if idx < 0 {
return
}
while idx >= mask.length() {
mask.push(false)
}
mask[idx] = true
}
///|
fn is_used_reg(mask : Array[Bool], idx : Int) -> Bool {
idx >= 0 && idx < mask.length() && mask[idx]
}
///|
fn add_used_reg(
used_int : Array[Bool],
used_fp : Array[Bool],
preg : @abi.PReg,
) -> Unit {
match preg.class {
Int => mark_used_reg(used_int, preg.index)
_ => mark_used_reg(used_fp, preg.index)
}
}
///|
const USED_REG_MARK_CAP : Int = 128
///|
fn mark_used(used_marks : Array[Int], idx : Int, epoch : Int) -> Unit {
guard idx >= 0 && idx < used_marks.length() else {
abort("preg index out of range for used-mark table: \{idx}")
}
used_marks[idx] = epoch
}
///|
fn is_used(used_marks : Array[Int], idx : Int, epoch : Int) -> Bool {
idx >= 0 && idx < used_marks.length() && used_marks[idx] == epoch
}
///|
fn add_used_reg_marks(
used_int_marks : Array[Int],
used_fp_marks : Array[Int],
epoch : Int,
preg : @abi.PReg,
) -> Unit {
match preg.class {
Int => mark_used(used_int_marks, preg.index, epoch)
_ => mark_used(used_fp_marks, preg.index, epoch)
}
}
///|
fn active_reload_get(
active_reload_epoch : Array[Int],
active_reload_preg_idx : Array[Int],
slot : Int,
cls : @abi.RegClass,
epoch : Int,
) -> @abi.PReg? {
if slot < 0 || slot >= active_reload_epoch.length() {
return None
}
if active_reload_epoch[slot] == epoch {
Some({ index: active_reload_preg_idx[slot], class: cls })
} else {
None
}
}
///|
fn active_reload_set(
active_reload_epoch : Array[Int],
active_reload_preg_idx : Array[Int],
slot : Int,
preg : @abi.PReg,
epoch : Int,
) -> Unit {
if slot < 0 || slot >= active_reload_epoch.length() {
return
}
active_reload_epoch[slot] = epoch
active_reload_preg_idx[slot] = preg.index
}
///|
fn active_reload_clear(active_reload_epoch : Array[Int], slot : Int) -> Unit {
if slot < 0 || slot >= active_reload_epoch.length() {
return
}
active_reload_epoch[slot] = 0
}
///|
fn pick_temp_preg(
out : Output,
isa : @isa.ISA,
env : @isa.MachineEnv,
cls : @abi.RegClass,
used_int_marks : Array[Int],
used_fp_marks : Array[Int],
used_epoch : Int,
temp_int_pool : Array[Int],
temp_fp_pool : Array[Int],
temp_vec_pool : Array[Int],
reserved_int_regs : Array[Int],
need_preserve : Bool,
cursor : Int,
) -> (@abi.PReg, Int, Int?) {
let scratch1 = isa.scratch_reg_1_index()
let scratch2 = isa.scratch_reg_2_index()
fn is_reserved_int_reg(idx : Int) -> Bool {
if idx == scratch1 || idx == scratch2 {
return true
}
reserved_int_regs.contains(idx)
}
fn alloc_temp_spill_slot(out : Output, cls : @abi.RegClass) -> Int {
// Spill slots are counted in 8-byte units.
// Keep the same rules as BundleSet::new_spill_bundle.
match cls {
Vector => {
if out.num_spillslots % 2 != 0 {
out.num_spillslots += 1
}
let slot = out.num_spillslots
out.num_spillslots += 2
slot
}
_ => {
let slot = out.num_spillslots
out.num_spillslots += 1
slot
}
}
}
match cls {
Int => {
for i in 0.. {
for i in 0.. {
for i in 0..preg/spillslot assignment.
///
/// Notes:
/// - All instruction operands must be assigned a register at the program point
/// of the instruction. Stack locations are only used between instructions.
/// - Stack loads/stores are expressed as `Edit::Move` between `Loc::Spill` and
/// `Loc::Reg`.
fn build_output(
func : @machv.Function,
liveness : LivenessResult,
alloc : RegAllocResult,
isa : @isa.ISA,
embedding_abi : @abi.EmbeddingABI,
) -> Output {
let out = Output::Output()
out.num_spillslots = alloc.num_spill_slots
let max_vreg_id = func.next_vreg_id
let assigned_pregs : Array[@abi.PReg?] = Array::make(max_vreg_id, None)
let spill_slots : Array[Int] = Array::make(max_vreg_id, -1)
for vreg_id, preg in alloc.assignments {
if vreg_id >= 0 && vreg_id < max_vreg_id {
assigned_pregs[vreg_id] = Some(preg)
}
}
for vreg_id, slot in alloc.spill_slots {
if vreg_id >= 0 && vreg_id < max_vreg_id {
spill_slots[vreg_id] = slot
}
}
// Param allocations (used by the prologue).
for p in func.params {
out.push_param_loc(vreg_home_loc_dense(p, assigned_pregs, spill_slots))
}
// Cranelift/regalloc2 does not run an extra per-block reload-coalescing pass
// during output construction; keep this disabled on the hot path for closer
// alignment and lower compile-time overhead.
let enable_reload_coalescing = false
let reload_intervals : Map[(Int, Int), ReloadInterval] = if enable_reload_coalescing {
let intervals = compute_reload_intervals(func, alloc)
allocate_reload_registers(
func, alloc, intervals, liveness, isa, embedding_abi,
)
intervals
} else {
Map([])
}
// Temp-reg pools for local reloads when no coalescing is available.
let used_int_regs : Array[Bool] = Array::make(USED_REG_MARK_CAP, false)
let used_fp_regs : Array[Bool] = Array::make(USED_REG_MARK_CAP, false)
for _, preg in alloc.assignments {
add_used_reg(used_int_regs, used_fp_regs, preg)
}
let reserve_extra_results_ptr = func.needs_extra_results_ptr_for_call_conv(
embedding_abi.call_conv,
) ||
func.calls_multi_value_function_for_call_conv(embedding_abi.call_conv)
let reserved_int_regs = embedding_abi.reserved_int_indices(
reserve_context_cache_0=func.should_reserve_context_cache_0(),
reserve_context_cache_1=func.should_reserve_context_cache_1(),
reserve_extra_results_ptr~,
)
let env = isa.machine_env(reserved_int_regs~)
// Reserve dedicated scratch regs (used by the move resolver).
for idx in env.scratch_int {
mark_used_reg(used_int_regs, idx)
}
for idx in env.scratch_float {
mark_used_reg(used_fp_regs, idx)
}
// Reserve embedding-owned registers.
for idx in reserved_int_regs {
mark_used_reg(used_int_regs, idx)
}
// Temp-reg pools for local reloads/move resolution:
// - Always include scratch regs.
// - Prefer higher-index regs (avoid common arg/return regs on most ABIs).
fn push_temp_reg(pool : Array[Int], used : Array[Bool], idx : Int) -> Unit {
// Pool sizes are tiny; linear scan avoids extra dependencies.
let mut already = false
for v in pool {
if v == idx {
already = true
}
}
if !is_used_reg(used, idx) && !already {
pool.push(idx)
}
}
let temp_int_pool : Array[Int] = []
for idx in env.scratch_int {
temp_int_pool.push(idx)
}
let int_candidates : Array[@abi.PReg] = []
for r in env.nonpreferred_int {
int_candidates.push(r)
}
for r in env.preferred_int {
int_candidates.push(r)
}
for r in int_candidates {
if r.index >= 8 {
push_temp_reg(temp_int_pool, used_int_regs, r.index)
}
}
for r in int_candidates {
if r.index < 8 {
push_temp_reg(temp_int_pool, used_int_regs, r.index)
}
}
let temp_fp_pool : Array[Int] = []
for idx in env.scratch_float {
temp_fp_pool.push(idx)
}
let fp_candidates : Array[@abi.PReg] = []
for r in env.nonpreferred_float {
fp_candidates.push(r)
}
for r in env.preferred_float {
fp_candidates.push(r)
}
for r in fp_candidates {
if r.index >= 8 {
push_temp_reg(temp_fp_pool, used_fp_regs, r.index)
}
}
for r in fp_candidates {
if r.index < 8 {
push_temp_reg(temp_fp_pool, used_fp_regs, r.index)
}
}
// Vector temps share the same physical register bank as floats on current ISAs.
let temp_vec_pool : Array[Int] = []
for idx in env.scratch_float {
temp_vec_pool.push(idx)
}
let vec_candidates : Array[@abi.PReg] = []
for r in env.nonpreferred_vector {
vec_candidates.push(r)
}
for r in env.preferred_vector {
vec_candidates.push(r)
}
for r in vec_candidates {
if r.index >= 8 {
push_temp_reg(temp_vec_pool, used_fp_regs, r.index)
}
}
for r in vec_candidates {
if r.index < 8 {
push_temp_reg(temp_vec_pool, used_fp_regs, r.index)
}
}
// Helper: record a resolved RegMove list as edits at a given program point.
fn push_resolved_moves(
out : Output,
block_id : Int,
inst_idx : Int,
pos : ProgPos,
moves : Array[RegMove],
) -> Unit {
if moves.is_empty() {
return
}
let resolved = resolve_parallel_moves(moves, isa)
for mv in resolved {
out.push_edit(block_id, inst_idx, pos, Move(mv.from, mv.to, mv.class))
}
}
// regalloc2 keeps edits as a position-sorted linear stream and consumes
// them with a forward cursor during output construction.
fn take_point_edits(
inst_edits : Array[(Int, Int, InstEdits)],
cursor : Int,
block_id : Int,
inst_idx : Int,
) -> (InstEdits?, Int) {
if cursor >= inst_edits.length() {
return (None, cursor)
}
let (edit_block, edit_inst, edits) = inst_edits[cursor]
if edit_block == block_id && edit_inst == inst_idx {
(Some(edits), cursor + 1)
} else if edit_block < block_id ||
(edit_block == block_id && edit_inst < inst_idx) {
abort(
"inst_edits stream out of order at block=\{block_id} inst=\{inst_idx}",
)
} else {
(None, cursor)
}
}
// Walk blocks and create per-operand allocations. Emit extra edits for spills
// (reloads before uses, stores after defs) as needed.
let used_int_marks = Array::make(USED_REG_MARK_CAP, 0)
let used_fp_marks = Array::make(USED_REG_MARK_CAP, 0)
let mut used_epoch = 1
let spill_use_tmp_idx = Array::make(max_vreg_id, -1)
let spill_use_tmp_epoch = Array::make(max_vreg_id, 0)
let spill_def_tmp_idx = Array::make(max_vreg_id, -1)
let spill_def_tmp_epoch = Array::make(max_vreg_id, 0)
let mut spill_tmp_epoch = 1
let mut inst_edits_cursor = 0
let active_reload_epoch : Array[Int] = Array::make(alloc.num_spill_slots, 0)
let active_reload_preg_idx : Array[Int] = Array::make(
alloc.num_spill_slots,
-1,
)
let mut block_reload_epoch = 0
for block_id, block in func.blocks {
block_reload_epoch = block_reload_epoch + 1
let reload_epoch = block_reload_epoch
let mut temp_cursor = 0
for inst_idx, inst in block.insts {
let (point_edits, next_inst_edits_cursor) = take_point_edits(
alloc.inst_edits,
inst_edits_cursor,
block_id,
inst_idx,
)
inst_edits_cursor = next_inst_edits_cursor
// Fixed-reg constraint edits at this point.
if point_edits is Some(edits) {
push_resolved_moves(out, block_id, inst_idx, Before, edits.before)
push_resolved_moves(out, block_id, inst_idx, After, edits.after)
}
// Fast path: if this instruction has no non-fixed spilled uses/defs, we
// do not need per-inst temp-reg selection or used-reg marking.
let mut needs_spill_temps = false
for i, use_reg in inst.uses {
if use_reg is Virtual(vreg) &&
assigned_preg_of(vreg.id, assigned_pregs) is None &&
fixed_use_preg(inst, i) is None {
needs_spill_temps = true
break
}
}
if !needs_spill_temps {
for i, def in inst.defs {
if def.reg is Virtual(vreg) &&
assigned_preg_of(vreg.id, assigned_pregs) is None &&
fixed_def_preg(inst, i) is None {
needs_spill_temps = true
break
}
}
}
if !needs_spill_temps {
let (alloc_start, alloc_total) = out.begin_inst_allocs(
block_id,
inst_idx,
false,
inst.defs.length(),
inst.uses.length(),
)
for i, def in inst.defs {
match fixed_def_preg(inst, i) {
Some(preg) => out.push_inst_alloc_loc(Reg(preg))
None =>
match def.reg {
Physical(preg) => out.push_inst_alloc_loc(Reg(preg))
Virtual(vreg) =>
match assigned_preg_of(vreg.id, assigned_pregs) {
Some(preg) => out.push_inst_alloc_loc(Reg(preg))
None => abort("missing def allocation for vreg \{vreg.id}")
}
}
}
}
for i, use_reg in inst.uses {
match fixed_use_preg(inst, i) {
Some(preg) => out.push_inst_alloc_loc(Reg(preg))
None =>
match use_reg {
Physical(preg) => out.push_inst_alloc_loc(Reg(preg))
Virtual(vreg) =>
match assigned_preg_of(vreg.id, assigned_pregs) {
Some(preg) => out.push_inst_alloc_loc(Reg(preg))
None => abort("missing use allocation for vreg \{vreg.id}")
}
}
}
}
out.end_inst_allocs(alloc_start, alloc_total)
continue
}
used_epoch = used_epoch + 1
let epoch = used_epoch
// First, mark all registers that will be used by non-spilled operands at
// this instruction (including fixed-reg constraints).
for i, def in inst.defs {
match fixed_def_preg(inst, i) {
Some(preg) =>
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, preg)
None =>
match def.reg {
Physical(preg) =>
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, preg)
Virtual(vreg) =>
match assigned_preg_of(vreg.id, assigned_pregs) {
Some(preg) =>
add_used_reg_marks(
used_int_marks, used_fp_marks, epoch, preg,
)
None => ()
}
}
}
}
for i, use_reg in inst.uses {
match fixed_use_preg(inst, i) {
Some(preg) =>
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, preg)
None =>
match use_reg {
Physical(preg) =>
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, preg)
Virtual(vreg) =>
match assigned_preg_of(vreg.id, assigned_pregs) {
Some(preg) =>
add_used_reg_marks(
used_int_marks, used_fp_marks, epoch, preg,
)
None => ()
}
}
}
}
// Also reserve any registers mentioned in fixed-reg constraint edits at this point.
// If we steal one of these registers and later restore it, we'd clobber the move result.
if point_edits is Some(edits) {
for mv in edits.before {
if mv.from is Reg(p) {
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, p)
}
if mv.to is Reg(p) {
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, p)
}
}
for mv in edits.after {
if mv.from is Reg(p) {
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, p)
}
if mv.to is Reg(p) {
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, p)
}
}
}
// Reload spilled uses into temps as needed.
spill_tmp_epoch = spill_tmp_epoch + 1
let spill_epoch = spill_tmp_epoch
for i, use_reg in inst.uses {
if use_reg is Virtual(vreg) &&
assigned_preg_of(vreg.id, assigned_pregs) is None {
let slot = spill_slot_of(vreg.id, spill_slots)
if slot < 0 {
continue
}
// Fixed-reg constraints are handled by constraint edits.
if fixed_use_preg(inst, i) is Some(_) {
continue
}
guard vreg.id >= 0 && vreg.id < max_vreg_id else {
abort("vreg id out of range in output build: \{vreg.id}")
}
// If already assigned a temp within this instruction, reuse it.
if spill_use_tmp_epoch[vreg.id] == spill_epoch {
continue
}
// If the slot is currently active in a coalesced reload reg, reuse it.
if active_reload_get(
active_reload_epoch,
active_reload_preg_idx,
slot,
vreg.class,
reload_epoch,
)
is Some(rp) {
spill_use_tmp_epoch[vreg.id] = spill_epoch
spill_use_tmp_idx[vreg.id] = rp.index
continue
}
// If this slot has a coalesced interval reg, load once and keep active.
if reload_intervals.get((block_id, slot)) is Some(interval) &&
interval.preg is Some(rp) {
out.push_edit(
block_id,
inst_idx,
Before,
Move(Spill(slot), Reg(rp), vreg.class),
)
active_reload_set(
active_reload_epoch, active_reload_preg_idx, slot, rp, reload_epoch,
)
spill_use_tmp_epoch[vreg.id] = spill_epoch
spill_use_tmp_idx[vreg.id] = rp.index
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, rp)
continue
}
// Otherwise, load into a scratch/temporary register local to this instruction.
let (tmp, new_cursor, save_slot) = pick_temp_preg(
out,
isa,
env,
vreg.class,
used_int_marks,
used_fp_marks,
epoch,
temp_int_pool,
temp_fp_pool,
temp_vec_pool,
reserved_int_regs,
true,
temp_cursor,
)
temp_cursor = new_cursor
// If we had to steal a register, save/restore around the instruction.
if save_slot is Some(s) {
out.push_edit(
block_id,
inst_idx,
Before,
Move(Reg(tmp), Spill(s), vreg.class),
)
out.push_edit(
block_id,
inst_idx,
After,
Move(Spill(s), Reg(tmp), vreg.class),
)
}
out.push_edit(
block_id,
inst_idx,
Before,
Move(Spill(slot), Reg(tmp), vreg.class),
)
spill_use_tmp_epoch[vreg.id] = spill_epoch
spill_use_tmp_idx[vreg.id] = tmp.index
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, tmp)
}
}
// Allocate spilled defs to temps and store after the instruction.
for i, def in inst.defs {
if def.reg is Virtual(vreg) &&
assigned_preg_of(vreg.id, assigned_pregs) is None {
let slot = spill_slot_of(vreg.id, spill_slots)
if slot < 0 {
continue
}
// Fixed-reg constraints are handled by constraint edits.
if fixed_def_preg(inst, i) is Some(_) {
continue
}
guard vreg.id >= 0 && vreg.id < max_vreg_id else {
abort("vreg id out of range in output build: \{vreg.id}")
}
let (tmp, new_cursor, save_slot) = pick_temp_preg(
out,
isa,
env,
vreg.class,
used_int_marks,
used_fp_marks,
epoch,
temp_int_pool,
temp_fp_pool,
temp_vec_pool,
reserved_int_regs,
true,
temp_cursor,
)
temp_cursor = new_cursor
spill_def_tmp_epoch[vreg.id] = spill_epoch
spill_def_tmp_idx[vreg.id] = tmp.index
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, tmp)
if save_slot is Some(s) {
out.push_edit(
block_id,
inst_idx,
Before,
Move(Reg(tmp), Spill(s), vreg.class),
)
}
out.push_edit(
block_id,
inst_idx,
After,
Move(Reg(tmp), Spill(slot), vreg.class),
)
if save_slot is Some(s) {
out.push_edit(
block_id,
inst_idx,
After,
Move(Spill(s), Reg(tmp), vreg.class),
)
}
// If this slot had an active reload register, it is now stale.
active_reload_clear(active_reload_epoch, slot)
}
}
// Record operand allocations for this instruction.
let (alloc_start, alloc_total) = out.begin_inst_allocs(
block_id,
inst_idx,
false,
inst.defs.length(),
inst.uses.length(),
)
// defs first.
for i, def in inst.defs {
match fixed_def_preg(inst, i) {
Some(preg) => out.push_inst_alloc_loc(Reg(preg))
None =>
match def.reg {
Physical(preg) => out.push_inst_alloc_loc(Reg(preg))
Virtual(vreg) =>
match assigned_preg_of(vreg.id, assigned_pregs) {
Some(preg) => out.push_inst_alloc_loc(Reg(preg))
None => {
guard vreg.id >= 0 && vreg.id < max_vreg_id else {
abort("vreg id out of range in output build: \{vreg.id}")
}
if spill_def_tmp_epoch[vreg.id] == spill_epoch {
let tmp = @abi.PReg::{
index: spill_def_tmp_idx[vreg.id],
class: vreg.class,
}
out.push_inst_alloc_loc(Reg(tmp))
} else {
abort("missing spilled def temp for vreg \{vreg.id}")
}
}
}
}
}
}
// then uses.
for i, use_reg in inst.uses {
match fixed_use_preg(inst, i) {
Some(preg) => out.push_inst_alloc_loc(Reg(preg))
None =>
match use_reg {
Physical(preg) => out.push_inst_alloc_loc(Reg(preg))
Virtual(vreg) =>
match assigned_preg_of(vreg.id, assigned_pregs) {
Some(preg) => out.push_inst_alloc_loc(Reg(preg))
None => {
guard vreg.id >= 0 && vreg.id < max_vreg_id else {
abort("vreg id out of range in output build: \{vreg.id}")
}
if spill_use_tmp_epoch[vreg.id] == spill_epoch {
let tmp = @abi.PReg::{
index: spill_use_tmp_idx[vreg.id],
class: vreg.class,
}
out.push_inst_alloc_loc(Reg(tmp))
} else {
abort("missing spilled use temp for vreg \{vreg.id}")
}
}
}
}
}
}
out.end_inst_allocs(alloc_start, alloc_total)
}
// Terminator allocations/edits.
if block.terminator is Some(term) {
let term_inst = block.insts.length()
let (term_edits, next_inst_edits_cursor) = take_point_edits(
alloc.inst_edits,
inst_edits_cursor,
block_id,
term_inst,
)
inst_edits_cursor = next_inst_edits_cursor
if term_edits is Some(edits) {
push_resolved_moves(out, block_id, term_inst, Before, edits.before)
push_resolved_moves(out, block_id, term_inst, After, edits.after)
}
// Jump args: materialize block params at predecessor end.
match term {
Jump(target, args) => {
let target_block = func.blocks[target]
let moves : Array[RegMove] = []
for i, param in target_block.params {
if i >= args.length() {
break
}
let from_loc = reg_home_loc_dense(
args[i],
assigned_pregs,
spill_slots,
)
let to_loc = vreg_home_loc_dense(param, assigned_pregs, spill_slots)
moves.push({ from: from_loc, to: to_loc, class: param.class })
}
if !moves.is_empty() {
push_resolved_moves(out, block_id, term_inst, Before, moves)
}
// Jump itself doesn't consume regs in the emitter; record an empty range.
let (alloc_start, alloc_total) = out.begin_inst_allocs(
block_id, term_inst, true, 0, 0,
)
out.end_inst_allocs(alloc_start, alloc_total)
}
_ => {
// For other terminators, treat their operands like uses.
used_epoch = used_epoch + 1
let epoch = used_epoch
let use_regs : Array[@abi.Reg] = match term {
Branch(cond, _, _) => [cond]
BranchCmp(lhs, rhs, _, _, _, _) => [lhs, rhs]
BranchZero(r, _, _, _, _) => [r]
BranchCmpImm(lhs, _, _, _, _, _) => [lhs]
Return(values) => values
BrTable(index, _, _) => [index]
Trap(_) => []
Jump(_, _) => []
}
for r in use_regs {
match r {
Physical(preg) =>
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, preg)
Virtual(vreg) =>
match assigned_preg_of(vreg.id, assigned_pregs) {
Some(preg) =>
add_used_reg_marks(
used_int_marks, used_fp_marks, epoch, preg,
)
None => ()
}
}
}
spill_tmp_epoch = spill_tmp_epoch + 1
let spill_epoch = spill_tmp_epoch
for r in use_regs {
if r is Virtual(vreg) &&
assigned_preg_of(vreg.id, assigned_pregs) is None {
let slot = spill_slot_of(vreg.id, spill_slots)
if slot < 0 {
continue
}
guard vreg.id >= 0 && vreg.id < max_vreg_id else {
abort("vreg id out of range in output build: \{vreg.id}")
}
if spill_use_tmp_epoch[vreg.id] == spill_epoch {
continue
}
if active_reload_get(
active_reload_epoch,
active_reload_preg_idx,
slot,
vreg.class,
reload_epoch,
)
is Some(rp) {
spill_use_tmp_epoch[vreg.id] = spill_epoch
spill_use_tmp_idx[vreg.id] = rp.index
continue
}
let (tmp, new_cursor, _save_slot) = pick_temp_preg(
out,
isa,
env,
vreg.class,
used_int_marks,
used_fp_marks,
epoch,
temp_int_pool,
temp_fp_pool,
temp_vec_pool,
reserved_int_regs,
false,
temp_cursor,
)
temp_cursor = new_cursor
out.push_edit(
block_id,
term_inst,
Before,
Move(Spill(slot), Reg(tmp), vreg.class),
)
spill_use_tmp_epoch[vreg.id] = spill_epoch
spill_use_tmp_idx[vreg.id] = tmp.index
add_used_reg_marks(used_int_marks, used_fp_marks, epoch, tmp)
}
}
let (alloc_start, alloc_total) = out.begin_inst_allocs(
block_id,
term_inst,
true,
0,
use_regs.length(),
)
for r in use_regs {
match r {
Physical(preg) => out.push_inst_alloc_loc(Reg(preg))
Virtual(vreg) =>
match assigned_preg_of(vreg.id, assigned_pregs) {
Some(preg) => out.push_inst_alloc_loc(Reg(preg))
None => {
guard vreg.id >= 0 && vreg.id < max_vreg_id else {
abort("vreg id out of range in output build: \{vreg.id}")
}
if spill_use_tmp_epoch[vreg.id] == spill_epoch {
let tmp = @abi.PReg::{
index: spill_use_tmp_idx[vreg.id],
class: vreg.class,
}
out.push_inst_alloc_loc(Reg(tmp))
} else {
abort(
"missing spilled terminator use temp for vreg \{vreg.id}",
)
}
}
}
}
}
out.end_inst_allocs(alloc_start, alloc_total)
}
}
}
}
guard inst_edits_cursor == alloc.inst_edits.length() else {
abort(
"unconsumed inst_edits entries: consumed=\{inst_edits_cursor} total=\{alloc.inst_edits.length()}",
)
}
out.finalize_edits()
out
}