1mod answer;
4pub(crate) mod bound;
5mod drive;
6mod frontier;
7mod quiet;
8mod slots;
9mod stream;
10pub(crate) mod table;
11mod terms;
12mod through;
13pub mod until;
14mod volatile;
15
16use std::collections::{BTreeMap, BTreeSet};
17
18use sva_ast::Graph;
19use sva_formula::{NodeId, SpectralSum};
20use sva_samples::{
21 AliasScore, Buffer, Extent, FilterTrace, Frames, Label, PSYCHOACOUSTIC_V1, Profile,
22};
23
24use crate::bindings::Binding;
25use crate::cache::{Cache, CachePolicy, CacheStats, Recording};
26use crate::error::{Diagnostic, EngineError, Located};
27use crate::instantiate;
28use crate::query::Ask;
29use crate::refs;
30use crate::schedule::{self, Schedule};
31use crate::typing::{self, Typing};
32use table::Table;
33use table::support::Supports;
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37pub struct Range {
38 pub start: Option<i64>,
39 pub end: Option<i64>,
40}
41
42#[derive(Clone, Debug, PartialEq)]
43pub struct RenderConfig {
44 pub rate: u32,
45 pub range: Range,
46 pub until: Option<Until>,
48 pub profile: Profile,
49 pub asks: Vec<Ask>,
51 pub flop_budget: u128,
53 pub volatile: Vec<String>,
55 pub cache_policy: Option<CachePolicy>,
56}
57
58impl RenderConfig {
59 pub fn at(rate: u32) -> RenderConfig {
60 RenderConfig {
61 rate,
62 range: Range::default(),
63 until: None,
64 profile: PSYCHOACOUSTIC_V1,
65 asks: Vec::new(),
66 flop_budget: PSYCHOACOUSTIC_V1.flop_budget,
67 volatile: Vec::new(),
68 cache_policy: None,
69 }
70 }
71
72 pub fn seconds(rate: u32, secs: f64) -> RenderConfig {
73 let end = (secs * f64::from(rate)).round() as i64;
74 RenderConfig {
75 range: Range {
76 start: Some(0),
77 end: Some(end),
78 },
79 ..RenderConfig::at(rate)
80 }
81 }
82
83 pub fn asking(mut self, asks: Vec<Ask>) -> RenderConfig {
84 self.asks = asks;
85 self
86 }
87}
88
89pub use answer::{answer, answer_buffer, sketch_atom};
90pub use drive::Block;
91pub use quiet::{QUIET_AFTER_SECS, QUIET_LEVEL, QuietTail, quiet_tails};
92pub use stream::{STREAMED, Stream, StreamConfig};
93pub use terms::{Handle, NOTES};
94pub use through::{render_through, warm};
95pub use until::Until;
96
97const BLOCK: usize = 1 << 12;
99
100pub struct Render {
101 pub root: NodeId,
102 pub tys: Typing,
103 pub buffers: BTreeMap<NodeId, Buffer>,
104 pub frames: BTreeMap<NodeId, Frames>,
105 pub symbolic: BTreeMap<NodeId, SpectralSum>,
106 pub labels: BTreeMap<NodeId, Label>,
107 pub traces: Vec<FilterTrace>,
108 pub config: RenderConfig,
109 pub schedule: Schedule,
110 pub bindings: BTreeMap<NodeId, Vec<Binding>>,
111 pub cache_stats: Option<CacheStats>,
112 pub held_bytes: usize,
114 pub range: Option<Extent>,
116 pub(crate) unranged: Option<EngineError>,
117 pub(crate) table: Option<Table>,
119}
120
121impl Render {
122 pub(crate) fn shell(
123 tys: Typing,
124 root: NodeId,
125 config: RenderConfig,
126 schedule: Schedule,
127 ) -> Self {
128 Render {
129 root,
130 tys,
131 buffers: BTreeMap::new(),
132 frames: BTreeMap::new(),
133 symbolic: BTreeMap::new(),
134 labels: BTreeMap::new(),
135 traces: Vec::new(),
136 config,
137 schedule,
138 bindings: BTreeMap::new(),
139 cache_stats: None,
140 held_bytes: 0,
141 range: None,
142 unranged: None,
143 table: None,
144 }
145 }
146
147 pub(crate) fn rate(&self) -> u32 {
148 self.config.rate
149 }
150
151 pub(crate) fn grid(&self, id: NodeId) -> sva_samples::Grid {
152 self.tys.grid(id)
153 }
154
155 pub fn work(&self) -> crate::flops::Work {
157 crate::flops::Work {
158 samples: self.range.map_or(0, |range| range.len() as u64),
159 priced_flops: crate::flops::total(self),
160 waves: None,
161 }
162 }
163
164 pub fn output(&self, node: NodeId) -> Result<Buffer, EngineError> {
166 match (self.buffers.get(&node), self.range) {
167 (Some(held), Some(range)) => Ok(held.over(range, held.extent())),
168 _ => Err(answer::unheld(self, node)),
169 }
170 }
171
172 pub fn buffer(&self, node: NodeId) -> Option<&Buffer> {
173 self.buffers.get(&node)
174 }
175
176 pub fn id(&self, path: &str) -> Option<NodeId> {
177 self.tys.id(path)
178 }
179
180 pub fn node(&self, path: &str) -> Result<NodeId, EngineError> {
182 self.tys.resolve(path)
183 }
184
185 pub fn evaluated(&self, node: NodeId) -> Vec<Extent> {
188 let Some(table) = &self.table else {
189 return Vec::new();
190 };
191 table
192 .of(node)
193 .map_or(Vec::new(), |at| table.values[at].evaluated.clone())
194 }
195
196 pub fn alias_oversample(&self, node: NodeId) -> Option<u32> {
198 self.config
199 .asks
200 .iter()
201 .find_map(|ask| match ask.representation {
202 crate::query::Representation::Alias { oversample }
203 if self.node(&ask.node).is_ok_and(|asked| asked == node) =>
204 {
205 Some(oversample)
206 }
207 _ => None,
208 })
209 }
210
211 pub fn alias_score(&self, node: NodeId) -> AliasScore {
212 match self.alias_oversample(node) {
213 Some(_) => AliasScore::Asked,
214 None => AliasScore::NotAsked,
215 }
216 }
217}
218
219pub fn render(
221 graph: &Graph,
222 target: &str,
223 config: RenderConfig,
224 cache: Option<&Cache>,
225) -> Result<Render, EngineError> {
226 let recording = Recording::over(cache, config.cache_policy);
227 let mut held = planned(
228 prepared(graph, target, config.rate)?,
229 config,
230 &BTreeSet::new(),
231 )?;
232 pulled(&mut held, recording)?;
233 closed(&mut held)?;
234 Ok(held)
235}
236
237fn closed(held: &mut Render) -> Result<(), EngineError> {
238 scored(held)?;
239 compose_read(held);
240 stamp(held);
241 Ok(())
242}
243
244pub(crate) struct Prepared<'g> {
245 pub(crate) instances: instantiate::Instances<'g>,
246 pub(crate) tys: Typing,
247 pub(crate) root: NodeId,
248}
249
250pub(crate) fn prepared<'g>(
251 graph: &'g Graph,
252 target: &str,
253 rate: u32,
254) -> Result<Prepared<'g>, EngineError> {
255 let instances = instantiate::instantiate(graph, target, rate)?;
256 let held = instances.instance_of(target)?;
257 let order = schedule::schedule_from(&instances, std::slice::from_ref(&held))?;
258 let tys = typing::infer_all(&instances, &order)?;
259 let root = tys
260 .id(&held)
261 .ok_or_else(|| EngineError::UnknownNode(held.clone()))?;
262 Ok(Prepared {
263 instances,
264 tys,
265 root,
266 })
267}
268
269fn planned(
270 prepared: Prepared<'_>,
271 config: RenderConfig,
272 bounds: &BTreeSet<NodeId>,
273) -> Result<Render, EngineError> {
274 let Prepared {
275 instances,
276 tys,
277 root,
278 } = prepared;
279 planned_over(&instances, (tys, root), config, bounds)
280}
281
282fn planned_over(
283 instances: &instantiate::Instances,
284 (tys, root): (Typing, NodeId),
285 config: RenderConfig,
286 bounds: &BTreeSet<NodeId>,
287) -> Result<Render, EngineError> {
288 let schedule = schedule::plan(&tys, root, &config.asks);
289 let bindings = tys
290 .paths()
291 .filter_map(|(path, id)| Some((id, resolved(instances, path)?)))
292 .collect();
293 let mut held = Render::shell(tys, root, config, schedule);
294 held.bindings = bindings;
295 ranged(&mut held, bounds)?;
296 let target = held.tys.name(held.root).to_string();
297 let volatile = volatile::mark(instances, &held, &target)?;
298 if let Some(table) = &mut held.table {
299 table.slots(|id| volatile.slot(id));
300 }
301 Ok(held)
302}
303
304pub fn plan(graph: &Graph, target: &str, config: RenderConfig) -> Result<Render, EngineError> {
306 planned(
307 prepared(graph, target, config.rate)?,
308 config,
309 &BTreeSet::new(),
310 )
311}
312
313fn ranged(held: &mut Render, bounds: &BTreeSet<NodeId>) -> Result<(), EngineError> {
315 let counts = counts(&held.config.asks);
316 let envelope = held.config.asks.iter().any(|ask| {
317 matches!(
318 ask.representation,
319 crate::query::Representation::Envelope { .. }
320 )
321 });
322 if !materializes(held) && !counts {
323 if envelope && let Err(refused) = range_of(held, Ends::Refused) {
324 held.unranged = Some(refused);
325 } else if envelope {
326 held.range = Some(range_of(held, Ends::Refused)?);
327 }
328 return Ok(());
329 }
330 held.range = Some(range_of(held, Ends::Refused)?);
331 let wanted: Vec<NodeId> = held.schedule.wanted.clone();
332 let root = (held.root, wanted.as_slice());
333 let mut table = Table::bounded(&held.tys, root, &held.config.profile, bounds)?;
334 table.plan(held.range.expect("a range was decided"));
335 held.table = Some(table);
336 Ok(())
337}
338
339fn materializes(held: &Render) -> bool {
340 !held.schedule.wanted.is_empty()
341}
342
343pub(crate) enum Ends {
344 Refused,
345 Pulled,
346}
347
348pub(crate) fn range_of(held: &Render, ends: Ends) -> Result<Extent, EngineError> {
349 let support = Supports::new(&held.tys).of(held.root);
350 let start = held
351 .config
352 .range
353 .start
354 .unwrap_or_else(|| default_start(support));
355 let end = match held.config.range.end.or(default_end(support)) {
356 Some(end) => end,
357 None => match ends {
358 Ends::Pulled => i64::MAX,
359 Ends::Refused => return Err(endless(held)),
360 },
361 };
362 Ok(Extent::new(start, end.max(start)))
363}
364
365fn default_start(support: Extent) -> i64 {
366 match support.is_empty() || support.start == i64::MIN || support.start > 0 {
367 true => 0,
368 false => support.start,
369 }
370}
371
372fn default_end(support: Extent) -> Option<i64> {
373 (support.end != i64::MAX).then_some(support.end)
374}
375
376fn endless(held: &Render) -> EngineError {
377 let name = held.tys.name(held.root);
378 EngineError::refused(Diagnostic {
379 code: "render.no_end".to_string(),
380 message: format!(
381 "`{name}` is read over an interval with no end, and its support never ends"
382 ),
383 location: Located::at(name, None),
384 help: "give the interval an end, as `[0, 2s]`, or crop it".to_string(),
385 })
386}
387
388fn counts(asks: &[Ask]) -> bool {
390 asks.iter()
391 .any(|ask| ask.representation == crate::query::Representation::Flops)
392}
393
394fn affordable(held: &Render) -> Result<(), EngineError> {
397 if !materializes(held) {
398 return Ok(());
399 }
400 let total = crate::flops::total(held);
401 if total <= held.config.flop_budget {
402 return Ok(());
403 }
404 let counted = crate::flops::tree(held);
405 let over = crate::flops::dominating(&counted).expect("a counted tree holds its root");
406 Err(EngineError::refused(Diagnostic {
407 code: "collapse.over_budget".to_string(),
408 message: format!(
409 "this render counts {} operations, over the budget of {}; `{}` dominates it at {} \
410 by {}",
411 counted.total, counted.budget, over.node, over.subtree, over.route
412 ),
413 location: Located::at(held.tys.name(held.root), None),
414 help: format!("pass --flop-budget {total} to render it anyway"),
415 }))
416}
417
418fn pulled(held: &mut Render, recording: Recording) -> Result<(), EngineError> {
420 if let Some(mut driver) = driving(held, recording)? {
421 while driver.pull()? {}
422 drove(held, driver, true);
423 }
424 Ok(())
425}
426
427fn driving(held: &mut Render, recording: Recording) -> Result<Option<drive::Driver>, EngineError> {
429 affordable(held)?;
430 let (Some(table), Some(range)) = (held.table.take(), held.range) else {
431 return Ok(None);
432 };
433 if !materializes(held) {
434 held.table = Some(table);
435 return Ok(None);
436 }
437 Ok(Some(drive::Driver::new(
438 table,
439 range,
440 BLOCK,
441 &held.config,
442 recording,
443 )))
444}
445
446fn drove(held: &mut Render, driver: drive::Driver, keep: bool) {
448 let range = held.range.expect("a pulled render has a range");
449 held.held_bytes = driver.most_bytes();
450 held.cache_stats = Some(driver.recording.stats());
451 if let Some(stop) = driver.stop().filter(|stop| *stop < range.end) {
452 held.range = Some(Extent::new(range.start, stop));
453 }
454 let range = held.range.expect("a pulled render has a range");
455 let table = driver.table;
456 let wanted = held.schedule.wanted.iter().filter(|_| keep);
457 for (id, at) in wanted.map(|id| (*id, table.of(*id))) {
458 let Some(at) = at else {
459 continue;
460 };
461 match &table.values[at].held {
462 table::Held::Frames(Some(frames)) => {
463 held.frames.insert(id, (**frames).clone());
464 }
465 _ => {
466 held.buffers.insert(id, table.samples(at, range));
467 held.labels.insert(id, table.label(at));
468 }
469 }
470 }
471 held.table = Some(table);
472}
473
474pub(crate) fn finer(
477 render: &Render,
478 node: NodeId,
479 fine: u32,
480 over: Extent,
481) -> Result<Buffer, EngineError> {
482 let profile = &render.config.profile;
483 let mut table = Table::finer(&render.tys, node, &[node], profile, i128::from(fine))?;
484 let at = table.root;
485 table.pull(over, &mut Recording::over(None, None))?;
486 let mut held = table.samples(at, over);
487 held.rate = render.config.rate * fine;
488 Ok(held)
489}
490
491fn scored(held: &mut Render) -> Result<(), EngineError> {
493 let asked: Vec<NodeId> = held
494 .labels
495 .keys()
496 .copied()
497 .filter(|id| held.alias_oversample(*id).is_some())
498 .collect();
499 for id in asked {
500 let sva_samples::Detail::Point {
501 rule,
502 alias_db: None,
503 } = held.labels[&id].detail
504 else {
505 continue;
506 };
507 let buffer = held.output(id)?;
508 let alias_db = Some(answer::alias_db(held, id, &buffer)?);
509 let label = held.labels.get_mut(&id).expect("an asked label");
510 label.detail = sva_samples::Detail::Point { rule, alias_db };
511 }
512 Ok(())
513}
514
515#[cfg(test)]
517pub(crate) fn render_apart(
518 graph: &Graph,
519 target: &str,
520 config: RenderConfig,
521) -> Result<Render, EngineError> {
522 let mut held = planned(
523 prepared(graph, target, config.rate)?,
524 config,
525 &BTreeSet::new(),
526 )?;
527 if let (Some(range), Some(_)) = (held.range, &held.table) {
528 let wanted = held.schedule.wanted.clone();
529 let mut table = Table::apart(&held.tys, held.root, &wanted, &held.config.profile)?;
530 table.plan(range);
531 held.table = Some(table);
532 }
533 pulled(&mut held, Recording::over(None, None))?;
534 Ok(held)
535}
536
537pub(crate) fn sampled(render: &Render, node: NodeId, over: Extent) -> Result<Buffer, EngineError> {
539 let mut table = Table::build(&render.tys, node, &[node], &render.config.profile)?;
540 let at = table.root;
541 table.pull(over, &mut Recording::over(None, None))?;
542 Ok(table.samples(at, over))
543}
544
545fn stamp(held: &mut Render) {
547 let root = held.root;
548 let Some(label) = held.labels.remove(&root) else {
549 return;
550 };
551 let counted = crate::flops::total(held);
552 let label = sva_samples::Label {
553 rate: held.config.rate,
554 moved: held.table.as_ref().map(|table| table.moved),
555 ..label.costing(counted, held.config.flop_budget)
556 };
557 held.labels.insert(root, label);
558}
559
560fn compose_read(held: &mut Render) {
563 for id in held.schedule.compose.clone() {
564 if held.symbolic.contains_key(&id) {
565 continue;
566 }
567 if let Ok(sum) = refs::spectral_sum_of(&held.tys, id, held.tys.var(id)) {
568 held.symbolic.insert(id, sum);
569 }
570 }
571}
572
573fn resolved(instances: &instantiate::Instances, path: &str) -> Option<Vec<Binding>> {
574 Some(
575 instances
576 .bindings(path)?
577 .into_iter()
578 .map(|(name, expr, cx)| Binding {
579 name: name.to_string(),
580 source: instances.render(expr, cx),
581 })
582 .collect(),
583 )
584}