///|
/// Which locals a control merge has to carry.
///
/// A merge block needs a parameter for a local only when the edges reaching it
/// can disagree about that local's value. Every edge into a structured region's
/// merge point leaves from inside that region, and every one of them starts
/// from the state at the region's entry, so two edges can disagree only about a
/// local the region writes. For any other local each edge carries the one value
/// that flowed in, and a parameter for it would be a phi whose arguments are
/// all the same value.
///
/// So a merge carries exactly the locals written somewhere in its region,
/// nested regions included. That is a fact about the values, not a heuristic: a
/// local outside the set provably cannot differ between predecessors, which is
/// what lets the translator keep using the value it already holds instead of
/// reading a parameter back.
///
/// This says nothing about locals that are written and then never read. That
/// half needs whole-function liveness, `eliminate_dead_block_params` in
/// `milkir` already computes it, and it can answer precisely what a pass over
/// the region tree here could only approximate.
///|
/// Mark every local index written anywhere in `body`, nested regions included.
/// `local.set` and `local.tee` are the only instructions that write a local.
fn mark_written_locals(
body : Array[@types.Instruction],
written : Array[Bool],
) -> Unit {
for instr in body {
match instr {
LocalSet(idx) | LocalTee(idx) =>
if idx >= 0 && idx < written.length() {
written[idx] = true
}
Block(_, inner) | Loop(_, inner) => mark_written_locals(inner, written)
If(_, then_body, else_body) => {
mark_written_locals(then_body, written)
mark_written_locals(else_body, written)
}
TryTable(_, _, inner) => mark_written_locals(inner, written)
_ => ()
}
}
}
///|
/// The local indices a region's merge block carries, ascending, which is also
/// the order they appear in the block's parameter list. `bodies` is every
/// instruction sequence the region covers -- two of them for an `if`.
fn Translator::region_local_params(
self : Translator,
bodies : Array[Array[@types.Instruction]],
) -> Array[Int] {
let written = Array::make(self.locals.length(), false)
for body in bodies {
mark_written_locals(body, written)
}
let indices = []
for idx, is_written in written {
if is_written {
indices.push(idx)
}
}
indices
}
///|
/// Give `block` one parameter per carried local, typed from the local it
/// stands for. Call once, right after the block's explicit parameters.
fn Translator::add_local_block_params(
self : Translator,
block : Block,
local_params : Array[Int],
) -> Unit {
for idx in local_params {
self.builder.add_block_param(block, self.locals[idx].ty) |> ignore
}
}
///|
/// Append the current value of each local the target block carries, in
/// parameter order, to a branch's argument list.
fn Translator::push_local_args(
self : Translator,
args : Array[Value],
local_params : Array[Int],
) -> Unit {
for idx in local_params {
args.push(self.locals[idx])
}
}
///|
/// Adopt a merge block's parameters as the current values of the locals it
/// carries. Locals it does not carry are left alone: they hold the same value
/// on every edge into the block, so the value already in hand is correct.
fn Translator::adopt_local_params(
self : Translator,
block : Block,
local_params : Array[Int],
start : Int,
) -> Unit {
for i, idx in local_params {
self.locals[idx] = block.params[start + i].0
}
}