1use crate::infer::GraphExt;
53use crate::op::CmpOp;
54use crate::{DType, Dim, Graph, NodeId, Op, Shape};
55
56pub trait LaneExt {
63 fn lane_mask_like_(&mut self, mask: NodeId, like: NodeId) -> Option<NodeId>;
71
72 fn reset_lanes_(&mut self, state: NodeId, reset_to: NodeId, mask: NodeId) -> NodeId;
76
77 fn reset_lanes_to_(&mut self, state: NodeId, value: f64, mask: NodeId) -> NodeId;
79
80 fn reset_lanes_many_(
87 &mut self,
88 states: &[NodeId],
89 reset_to: &[NodeId],
90 mask: NodeId,
91 ) -> Vec<NodeId>;
92}
93
94fn static_dims(shape: &Shape) -> Option<Vec<usize>> {
96 shape
97 .dims()
98 .iter()
99 .map(|d| match d {
100 Dim::Static(n) => Some(*n),
101 Dim::Dynamic(_) => None,
102 })
103 .collect()
104}
105
106impl LaneExt for Graph {
107 fn lane_mask_like_(&mut self, mask: NodeId, like: NodeId) -> Option<NodeId> {
108 let target = static_dims(&self.node(like).shape.clone())?;
109 if target.is_empty() {
110 return None;
111 }
112 let mask_dims = static_dims(&self.node(mask).shape.clone())?;
113 if mask_dims.len() != 1 || mask_dims[0] != target[0] {
114 return None;
115 }
116 let n_lanes = target[0];
117 let dtype = self.node(mask).shape.dtype();
118
119 let mut keep = vec![n_lanes];
121 keep.extend(std::iter::repeat_n(1usize, target.len() - 1));
122 let reshaped = self.add_node(
123 Op::Reshape {
124 new_shape: keep.iter().map(|&d| d as i64).collect(),
125 },
126 vec![mask],
127 Shape::new(&keep, dtype),
128 );
129
130 let zero = self.constant(0.0, dtype);
133 let cond = self.add_node(
134 Op::Compare(CmpOp::Ne),
135 vec![reshaped, zero],
136 Shape::new(&keep, DType::Bool),
137 );
138
139 Some(self.add_node(
140 Op::Expand {
141 target_shape: target.iter().map(|&d| d as i64).collect(),
142 },
143 vec![cond],
144 Shape::new(&target, DType::Bool),
145 ))
146 }
147
148 fn reset_lanes_(&mut self, state: NodeId, reset_to: NodeId, mask: NodeId) -> NodeId {
149 let shape = self.node(state).shape.clone();
150 assert_eq!(
151 shape,
152 self.node(reset_to).shape,
153 "reset_lanes_: state and reset value must have the same shape"
154 );
155 let cond = self
156 .lane_mask_like_(mask, state)
157 .expect("reset_lanes_: mask must be [n_lanes] matching the state's leading dim");
158 self.add_node(Op::Where, vec![cond, reset_to, state], shape)
159 }
160
161 fn reset_lanes_to_(&mut self, state: NodeId, value: f64, mask: NodeId) -> NodeId {
162 let shape = self.node(state).shape.clone();
163 let dims = static_dims(&shape).expect("reset_lanes_to_: static shape required");
164 let fill = self.full(&dims, value as f32, shape.dtype());
165 self.reset_lanes_(state, fill, mask)
166 }
167
168 fn reset_lanes_many_(
169 &mut self,
170 states: &[NodeId],
171 reset_to: &[NodeId],
172 mask: NodeId,
173 ) -> Vec<NodeId> {
174 assert_eq!(
175 states.len(),
176 reset_to.len(),
177 "reset_lanes_many_: one reset value per state tensor"
178 );
179 let mut cache: Vec<(Shape, NodeId)> = Vec::new();
182 let mut out = Vec::with_capacity(states.len());
183 for (&s, &r) in states.iter().zip(reset_to.iter()) {
184 let shape = self.node(s).shape.clone();
185 let cond = match cache.iter().find(|(sh, _)| *sh == shape) {
186 Some((_, c)) => *c,
187 None => {
188 let c = self
189 .lane_mask_like_(mask, s)
190 .expect("reset_lanes_many_: mask must match each state's leading dim");
191 cache.push((shape.clone(), c));
192 c
193 }
194 };
195 out.push(self.add_node(Op::Where, vec![cond, r, s], shape));
196 }
197 out
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 fn graph_shape_signature(g: &Graph) -> Vec<(String, Vec<Dim>)> {
206 g.nodes()
207 .iter()
208 .map(|n| (format!("{:?}", n.op.kind()), n.shape.dims().to_vec()))
209 .collect()
210 }
211
212 #[test]
213 fn lane_mask_broadcasts_to_state_rank() {
214 let mut g = Graph::new("t");
215 let state = g.input("s", Shape::new(&[4, 3, 2], DType::F32));
216 let mask = g.input("m", Shape::new(&[4], DType::F32));
217 let cond = g.lane_mask_like_(mask, state).expect("broadcast");
218 assert_eq!(g.node(cond).shape.dims(), state_dims(&g, state).as_slice());
219 assert_eq!(g.node(cond).shape.dtype(), DType::Bool);
220 }
221
222 fn state_dims(g: &Graph, id: NodeId) -> Vec<Dim> {
223 g.node(id).shape.dims().to_vec()
224 }
225
226 #[test]
229 fn lane_mask_refuses_ill_defined_broadcasts() {
230 let mut g = Graph::new("t");
231 let state = g.input("s", Shape::new(&[4, 3], DType::F32));
232 let wrong_len = g.input("m1", Shape::new(&[5], DType::F32));
233 assert!(g.lane_mask_like_(wrong_len, state).is_none());
234
235 let rank2 = g.input("m2", Shape::new(&[4, 1], DType::F32));
236 assert!(g.lane_mask_like_(rank2, state).is_none());
237
238 let scalar = g.input("sc", Shape::from_dims(&[], DType::F32));
239 let m = g.input("m3", Shape::new(&[4], DType::F32));
240 assert!(g.lane_mask_like_(m, scalar).is_none());
241
242 let dynamic = g.input(
243 "d",
244 Shape::from_dims(&[Dim::Dynamic(0), Dim::Static(3)], DType::F32),
245 );
246 assert!(g.lane_mask_like_(m, dynamic).is_none());
247 }
248
249 #[test]
256 fn graph_structure_is_independent_of_mask_contents() {
257 let build = || {
258 let mut g = Graph::new("b");
259 let s = g.input("s", Shape::new(&[4, 8], DType::F32));
260 let m = g.input("m", Shape::new(&[4], DType::F32));
261 let out = g.reset_lanes_to_(s, 0.0, m);
262 g.set_outputs(vec![out]);
263 g
264 };
265 let a = build();
266 let b = build();
267 assert_eq!(graph_shape_signature(&a), graph_shape_signature(&b));
268 assert!(
270 a.nodes()
271 .iter()
272 .any(|n| matches!(&n.op, Op::Input { name } if name == "m")),
273 "the mask must remain a graph input"
274 );
275 }
276
277 #[test]
281 fn many_reuses_the_broadcast_per_shape() {
282 let mut g = Graph::new("many");
283 let shape = Shape::new(&[3, 5], DType::F32);
284 let states: Vec<NodeId> = (0..6)
285 .map(|i| g.input(format!("s{i}"), shape.clone()))
286 .collect();
287 let zeros: Vec<NodeId> = states
288 .iter()
289 .map(|_| g.zeros(&[3, 5], DType::F32))
290 .collect();
291 let mask = g.input("m", Shape::new(&[3], DType::F32));
292 let out = g.reset_lanes_many_(&states, &zeros, mask);
293 assert_eq!(out.len(), 6);
294 let expands = g
295 .nodes()
296 .iter()
297 .filter(|n| matches!(n.op, Op::Expand { .. }))
298 .count();
299 assert_eq!(expands, 1, "broadcast should be built once for one shape");
300 let wheres = g.nodes().iter().filter(|n| n.op == Op::Where).count();
301 assert_eq!(wheres, 6);
302 }
303}