Skip to main content

sva_engine/render/
quiet.rs

1// Concern: finds each instance whose extent runs well past where its bound is under 24 bits | Non-concern: the bound (bound/), what lint says of it | IO: (&Graph, target) -> Vec<QuietTail>
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use sva_ast::Graph;
6use sva_formula::NodeId;
7
8use super::bound::Tail;
9use super::table::Table;
10use super::{Ends, Render, RenderConfig, prepared, range_of};
11use crate::error::EngineError;
12use crate::schedule;
13
14/// Half a 24-bit step.
15pub const QUIET_LEVEL: f64 = 1.0 / (1u64 << 24) as f64;
16
17pub const QUIET_AFTER_SECS: f64 = 1.0;
18
19/// Seconds in the instance's own time.
20#[derive(Clone, Debug, PartialEq)]
21pub struct QuietTail {
22    pub instance: String,
23    pub file: String,
24    pub quiet_from: f64,
25    pub runs_to: f64,
26}
27
28/// A root with no end is read on, as a stream reads it. An instance inherits the tail of
29/// one it reads, so only that one is named.
30pub fn quiet_tails(
31    graph: &Graph,
32    target: &str,
33    config: RenderConfig,
34) -> Result<Vec<QuietTail>, EngineError> {
35    let held = prepared(graph, target, config.rate)?;
36    let schedule = schedule::plan(&held.tys, held.root, &[]);
37    let shell = Render::shell(held.tys.clone(), held.root, config, schedule);
38    let range = range_of(&shell, Ends::Pulled)?;
39    let table = Table::build(&shell.tys, shell.root, &[], &shell.config.profile)?;
40    let needs = table.demand(range);
41    let mut out = BTreeMap::new();
42    for (path, id) in held.tys.paths().filter(|(p, _)| held.instances.holds(p)) {
43        let Some(at) = table.of(id) else {
44            continue;
45        };
46        let asked = needs[at].hold.hull();
47        if asked.is_empty() {
48            continue;
49        }
50        let grid = shell.grid(id);
51        let edge = |n: i64| match n {
52            i64::MIN => f64::NEG_INFINITY,
53            i64::MAX => f64::INFINITY,
54            n => grid.instant(n),
55        };
56        let (from, to) = (edge(asked.start), edge(asked.end));
57        let Some(tail) = Tail::of(&held.tys, &shell.config, id) else {
58            continue;
59        };
60        if let Some(quiet_from) = quiet(&tail, from.max(0.0), to) {
61            let file = held.instances.origin(path).unwrap_or(path).to_string();
62            let tail = QuietTail {
63                instance: path.to_string(),
64                file,
65                quiet_from,
66                runs_to: to,
67            };
68            out.insert(id, tail);
69        }
70    }
71    let named: BTreeSet<NodeId> = out.keys().copied().collect();
72    Ok(out
73        .into_iter()
74        .filter(|(id, _)| under(&held.tys, *id).is_disjoint(&named))
75        .map(|(_, tail)| tail)
76        .collect())
77}
78
79/// Every node `id` reads, however deep.
80fn under(tys: &crate::typing::Typing, id: NodeId) -> BTreeSet<NodeId> {
81    let (mut seen, mut work) = (BTreeSet::new(), schedule::read_operands(tys, id));
82    while let Some(next) = work.pop() {
83        if next != id && seen.insert(next) {
84            work.extend(schedule::read_operands(tys, next));
85        }
86    }
87    seen
88}
89
90/// Where the bound falls under the level, to the millisecond, if more than
91/// [`QUIET_AFTER_SECS`] before `to`; a node under it from its start has no tail.
92fn quiet(tail: &Tail, from: f64, to: f64) -> Option<f64> {
93    let under = |t: f64| tail.from(t) < QUIET_LEVEL;
94    let last = match to.is_finite() {
95        true => to - QUIET_AFTER_SECS,
96        false => (0..24)
97            .map(|k| from + f64::from(1 << k))
98            .find(|t| under(*t))?,
99    };
100    if last < from || !under(last) {
101        return None;
102    }
103    let (mut lo, mut hi) = (from, last);
104    if under(lo) {
105        return None;
106    }
107    while hi - lo > 1e-3 {
108        let mid = 0.5 * (lo + hi);
109        match under(mid) {
110            true => hi = mid,
111            false => lo = mid,
112        }
113    }
114    Some(hi)
115}