From d37e9e3214abd4c4d0aa7db8cb70e8d8ce251adf Mon Sep 17 00:00:00 2001 From: Rahul Date: Mon, 12 Oct 2026 13:01:46 -0700 Subject: [PATCH] ion: preserve register limits through reuse and fixups --- src/fastalloc/tests.rs | 82 ++++++++++++++++++++++++++++++++++++++++++ src/ion/liveranges.rs | 2 +- src/ion/merge.rs | 4 +-- src/ion/requirement.rs | 16 +++++++-- 4 files changed, 98 insertions(+), 6 deletions(-) diff --git a/src/fastalloc/tests.rs b/src/fastalloc/tests.rs index 26767aa7..b5f54217 100644 --- a/src/fastalloc/tests.rs +++ b/src/fastalloc/tests.rs @@ -7,6 +7,88 @@ use crate::{ use alloc::vec; use alloc::vec::Vec; +#[test] +fn ion_limited_operands_and_reuse() { + let env = mach_env(32); + let function = RealFunction::new(vec![BlockBuildInfo { + insts: vec![ + vec![op(Def, 0, FixedReg(p(31)))], + vec![op(Def, 1, Limit(16))], + vec![op(Use, 0, Limit(16)), op(Use, 1, Reg), op(Def, 2, Reuse(0))], + vec![op(Use, 2, FixedReg(p(30))), op(Use, 1, Limit(8))], + vec![op(Use, 0, Limit(1)), op(Use, 2, Limit(16))], + ], + }]); + let options = RegallocOptions { + algorithm: Algorithm::Ion, + validate_ssa: true, + ..RegallocOptions::default() + }; + let output = run(&function, &env, &options).unwrap(); + for index in 0..function.num_insts() { + for (operand, allocation) in function + .inst_operands(i(index)) + .iter() + .zip(output.inst_allocs(i(index))) + { + if let Limit(limit) = operand.constraint() { + assert!( + allocation.as_reg().unwrap().hw_enc() < limit, + "instruction {}, {:?} -> {:?}", + index, + operand, + allocation + ); + } + } + } + assert_eq!(output.inst_allocs(i(2))[0], output.inst_allocs(i(2))[2]); + #[cfg(feature = "checker")] + { + let mut checker = crate::checker::Checker::new(&function, &env); + checker.prepare(&output); + checker.run().unwrap(); + } +} + +#[test] +fn ion_limited_operand_with_multiple_fixed_uses() { + let mut env = mach_env(32); + env.fixed_stack_slots = vec![p(38), p(53)]; + let mut uses = vec![ + op(Use, 0, FixedReg(p(16))), + op(Use, 0, FixedReg(p(53))), + op(Use, 0, Limit(16)), + op(Use, 0, FixedReg(p(38))), + op(Use, 0, Any), + ]; + for _ in 0..uses.len() { + uses.rotate_left(1); + let function = RealFunction::new(vec![BlockBuildInfo { + insts: vec![vec![op(Def, 0, FixedReg(p(31)))], uses.clone()], + }]); + let options = RegallocOptions { + algorithm: Algorithm::Ion, + validate_ssa: true, + ..RegallocOptions::default() + }; + let output = run(&function, &env, &options).unwrap(); + for (operand, allocation) in uses.iter().zip(output.inst_allocs(i(1))) { + match operand.constraint() { + FixedReg(preg) => assert_eq!(*allocation, alloc(preg)), + Limit(limit) => assert!(allocation.as_reg().unwrap().hw_enc() < limit), + _ => {} + } + } + #[cfg(feature = "checker")] + { + let mut checker = crate::checker::Checker::new(&function, &env); + checker.prepare(&output); + checker.run().unwrap(); + } + } +} + #[test] fn test_debug_locations1() { let mach_env = mach_env(10); diff --git a/src/ion/liveranges.rs b/src/ion/liveranges.rs index 09040f6c..fc9cb988 100644 --- a/src/ion/liveranges.rs +++ b/src/ion/liveranges.rs @@ -817,7 +817,7 @@ impl<'a, F: Function> Env<'a, F> { requires_reg = true; } OperandConstraint::Limit(max) => { - first_reg_slot.get_or_insert(u.slot); + first_reg_slot = Some(u.slot); min_limit = min_limit.min(max); requires_reg = true; } diff --git a/src/ion/merge.rs b/src/ion/merge.rs index ff33ddc0..9d05f982 100644 --- a/src/ion/merge.rs +++ b/src/ion/merge.rs @@ -287,7 +287,7 @@ impl<'a, F: Function> Env<'a, F> { for entry in &self.bundles[bundle].ranges { for u in &self.ranges[entry.index].uses { use OperandConstraint::*; - match u.operand.constraint() { + match self.operand_constraint(u.operand, u.pos.inst()) { FixedReg(_) => { fixed = true; if u.operand.kind() == OperandKind::Def { @@ -402,7 +402,7 @@ impl<'a, F: Function> Env<'a, F> { for entry in &self.bundles[bundle].ranges { for u in &self.ranges[entry.index].uses { use OperandConstraint::*; - match u.operand.constraint() { + match self.operand_constraint(u.operand, u.pos.inst()) { Limit(current) => { let current = u8::try_from(current) .expect("the current limit is too large to fit in a u8"); diff --git a/src/ion/requirement.rs b/src/ion/requirement.rs index 85890130..af5d8ae7 100644 --- a/src/ion/requirement.rs +++ b/src/ion/requirement.rs @@ -112,9 +112,19 @@ impl Requirement { } impl<'a, F: Function> Env<'a, F> { + pub fn operand_constraint(&self, op: Operand, inst: Inst) -> OperandConstraint { + if let OperandConstraint::Reuse(input) = op.constraint() { + let constraint = self.func.inst_operands(inst)[input].constraint(); + if matches!(constraint, OperandConstraint::Limit(_)) { + return constraint; + } + } + op.constraint() + } + #[inline(always)] - pub fn requirement_from_operand(&self, op: Operand) -> Requirement { - match op.constraint() { + pub fn requirement_from_operand(&self, op: Operand, inst: Inst) -> Requirement { + match self.operand_constraint(op, inst) { OperandConstraint::FixedReg(preg) => { if self.pregs[preg.index()].is_stack { Requirement::FixedStack(preg) @@ -141,7 +151,7 @@ impl<'a, F: Function> Env<'a, F> { trace!(" -> LR {:?}: {:?}", entry.index, entry.range); for u in &self.ranges[entry.index].uses { trace!(" -> use {:?}", u); - let r = self.requirement_from_operand(u.operand); + let r = self.requirement_from_operand(u.operand, u.pos.inst()); req = req.merge(r).map_err(|_| { trace!(" -> conflict"); if req.is_stack() && r.is_reg() {