1use super::forcing::{ForcingSet, ForcingSlot, RhsError};
14use super::walk::{AxisSpec, Read, Refusal, Scope, VarInfo, Walker};
15use super::{
16 Blocks, Decomposition, ProgramModel, RhsProgram, RhsWorkspace, TileBox, TileInput, TileLayout,
17 WsExec, check_forcing, check_lengths, check_owner, index_set_sizes, next_program_id,
18 range_error, slot_map, tape_fault, var_infos,
19};
20use crate::diagnostic::codes;
21use crate::simulate::SimulateError;
22use crate::simulate_array::tape::{
23 CallInputs, DeclinedTape, ForcingView, TapeCtx, TapeProgram, TileAxisDom, TileLowering,
24 TileObserved, TilePiece, run_tape_inputs,
25};
26use crate::simulate_array::vectorized::lhs_constant_shifts;
27use crate::simulate_array::{AlgebraicRule, ArrayCompiled, RhsRule, VarShape};
28use crate::types::Expr;
29use indexmap::IndexMap;
30use rustc_hash::FxHashMap;
31use std::collections::{HashMap, HashSet};
32use std::sync::Arc;
33
34#[derive(Clone)]
42pub struct TileProgram {
43 inner: Arc<TileInner>,
44}
45
46struct TileInner {
47 id: u64,
48 prog: Arc<TapeProgram>,
49 blocks: Blocks,
50 layout: TileLayout,
51 slots: Arc<[ForcingSlot]>,
52 slot_of: Vec<u32>,
53 n_params: usize,
54 declared: Arc<HashSet<String>>,
55 precision: crate::precision::Precision,
56}
57
58impl std::fmt::Debug for TileProgram {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.debug_struct("TileProgram")
61 .field("layout", &self.inner.layout)
62 .finish()
63 }
64}
65
66impl TileProgram {
67 pub fn layout(&self) -> &TileLayout {
69 &self.inner.layout
70 }
71
72 pub fn forcing_slots(&self) -> &[ForcingSlot] {
74 &self.inner.slots
75 }
76
77 pub fn new_workspace(&self) -> RhsWorkspace {
79 let n = self.inner.blocks.u_len + self.inner.blocks.aux_len;
80 let mut ctx = TapeCtx::new(Arc::clone(&self.inner.prog));
81 ctx.set_serial();
82 RhsWorkspace {
83 owner: self.inner.id,
84 state: vec![0.0; n],
85 dy: Vec::new(),
86 exec: WsExec::Tape(Box::new(ctx)),
87 _not_sync: std::marker::PhantomData,
88 }
89 }
90
91 #[allow(clippy::too_many_arguments)]
96 pub fn eval_into(
97 &self,
98 u: &[f64],
99 aux: &[f64],
100 p: &[f64],
101 t: f64,
102 forcing: &ForcingSet,
103 du: &mut [f64],
104 ws: &mut RhsWorkspace,
105 ) -> Result<(), RhsError> {
106 let inner = &*self.inner;
107 check_lengths(&inner.blocks, u, aux, du, p, inner.n_params)?;
108 check_owner(ws, inner.id)?;
109 check_forcing(forcing, &inner.slots)?;
110 let nu = inner.blocks.u_len;
111 ws.state[..nu].copy_from_slice(u);
112 ws.state[nu..].copy_from_slice(aux);
113 let WsExec::Tape(ctx) = &mut ws.exec else {
114 unreachable!("a tile workspace runs a tape");
115 };
116 let _precision = crate::precision::enter(inner.precision);
117 let scope = crate::simulate_array::FaultScope::enter();
118 let inputs = CallInputs {
119 state: &ws.state,
120 params: p,
121 forcing: ForcingView::Slots {
122 bufs: forcing.bufs(),
123 slot_of: &inner.slot_of,
124 },
125 forcing_epoch: Some(forcing.epoch()),
126 t,
127 declared: &inner.declared,
128 };
129 run_tape_inputs(ctx, &inputs, du, false);
130 tape_fault(scope, ctx)
131 }
132}
133
134fn box_invalid(message: String) -> SimulateError {
135 range_error(codes::RANGE_BOX_INVALID, message)
136}
137
138pub(super) fn refusal_error(r: Refusal) -> SimulateError {
139 if r.code == codes::COMPILER_REFUSED_RULE {
140 refused(r.message)
141 } else {
142 range_error(r.code, r.message)
143 }
144}
145
146pub(super) fn refused(reason: String) -> SimulateError {
148 SimulateError::Compile(crate::compile_error::CompileError::CompilerRefusedRule {
149 compiler: "native",
150 kind: "state derivative",
151 rule: reason
152 .split('`')
153 .nth(1)
154 .unwrap_or("(range restriction)")
155 .to_string(),
156 tier: "continuous",
157 reason,
158 })
159}
160
161pub(super) fn declined_error(d: &DeclinedTape, what: &str) -> SimulateError {
164 let declined: Vec<(usize, &String)> = d
165 .reasons
166 .iter()
167 .enumerate()
168 .filter_map(|(k, r)| r.as_ref().map(|r| (k, r)))
169 .collect();
170 for code in [codes::RANGE_HALO_INSUFFICIENT, codes::RANGE_GLOBAL_LOCALITY] {
171 if let Some((k, r)) = declined.iter().find(|(_, r)| r.starts_with(code)) {
172 let msg = r.trim_start_matches(code).trim_start_matches(':').trim();
173 return range_error(code, format!("{} (rule `{}`)", msg, d.rules[*k].name));
174 }
175 }
176 let (k, reason) = declined[0];
177 let rule = &d.rules[k];
178 let reason = if reason.contains(what) {
179 reason.clone()
180 } else {
181 format!("{what}: {reason}")
182 };
183 SimulateError::Compile(crate::compile_error::CompileError::CompilerRefusedRule {
184 compiler: "native",
185 kind: rule.kind_name(),
186 rule: rule.name.clone(),
187 tier: rule.cadence_name(),
188 reason,
189 })
190}
191
192struct Boxes {
194 owned: HashMap<String, (i64, i64)>,
195 storage: HashMap<String, (i64, i64)>,
196}
197
198fn validate(prog: &RhsProgram, dec: &Decomposition, bx: &TileBox) -> Result<Boxes, SimulateError> {
199 let sizes = index_set_sizes(prog.inner.flat());
200 for a in &dec.axes {
201 if !sizes.contains_key(&a.index_set) {
202 return Err(box_invalid(format!(
203 "the decomposition cuts `{}`, which is not an interval index set of this program",
204 a.index_set
205 )));
206 }
207 if let Some(p) = a.period
208 && p != sizes[&a.index_set]
209 {
210 return Err(box_invalid(format!(
211 "the decomposition gives `{}` period {p}, and it has {} cells",
212 a.index_set, sizes[&a.index_set]
213 )));
214 }
215 }
216 for (face, cell) in &dec.faces {
217 if !dec.axes.iter().any(|a| &a.index_set == cell) {
218 return Err(box_invalid(format!(
219 "face set `{face}` is declared against `{cell}`, which the decomposition does \
220 not cut"
221 )));
222 }
223 if !sizes.contains_key(face) {
224 return Err(box_invalid(format!(
225 "face set `{face}` is not an interval index set of this program"
226 )));
227 }
228 }
229 let collect = |list: &[(String, std::ops::RangeInclusive<i64>)], what: &str| {
230 let mut out: HashMap<String, (i64, i64)> = HashMap::new();
231 for (set, r) in list {
232 if !dec.axes.iter().any(|a| &a.index_set == set) {
233 return Err(box_invalid(format!(
234 "the {what} box names `{set}`, which the decomposition does not cut"
235 )));
236 }
237 if out.insert(set.clone(), (*r.start(), *r.end())).is_some() {
238 return Err(box_invalid(format!("the {what} box names `{set}` twice")));
239 }
240 }
241 for a in &dec.axes {
242 if !out.contains_key(&a.index_set) {
243 return Err(box_invalid(format!(
244 "the {what} box has no range for the decomposed set `{}`",
245 a.index_set
246 )));
247 }
248 }
249 Ok(out)
250 };
251 let owned = collect(&bx.owned, "owned")?;
252 let storage = collect(&bx.storage, "storage")?;
253 for a in &dec.axes {
254 let set = &a.index_set;
255 let n = sizes[set];
256 let (olo, ohi) = owned[set];
257 let (slo, shi) = storage[set];
258 if olo > ohi || olo < 1 || ohi > n {
259 return Err(box_invalid(format!(
260 "the owned range {olo}..{ohi} of `{set}` is not a non-empty range inside 1..{n}"
261 )));
262 }
263 if slo > olo || shi < ohi {
264 return Err(box_invalid(format!(
265 "the owned range {olo}..{ohi} of `{set}` is not inside its storage range \
266 {slo}..{shi}"
267 )));
268 }
269 if a.period.is_some() && shi - slo + 1 > 2 * n {
270 return Err(box_invalid(format!(
271 "the storage range {slo}..{shi} of the periodic set `{set}` spans more than two \
272 periods"
273 )));
274 }
275 }
276 Ok(Boxes { owned, storage })
277}
278
279fn axis_ranges(spec: &AxisSpec, extent: (i64, i64), boxes: &Boxes) -> ((i64, i64), (i64, i64)) {
281 match spec {
282 AxisSpec::Whole { .. } => (extent, extent),
283 AxisSpec::Cut { cell, face, hi, .. } => {
284 let (slo, shi) = boxes.storage[cell];
285 let (olo, ohi) = boxes.owned[cell];
286 if *face {
287 let n_cells = hi - 1;
288 (
289 (slo, shi + 1),
290 (olo, if ohi == n_cells { ohi + 1 } else { ohi }),
291 )
292 } else {
293 ((slo, shi), (olo, ohi))
294 }
295 }
296 }
297}
298
299pub(super) fn state_dependent(compiled: &ProgramModel) -> HashSet<String> {
302 let states: HashSet<&str> = compiled.var_shapes.keys().map(String::as_str).collect();
303 let mut dep: HashSet<String> = HashSet::new();
304 for rule in &compiled.observed_rules {
305 let (var, body) = observed_parts(rule);
306 let free = crate::expression::free_variables(body);
307 if free
308 .iter()
309 .any(|n| states.contains(n.as_str()) || dep.contains(n))
310 {
311 dep.insert(var.clone());
312 }
313 }
314 dep
315}
316
317fn observed_parts(rule: &AlgebraicRule) -> (&String, &Expr) {
318 match rule {
319 AlgebraicRule::Scalar { var, body, .. }
320 | AlgebraicRule::ArrayLoop { var, body, .. }
321 | AlgebraicRule::Recurrence { var, body, .. } => (var, body),
322 }
323}
324
325pub(super) fn as_loop(rule: &AlgebraicRule) -> Option<AlgebraicRule> {
330 let AlgebraicRule::Scalar {
331 var,
332 body,
333 declared_shape: Some(shape),
334 } = rule
335 else {
336 return None;
337 };
338 let Expr::Operator(node) = &**body else {
339 return None;
340 };
341 if !matches!(node.op.as_str(), "faq" | "aggregate")
342 || node.filter.is_some()
343 || node.join.is_some()
344 {
345 return None;
346 }
347 let out = node.output_idx.as_ref().filter(|o| !o.is_empty())?;
348 let ranges = node.ranges.as_ref()?;
349 if out.len() != shape.len() || ranges.keys().any(|k| !out.contains(k)) {
350 return None;
351 }
352 let mut bounds = Vec::with_capacity(out.len());
353 for (d, name) in out.iter().enumerate() {
354 let [lo, hi] = ranges.get(name)?.bounds()?;
355 if lo != 1 || hi != shape[d] as i64 {
356 return None;
357 }
358 bounds.push((lo, hi));
359 }
360 Some(AlgebraicRule::ArrayLoop {
361 var: var.clone(),
362 output_idx_names: out.clone(),
363 output_ranges: bounds,
364 body: std::sync::Arc::new(node.expr.as_deref()?.clone()),
365 })
366}
367
368type AxisReads = HashMap<String, Vec<Vec<(i64, i64)>>>;
370
371fn collect_reads(reads: &[Read], into: &mut AxisReads, vars: &HashMap<String, VarInfo>) {
373 for r in reads {
374 let Some(info) = vars.get(&r.var) else {
375 continue;
376 };
377 let entry = into
378 .entry(r.var.clone())
379 .or_insert_with(|| vec![Vec::new(); info.axes.len()]);
380 if let Some(e) = entry.get_mut(r.axis) {
381 e.push((r.lo, r.hi));
382 }
383 }
384}
385
386#[derive(Debug, PartialEq)]
390struct AxisNeed {
391 lo: i64,
392 hi: i64,
393 pieces: Vec<(i64, i64, i64)>,
394}
395
396fn axis_need(reads: &[(i64, i64)], hi: i64, period: Option<i64>) -> AxisNeed {
402 let mut iv: Vec<(i64, i64)> = reads
403 .iter()
404 .map(|&(a, b)| (a.max(1), b.min(hi)))
405 .filter(|(a, b)| a <= b)
406 .collect();
407 if iv.is_empty() {
408 return AxisNeed {
409 lo: 1,
410 hi,
411 pieces: vec![(1, hi, 0)],
412 };
413 }
414 iv.sort_unstable();
415 let mut merged: Vec<(i64, i64)> = Vec::with_capacity(iv.len());
416 for (a, b) in iv {
417 match merged.last_mut() {
418 Some(m) if a <= m.1 + 1 => m.1 = m.1.max(b),
419 _ => merged.push((a, b)),
420 }
421 }
422 let first = merged[0].0;
423 let last = merged[merged.len() - 1].1;
424 let hull = AxisNeed {
425 lo: first,
426 hi: last,
427 pieces: vec![(first, last, 0)],
428 };
429 if period != Some(hi) || merged.len() < 2 {
430 return hull;
431 }
432 let seam_gap = (hi - last) + (first - 1);
434 let (k, gap) = merged
435 .windows(2)
436 .enumerate()
437 .map(|(k, w)| (k, w[1].0 - w[0].1 - 1))
438 .max_by_key(|&(k, g)| (g, std::cmp::Reverse(k)))
439 .expect("two runs have a gap");
440 if gap <= seam_gap {
441 return hull;
442 }
443 let a = merged[k + 1].0;
445 let b = merged[k].1;
446 AxisNeed {
447 lo: a,
448 hi: b + hi,
449 pieces: vec![(a, hi, 0), (1, b, hi - a + 1)],
450 }
451}
452
453pub(super) struct ClippedRules {
455 pub(super) rhs: Vec<RhsRule>,
456 pub(super) observed: Vec<AlgebraicRule>,
457 pub(super) need: FxHashMap<String, TileObserved>,
458}
459
460pub(super) fn clip_rules(
463 compiled: &ProgramModel,
464 vars: &HashMap<String, VarInfo>,
465 owned: &HashMap<String, Vec<(i64, i64)>>,
466) -> Result<ClippedRules, SimulateError> {
467 let mut reads: AxisReads = HashMap::new();
468 let mut needed: HashSet<String> = HashSet::new();
469 let mut rhs = Vec::new();
470 for rule in &compiled.rhs_rules {
471 match rule {
472 RhsRule::Scalar { slot, .. } => {
473 let name = super::super::layout::slot_name(&compiled.var_shapes, *slot)
474 .unwrap_or_else(|| format!("slot {slot}"));
475 return Err(range_error(
476 codes::RANGE_GLOBAL_LOCALITY,
477 format!("rule `D({name})` writes a 0-d state, which no tile owns"),
478 ));
479 }
480 RhsRule::IndexedScalar { slot, .. } => {
481 let name = super::super::layout::slot_name(&compiled.var_shapes, *slot)
482 .unwrap_or_else(|| format!("slot {slot}"));
483 return Err(refused(format!(
484 "tile restriction: rule `D({name})` is a per-cell scalar rule, which a tile \
485 does not restrict"
486 )));
487 }
488 RhsRule::ArrayLoop {
489 var_name,
490 output_idx_names,
491 output_ranges,
492 lhs_idx_exprs,
493 body,
494 contract_names,
495 contract_dims,
496 reduce,
497 filter,
498 } => {
499 let rule_name = format!("D({var_name})");
500 let info = &vars[var_name];
501 let Some(shifts) = lhs_constant_shifts(lhs_idx_exprs, output_idx_names) else {
502 return Err(refused(format!(
503 "tile restriction: rule `{rule_name}` writes through a computed \
504 left-hand side"
505 )));
506 };
507 let own = &owned[var_name];
508 let mut ranges = Vec::with_capacity(output_ranges.len());
509 let mut sets = Vec::with_capacity(output_ranges.len());
510 for (d, &(lo, hi)) in output_ranges.iter().enumerate() {
511 let (olo, ohi) = own[d];
512 ranges.push((lo.max(olo - shifts[d]), hi.min(ohi - shifts[d])));
513 sets.push(info.axes[d].cut_cell().map(str::to_string));
514 }
515 if ranges.iter().any(|(lo, hi)| lo > hi) {
516 continue;
517 }
518 let sc = Scope {
519 syms: output_idx_names.clone(),
520 lo: ranges.iter().map(|r| r.0).collect(),
521 hi: ranges.iter().map(|r| r.1).collect(),
522 sets,
523 contracted: contract_names.clone(),
524 };
525 let mut w = Walker::new(vars, rule_name);
526 let body = w.expr(body, &sc).map_err(refusal_error)?;
527 let filter = match filter {
528 Some(f) => Some(Box::new(w.expr(f, &sc).map_err(refusal_error)?)),
529 None => None,
530 };
531 collect_reads(&w.reads, &mut reads, vars);
532 needed.extend(crate::expression::free_variables(&body));
533 if let Some(f) = &filter {
534 needed.extend(crate::expression::free_variables(f));
535 }
536 rhs.push(RhsRule::ArrayLoop {
537 var_name: var_name.clone(),
538 output_idx_names: output_idx_names.clone(),
539 output_ranges: ranges,
540 lhs_idx_exprs: lhs_idx_exprs.clone(),
541 body: Box::new(body),
542 contract_names: contract_names.clone(),
543 contract_dims: contract_dims.clone(),
544 reduce: *reduce,
545 filter,
546 });
547 }
548 }
549 }
550
551 let dependent = state_dependent(compiled);
552 let mut need = FxHashMap::default();
553 let mut kept: Vec<Option<AlgebraicRule>> = vec![None; compiled.observed_rules.len()];
554 for (k, rule) in compiled.observed_rules.iter().enumerate().rev() {
555 let as_array = as_loop(rule);
556 let rule = as_array.as_ref().unwrap_or(rule);
557 let (var, body) = observed_parts(rule);
558 if !needed.contains(var) {
559 continue;
560 }
561 let info = &vars[var];
562 match rule {
563 AlgebraicRule::ArrayLoop {
564 output_idx_names,
565 output_ranges,
566 ..
567 } if dependent.contains(var) && info.is_cut() => {
568 let read = reads.get(var).cloned().unwrap_or_default();
569 let axes: Vec<AxisNeed> = output_ranges
570 .iter()
571 .enumerate()
572 .map(|(d, &(_, hi))| match &info.axes[d] {
573 AxisSpec::Cut { period, face, .. } => {
574 let r = read.get(d).map(Vec::as_slice).unwrap_or(&[]);
575 axis_need(r, hi, if *face { None } else { *period })
576 }
577 AxisSpec::Whole { .. } => AxisNeed {
578 lo: 1,
579 hi,
580 pieces: vec![(1, hi, 0)],
581 },
582 })
583 .collect();
584 let sets: Vec<Option<String>> = info
585 .axes
586 .iter()
587 .map(|a| a.cut_cell().map(str::to_string))
588 .collect();
589 let mut pieces = Vec::new();
592 let mut combo = vec![0usize; axes.len()];
593 'combos: loop {
594 let mut ranges = Vec::with_capacity(axes.len());
595 let mut dest_lo = Vec::with_capacity(axes.len());
596 for (d, ax) in axes.iter().enumerate() {
597 let (glo, ghi, off) = ax.pieces[combo[d]];
598 let (lo, hi) = (output_ranges[d].0.max(glo), output_ranges[d].1.min(ghi));
599 ranges.push((lo, hi));
600 dest_lo.push((off + lo - glo).max(0) as usize);
601 }
602 if ranges.iter().all(|(lo, hi)| lo <= hi) {
603 let sc = Scope {
604 syms: output_idx_names.clone(),
605 lo: ranges.iter().map(|r| r.0).collect(),
606 hi: ranges.iter().map(|r| r.1).collect(),
607 sets: sets.clone(),
608 contracted: Vec::new(),
609 };
610 let mut w = Walker::new(vars, var.clone());
611 let b = w.expr(body, &sc).map_err(refusal_error)?;
612 collect_reads(&w.reads, &mut reads, vars);
613 needed.extend(crate::expression::free_variables(&b));
614 pieces.push(TilePiece {
615 ranges,
616 dest_lo,
617 body: std::rc::Rc::new(b),
618 });
619 }
620 for d in (0..axes.len()).rev() {
621 combo[d] += 1;
622 if combo[d] < axes[d].pieces.len() {
623 continue 'combos;
624 }
625 combo[d] = 0;
626 }
627 break;
628 }
629 need.insert(
630 var.clone(),
631 TileObserved {
632 lo: axes.iter().map(|a| a.lo).collect(),
633 hi: axes.iter().map(|a| a.hi).collect(),
634 period: axes
635 .iter()
636 .map(|a| (a.pieces.len() > 1).then(|| a.pieces[0].1))
637 .collect(),
638 pieces,
639 },
640 );
641 kept[k] = Some(rule.clone());
642 }
643 AlgebraicRule::Recurrence { axis, .. } if dependent.contains(var) && info.is_cut() => {
644 if let Some(AxisSpec::Cut { set, .. }) = info.axes.get(*axis) {
645 return Err(range_error(
646 codes::RANGE_GLOBAL_LOCALITY,
647 format!(
648 "observed `{var}` is a recurrence along the decomposed axis `{set}`"
649 ),
650 ));
651 }
652 return Err(refused(format!(
653 "tile restriction: observed `{var}` is a recurrence over a decomposed array"
654 )));
655 }
656 _ => {
657 if dependent.contains(var) && info.is_cut() {
658 return Err(refused(format!(
659 "tile restriction: observed `{var}` is a whole-array expression of the \
660 state"
661 )));
662 }
663 needed.extend(crate::expression::free_variables(body));
664 kept[k] = Some(rule.clone());
665 }
666 }
667 }
668 Ok(ClippedRules {
669 rhs,
670 observed: kept.into_iter().flatten().collect(),
671 need,
672 })
673}
674
675pub(super) fn shell(
678 compiled: &ProgramModel,
679 var_shapes: IndexMap<String, VarShape>,
680 rhs_rules: Vec<RhsRule>,
681 observed_rules: Vec<AlgebraicRule>,
682 held: HashSet<String>,
683) -> ArrayCompiled {
684 compiled.shell(var_shapes, rhs_rules, observed_rules, held)
685}
686
687pub(super) fn plain_precision(
690 compiled: &ProgramModel,
691 what: &str,
692) -> Result<crate::precision::Precision, SimulateError> {
693 let _env = compiled.precision.enter();
694 if crate::precision::has_variable_overrides() {
695 return Err(refused(format!(
696 "{what}: the document declares per-variable element types, which a range-restricted \
697 program does not carry"
698 )));
699 }
700 Ok(compiled.precision.document)
701}
702
703pub(super) fn build(
704 prog: &RhsProgram,
705 dec: &Decomposition,
706 bx: &TileBox,
707) -> Result<TileProgram, SimulateError> {
708 let boxes = validate(prog, dec, bx)?;
709 let compiled = &*prog.inner.model;
710 let precision = plain_precision(compiled, "tile restriction")?;
711 let vars = var_infos(prog, dec);
712 for w in &prog.inner.writes {
713 if !vars[w].is_cut() {
714 return Err(range_error(
715 codes::RANGE_GLOBAL_LOCALITY,
716 format!(
717 "the program writes `{w}`, which has no decomposed axis, so no tile owns its \
718 rows (rule `D({w})`)"
719 ),
720 ));
721 }
722 }
723 let mut owned: HashMap<String, Vec<(i64, i64)>> = HashMap::new();
724 let mut entries = Vec::new();
725 let set_names = super::state_set_names(prog.inner.flat(), compiled);
726 for (input, names) in [
727 (TileInput::U, &prog.inner.writes),
728 (TileInput::Aux, &prog.inner.aux),
729 ] {
730 for name in names {
731 let info = &vars[name];
732 let mut storage = Vec::new();
733 let mut own = Vec::new();
734 for (d, spec) in info.axes.iter().enumerate() {
735 let (s, o) = axis_ranges(spec, info.extent[d], &boxes);
736 storage.push(s);
737 own.push(o);
738 }
739 owned.insert(name.clone(), own.clone());
740 entries.push((name.clone(), input, set_names[name].clone(), storage, own));
741 }
742 }
743 let layout = Blocks::build(entries);
744 let clipped = clip_rules(compiled, &vars, &owned)?;
745
746 let mut var_shapes: IndexMap<String, VarShape> = IndexMap::new();
747 let mut doms = Vec::new();
748 for v in &layout.vars {
749 let flat_offset = match v.input {
750 TileInput::U => v.base,
751 TileInput::Aux => layout.u_len + v.base,
752 };
753 var_shapes.insert(
754 v.name.clone(),
755 VarShape {
756 shape: v
757 .storage
758 .iter()
759 .map(|(lo, hi)| (hi - lo + 1) as usize)
760 .collect(),
761 origin: v.storage.iter().map(|r| r.0).collect(),
762 flat_offset,
763 },
764 );
765 let info = &vars[&v.name];
766 doms.push(
767 info.axes
768 .iter()
769 .enumerate()
770 .map(|(d, a)| match a {
771 AxisSpec::Whole { set } => TileAxisDom {
772 lo: info.extent[d].0,
773 hi: info.extent[d].1,
774 period: None,
775 set: set.clone().unwrap_or_else(|| format!("axis {}", d + 1)),
776 },
777 AxisSpec::Cut {
778 set, hi, period, ..
779 } => TileAxisDom {
780 lo: 1,
781 hi: *hi,
782 period: *period,
783 set: set.clone(),
784 },
785 })
786 .collect(),
787 );
788 }
789 let lowering = TileLowering {
790 states: doms,
791 observed: clipped.need,
792 };
793 let held: HashSet<String> = prog.inner.aux.iter().cloned().collect();
794 let shell = shell(compiled, var_shapes, clipped.rhs, clipped.observed, held);
795 let tape = {
796 let _env = compiled.precision.enter();
797 shell
798 .build_tile_tape(&lowering)
799 .map_err(|d| declined_error(&d, "tile restriction"))?
800 };
801 let slot_of = slot_map(&tape, &prog.inner.slots)?;
802 Ok(TileProgram {
803 inner: Arc::new(TileInner {
804 id: next_program_id(),
805 prog: Arc::new(tape),
806 layout: layout.public(),
807 blocks: layout,
808 slots: Arc::clone(&prog.inner.slots),
809 slot_of,
810 n_params: compiled.param_names.len(),
811 declared: Arc::clone(&prog.inner.declared),
812 precision,
813 }),
814 })
815}
816
817#[cfg(test)]
818mod tests {
819 use super::{AxisNeed, axis_need};
820
821 #[test]
822 fn a_need_across_a_periodic_seam_is_two_pieces() {
823 assert_eq!(
825 axis_need(&[(1, 4), (64, 64), (0, 3)], 64, Some(64)),
826 AxisNeed {
827 lo: 64,
828 hi: 68,
829 pieces: vec![(64, 64, 0), (1, 4, 1)],
830 }
831 );
832 assert_eq!(
834 axis_need(&[(1, 4), (64, 64)], 64, None),
835 AxisNeed {
836 lo: 1,
837 hi: 64,
838 pieces: vec![(1, 64, 0)],
839 }
840 );
841 assert_eq!(
843 axis_need(&[(10, 12), (20, 22)], 64, Some(64)),
844 AxisNeed {
845 lo: 10,
846 hi: 22,
847 pieces: vec![(10, 22, 0)],
848 }
849 );
850 assert_eq!(axis_need(&[], 8, Some(8)).pieces, vec![(1, 8, 0)]);
852 }
853}