use super::{Env, LiveBundleIndex};
use crate::{Function, Inst, Operand, OperandConstraint, PReg, ProgPoint};
pub struct RequirementConflict;
#[derive(Clone, Copy, Debug)]
pub enum RequirementConflictAt {
StackToReg(ProgPoint),
RegToStack(ProgPoint),
Other(ProgPoint),
}
impl RequirementConflictAt {
#[inline(always)]
pub fn should_trim_edges_around_split(self) -> bool {
match self {
RequirementConflictAt::RegToStack(..) | RequirementConflictAt::StackToReg(..) => false,
RequirementConflictAt::Other(..) => true,
}
}
#[inline(always)]
pub fn suggested_split_point(self) -> ProgPoint {
match self {
RequirementConflictAt::RegToStack(pt)
| RequirementConflictAt::StackToReg(pt)
| RequirementConflictAt::Other(pt) => pt,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Requirement {
FixedReg(PReg),
FixedStack(PReg),
Register,
Stack,
Any,
}
impl Requirement {
#[inline(always)]
pub fn merge(self, other: Requirement) -> Result<Requirement, RequirementConflict> {
match (self, other) {
(other, Requirement::Any) | (Requirement::Any, other) => Ok(other),
(Requirement::Register, Requirement::Register) => Ok(self),
(Requirement::Stack, Requirement::Stack) => Ok(self),
(Requirement::Register, Requirement::FixedReg(preg))
| (Requirement::FixedReg(preg), Requirement::Register) => {
Ok(Requirement::FixedReg(preg))
}
(Requirement::Stack, Requirement::FixedStack(preg))
| (Requirement::FixedStack(preg), Requirement::Stack) => {
Ok(Requirement::FixedStack(preg))
}
(Requirement::FixedReg(a), Requirement::FixedReg(b)) if a == b => Ok(self),
(Requirement::FixedStack(a), Requirement::FixedStack(b)) if a == b => Ok(self),
_ => Err(RequirementConflict),
}
}
#[inline(always)]
pub fn is_stack(self) -> bool {
match self {
Requirement::Stack | Requirement::FixedStack(..) => true,
Requirement::Register | Requirement::FixedReg(..) => false,
Requirement::Any => false,
}
}
#[inline(always)]
pub fn is_reg(self) -> bool {
match self {
Requirement::Register | Requirement::FixedReg(..) => true,
Requirement::Stack | Requirement::FixedStack(..) => false,
Requirement::Any => false,
}
}
}
impl<'a, F: Function> Env<'a, F> {
#[inline(always)]
pub fn requirement_from_operand(&self, op: Operand) -> Requirement {
match op.constraint() {
OperandConstraint::FixedReg(preg) => {
if self.pregs[preg.index()].is_stack {
Requirement::FixedStack(preg)
} else {
Requirement::FixedReg(preg)
}
}
OperandConstraint::Reg | OperandConstraint::Reuse(_) => Requirement::Register,
OperandConstraint::Stack => Requirement::Stack,
OperandConstraint::Any => Requirement::Any,
}
}
pub fn compute_requirement(
&self,
bundle: LiveBundleIndex,
) -> Result<Requirement, RequirementConflictAt> {
let mut req = Requirement::Any;
let mut last_pos = ProgPoint::before(Inst::new(0));
trace!("compute_requirement: {:?}", bundle);
let ranges = &self.bundles[bundle.index()].ranges;
for entry in ranges {
trace!(" -> LR {:?}: {:?}", entry.index, entry.range);
for u in &self.ranges[entry.index.index()].uses {
trace!(" -> use {:?}", u);
let r = self.requirement_from_operand(u.operand);
req = req.merge(r).map_err(|_| {
trace!(" -> conflict");
if req.is_stack() && r.is_reg() {
RequirementConflictAt::StackToReg(u.pos)
} else if req.is_reg() && r.is_stack() {
RequirementConflictAt::RegToStack(last_pos)
} else {
RequirementConflictAt::Other(u.pos)
}
})?;
last_pos = u.pos;
trace!(" -> req {:?}", req);
}
}
trace!(" -> final: {:?}", req);
Ok(req)
}
pub fn merge_bundle_requirements(
&self,
a: LiveBundleIndex,
b: LiveBundleIndex,
) -> Result<Requirement, RequirementConflict> {
let req_a = self
.compute_requirement(a)
.map_err(|_| RequirementConflict)?;
let req_b = self
.compute_requirement(b)
.map_err(|_| RequirementConflict)?;
req_a.merge(req_b)
}
}