/src/regalloc2/src/ion/requirement.rs
Line | Count | Source |
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 | | |
15 | | use super::{Env, LiveBundleIndex}; |
16 | | use crate::{Function, Inst, Operand, OperandConstraint, PReg, ProgPoint}; |
17 | | |
18 | | pub struct RequirementConflict; |
19 | | |
20 | | #[derive(Clone, Copy, Debug)] |
21 | | pub 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 | | |
40 | | impl RequirementConflictAt { |
41 | | #[inline(always)] |
42 | 222k | pub fn should_trim_edges_around_split(self) -> bool { |
43 | 222k | match self { |
44 | 182k | RequirementConflictAt::RegToStack(..) | RequirementConflictAt::StackToReg(..) => false, |
45 | 40.2k | RequirementConflictAt::Other(..) => true, |
46 | | } |
47 | 222k | } |
48 | | |
49 | | #[inline(always)] |
50 | 222k | pub fn suggested_split_point(self) -> ProgPoint { |
51 | 222k | match self { |
52 | 93.6k | RequirementConflictAt::RegToStack(pt) |
53 | 88.7k | | RequirementConflictAt::StackToReg(pt) |
54 | 222k | | RequirementConflictAt::Other(pt) => pt, |
55 | | } |
56 | 222k | } |
57 | | } |
58 | | |
59 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
60 | | pub enum Requirement { |
61 | | Any, |
62 | | Register, |
63 | | FixedReg(PReg), |
64 | | Limit(usize), |
65 | | Stack, |
66 | | FixedStack(PReg), |
67 | | } |
68 | | impl Requirement { |
69 | | #[inline(always)] |
70 | 28.0M | pub fn merge(self, other: Requirement) -> Result<Requirement, RequirementConflict> { |
71 | | use Requirement::*; |
72 | | |
73 | 28.0M | match (self, other) { |
74 | | // `Any` matches anything. |
75 | 17.9M | (other, Any) | (Any, other) => Ok(other), |
76 | | // Same kinds match. |
77 | 5.10M | (Register, Register) => Ok(self), |
78 | 0 | (Stack, Stack) => Ok(self), |
79 | 0 | (Limit(a), Limit(b)) => Ok(Limit(a.min(b))), |
80 | 116k | (FixedReg(a), FixedReg(b)) if a == b => Ok(self), |
81 | 66.3k | (FixedStack(a), FixedStack(b)) if a == b => Ok(self), |
82 | | // Limit a 'Register|FixedReg`. |
83 | 0 | (Limit(a), Register) | (Register, Limit(a)) => Ok(Limit(a)), |
84 | 0 | (Limit(a), FixedReg(b)) | (FixedReg(b), Limit(a)) if usize::from(a) > b.hw_enc() => { |
85 | 0 | Ok(FixedReg(b)) |
86 | | } |
87 | | // Constrain `Register|Stack` to `Fixed{Reg|Stack}`. |
88 | 4.63M | (Register, FixedReg(preg)) | (FixedReg(preg), Register) => Ok(FixedReg(preg)), |
89 | 0 | (Stack, FixedStack(preg)) | (FixedStack(preg), Stack) => Ok(FixedStack(preg)), |
90 | | // Fail otherwise. |
91 | 240k | _ => Err(RequirementConflict), |
92 | | } |
93 | 28.0M | } |
94 | | |
95 | | #[inline(always)] |
96 | 364k | pub fn is_stack(self) -> bool { |
97 | 364k | match self { |
98 | 216k | Requirement::Stack | Requirement::FixedStack(..) => true, |
99 | 147k | Requirement::Register | Requirement::FixedReg(..) | Requirement::Limit(..) => false, |
100 | 0 | Requirement::Any => false, |
101 | | } |
102 | 364k | } |
103 | | |
104 | | #[inline(always)] |
105 | 259k | pub fn is_reg(self) -> bool { |
106 | 259k | match self { |
107 | 217k | Requirement::Register | Requirement::FixedReg(..) | Requirement::Limit(..) => true, |
108 | 42.4k | Requirement::Stack | Requirement::FixedStack(..) => false, |
109 | 0 | Requirement::Any => false, |
110 | | } |
111 | 259k | } |
112 | | } |
113 | | |
114 | | impl<'a, F: Function> Env<'a, F> { |
115 | | #[inline(always)] |
116 | 28.0M | pub fn requirement_from_operand(&self, op: Operand) -> Requirement { |
117 | 28.0M | match op.constraint() { |
118 | 1.14M | OperandConstraint::FixedReg(preg) => { |
119 | 1.14M | if self.pregs[preg.index()].is_stack { |
120 | 569k | Requirement::FixedStack(preg) |
121 | | } else { |
122 | 578k | Requirement::FixedReg(preg) |
123 | | } |
124 | | } |
125 | 12.4M | OperandConstraint::Reg | OperandConstraint::Reuse(_) => Requirement::Register, |
126 | 0 | OperandConstraint::Limit(max) => Requirement::Limit(max), |
127 | 0 | OperandConstraint::Stack => Requirement::Stack, |
128 | 14.4M | OperandConstraint::Any => Requirement::Any, |
129 | | } |
130 | 28.0M | } |
131 | | |
132 | 10.2M | pub fn compute_requirement( |
133 | 10.2M | &self, |
134 | 10.2M | bundle: LiveBundleIndex, |
135 | 10.2M | ) -> Result<Requirement, RequirementConflictAt> { |
136 | 10.2M | let mut req = Requirement::Any; |
137 | 10.2M | let mut last_pos = ProgPoint::before(Inst::new(0)); |
138 | 10.2M | trace!("compute_requirement: {:?}", bundle); |
139 | 10.2M | let ranges = &self.bundles[bundle].ranges; |
140 | 17.5M | for entry in ranges { |
141 | 17.5M | trace!(" -> LR {:?}: {:?}", entry.index, entry.range); |
142 | 28.0M | for u in &self.ranges[entry.index].uses { |
143 | 28.0M | trace!(" -> use {:?}", u); |
144 | 28.0M | let r = self.requirement_from_operand(u.operand); |
145 | 28.0M | req = req.merge(r).map_err(|_| { |
146 | 238k | trace!(" -> conflict"); |
147 | 238k | if req.is_stack() && r.is_reg() { |
148 | | // Suggested split point just before the reg (i.e., late split). |
149 | 91.8k | RequirementConflictAt::StackToReg(u.pos) |
150 | 146k | } 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 | 103k | RequirementConflictAt::RegToStack(last_pos) |
157 | | } else { |
158 | 43.0k | RequirementConflictAt::Other(u.pos) |
159 | | } |
160 | 238k | })?; |
161 | 27.7M | last_pos = u.pos; |
162 | 27.7M | trace!(" -> req {:?}", req); |
163 | | } |
164 | | } |
165 | 10.0M | trace!(" -> final: {:?}", req); |
166 | 10.0M | Ok(req) |
167 | 10.2M | } |
168 | | |
169 | 54.9k | pub fn merge_bundle_requirements( |
170 | 54.9k | &self, |
171 | 54.9k | a: LiveBundleIndex, |
172 | 54.9k | b: LiveBundleIndex, |
173 | 54.9k | ) -> Result<Requirement, RequirementConflict> { |
174 | 54.9k | let req_a = self |
175 | 54.9k | .compute_requirement(a) |
176 | 54.9k | .map_err(|_| RequirementConflict)?; |
177 | 44.8k | let req_b = self |
178 | 44.8k | .compute_requirement(b) |
179 | 44.8k | .map_err(|_| RequirementConflict)?; |
180 | 38.9k | req_a.merge(req_b) |
181 | 54.9k | } |
182 | | } |