use std::collections::{HashMap, HashSet};
use crate::compiler::maglev::ir::{ControlNode, MaglevGraph, NodeId, ValueNode};
use crate::compiler::maglev::licm::detect_loops;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Location {
Register(u32),
StackSlot(u32),
}
#[derive(Debug, Clone)]
pub struct AllocationResult {
assignments: HashMap<NodeId, Location>,
spill_count: u32,
live_caller_saved: HashMap<NodeId, u8>,
}
impl AllocationResult {
pub fn location(&self, id: NodeId) -> Option<Location> {
self.assignments.get(&id).copied()
}
pub fn spill_count(&self) -> u32 {
self.spill_count
}
pub fn live_caller_saved_at(&self, id: NodeId) -> u8 {
self.live_caller_saved.get(&id).copied().unwrap_or(0)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct LiveInterval {
pub(crate) id: NodeId,
pub(crate) start: u32,
pub(crate) end: u32,
pre_ext_end: Option<u32>,
}
pub(crate) fn compute_live_intervals(graph: &MaglevGraph) -> Vec<LiveInterval> {
let mut def_point: HashMap<NodeId, u32> = HashMap::new();
let mut pp: u32 = 0;
for block in graph.blocks() {
for &(id, _) in &block.nodes {
def_point.insert(id, pp);
pp += 1;
}
if block.control.is_some() {
pp += 1; }
}
let mut end_point: HashMap<NodeId, u32> = HashMap::new();
let mut record_use = |id: NodeId, at: u32| {
let e = end_point.entry(id).or_insert(at);
if at > *e {
*e = at;
}
};
let mut pp: u32 = 0;
for block in graph.blocks() {
for (_, node) in &block.nodes {
let node_pp = pp;
collect_inputs(node, &mut |inp| record_use(inp, node_pp));
pp += 1;
}
if let Some(ctrl) = &block.control {
let ctrl_pp = pp;
collect_control_inputs(ctrl, &mut |inp| record_use(inp, ctrl_pp));
pp += 1;
}
}
let mut block_pp_ranges: Vec<(u32, u32)> = Vec::new();
{
let mut bp: u32 = 0;
for block in graph.blocks() {
let first = bp;
bp += block.nodes.len() as u32;
if block.control.is_some() {
bp += 1;
}
block_pp_ranges.push((first, bp));
}
}
let loops = detect_loops(graph);
for lp in &loops {
let header_pp = block_pp_ranges[lp.header as usize].0;
let earliest_body_pp = lp
.body
.iter()
.filter(|&&b| b != lp.header)
.filter_map(|&b| block_pp_ranges.get(b as usize).map(|&(s, _)| s))
.min();
if let Some(min_pp) = earliest_body_pp
&& min_pp < header_pp
&& let Some(header_block) = graph.block(lp.header)
{
for (phi_id, node) in &header_block.nodes {
if let ValueNode::Phi { .. } = node
&& let Some(def) = def_point.get_mut(phi_id)
&& min_pp < *def
{
*def = min_pp;
}
}
}
}
let mut header_phi_ids: HashSet<NodeId> = HashSet::new();
for lp in &loops {
if let Some(header_block) = graph.block(lp.header) {
for (phi_id, node) in &header_block.nodes {
if let ValueNode::Phi { .. } = node {
header_phi_ids.insert(*phi_id);
}
}
}
}
let mut pre_ext_ends: HashMap<NodeId, u32> = HashMap::new();
loop {
let mut changed = false;
for lp in &loops {
let loop_start_pp = lp
.body
.iter()
.filter_map(|&b| block_pp_ranges.get(b as usize).map(|&(s, _)| s))
.min()
.unwrap_or(0);
let loop_end_pp = lp
.body
.iter()
.filter_map(|&b| block_pp_ranges.get(b as usize).map(|&(_, e)| e))
.max()
.unwrap_or(0);
if loop_start_pp >= loop_end_pp {
continue;
}
let target_end = loop_end_pp.saturating_sub(1);
let header_first_pp = block_pp_ranges[lp.header as usize].0;
let mut phi_entry_only: HashSet<NodeId> = HashSet::new();
if let Some(header_block) = graph.block(lp.header) {
let preheader_pos = header_block
.predecessors
.iter()
.position(|&p| p == lp.preheader);
if let Some(pos) = preheader_pos {
for (_, node) in &header_block.nodes {
if let ValueNode::Phi { inputs, .. } = node
&& let Some(&entry_id) = inputs.get(pos)
&& end_point
.get(&entry_id)
.is_none_or(|&e| e <= header_first_pp)
{
phi_entry_only.insert(entry_id);
}
}
}
}
for (id, end) in end_point.iter_mut() {
if phi_entry_only.contains(id) {
continue;
}
if *end >= loop_start_pp
&& *end < loop_end_pp
&& let Some(&start) = def_point.get(id)
&& start < loop_end_pp
&& *end < target_end
{
if header_phi_ids.contains(id) && !pre_ext_ends.contains_key(id) {
pre_ext_ends.insert(*id, *end);
}
*end = target_end;
changed = true;
}
}
}
if !changed {
break;
}
}
def_point
.into_iter()
.map(|(id, start)| {
let end = end_point.get(&id).map_or(start + 1, |&e| e + 1);
let pre_ext_end = pre_ext_ends.get(&id).map(|&e| e + 1);
LiveInterval {
id,
start,
end,
pre_ext_end,
}
})
.collect()
}
#[allow(clippy::too_many_lines)]
fn collect_inputs(node: &ValueNode, f: &mut impl FnMut(NodeId)) {
match node {
ValueNode::SmiConstant { .. }
| ValueNode::Float64Constant { .. }
| ValueNode::Int32Constant { .. }
| ValueNode::Uint32Constant { .. }
| ValueNode::BigIntConstant { .. }
| ValueNode::TrueConstant
| ValueNode::FalseConstant
| ValueNode::NullConstant
| ValueNode::UndefinedConstant
| ValueNode::RootConstant { .. }
| ValueNode::ExternalConstant { .. }
| ValueNode::StringConstant { .. }
| ValueNode::ConstantPoolEntry { .. }
| ValueNode::Parameter { .. }
| ValueNode::RegisterInput { .. }
| ValueNode::ArgumentsLength
| ValueNode::RestLength
| ValueNode::LoadGlobal { .. }
| ValueNode::LoadCurrentContextSlot { .. }
| ValueNode::ArgumentsElements { .. }
| ValueNode::RestElements { .. }
| ValueNode::VirtualObject { .. }
| ValueNode::CreateFunctionContext { .. }
| ValueNode::CreateBlockContext { .. }
| ValueNode::CreateShallowObjectLiteral { .. }
| ValueNode::CreateShallowArrayLiteral { .. }
| ValueNode::CreateObjectLiteral { .. }
| ValueNode::CreateArrayLiteral { .. }
| ValueNode::CreateEmptyObjectLiteral
| ValueNode::CreateEmptyArrayLiteral { .. }
| ValueNode::CreateMappedArguments
| ValueNode::CreateUnmappedArguments
| ValueNode::CreateRestParameter
| ValueNode::CreateRegExpLiteral { .. }
| ValueNode::CreateClosure { .. }
| ValueNode::FastCreateClosure { .. }
| ValueNode::GetTemplateObject { .. }
| ValueNode::Debugger
| ValueNode::Abort { .. } => {}
ValueNode::GetArgument { index } => f(*index),
ValueNode::CheckedSmiIncrement { value }
| ValueNode::CheckedSmiDecrement { value }
| ValueNode::Int32Negate { value }
| ValueNode::Int32Increment { value }
| ValueNode::Int32Decrement { value }
| ValueNode::Float64Negate { value }
| ValueNode::Float64Ieee754Unary { value, .. }
| ValueNode::GenericBitwiseNot { value, .. }
| ValueNode::GenericNegate { value, .. }
| ValueNode::GenericIncrement { value, .. }
| ValueNode::GenericDecrement { value, .. }
| ValueNode::ChangeInt32ToFloat64 { input: value }
| ValueNode::ChangeUint32ToFloat64 { input: value }
| ValueNode::ChangeFloat64ToInt32 { input: value }
| ValueNode::CheckedFloat64ToInt32 { input: value }
| ValueNode::ChangeInt32ToTagged { input: value }
| ValueNode::ChangeUint32ToTagged { input: value }
| ValueNode::ChangeFloat64ToTagged { input: value }
| ValueNode::ChangeTaggedToInt32 { input: value }
| ValueNode::ChangeTaggedToUint32 { input: value }
| ValueNode::ChangeTaggedToFloat64 { input: value }
| ValueNode::CheckedTaggedToInt32 { input: value }
| ValueNode::CheckedTaggedToFloat64 { input: value }
| ValueNode::ToBoolean { value }
| ValueNode::ToString { value, .. }
| ValueNode::ToObject { value, .. }
| ValueNode::ToName { value, .. }
| ValueNode::ToNumber { value, .. }
| ValueNode::ToNumberOrNumeric { value, .. }
| ValueNode::TypeOf { value }
| ValueNode::NumberToString { value, .. }
| ValueNode::TestUndetectable { value }
| ValueNode::TestNullOrUndefined { value }
| ValueNode::TestTypeOf { value, .. } => f(*value),
ValueNode::CheckSmi { receiver }
| ValueNode::CheckNumber { receiver }
| ValueNode::CheckHeapObject { receiver }
| ValueNode::CheckSymbol { receiver }
| ValueNode::CheckString { receiver }
| ValueNode::CheckStringOrStringWrapper { receiver }
| ValueNode::CheckSeqOneByteString { receiver }
| ValueNode::CheckMaps { receiver, .. }
| ValueNode::CheckMapsWithMigration { receiver, .. }
| ValueNode::CheckValue { receiver, .. } => f(*receiver),
ValueNode::CheckDynamicValue { receiver, expected } => {
f(*receiver);
f(*expected);
}
ValueNode::CheckInt32IsSmi { input }
| ValueNode::CheckUint32IsSmi { input }
| ValueNode::CheckHoleyFloat64IsSmi { input }
| ValueNode::CheckFloat64IsNan { input } => f(*input),
ValueNode::LoadField { object, .. }
| ValueNode::LoadTaggedField { object, .. }
| ValueNode::LoadDoubleField { object, .. }
| ValueNode::LoadNamedGeneric { object, .. }
| ValueNode::ForInPrepare {
enumerator: object, ..
}
| ValueNode::StringLength { string: object } => f(*object),
ValueNode::LoadEnumCacheLength { map } => f(*map),
ValueNode::LoadKeyedGeneric { object, key, .. } => {
f(*object);
f(*key);
}
ValueNode::HasInPrototypeChain { object, prototype } => {
f(*object);
f(*prototype);
}
ValueNode::StoreField { object, value, .. } => {
f(*object);
f(*value);
}
ValueNode::StoreCurrentContextSlot { value, .. } | ValueNode::StoreGlobal { value, .. } => {
f(*value);
}
ValueNode::LoadContextSlot { context, .. } => f(*context),
ValueNode::StoreContextSlot { context, value, .. } => {
f(*context);
f(*value);
}
ValueNode::PushContext { context } | ValueNode::PopContext { context } => f(*context),
ValueNode::LoadFixedArrayElement { elements, index }
| ValueNode::LoadFixedDoubleArrayElement { elements, index }
| ValueNode::LoadHoleyFixedDoubleArrayElement { elements, index } => {
f(*elements);
f(*index);
}
ValueNode::StoreFixedArrayElement {
elements,
index,
value,
}
| ValueNode::StoreFixedDoubleArrayElement {
elements,
index,
value,
} => {
f(*elements);
f(*index);
f(*value);
}
ValueNode::StoreNamedGeneric { object, value, .. } => {
f(*object);
f(*value);
}
ValueNode::CreateObjectLiteralWithProperties { values, .. } => {
for &v in values {
f(v);
}
}
ValueNode::StoreKeyedGeneric {
object, key, value, ..
} => {
f(*object);
f(*key);
f(*value);
}
ValueNode::CheckedSmiAdd { left, right }
| ValueNode::CheckedSmiSubtract { left, right }
| ValueNode::CheckedSmiMultiply { left, right }
| ValueNode::CheckedSmiDivide { left, right }
| ValueNode::CheckedSmiModulus { left, right }
| ValueNode::Int32Add { left, right }
| ValueNode::Int32Subtract { left, right }
| ValueNode::Int32Multiply { left, right }
| ValueNode::Int32Divide { left, right }
| ValueNode::Int32Modulus { left, right }
| ValueNode::Int32BitwiseAnd { left, right }
| ValueNode::Int32BitwiseOr { left, right }
| ValueNode::Int32BitwiseXor { left, right }
| ValueNode::Int32ShiftLeft { left, right }
| ValueNode::Int32ShiftRight { left, right }
| ValueNode::Int32ShiftRightLogical { left, right }
| ValueNode::Uint32Add { left, right }
| ValueNode::Uint32Subtract { left, right }
| ValueNode::Uint32Multiply { left, right }
| ValueNode::Uint32Divide { left, right }
| ValueNode::Uint32Modulus { left, right }
| ValueNode::Float64Add { left, right }
| ValueNode::Float64Subtract { left, right }
| ValueNode::Float64Multiply { left, right }
| ValueNode::Float64Divide { left, right }
| ValueNode::Float64Modulus { left, right }
| ValueNode::Float64Exponentiate { left, right }
| ValueNode::Int32Equal { left, right }
| ValueNode::Int32StrictEqual { left, right }
| ValueNode::Int32LessThan { left, right }
| ValueNode::Int32LessThanOrEqual { left, right }
| ValueNode::Int32GreaterThan { left, right }
| ValueNode::Int32GreaterThanOrEqual { left, right }
| ValueNode::Float64Equal { left, right }
| ValueNode::Float64LessThan { left, right }
| ValueNode::Float64LessThanOrEqual { left, right }
| ValueNode::Float64GreaterThan { left, right }
| ValueNode::Float64GreaterThanOrEqual { left, right }
| ValueNode::StringConcat { left, right }
| ValueNode::StringEqual { left, right }
| ValueNode::GenericAdd { left, right, .. }
| ValueNode::GenericSubtract { left, right, .. }
| ValueNode::GenericMultiply { left, right, .. }
| ValueNode::GenericDivide { left, right, .. }
| ValueNode::GenericModulus { left, right, .. }
| ValueNode::GenericExponentiate { left, right, .. }
| ValueNode::GenericBitwiseAnd { left, right, .. }
| ValueNode::GenericBitwiseOr { left, right, .. }
| ValueNode::GenericBitwiseXor { left, right, .. }
| ValueNode::GenericShiftLeft { left, right, .. }
| ValueNode::GenericShiftRight { left, right, .. }
| ValueNode::GenericShiftRightLogical { left, right, .. }
| ValueNode::TaggedEqual { left, right, .. }
| ValueNode::TaggedNotEqual { left, right, .. } => {
f(*left);
f(*right);
}
ValueNode::CheckInt32Condition { left, right, .. } => {
f(*left);
f(*right);
}
ValueNode::CheckCacheIndicesNotCleared { receiver, indices } => {
f(*receiver);
f(*indices);
}
ValueNode::TestInstanceOf {
object, callable, ..
} => {
f(*object);
f(*callable);
}
ValueNode::TestIn { key, object, .. } => {
f(*key);
f(*object);
}
ValueNode::StringAt { string, index } => {
f(*string);
f(*index);
}
ValueNode::ForInNext {
receiver,
cache_index,
cache_array,
..
} => {
f(*receiver);
f(*cache_index);
f(*cache_array);
}
ValueNode::DeleteProperty { object, key, .. } => {
f(*object);
f(*key);
}
ValueNode::CreateCatchContext { exception, .. } => f(*exception),
ValueNode::CreateWithContext { object, .. } => f(*object),
ValueNode::Call {
callee,
receiver,
args,
..
}
| ValueNode::CallWithSpread {
callee,
receiver,
args,
..
}
| ValueNode::CallKnownFunction {
callee,
receiver,
args,
} => {
f(*callee);
f(*receiver);
for &a in args {
f(a);
}
}
ValueNode::CallArrayPush { receiver, args, .. } => {
f(*receiver);
for &a in args {
f(a);
}
}
ValueNode::SpeculativeCallFusion { callee, .. } => {
f(*callee);
}
ValueNode::SpeculativeSumFusion { array } => {
f(*array);
}
ValueNode::SpeculativePushFusion { array, .. } => {
f(*array);
}
ValueNode::SpeculativeFillTrueFusion { array, count } => {
f(*array);
f(*count);
}
ValueNode::SpeculativeCountTruthyFusion { array, count } => {
f(*array);
f(*count);
}
ValueNode::CallBuiltin { args, .. } | ValueNode::CallRuntime { args, .. } => {
for &a in args {
f(a);
}
}
ValueNode::Construct {
constructor, args, ..
}
| ValueNode::ConstructWithSpread {
constructor, args, ..
} => {
f(*constructor);
for &a in args {
f(a);
}
}
ValueNode::Phi { inputs } => {
for &inp in inputs {
f(inp);
}
}
}
}
fn collect_control_inputs(ctrl: &ControlNode, f: &mut impl FnMut(NodeId)) {
match ctrl {
ControlNode::Return { value } => f(*value),
ControlNode::Branch { condition, .. } => f(*condition),
ControlNode::Jump { .. } | ControlNode::Deoptimize { .. } => {}
}
}
pub fn allocate(graph: &MaglevGraph, num_regs: u32) -> AllocationResult {
assert!(num_regs > 0, "allocate: num_regs must be > 0");
let mut intervals = compute_live_intervals(graph);
intervals.sort_by_key(|iv| (iv.start, iv.id.0));
let mut assignments: HashMap<NodeId, Location> = HashMap::new();
let mut active: Vec<(u32, LiveInterval)> = Vec::new();
let mut next_spill: u32 = 0;
let mut free_regs: Vec<u32> = (0..num_regs).rev().collect();
let mut phi_affinity_phi: HashMap<NodeId, NodeId> = HashMap::new();
for block in graph.blocks() {
let back_edge_positions: Vec<usize> = block
.predecessors
.iter()
.enumerate()
.filter(|&(_, &pred_idx)| pred_idx >= block.id)
.map(|(pos, _)| pos)
.collect();
if back_edge_positions.is_empty() {
continue;
}
for (phi_id, node) in &block.nodes {
if let ValueNode::Phi { inputs } = node {
for &pos in &back_edge_positions {
if let Some(&back_input) = inputs.get(pos)
&& back_input != *phi_id
{
phi_affinity_phi.insert(back_input, *phi_id);
}
}
}
}
}
let mut phi_affinity_reg: HashMap<NodeId, u32> = HashMap::new();
let mut phi_back_inputs: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (&back_input, &phi_id) in &phi_affinity_phi {
phi_back_inputs.entry(phi_id).or_default().push(back_input);
}
for iv in &intervals {
let mut still_active: Vec<(u32, LiveInterval)> = Vec::new();
for (reg, active_iv) in active.drain(..) {
if active_iv.end <= iv.start {
free_regs.push(reg);
} else {
still_active.push((reg, active_iv));
}
}
active = still_active;
if let Some(&pref_reg) = phi_affinity_reg.get(&iv.id)
&& let Some(&phi_id) = phi_affinity_phi.get(&iv.id)
&& let Some(active_idx) = active
.iter()
.position(|(r, aiv)| *r == pref_reg && aiv.id == phi_id)
{
let phi_eff_end = active[active_idx]
.1
.pre_ext_end
.unwrap_or(active[active_idx].1.end);
if phi_eff_end <= iv.start + 1 {
active.remove(active_idx);
free_regs.push(pref_reg);
}
}
if let Some(reg) = {
let preferred = phi_affinity_reg.get(&iv.id).copied();
if let Some(pref) = preferred {
if let Some(pos) = free_regs.iter().position(|&r| r == pref) {
Some(free_regs.remove(pos))
} else {
free_regs.pop()
}
} else {
free_regs.pop()
}
} {
assignments.insert(iv.id, Location::Register(reg));
active.push((reg, *iv));
if let Some(back_inputs) = phi_back_inputs.get(&iv.id) {
for &back_input in back_inputs {
phi_affinity_reg.insert(back_input, reg);
}
}
} else {
let spill_idx = active
.iter()
.enumerate()
.max_by_key(|(_, (_, a))| a.end)
.map(|(i, _)| i);
let idx = spill_idx.expect("active must be non-empty when no free regs");
if active[idx].1.end > iv.end {
let (reg, spilled_iv) = active.remove(idx);
let slot = next_spill;
next_spill += 1;
assignments.insert(spilled_iv.id, Location::StackSlot(slot));
assignments.insert(iv.id, Location::Register(reg));
active.push((reg, *iv));
} else {
let slot = next_spill;
next_spill += 1;
assignments.insert(iv.id, Location::StackSlot(slot));
}
}
}
coalesce_loop_phis(&mut assignments, graph, &intervals);
for block in graph.blocks() {
for (_, node) in &block.nodes {
if matches!(node, ValueNode::Phi { .. }) {
continue;
}
let mut seen: Vec<(u32, NodeId)> = Vec::new();
collect_inputs(node, &mut |inp| {
if let Some(&Location::Register(r)) = assignments.get(&inp) {
let dominated = seen.iter().any(|&(sr, sid)| sr == r && sid != inp);
if dominated {
let slot = next_spill;
next_spill += 1;
assignments.insert(inp, Location::StackSlot(slot));
} else if !seen.iter().any(|&(_, sid)| sid == inp) {
seen.push((r, inp));
}
}
});
}
}
let cs_intervals: Vec<(u32, u32, u8)> = intervals
.iter()
.filter_map(|iv| {
if let Some(Location::Register(n)) = assignments.get(&iv.id)
&& (1..=5).contains(n)
{
return Some((iv.start, iv.end, 1u8 << n));
}
None
})
.collect();
let mut live_caller_saved: HashMap<NodeId, u8> = HashMap::new();
if !cs_intervals.is_empty() {
let mut pp: u32 = 0;
for block in graph.blocks() {
for &(nid, _) in &block.nodes {
let mut mask: u8 = 0;
for &(start, end, bit) in &cs_intervals {
if start < pp && end > pp + 1 {
mask |= bit;
}
}
if mask != 0 {
live_caller_saved.insert(nid, mask);
}
pp += 1;
}
if block.control.is_some() {
pp += 1;
}
}
}
AllocationResult {
assignments,
spill_count: next_spill,
live_caller_saved,
}
}
fn coalesce_loop_phis(
assignments: &mut HashMap<NodeId, Location>,
graph: &MaglevGraph,
intervals: &[LiveInterval],
) {
let interval_map: HashMap<NodeId, &LiveInterval> =
intervals.iter().map(|iv| (iv.id, iv)).collect();
for block in graph.blocks() {
let back_edge_positions: Vec<usize> = block
.predecessors
.iter()
.enumerate()
.filter(|&(_, &pred_idx)| pred_idx >= block.id)
.map(|(pos, _)| pos)
.collect();
if back_edge_positions.is_empty() {
continue;
}
for (phi_id, node) in &block.nodes {
let inputs = match node {
ValueNode::Phi { inputs } => inputs,
_ => continue,
};
let phi_reg = match assignments.get(phi_id) {
Some(Location::Register(r)) => *r,
_ => continue,
};
let phi_iv = match interval_map.get(phi_id) {
Some(iv) => iv,
None => continue,
};
let phi_effective_end = phi_iv.pre_ext_end.unwrap_or(phi_iv.end);
for &pos in &back_edge_positions {
let back_input = match inputs.get(pos) {
Some(&id) => id,
None => continue,
};
if back_input == *phi_id {
continue;
}
let back_reg = match assignments.get(&back_input) {
Some(Location::Register(r)) => *r,
_ => continue,
};
if back_reg == phi_reg {
continue;
}
let back_iv = match interval_map.get(&back_input) {
Some(iv) => iv,
None => continue,
};
if phi_effective_end > back_iv.start + 1 {
continue;
}
let has_conflict = intervals.iter().any(|iv| {
if iv.id == back_input || iv.id == *phi_id {
return false;
}
if iv.start < back_iv.end && back_iv.start < iv.end {
matches!(
assignments.get(&iv.id),
Some(Location::Register(r)) if *r == phi_reg
)
} else {
false
}
});
if !has_conflict {
assignments.insert(back_input, Location::Register(phi_reg));
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compiler::maglev::ir::{BasicBlock, ControlNode, MaglevGraph, ValueNode};
fn assert_no_conflicts(graph: &MaglevGraph, result: &AllocationResult, num_regs: u32) {
let intervals = compute_live_intervals(graph);
for i in 0..intervals.len() {
for j in (i + 1)..intervals.len() {
let a = &intervals[i];
let b = &intervals[j];
if a.start < b.end && b.start < a.end {
if let (Some(Location::Register(ra)), Some(Location::Register(rb))) =
(result.location(a.id), result.location(b.id))
{
assert_ne!(
ra, rb,
"register conflict: {:?} and {:?} both in register {} \
(intervals [{},{}) and [{},{}) with {} regs)",
a.id, b.id, ra, a.start, a.end, b.start, b.end, num_regs
);
}
}
}
}
}
#[test]
fn test_single_parameter_gets_register() {
let mut graph = MaglevGraph::new(1);
let mut block = BasicBlock::new(0);
let p0 = block.push_value(ValueNode::Parameter { index: 0 });
block.set_control(ControlNode::Return { value: p0 });
graph.add_block(block);
let result = allocate(&graph, 8);
assert!(matches!(result.location(p0), Some(Location::Register(_))));
assert_eq!(result.spill_count(), 0);
assert_no_conflicts(&graph, &result, 8);
}
#[test]
fn test_two_independent_values_no_spill() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let c1 = block.push_value(ValueNode::Int32Constant { value: 1 });
let c2 = block.push_value(ValueNode::Int32Constant { value: 2 });
let add = block.push_value(ValueNode::Int32Add {
left: c1,
right: c2,
});
block.set_control(ControlNode::Return { value: add });
graph.add_block(block);
let result = allocate(&graph, 8);
assert_no_conflicts(&graph, &result, 8);
assert_eq!(result.spill_count(), 0);
}
#[test]
fn test_spill_with_one_register() {
let mut graph = MaglevGraph::new(2);
let mut block = BasicBlock::new(0);
let p0 = block.push_value(ValueNode::Parameter { index: 0 });
let p1 = block.push_value(ValueNode::Parameter { index: 1 });
let add = block.push_value(ValueNode::Int32Add {
left: p0,
right: p1,
});
block.set_control(ControlNode::Return { value: add });
graph.add_block(block);
let result = allocate(&graph, 1);
assert_no_conflicts(&graph, &result, 1);
assert!(result.spill_count() > 0, "expected at least one spill");
}
#[test]
fn test_two_block_graph() {
let mut graph = MaglevGraph::new(1);
let mut b0 = BasicBlock::new(0);
let p0 = b0.push_value(ValueNode::Parameter { index: 0 });
b0.set_control(ControlNode::Jump { target: 1 });
graph.add_block(b0);
let mut b1 = BasicBlock::new(1);
b1.set_control(ControlNode::Return { value: p0 });
graph.add_block(b1);
let result = allocate(&graph, 4);
assert_no_conflicts(&graph, &result, 4);
assert!(matches!(result.location(p0), Some(Location::Register(_))));
}
#[test]
fn test_branch_graph() {
let mut graph = MaglevGraph::new(1);
let mut b0 = BasicBlock::new(0);
let p0 = b0.push_value(ValueNode::Parameter { index: 0 });
let cond = b0.push_value(ValueNode::ToBoolean { value: p0 });
b0.set_control(ControlNode::Branch {
condition: cond,
if_true: 1,
if_false: 2,
});
graph.add_block(b0);
let mut b1 = BasicBlock::new(1);
let t = b1.push_value(ValueNode::TrueConstant);
b1.set_control(ControlNode::Return { value: t });
graph.add_block(b1);
let mut b2 = BasicBlock::new(2);
let f = b2.push_value(ValueNode::FalseConstant);
b2.set_control(ControlNode::Return { value: f });
graph.add_block(b2);
let result = allocate(&graph, 4);
assert_no_conflicts(&graph, &result, 4);
}
#[test]
fn test_no_spills_with_sufficient_registers() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let c0 = block.push_value(ValueNode::Int32Constant { value: 42 });
let neg0 = block.push_value(ValueNode::Int32Negate { value: c0 });
let neg1 = block.push_value(ValueNode::Int32Negate { value: neg0 });
let neg2 = block.push_value(ValueNode::Int32Negate { value: neg1 });
block.set_control(ControlNode::Return { value: neg2 });
graph.add_block(block);
let result = allocate(&graph, 4);
assert_eq!(result.spill_count(), 0);
assert_no_conflicts(&graph, &result, 4);
}
#[test]
fn test_all_nodes_get_location() {
let mut graph = MaglevGraph::new(2);
let mut block = BasicBlock::new(0);
let p0 = block.push_value(ValueNode::Parameter { index: 0 });
let p1 = block.push_value(ValueNode::Parameter { index: 1 });
let add = block.push_value(ValueNode::Int32Add {
left: p0,
right: p1,
});
block.set_control(ControlNode::Return { value: add });
graph.add_block(block);
let result = allocate(&graph, 2);
assert!(result.location(p0).is_some(), "p0 missing location");
assert!(result.location(p1).is_some(), "p1 missing location");
assert!(result.location(add).is_some(), "add missing location");
assert_no_conflicts(&graph, &result, 2);
}
#[test]
fn test_phi_node_allocation() {
let mut graph = MaglevGraph::new(1);
let mut b0 = BasicBlock::new(0);
let p0 = b0.push_value(ValueNode::Parameter { index: 0 });
let cond = b0.push_value(ValueNode::ToBoolean { value: p0 });
b0.set_control(ControlNode::Branch {
condition: cond,
if_true: 1,
if_false: 2,
});
graph.add_block(b0);
let mut b1 = BasicBlock::new(1);
let v1 = b1.push_value(ValueNode::Int32Constant { value: 1 });
b1.set_control(ControlNode::Jump { target: 3 });
graph.add_block(b1);
let mut b2 = BasicBlock::new(2);
let v2 = b2.push_value(ValueNode::Int32Constant { value: 2 });
b2.set_control(ControlNode::Jump { target: 3 });
graph.add_block(b2);
let mut b3 = BasicBlock::new(3);
let phi = b3.push_value(ValueNode::Phi {
inputs: vec![v1, v2],
});
b3.set_control(ControlNode::Return { value: phi });
graph.add_block(b3);
let result = allocate(&graph, 4);
assert_no_conflicts(&graph, &result, 4);
assert!(result.location(phi).is_some(), "phi missing location");
}
#[test]
fn test_phi_coalescing_loop() {
let mut graph = MaglevGraph::new(1);
graph.add_block(BasicBlock::new(0));
let param = graph
.add_value_node(0, ValueNode::Parameter { index: 0 })
.unwrap();
let init = graph
.add_value_node(0, ValueNode::Int32Constant { value: 0 })
.unwrap();
let c1 = graph
.add_value_node(0, ValueNode::Int32Constant { value: 1 })
.unwrap();
graph
.block_mut(0)
.unwrap()
.set_control(ControlNode::Jump { target: 1 });
graph.add_block(BasicBlock::new(1));
graph.block_mut(1).unwrap().add_predecessor(0);
graph.block_mut(1).unwrap().add_predecessor(1);
let add_id = graph.alloc_node_id();
let phi = graph
.add_value_node(
1,
ValueNode::Phi {
inputs: vec![init, add_id],
},
)
.unwrap();
graph.block_mut(1).unwrap().push_with_id(
add_id,
ValueNode::Int32Add {
left: phi,
right: c1,
},
);
graph
.block_mut(1)
.unwrap()
.set_control(ControlNode::Branch {
condition: param,
if_true: 1,
if_false: 2,
});
graph.add_block(BasicBlock::new(2));
graph.block_mut(2).unwrap().add_predecessor(1);
graph
.block_mut(2)
.unwrap()
.set_control(ControlNode::Return { value: add_id });
let result = allocate(&graph, 4);
assert!(result.location(phi).is_some(), "phi missing location");
assert!(result.location(add_id).is_some(), "add missing location");
assert!(
matches!(result.location(phi), Some(Location::Register(_))),
"phi should be in a register, got {:?}",
result.location(phi)
);
assert_eq!(
result.location(phi),
result.location(add_id),
"phi and back-edge input (add) should be coalesced into the \
same register — phi={:?}, add={:?}",
result.location(phi),
result.location(add_id)
);
let _ = param;
}
#[test]
fn test_phi_coalescing_skipped_on_conflict() {
let mut graph = MaglevGraph::new(0);
graph.add_block(BasicBlock::new(0));
let init = graph
.add_value_node(0, ValueNode::Int32Constant { value: 0 })
.unwrap();
graph
.block_mut(0)
.unwrap()
.set_control(ControlNode::Jump { target: 1 });
graph.add_block(BasicBlock::new(1));
graph.block_mut(1).unwrap().add_predecessor(0);
graph.block_mut(1).unwrap().add_predecessor(1);
let add_id = graph.alloc_node_id();
let phi = graph
.add_value_node(
1,
ValueNode::Phi {
inputs: vec![init, add_id],
},
)
.unwrap();
graph.block_mut(1).unwrap().push_with_id(
add_id,
ValueNode::Int32Add {
left: phi,
right: init,
},
);
let extra_use = graph
.add_value_node(1, ValueNode::ToBoolean { value: phi })
.unwrap();
let cond = graph
.add_value_node(
1,
ValueNode::Int32Add {
left: add_id,
right: extra_use,
},
)
.unwrap();
graph
.block_mut(1)
.unwrap()
.set_control(ControlNode::Branch {
condition: cond,
if_true: 1,
if_false: 2,
});
graph.add_block(BasicBlock::new(2));
graph.block_mut(2).unwrap().add_predecessor(1);
graph
.block_mut(2)
.unwrap()
.set_control(ControlNode::Return { value: cond });
let result = allocate(&graph, 4);
assert_no_conflicts(&graph, &result, 4);
}
}