regalloc2/ion/
requirement.rs

1/*
2 * This file was initially derived from the files
3 * `js/src/jit/BacktrackingAllocator.h` and
4 * `js/src/jit/BacktrackingAllocator.cpp` in Mozilla Firefox, and was
5 * originally licensed under the Mozilla Public License 2.0. We
6 * subsequently relicensed it to Apache-2.0 WITH LLVM-exception (see
7 * https://github.com/bytecodealliance/regalloc2/issues/7).
8 *
9 * Since the initial port, the design has been substantially evolved
10 * and optimized.
11 */
12
13//! Requirements computation.
14
15use super::{Env, LiveBundleIndex};
16use crate::{Function, Inst, Operand, OperandConstraint, PReg, ProgPoint};
17
18pub struct RequirementConflict;
19
20#[derive(Clone, Copy, Debug)]
21pub enum RequirementConflictAt {
22    /// A transition from a stack-constrained to a reg-constrained
23    /// segment. The suggested split point is late, to keep the
24    /// intervening region with the stackslot (which is cheaper).
25    StackToReg(ProgPoint),
26    /// A transition from a reg-constraint to a stack-constrained
27    /// segment. Mirror of above: the suggested split point is early
28    /// (just after the last register use).
29    RegToStack(ProgPoint),
30    /// Any other transition. The suggested split point is late (just
31    /// before the conflicting use), but the split will also trim the
32    /// ends and create a split bundle, so the intervening region will
33    /// not appear with either side. This is probably for the best
34    /// when e.g. the two sides of the split are both constrained to
35    /// different physical registers: the part in the middle should be
36    /// constrained to neither.
37    Other(ProgPoint),
38}
39
40impl RequirementConflictAt {
41    #[inline(always)]
42    pub fn should_trim_edges_around_split(self) -> bool {
43        match self {
44            RequirementConflictAt::RegToStack(..) | RequirementConflictAt::StackToReg(..) => false,
45            RequirementConflictAt::Other(..) => true,
46        }
47    }
48
49    #[inline(always)]
50    pub fn suggested_split_point(self) -> ProgPoint {
51        match self {
52            RequirementConflictAt::RegToStack(pt)
53            | RequirementConflictAt::StackToReg(pt)
54            | RequirementConflictAt::Other(pt) => pt,
55        }
56    }
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub enum Requirement {
61    Any,
62    Register,
63    FixedReg(PReg),
64    Limit(usize),
65    Stack,
66    FixedStack(PReg),
67}
68impl Requirement {
69    #[inline(always)]
70    pub fn merge(self, other: Requirement) -> Result<Requirement, RequirementConflict> {
71        use Requirement::*;
72
73        match (self, other) {
74            // `Any` matches anything.
75            (other, Any) | (Any, other) => Ok(other),
76            // Same kinds match.
77            (Register, Register) => Ok(self),
78            (Stack, Stack) => Ok(self),
79            (Limit(a), Limit(b)) => Ok(Limit(a.min(b))),
80            (FixedReg(a), FixedReg(b)) if a == b => Ok(self),
81            (FixedStack(a), FixedStack(b)) if a == b => Ok(self),
82            // Limit a 'Register|FixedReg`.
83            (Limit(a), Register) | (Register, Limit(a)) => Ok(Limit(a)),
84            (Limit(a), FixedReg(b)) | (FixedReg(b), Limit(a)) if usize::from(a) > b.hw_enc() => {
85                Ok(FixedReg(b))
86            }
87            // Constrain `Register|Stack` to `Fixed{Reg|Stack}`.
88            (Register, FixedReg(preg)) | (FixedReg(preg), Register) => Ok(FixedReg(preg)),
89            (Stack, FixedStack(preg)) | (FixedStack(preg), Stack) => Ok(FixedStack(preg)),
90            // Fail otherwise.
91            _ => Err(RequirementConflict),
92        }
93    }
94
95    #[inline(always)]
96    pub fn is_stack(self) -> bool {
97        match self {
98            Requirement::Stack | Requirement::FixedStack(..) => true,
99            Requirement::Register | Requirement::FixedReg(..) | Requirement::Limit(..) => false,
100            Requirement::Any => false,
101        }
102    }
103
104    #[inline(always)]
105    pub fn is_reg(self) -> bool {
106        match self {
107            Requirement::Register | Requirement::FixedReg(..) | Requirement::Limit(..) => true,
108            Requirement::Stack | Requirement::FixedStack(..) => false,
109            Requirement::Any => false,
110        }
111    }
112}
113
114impl<'a, F: Function> Env<'a, F> {
115    #[inline(always)]
116    pub fn requirement_from_operand(&self, op: Operand) -> Requirement {
117        match op.constraint() {
118            OperandConstraint::FixedReg(preg) => {
119                if self.pregs[preg.index()].is_stack {
120                    Requirement::FixedStack(preg)
121                } else {
122                    Requirement::FixedReg(preg)
123                }
124            }
125            OperandConstraint::Reg | OperandConstraint::Reuse(_) => Requirement::Register,
126            OperandConstraint::Limit(max) => Requirement::Limit(max),
127            OperandConstraint::Stack => Requirement::Stack,
128            OperandConstraint::Any => Requirement::Any,
129        }
130    }
131
132    pub fn compute_requirement(
133        &self,
134        bundle: LiveBundleIndex,
135    ) -> Result<Requirement, RequirementConflictAt> {
136        let mut req = Requirement::Any;
137        let mut last_pos = ProgPoint::before(Inst::new(0));
138        trace!("compute_requirement: {:?}", bundle);
139        let ranges = &self.bundles[bundle].ranges;
140        for entry in ranges {
141            trace!(" -> LR {:?}: {:?}", entry.index, entry.range);
142            for u in &self.ranges[entry.index].uses {
143                trace!("  -> use {:?}", u);
144                let r = self.requirement_from_operand(u.operand);
145                req = req.merge(r).map_err(|_| {
146                    trace!("     -> conflict");
147                    if req.is_stack() && r.is_reg() {
148                        // Suggested split point just before the reg (i.e., late split).
149                        RequirementConflictAt::StackToReg(u.pos)
150                    } else if req.is_reg() && r.is_stack() {
151                        // Suggested split point just after the stack
152                        // (i.e., early split). Note that splitting
153                        // with a use *right* at the beginning is
154                        // interpreted by `split_and_requeue_bundle`
155                        // as splitting off the first use.
156                        RequirementConflictAt::RegToStack(last_pos)
157                    } else {
158                        RequirementConflictAt::Other(u.pos)
159                    }
160                })?;
161                last_pos = u.pos;
162                trace!("     -> req {:?}", req);
163            }
164        }
165        trace!(" -> final: {:?}", req);
166        Ok(req)
167    }
168
169    pub fn merge_bundle_requirements(
170        &self,
171        a: LiveBundleIndex,
172        b: LiveBundleIndex,
173    ) -> Result<Requirement, RequirementConflict> {
174        let req_a = self
175            .compute_requirement(a)
176            .map_err(|_| RequirementConflict)?;
177        let req_b = self
178            .compute_requirement(b)
179            .map_err(|_| RequirementConflict)?;
180        req_a.merge(req_b)
181    }
182}