Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/fastalloc/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the test in fastalloc named with ion?

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,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see -- you put a test for Ion in fastalloc. Although it seems weird, the Ion backend actually doesn't have unit tests -- they're verbose and hard to write and maintain, and instead, we update the fuzzer to generate the cases we want to accept (and the crate is continuously fuzzed so this will quickly find issues).

Rather than add this test, can you update the fuzzing function generator to emit functions that hit the newly problematic cases?

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);
Expand Down
2 changes: 1 addition & 1 deletion src/ion/liveranges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain (in a comment ideally) why we overwrite first_reg_slot each time we see a Limit, rather than save the first one?

If there is some lattice meet semantics we want here, e.g. Limit overrides Reg, we should do that explicitly rather than rely on visit order, which can be brittle...

min_limit = min_limit.min(max);
requires_reg = true;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ion/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why we need to "look through" Reuse constraints here -- we don't do that anywhere else, and it's likely to break things wrt minimal bundles, I suspect.

Why can't we propagate any constraints after we merge, if we merge?

FixedReg(_) => {
fixed = true;
if u.operand.kind() == OperandKind::Def {
Expand Down Expand Up @@ -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");
Expand Down
16 changes: 13 additions & 3 deletions src/ion/requirement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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() {
Expand Down
Loading