logicaffeine_compile/tensor_graph/
facts.rs1use std::cell::{RefCell, RefMut};
10use std::collections::{BTreeSet, HashMap};
11
12use super::graph::Op;
13
14pub fn reads(op: &Op) -> Vec<&str> {
16 let mut r: Vec<&str> = op.ins.iter().map(String::as_str).collect();
17 r.extend(op.row_params());
18 r.sort_unstable();
19 r.dedup();
20 r
21}
22
23pub fn writes(op: &Op) -> Vec<&str> {
25 op.writes()
26}
27
28pub fn result(op: &Op) -> Option<&str> {
30 op.result()
31}
32
33#[derive(Clone)]
35pub struct Slots {
36 slots: Vec<Option<Op>>,
37 index: RefCell<Index>,
38}
39
40#[derive(Clone, Default)]
41struct Index {
42 reads: HashMap<String, BTreeSet<usize>>,
43 writes: HashMap<String, BTreeSet<usize>>,
44 results: HashMap<String, BTreeSet<usize>>,
45 of: Vec<[Vec<String>; 3]>,
46 changed: BTreeSet<usize>,
47 moved: bool,
48}
49
50impl Index {
51 fn maps(&mut self) -> [&mut HashMap<String, BTreeSet<usize>>; 3] {
52 [&mut self.reads, &mut self.writes, &mut self.results]
53 }
54
55 fn enter(&mut self, i: usize, slot: &Option<Op>) {
56 let names = |v: Vec<&str>| v.into_iter().map(String::from).collect::<BTreeSet<_>>().into_iter().collect::<Vec<_>>();
57 let of = match slot {
58 Some(op) => [names(reads(op)), names(writes(op)), result(op).map(String::from).into_iter().collect()],
59 None => Default::default(),
60 };
61 for (map, names) in self.maps().into_iter().zip(&of) {
62 for t in names {
63 map.entry(t.clone()).or_default().insert(i);
64 }
65 }
66 self.of[i] = of;
67 }
68
69 fn leave(&mut self, i: usize) {
70 let of = std::mem::take(&mut self.of[i]);
71 for (map, names) in self.maps().into_iter().zip(&of) {
72 for t in names {
73 if let Some(at) = map.get_mut(t) {
74 at.remove(&i);
75 if at.is_empty() {
76 map.remove(t);
77 }
78 }
79 }
80 }
81 }
82}
83
84impl Slots {
85 pub fn new(ops: Vec<Op>) -> Self {
86 Slots { slots: ops.into_iter().map(Some).collect(), index: RefCell::new(Index { moved: true, ..Index::default() }) }
87 }
88
89 fn index(&self) -> RefMut<'_, Index> {
90 let mut ix = self.index.borrow_mut();
91 if ix.moved {
92 *ix = Index { of: vec![Default::default(); self.slots.len()], ..Index::default() };
93 for (i, slot) in self.slots.iter().enumerate() {
94 ix.enter(i, slot);
95 }
96 } else {
97 for i in std::mem::take(&mut ix.changed) {
98 ix.leave(i);
99 ix.enter(i, &self.slots[i]);
100 }
101 }
102 ix
103 }
104
105 fn changed(&self, i: usize) {
106 self.index.borrow_mut().changed.insert(i);
107 }
108
109 pub fn len(&self) -> usize {
110 self.slots.len()
111 }
112
113 pub fn is_empty(&self) -> bool {
114 self.slots.is_empty()
115 }
116
117 pub fn slot(&self, i: usize) -> Option<&Op> {
118 self.slots[i].as_ref()
119 }
120
121 pub fn get(&self, i: usize) -> &Op {
122 self.slots[i].as_ref().expect("a live slot")
123 }
124
125 pub fn get_mut(&mut self, i: usize) -> &mut Op {
126 self.changed(i);
127 self.slots[i].as_mut().expect("a live slot")
128 }
129
130 pub fn take(&mut self, i: usize) -> Option<Op> {
131 self.changed(i);
132 self.slots[i].take()
133 }
134
135 pub fn put(&mut self, i: usize, op: Op) {
136 self.changed(i);
137 self.slots[i] = Some(op);
138 }
139
140 pub fn insert(&mut self, i: usize, op: Op) {
142 self.slots.insert(i, Some(op));
143 self.index.get_mut().moved = true;
144 }
145
146 pub fn live(&self) -> impl Iterator<Item = (usize, &Op)> {
147 self.slots.iter().enumerate().filter_map(|(i, s)| s.as_ref().map(|op| (i, op)))
148 }
149
150 pub fn into_ops(self) -> Vec<Op> {
151 self.slots.into_iter().flatten().collect()
152 }
153
154 pub fn readers(&self, t: &str) -> Vec<usize> {
156 self.index().reads.get(t).map_or_else(Vec::new, |at| at.iter().copied().collect())
157 }
158
159 pub fn producer(&self, t: &str) -> Option<usize> {
161 self.index().results.get(t).and_then(|at| at.first().copied())
162 }
163
164 pub fn maker(&self, t: &str) -> Option<usize> {
166 self.producer(t).or_else(|| {
167 let at = self.index().writes.get(t).cloned().unwrap_or_default();
168 at.into_iter().find(|&k| self.get(k).keeps.iter().any(|kept| kept == t))
169 })
170 }
171
172 pub fn untouched(&self, t: &str, from: usize, to: usize, reads: bool, skip: &[usize]) -> bool {
174 let ix = self.index();
175 let none_in = |map: &HashMap<String, BTreeSet<usize>>| {
176 map.get(t).is_none_or(|at| at.range(from + 1..to.max(from + 1)).all(|b| skip.contains(b)))
177 };
178 none_in(&ix.writes) && (!reads || none_in(&ix.reads))
179 }
180
181 pub fn crosses(&self, i: usize, j: usize, group: &[usize]) -> bool {
184 self.crosses_from(j, i, j, group)
185 }
186
187 pub fn sinks_past(&self, i: usize, j: usize, group: &[usize]) -> bool {
189 self.crosses_from(i, i, j, group)
190 }
191
192 fn crosses_from(&self, at: usize, lo: usize, hi: usize, group: &[usize]) -> bool {
195 let op = self.get(at);
196 let (r, w) = (reads(op), writes(op));
197 let ix = self.index();
198 let between = |map: &HashMap<String, BTreeSet<usize>>, t: &str| {
199 map.get(t).is_some_and(|slots| slots.range(lo + 1..hi.max(lo + 1)).any(|b| !group.contains(b)))
200 };
201 r.iter().chain(&w).any(|t| between(&ix.writes, t)) || w.iter().any(|t| between(&ix.reads, t))
202 }
203}
204
205#[derive(Clone, Copy, Debug, PartialEq, Eq)]
208pub enum Accumulates {
209 Nothing,
210 Unproven,
211 Within(i128, i128),
212}
213
214impl Accumulates {
215 pub fn bits(self) -> Option<u32> {
217 match self {
218 Accumulates::Within(lo, hi) => {
219 let magnitude = lo.unsigned_abs().max(hi.unsigned_abs());
220 Some(129 - magnitude.leading_zeros())
221 }
222 _ => None,
223 }
224 }
225}
226
227pub fn product_sums(a: (i64, i64), b: (i64, i64), k: usize) -> (i128, i128) {
230 let corners = [a.0 as i128 * b.0 as i128, a.0 as i128 * b.1 as i128, a.1 as i128 * b.0 as i128, a.1 as i128 * b.1 as i128];
231 let (lo, hi) = (*corners.iter().min().expect("four corners"), *corners.iter().max().expect("four corners"));
232 let k = k as i128;
233 (lo.saturating_mul(k).min(0), hi.saturating_mul(k).max(0))
234}
235
236pub fn accumulator(op: &super::graph::Op, g: &super::graph::TensorGraph) -> Accumulates {
239 use super::front::TensorOpSpec;
240 use super::graph::Elem;
241 let int = |k: usize| op.ins.get(k).and_then(|t| g.tensors.get(t)).is_some_and(|t| t.elem == Elem::Int);
242 let range = |k: usize| op.ins.get(k).and_then(|t| g.tensors.get(t)).and_then(|t| t.range);
243 let two = |f: &dyn Fn((i64, i64), (i64, i64)) -> (i128, i128)| match (range(0), range(1)) {
244 (Some(a), Some(b)) => {
245 let (lo, hi) = f(a, b);
246 Accumulates::Within(lo, hi)
247 }
248 _ => Accumulates::Unproven,
249 };
250 match op.prim() {
251 Some(TensorOpSpec::Matmul(m)) if int(0) && int(1) => two(&|a, b| product_sums(a, b, m.k)),
252 Some(TensorOpSpec::Elementwise(e)) if int(0) && int(1) => match e.op {
253 crate::tensor_recognize::ElementwiseOp::Add => two(&|a, b| (a.0 as i128 + b.0 as i128, a.1 as i128 + b.1 as i128)),
254 crate::tensor_recognize::ElementwiseOp::Mul => two(&|a, b| product_sums(a, b, 1)),
255 },
256 Some(TensorOpSpec::IntRequant(q)) => match range(0) {
257 Some(x) => {
258 let (a, b) = (x.0 as i128 * q.m as i128, x.1 as i128 * q.m as i128);
259 Accumulates::Within(a.min(b), a.max(b))
260 }
261 None => Accumulates::Unproven,
262 },
263 _ => Accumulates::Nothing,
264 }
265}