1use super::forcing::{ForcingSet, ForcingSlot, RhsError, shape};
22use super::tile::{declined_error, plain_precision, refused, shell};
23use super::walk::AxisSpec;
24use super::{
25 Blocks, Decomposition, LaneKind, LaneLayout, ProgramModel, RhsProgram, RhsWorkspace, TileInput,
26 WsExec, build_error, check_forcing, check_lengths, check_owner, index_set_sizes,
27 next_program_id, slot_map, tape_fault, var_infos,
28};
29use crate::simulate::SimulateError;
30use crate::simulate_array::tape::{
31 CallInputs, ForcingView, TapeCtx, TapeProgram, TileLowering, run_tape_inputs,
32};
33use crate::simulate_array::{AlgebraicRule, ArrayCompiled, RhsRule, VarShape};
34use crate::types::{Expr, ExpressionNode, RegionBound};
35use indexmap::IndexMap;
36use rustc_hash::FxHashMap;
37use std::collections::{HashMap, HashSet};
38use std::sync::Arc;
39
40const LANE: &str = "__lane";
42
43fn cell_var(set: &str) -> String {
44 format!("__cell_{set}")
45}
46
47fn input_var(k: usize) -> String {
48 format!("__lane_input_{k}")
49}
50
51#[derive(Clone)]
54pub struct LaneProgram {
55 inner: Arc<LaneInner>,
56}
57
58struct LaneInner {
59 id: u64,
60 main: Arc<TapeProgram>,
61 pre: Arc<TapeProgram>,
62 gather: Arc<TapeProgram>,
63 slot_of: [Vec<u32>; 3],
66 blocks: Blocks,
67 layout: LaneLayout,
68 slots: Arc<[ForcingSlot]>,
69 capacity: usize,
70 sets: Vec<String>,
71 extents: Vec<i64>,
73 n_inputs: usize,
74 n_params: usize,
75 declared: Arc<HashSet<String>>,
76 precision: crate::precision::Precision,
77}
78
79impl std::fmt::Debug for LaneProgram {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_struct("LaneProgram")
82 .field("capacity", &self.inner.capacity)
83 .field("sets", &self.inner.sets)
84 .field("layout", &self.inner.layout)
85 .finish()
86 }
87}
88
89pub struct LaneInputs {
95 owner: u64,
96 forcing: Option<ForcingSet>,
97 values: Vec<f64>,
98 state: Vec<f64>,
99 ctx: Box<TapeCtx>,
100}
101
102impl std::fmt::Debug for LaneInputs {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("LaneInputs")
105 .field("len", &self.values.len())
106 .finish()
107 }
108}
109
110impl LaneInputs {
111 pub fn values(&self) -> &[f64] {
113 &self.values
114 }
115}
116
117const MAIN: usize = 0;
118const PRE: usize = 1;
119const GATHER: usize = 2;
120
121impl LaneProgram {
122 pub fn layout(&self) -> &LaneLayout {
126 &self.inner.layout
127 }
128
129 pub fn capacity(&self) -> usize {
131 self.inner.capacity
132 }
133
134 pub fn n_lane_inputs(&self) -> usize {
136 self.inner.n_inputs
137 }
138
139 pub fn forcing_slots(&self) -> &[ForcingSlot] {
141 &self.inner.slots
142 }
143
144 pub fn new_workspace(&self) -> RhsWorkspace {
146 let inner = &*self.inner;
147 let n = inner.blocks.u_len
148 + inner.blocks.aux_len
149 + inner.capacity * (inner.sets.len() + inner.n_inputs);
150 let serial = |t: &Arc<TapeProgram>| {
151 let mut ctx = TapeCtx::new(Arc::clone(t));
152 ctx.set_serial();
153 Box::new(ctx)
154 };
155 RhsWorkspace {
156 owner: inner.id,
157 state: vec![0.0; n],
158 dy: Vec::new(),
159 exec: WsExec::Lanes {
160 main: serial(&inner.main),
161 pre: serial(&inner.pre),
162 },
163 _not_sync: std::marker::PhantomData,
164 }
165 }
166
167 fn write_cells(
173 &self,
174 state: &mut [f64],
175 at: usize,
176 cells: &[i32],
177 n: usize,
178 ) -> Result<(), RhsError> {
179 let inner = &*self.inner;
180 let d = inner.sets.len();
181 if n > inner.capacity {
182 return Err(shape("n (lanes past the capacity)", inner.capacity, n));
183 }
184 if !cells.len().is_multiple_of(d.max(1))
185 || cells.len() / d.max(1) < n
186 || cells.len() / d.max(1) > inner.capacity
187 {
188 return Err(shape(
189 "cells (D times a lane count between n and the capacity)",
190 d * n.max(1),
191 cells.len(),
192 ));
193 }
194 for (a, &ext) in inner.extents.iter().enumerate() {
195 let rows = &mut state[at + a * inner.capacity..at + (a + 1) * inner.capacity];
196 for (l, row) in rows.iter_mut().enumerate() {
197 let c = match (l < n, n > 0) {
198 (true, _) => cells[l * d + a],
199 (false, true) => cells[a],
200 (false, false) => 1,
201 };
202 if l < n && (i64::from(c) < 1 || i64::from(c) > ext) {
203 return Err(shape(
204 "cells (a lane's cell outside its index set)",
205 ext as usize,
206 c.max(0) as usize,
207 ));
208 }
209 *row = f64::from(c);
210 }
211 }
212 Ok(())
213 }
214
215 #[allow(clippy::too_many_arguments)]
218 pub fn eval_into(
219 &self,
220 u: &[f64],
221 aux: &[f64],
222 p: &[f64],
223 t: f64,
224 forcing: &ForcingSet,
225 cells: &[i32],
226 n: usize,
227 du: &mut [f64],
228 ws: &mut RhsWorkspace,
229 ) -> Result<(), RhsError> {
230 self.run(u, aux, None, p, t, forcing, cells, n, du, ws)
231 }
232
233 pub fn lane_inputs(&self) -> LaneInputs {
235 let inner = &*self.inner;
236 let mut ctx = TapeCtx::new(Arc::clone(&inner.gather));
237 ctx.set_serial();
238 LaneInputs {
239 owner: inner.id,
240 forcing: None,
241 values: vec![0.0; inner.capacity * inner.n_inputs],
242 state: vec![0.0; inner.capacity * (inner.n_inputs + inner.sets.len())],
243 ctx: Box::new(ctx),
244 }
245 }
246
247 pub fn gather_lane_inputs(
250 &self,
251 inputs: &mut LaneInputs,
252 p: &[f64],
253 t: f64,
254 forcing: &ForcingSet,
255 cells: &[i32],
256 n: usize,
257 ) -> Result<(), RhsError> {
258 let inner = &*self.inner;
259 if inputs.owner != inner.id {
260 return Err(RhsError::ForeignProgram {
261 what: "lane inputs",
262 });
263 }
264 inputs.forcing = None;
267 if p.len() != inner.n_params {
268 return Err(shape("p", inner.n_params, p.len()));
269 }
270 check_forcing(forcing, &inner.slots)?;
271 self.write_cells(&mut inputs.state, inner.capacity * inner.n_inputs, cells, n)?;
272 let _precision = crate::precision::enter(inner.precision);
273 let scope = crate::simulate_array::FaultScope::enter();
274 let call = CallInputs {
275 state: &inputs.state,
276 params: p,
277 forcing: ForcingView::Slots {
278 bufs: forcing.bufs(),
279 slot_of: &inner.slot_of[GATHER],
280 },
281 forcing_epoch: Some(forcing.epoch()),
282 t,
283 declared: &inner.declared,
284 };
285 run_tape_inputs(&mut inputs.ctx, &call, &mut inputs.values, false);
286 tape_fault(scope, &mut inputs.ctx)?;
287 inputs.forcing = Some(forcing.clone());
290 Ok(())
291 }
292
293 #[allow(clippy::too_many_arguments)]
296 pub fn eval_gathered_into(
297 &self,
298 u: &[f64],
299 aux: &[f64],
300 inputs: &LaneInputs,
301 p: &[f64],
302 t: f64,
303 cells: &[i32],
304 n: usize,
305 du: &mut [f64],
306 ws: &mut RhsWorkspace,
307 ) -> Result<(), RhsError> {
308 if inputs.owner != self.inner.id {
309 return Err(RhsError::ForeignProgram {
310 what: "lane inputs",
311 });
312 }
313 let Some(forcing) = &inputs.forcing else {
314 return Err(RhsError::NotGathered);
315 };
316 self.run(
317 u,
318 aux,
319 Some(&inputs.values),
320 p,
321 t,
322 forcing,
323 cells,
324 n,
325 du,
326 ws,
327 )
328 }
329
330 #[allow(clippy::too_many_arguments)]
331 fn run(
332 &self,
333 u: &[f64],
334 aux: &[f64],
335 gathered: Option<&[f64]>,
336 p: &[f64],
337 t: f64,
338 forcing: &ForcingSet,
339 cells: &[i32],
340 n: usize,
341 du: &mut [f64],
342 ws: &mut RhsWorkspace,
343 ) -> Result<(), RhsError> {
344 let inner = &*self.inner;
345 check_lengths(&inner.blocks, u, aux, du, p, inner.n_params)?;
346 check_owner(ws, inner.id)?;
347 check_forcing(forcing, &inner.slots)?;
348 let (nu, na) = (inner.blocks.u_len, inner.blocks.aux_len);
349 ws.state[..nu].copy_from_slice(u);
350 ws.state[nu..nu + na].copy_from_slice(aux);
351 let mut state = std::mem::take(&mut ws.state);
352 let res = self.write_cells(&mut state, nu + na, cells, n);
353 let at = nu + na + inner.capacity * inner.sets.len();
354 if let Some(g) = gathered {
355 state[at..at + g.len()].copy_from_slice(g);
356 }
357 ws.state = state;
358 res?;
359 let WsExec::Lanes { main, pre } = &mut ws.exec else {
360 unreachable!("a lane workspace runs lane tapes");
361 };
362 let (ctx, which) = if gathered.is_some() {
363 (pre, PRE)
364 } else {
365 (main, MAIN)
366 };
367 let _precision = crate::precision::enter(inner.precision);
368 let scope = crate::simulate_array::FaultScope::enter();
369 let call = CallInputs {
370 state: &ws.state,
371 params: p,
372 forcing: ForcingView::Slots {
373 bufs: forcing.bufs(),
374 slot_of: &inner.slot_of[which],
375 },
376 forcing_epoch: Some(forcing.epoch()),
377 t,
378 declared: &inner.declared,
379 };
380 run_tape_inputs(ctx, &call, du, false);
381 tape_fault(scope, ctx)
382 }
383}
384
385struct Rewriter<'a> {
387 axes: &'a HashMap<String, Vec<Option<String>>>,
389 lane_vars: &'a HashSet<String>,
391 sets: &'a [String],
392 sizes: &'a HashMap<String, i64>,
393 cells: bool,
394 capacity: i64,
395 rule: String,
396 gathers: Vec<Expr>,
398 pre: bool,
400}
401
402#[derive(Clone, Default)]
405struct LScope {
406 lane_syms: Vec<(String, String)>,
407 inner: Vec<String>,
408}
409
410impl LScope {
411 fn lane_set(&self, sym: &str) -> Option<&str> {
412 self.lane_syms
413 .iter()
414 .find(|(s, _)| s == sym)
415 .map(|(_, set)| set.as_str())
416 }
417}
418
419type RResult<T> = Result<T, SimulateError>;
420
421fn lane_read(set: &str) -> Expr {
422 Expr::operator(ExpressionNode {
423 op: "index".to_string(),
424 args: vec![
425 Expr::Variable(cell_var(set)),
426 Expr::Variable(LANE.to_string()),
427 ],
428 ..Default::default()
429 })
430}
431
432fn mentions(e: &Expr, pred: &mut dyn FnMut(&str) -> bool) -> bool {
433 match e {
434 Expr::Variable(v) => pred(v),
435 Expr::Operator(n) => n.any_child(&mut |c| mentions(c, pred)),
436 _ => false,
437 }
438}
439
440impl Rewriter<'_> {
441 fn refuse<T>(&self, what: impl std::fmt::Display) -> RResult<T> {
442 Err(refused(format!("lane: rule `{}` {what}", self.rule)))
443 }
444
445 fn expr(&mut self, e: &Expr, sc: &LScope) -> RResult<Expr> {
446 match e {
447 Expr::Number(_) | Expr::Integer(_) => Ok(e.clone()),
448 Expr::Variable(v) => {
449 if let Some(set) = sc.lane_set(v) {
450 return Ok(lane_read(set));
451 }
452 if sc.inner.contains(v) {
453 return Ok(e.clone());
454 }
455 if self.lane_vars.contains(v) && self.axes.get(v).is_some_and(|a| !a.is_empty()) {
456 return self.refuse(format_args!("reads the array `{v}` whole"));
457 }
458 Ok(e.clone())
459 }
460 Expr::Operator(node) => match node.op.as_str() {
461 "index" if !node.args.is_empty() => self.index(node, sc),
462 "makearray" => self.refuse("holds a makearray outside a subscripted read"),
463 "faq" | "aggregate" => {
464 if node.output_idx.as_ref().is_some_and(|o| !o.is_empty()) {
465 return self.refuse("holds an array-valued faq in an elementwise position");
466 }
467 let mut inner = sc.clone();
468 if let Some(r) = &node.ranges {
469 for k in r.keys() {
470 inner.lane_syms.retain(|(s, _)| s != k);
471 if !inner.inner.contains(k) {
472 inner.inner.push(k.clone());
473 }
474 }
475 }
476 let mut n = ExpressionNode::clone(node);
477 if let Some(b) = node.expr.as_deref() {
478 n.expr = Some(Box::new(self.expr(b, &inner)?));
479 }
480 if let Some(f) = node.filter.as_deref() {
481 n.filter = Some(Box::new(self.expr(f, &inner)?));
482 }
483 Ok(Expr::operator(n))
484 }
485 _ => {
486 let mut err = None;
487 let out = node.map_children(&mut |c| match self.expr(c, sc) {
488 Ok(x) => x,
489 Err(r) => {
490 err.get_or_insert(r);
491 c.clone()
492 }
493 });
494 match err {
495 Some(r) => Err(r),
496 None => Ok(Expr::operator(out)),
497 }
498 }
499 },
500 }
501 }
502
503 fn index(&mut self, node: &Arc<ExpressionNode>, sc: &LScope) -> RResult<Expr> {
504 let base = &node.args[0];
505 let subs = &node.args[1..];
506 match base {
507 Expr::Variable(v) if self.lane_vars.contains(v) => {
508 let axes = self.axes.get(v).cloned().unwrap_or_default();
509 if axes.len() != subs.len() {
510 return self.refuse(format_args!("reads `{v}` at the wrong rank"));
511 }
512 let mut new_args = vec![base.clone(), Expr::Variable(LANE.to_string())];
513 for (d, s) in subs.iter().enumerate() {
514 match axes[d]
515 .as_deref()
516 .filter(|set| self.sets.iter().any(|x| x == set))
517 {
518 Some(set) => {
519 let own = matches!(s, Expr::Variable(x) if sc.lane_set(x) == Some(set));
520 if !own {
521 return self.refuse(format_args!(
522 "reads `{v}` off its own lane's cell along `{set}`, which is \
523 not {} locality",
524 if self.cells { "pointwise" } else { "column" }
525 ));
526 }
527 }
528 None => new_args.push(self.expr(s, sc)?),
529 }
530 }
531 let mut n = ExpressionNode::clone(node);
532 n.args = new_args;
533 Ok(Expr::operator(n))
534 }
535 Expr::Variable(_) => {
536 let mut new_subs = Vec::with_capacity(subs.len());
537 for s in subs {
538 new_subs.push(self.expr(s, sc)?);
539 }
540 let mut n = ExpressionNode::clone(node);
541 n.args = std::iter::once(base.clone()).chain(new_subs).collect();
542 let read = Expr::operator(n);
543 let by_cell = read_mentions_lane(&read)
546 && !mentions(&read, &mut |x| sc.inner.iter().any(|s| s == x));
547 if !by_cell {
548 return Ok(read);
549 }
550 let k = match self.gathers.iter().position(|g| *g == read) {
551 Some(k) => k,
552 None => {
553 self.gathers.push(read.clone());
554 self.gathers.len() - 1
555 }
556 };
557 if self.pre {
558 return Ok(Expr::operator(ExpressionNode {
559 op: "index".to_string(),
560 args: vec![
561 Expr::Variable(input_var(k)),
562 Expr::Variable(LANE.to_string()),
563 ],
564 ..Default::default()
565 }));
566 }
567 Ok(read)
568 }
569 Expr::Operator(m) if m.op == "makearray" => self.makearray_read(node, m, sc),
570 _ => self.refuse("subscripts an array expression a lane cannot restrict"),
571 }
572 }
573
574 fn makearray_read(
578 &mut self,
579 node: &Arc<ExpressionNode>,
580 m: &Arc<ExpressionNode>,
581 sc: &LScope,
582 ) -> RResult<Expr> {
583 let subs = &node.args[1..];
584 let (Some(regions), Some(values)) = (m.regions.as_ref(), m.values.as_ref()) else {
585 return self.refuse("holds a makearray with no regions");
586 };
587 let mut lane_axes: Vec<Option<String>> = Vec::with_capacity(subs.len());
589 let mut inner_subs = Vec::new();
590 for s in subs {
591 match s {
592 Expr::Variable(x) if sc.lane_set(x).is_some() => {
593 lane_axes.push(sc.lane_set(x).map(str::to_string));
594 }
595 Expr::Variable(x) if sc.inner.contains(x) => {
596 lane_axes.push(None);
597 inner_subs.push(s.clone());
598 }
599 _ => {
600 return self.refuse("subscripts a makearray at a shifted or computed position");
601 }
602 }
603 }
604 let mut new_regions = Vec::with_capacity(regions.len());
605 let mut new_values = Vec::with_capacity(values.len());
606 for (region, value) in regions.iter().zip(values) {
607 if region.len() != subs.len() {
608 return self.refuse("reads a makearray at the wrong rank");
609 }
610 let mut r = vec![[RegionBound::Int(1), RegionBound::Int(self.capacity)]];
611 let inner_sc = sc.clone();
612 for (d, b) in region.iter().enumerate() {
613 let Some([lo, hi]) = crate::types::region_bounds(b) else {
614 return self.refuse("holds a makearray with a symbolic region bound");
615 };
616 match &lane_axes[d] {
617 Some(set) => {
618 let n = self.sizes.get(set).copied().unwrap_or(-1);
619 if lo != 1 || hi != n {
620 return self.refuse(format_args!(
621 "holds a makearray region {lo}..{hi} along the lane set `{set}`, \
622 which depends on the lane's cell"
623 ));
624 }
625 }
626 None => r.push(b.clone()),
627 }
628 }
629 if let Expr::Operator(v) = value
630 && (v.op == "faq" || v.op == "aggregate")
631 && v.output_idx.as_ref().is_some_and(|o| !o.is_empty())
632 {
633 return self.refuse("holds a faq region value");
634 }
635 new_values.push(self.expr(value, &inner_sc)?);
636 new_regions.push(r);
637 }
638 let mut nm = ExpressionNode::clone(m);
639 nm.regions = Some(new_regions);
640 nm.values = Some(new_values);
641 let mut n = ExpressionNode::clone(node);
642 n.args = std::iter::once(Expr::operator(nm))
643 .chain(std::iter::once(Expr::Variable(LANE.to_string())))
644 .chain(inner_subs)
645 .collect();
646 Ok(Expr::operator(n))
647 }
648}
649
650fn read_mentions_lane(e: &Expr) -> bool {
651 mentions(e, &mut |x| x.starts_with("__cell_"))
652}
653
654struct Rewritten {
655 rhs: Vec<RhsRule>,
656 observed: Vec<AlgebraicRule>,
657 gathers: Vec<Expr>,
658}
659
660#[allow(clippy::too_many_arguments)]
661fn rewrite(
662 compiled: &ProgramModel,
663 axes: &HashMap<String, Vec<Option<String>>>,
664 lane_vars: &HashSet<String>,
665 dependent: &HashSet<String>,
666 sets: &[String],
667 sizes: &HashMap<String, i64>,
668 cells: bool,
669 capacity: i64,
670 pre: bool,
671) -> RResult<Rewritten> {
672 let mut rw = Rewriter {
673 axes,
674 lane_vars,
675 sets,
676 sizes,
677 cells,
678 capacity,
679 rule: String::new(),
680 gathers: Vec::new(),
681 pre,
682 };
683 let lane_box = |rw: &Rewriter,
685 var: &str,
686 syms: &[String],
687 ranges: &[(i64, i64)]|
688 -> RResult<(LScope, Vec<String>, Vec<(i64, i64)>)> {
689 let var_axes = axes.get(var).cloned().unwrap_or_default();
690 if var_axes.len() != syms.len() {
691 return rw.refuse(format_args!(
692 "writes `{var}` through a reshaped left-hand side"
693 ));
694 }
695 let mut sc = LScope::default();
696 let mut out_syms = vec![LANE.to_string()];
697 let mut out_ranges = vec![(1, capacity)];
698 for (d, s) in syms.iter().enumerate() {
699 match var_axes[d]
700 .as_deref()
701 .filter(|set| sets.iter().any(|x| x == set))
702 {
703 Some(set) => {
704 let n = sizes.get(set).copied().unwrap_or(-1);
705 if ranges[d] != (1, n) {
706 return rw.refuse(format_args!(
707 "writes `{var}` over {}..{} of the lane set `{set}`, not the whole of it",
708 ranges[d].0, ranges[d].1
709 ));
710 }
711 sc.lane_syms.push((s.clone(), set.to_string()));
712 }
713 None => {
714 if cells {
715 return rw.refuse(format_args!(
716 "writes `{var}` along `{}`, which is not one of the lane's sets",
717 var_axes[d].as_deref().unwrap_or("an unnamed axis")
718 ));
719 }
720 sc.inner.push(s.clone());
721 out_syms.push(s.clone());
722 out_ranges.push(ranges[d]);
723 }
724 }
725 }
726 Ok((sc, out_syms, out_ranges))
727 };
728 let mut rhs = Vec::new();
729 for rule in &compiled.rhs_rules {
730 let RhsRule::ArrayLoop {
731 var_name,
732 output_idx_names,
733 output_ranges,
734 lhs_idx_exprs,
735 body,
736 contract_names,
737 contract_dims,
738 reduce,
739 filter,
740 } = rule
741 else {
742 return Err(refused(
743 "lane: a scalar or per-cell rule has no lane form".to_string(),
744 ));
745 };
746 rw.rule = format!("D({var_name})");
747 let identity = lhs_idx_exprs.len() == output_idx_names.len()
748 && lhs_idx_exprs
749 .iter()
750 .zip(output_idx_names)
751 .all(|(e, s)| matches!(e, Expr::Variable(x) if x == s));
752 if !identity {
753 return rw.refuse("writes through a shifted or computed left-hand side");
754 }
755 let (mut sc, syms, ranges) = lane_box(&rw, var_name, output_idx_names, output_ranges)?;
756 sc.inner.extend(contract_names.iter().cloned());
757 let body = rw.expr(body, &sc)?;
758 let filter = match filter {
759 Some(f) => Some(Box::new(rw.expr(f, &sc)?)),
760 None => None,
761 };
762 rhs.push(RhsRule::ArrayLoop {
763 var_name: var_name.clone(),
764 lhs_idx_exprs: syms.iter().map(|s| Expr::Variable(s.clone())).collect(),
765 output_idx_names: syms,
766 output_ranges: ranges,
767 body: Box::new(body),
768 contract_names: contract_names.clone(),
769 contract_dims: contract_dims.clone(),
770 reduce: *reduce,
771 filter,
772 });
773 }
774 let mut observed = Vec::new();
775 for rule in &compiled.observed_rules {
776 let var = match rule {
777 AlgebraicRule::Scalar { var, .. }
778 | AlgebraicRule::ArrayLoop { var, .. }
779 | AlgebraicRule::Recurrence { var, .. } => var,
780 };
781 if !dependent.contains(var) {
782 observed.push(rule.clone());
783 continue;
784 }
785 rw.rule = var.clone();
786 match rule {
787 AlgebraicRule::ArrayLoop {
788 output_idx_names,
789 output_ranges,
790 body,
791 ..
792 } if lane_vars.contains(var) => {
793 let (sc, syms, ranges) = lane_box(&rw, var, output_idx_names, output_ranges)?;
794 let body = rw.expr(body, &sc)?;
795 observed.push(AlgebraicRule::ArrayLoop {
796 var: var.clone(),
797 output_idx_names: syms,
798 output_ranges: ranges,
799 body: std::sync::Arc::new(body),
800 });
801 }
802 AlgebraicRule::Scalar { body, .. } if axes.get(var).is_none_or(Vec::is_empty) => {
803 let body = rw.expr(body, &LScope::default())?;
804 if mentions(&body, &mut |x| x.starts_with("__cell_") || x == LANE) {
805 return rw.refuse("is a scalar observed that reads the lane state");
806 }
807 observed.push(rule.clone());
808 }
809 _ => return rw.refuse("is a state-dependent observed with no lane form"),
810 }
811 }
812 Ok(Rewritten {
813 rhs,
814 observed,
815 gathers: rw.gathers,
816 })
817}
818
819pub(super) fn build(
820 prog: &RhsProgram,
821 kind: &LaneKind,
822 capacity: usize,
823) -> Result<LaneProgram, SimulateError> {
824 let compiled = &*prog.inner.model;
825 let precision = plain_precision(compiled, "lane")?;
826 if capacity == 0 {
827 return Err(build_error(
828 "`lanes`: capacity must be at least 1".to_string(),
829 ));
830 }
831 let sets: Vec<String> = kind.sets().to_vec();
832 let sizes = index_set_sizes(prog.inner.flat());
833 let box_invalid =
834 |m: String| super::range_error(crate::diagnostic::codes::RANGE_BOX_INVALID, m);
835 for s in &sets {
836 if !sizes.contains_key(s) {
837 return Err(box_invalid(format!(
838 "`lanes`: `{s}` is not an interval index set of this program"
839 )));
840 }
841 }
842 let cells_mode = matches!(kind, LaneKind::Cells { .. });
843 let infos = var_infos(prog, &Decomposition::default());
846 let axes: HashMap<String, Vec<Option<String>>> = infos
847 .iter()
848 .map(|(k, v)| {
849 (
850 k.clone(),
851 v.axes
852 .iter()
853 .map(|a| match a {
854 AxisSpec::Whole { set } => set.clone(),
855 AxisSpec::Cut { set, .. } => Some(set.clone()),
856 })
857 .collect(),
858 )
859 })
860 .collect();
861 for name in prog.inner.writes.iter().chain(&prog.inner.aux) {
864 let var_sets: Vec<String> = axes[name].iter().flatten().cloned().collect();
865 if let Some(s) = sets.iter().find(|s| !var_sets.contains(s)) {
866 return Err(box_invalid(format!(
867 "`lanes`: the state `{name}` does not carry the lane set `{s}`"
868 )));
869 }
870 if cells_mode
871 && prog.inner.writes.contains(name)
872 && let Some(s) = var_sets.iter().find(|s| !sets.contains(s))
873 {
874 return Err(box_invalid(format!(
875 "`lanes`: `cells` lanes name every index set of the written variables, and \
876 `{name}` also has `{s}`"
877 )));
878 }
879 }
880 let dependent = super::tile::state_dependent(compiled);
881 let mut lane_vars: HashSet<String> = compiled.var_shapes.keys().cloned().collect();
882 for rule in &compiled.observed_rules {
883 if let AlgebraicRule::ArrayLoop { var, .. } = rule
884 && dependent.contains(var)
885 {
886 lane_vars.insert(var.clone());
887 }
888 }
889 let cap = capacity as i64;
890 let main = rewrite(
891 compiled, &axes, &lane_vars, &dependent, &sets, &sizes, cells_mode, cap, false,
892 )?;
893 let pre = rewrite(
894 compiled, &axes, &lane_vars, &dependent, &sets, &sizes, cells_mode, cap, true,
895 )?;
896
897 let mut entries = Vec::new();
899 for (input, names) in [
900 (TileInput::U, &prog.inner.writes),
901 (TileInput::Aux, &prog.inner.aux),
902 ] {
903 for name in names {
904 let vs = &compiled.var_shapes[name];
905 let var_axes = &axes[name];
906 if cells_mode
907 && var_axes
908 .iter()
909 .any(|a| a.as_deref().is_none_or(|s| !sets.iter().any(|x| x == s)))
910 && !vs.shape.is_empty()
911 {
912 return Err(refused(format!(
913 "lane: `{name}` has an axis outside the lane's sets {sets:?}"
914 )));
915 }
916 let mut storage = vec![(1, cap)];
917 let mut index_sets = vec!["lane".to_string()];
918 for (d, a) in var_axes.iter().enumerate() {
919 if a.as_deref().is_some_and(|s| sets.iter().any(|x| x == s)) {
920 continue;
921 }
922 let o = vs.origin[d];
923 storage.push((o, o + vs.shape[d] as i64 - 1));
924 index_sets.push(a.clone().unwrap_or_default());
925 }
926 if vs.shape.is_empty() {
927 return Err(refused(format!(
928 "lane: `{name}` is a 0-d state, which no lane holds"
929 )));
930 }
931 entries.push((name.clone(), input, index_sets, storage.clone(), storage));
932 }
933 }
934 let layout = Blocks::build(entries);
935
936 let mut var_shapes: IndexMap<String, VarShape> = IndexMap::new();
938 for v in &layout.vars {
939 let flat_offset = match v.input {
940 TileInput::U => v.base,
941 TileInput::Aux => layout.u_len + v.base,
942 };
943 var_shapes.insert(
944 v.name.clone(),
945 VarShape {
946 shape: v
947 .storage
948 .iter()
949 .map(|(lo, hi)| (hi - lo + 1) as usize)
950 .collect(),
951 origin: v.storage.iter().map(|r| r.0).collect(),
952 flat_offset,
953 },
954 );
955 }
956 let add = |shapes: &mut IndexMap<String, VarShape>, name: String, at: &mut usize| {
957 shapes.insert(
958 name,
959 VarShape {
960 shape: vec![capacity],
961 origin: vec![1],
962 flat_offset: *at,
963 },
964 );
965 *at += capacity;
966 };
967 let mut at = layout.u_len + layout.aux_len;
968 for s in &sets {
969 add(&mut var_shapes, cell_var(s), &mut at);
970 }
971 let mut pre_shapes = var_shapes.clone();
972 let mut pre_at = at;
973 for k in 0..pre.gathers.len() {
974 add(&mut pre_shapes, input_var(k), &mut pre_at);
975 }
976 let held: HashSet<String> = prog
977 .inner
978 .aux
979 .iter()
980 .cloned()
981 .chain(sets.iter().map(|s| cell_var(s)))
982 .collect();
983 let empty = TileLowering {
984 states: Vec::new(),
985 observed: FxHashMap::default(),
986 };
987 let build_tape = |c: &ArrayCompiled| -> Result<TapeProgram, SimulateError> {
988 let _env = compiled.precision.enter();
989 c.build_tile_tape(&empty)
990 .map_err(|d| declined_error(&d, "lane"))
991 };
992 let main_tape = build_tape(&shell(
993 compiled,
994 var_shapes.clone(),
995 main.rhs,
996 main.observed,
997 held.clone(),
998 ))?;
999 let mut pre_held = held.clone();
1000 pre_held.extend((0..pre.gathers.len()).map(input_var));
1001 let pre_tape = build_tape(&shell(
1002 compiled,
1003 pre_shapes,
1004 pre.rhs,
1005 pre.observed,
1006 pre_held,
1007 ))?;
1008
1009 let mut gshapes: IndexMap<String, VarShape> = IndexMap::new();
1011 let mut gat = 0usize;
1012 for k in 0..main.gathers.len() {
1013 add(&mut gshapes, input_var(k), &mut gat);
1014 }
1015 for s in &sets {
1016 add(&mut gshapes, cell_var(s), &mut gat);
1017 }
1018 let n_inputs = main.gathers.len();
1021 let grules: Vec<RhsRule> = main
1022 .gathers
1023 .iter()
1024 .enumerate()
1025 .map(|(k, g)| RhsRule::ArrayLoop {
1026 var_name: input_var(k),
1027 output_idx_names: vec![LANE.to_string()],
1028 output_ranges: vec![(1, cap)],
1029 lhs_idx_exprs: vec![Expr::Variable(LANE.to_string())],
1030 body: Box::new(g.clone()),
1031 contract_names: Vec::new(),
1032 contract_dims: Vec::new(),
1033 reduce: crate::faq::ReduceKind::Sum,
1034 filter: None,
1035 })
1036 .collect();
1037 let gobserved: Vec<AlgebraicRule> = compiled
1038 .observed_rules
1039 .iter()
1040 .filter(|r| {
1041 let var = match r {
1042 AlgebraicRule::Scalar { var, .. }
1043 | AlgebraicRule::ArrayLoop { var, .. }
1044 | AlgebraicRule::Recurrence { var, .. } => var,
1045 };
1046 !dependent.contains(var)
1047 })
1048 .cloned()
1049 .collect();
1050 let gheld: HashSet<String> = sets.iter().map(|s| cell_var(s)).collect();
1051 let gather_tape = if n_inputs == 0 {
1052 build_tape(&shell(compiled, gshapes, Vec::new(), Vec::new(), gheld))?
1053 } else {
1054 build_tape(&shell(compiled, gshapes, grules, gobserved, gheld))?
1055 };
1056 let slot_of = [
1057 slot_map(&main_tape, &prog.inner.slots)?,
1058 slot_map(&pre_tape, &prog.inner.slots)?,
1059 slot_map(&gather_tape, &prog.inner.slots)?,
1060 ];
1061 Ok(LaneProgram {
1062 inner: Arc::new(LaneInner {
1063 id: next_program_id(),
1064 main: Arc::new(main_tape),
1065 pre: Arc::new(pre_tape),
1066 gather: Arc::new(gather_tape),
1067 slot_of,
1068 layout: layout.lane_public(&sets, capacity),
1069 blocks: layout,
1070 slots: Arc::clone(&prog.inner.slots),
1071 capacity,
1072 extents: sets.iter().map(|s| sizes[s]).collect(),
1073 sets,
1074 n_inputs,
1075 n_params: compiled.param_names.len(),
1076 declared: Arc::clone(&prog.inner.declared),
1077 precision,
1078 }),
1079 })
1080}