1mod envelope;
4mod floor;
5mod range;
6mod ringing;
7
8use std::borrow::Cow;
9use std::cell::RefCell;
10use std::collections::{BTreeMap, BTreeSet};
11
12use sva_ast::Graph;
13use sva_formula::{Hash, NodeId};
14use sva_samples::{Buffer, Horizon, Label};
15
16pub(crate) use envelope::Live;
17use envelope::{Bounds, Envelope, Forms, Grid, STEP, Unbounded};
18
19use super::{Prepared, Render, RenderConfig, materialize, prepared, run};
20use crate::cache::{Cache, Cost, Expected, Payload, Recording, Slots};
21use crate::error::{Diagnostic, EngineError, Located};
22use crate::schedule::{Schedule, dependencies_first};
23
24#[derive(Clone, Copy, Debug, PartialEq)]
26pub struct Silent {
27 pub bits: u32,
28 pub max_secs: f64,
29}
30
31impl Silent {
32 pub fn threshold(self) -> f64 {
33 2f64.powi(-(self.bits as i32))
34 }
35}
36
37pub fn render_until_silent(
40 graph: &Graph,
41 target: &str,
42 config: RenderConfig,
43 silent: Silent,
44 cache: Option<&dyn Cache>,
45 slots: Option<&Slots>,
46) -> Result<Render, EngineError> {
47 let held = prepared(graph, target)?;
48 let rate = config.rate;
49 let width = held.tys.ty(held.root).width as usize;
50 let key = silent_key(held.identity(&config.asks)?, &config, silent);
51 let volatile = super::volatile::mark(&held.instances, &held.tys, &config, &held.target)?;
53 let recording = cache
54 .filter(|_| volatile.slot(held.root).is_none())
55 .map(|c| Recording::over(c, slots));
56 let lens = recording.as_ref().map(|r| r.at(None));
57 let store = lens.as_ref().map(|l| l as &dyn Cache);
58 if let Some((buffer, label)) = recalled(store, key, &held, rate, width) {
59 let config = ended(config, buffer.len());
60 let render = run(held, config, cache, slots, Some((buffer, label)))?;
61 return Ok(counted(render, recording));
62 }
63 let began = Cost::begun();
64 let (proven, proofs) = proven_at(&held, &config, silent)?;
65 let mut render = run(held, ended(config, proven.max(1)), cache, slots, None)?;
66 render.proofs = proofs;
67 let heard = render.buffers.get(&render.root).map_or(0, |b| {
68 (0..b.len())
69 .rev()
70 .find(|&n| (0..b.width).any(|c| b.plane(c)[n].abs() >= silent.threshold()))
71 .map_or(0, |n| n + 1)
72 });
73 trimmed(&mut render, heard.max(1));
74 if let (Some(store), Some(buffer), Some(label)) = (
75 store,
76 render.buffers.get(&render.root),
77 render.labels.get(&render.root),
78 ) {
79 remember(store, key, buffer, label, began.elapsed());
80 }
81 Ok(counted(render, recording))
82}
83
84fn counted(mut render: Render, recording: Option<Recording>) -> Render {
86 let Some(recording) = recording else {
87 return render;
88 };
89 let mut stats = recording.finish();
90 if let Some(made) = render.cache_stats.take() {
91 stats.lookups.extend(made.lookups);
92 }
93 render.cache_stats = Some(stats);
94 render
95}
96
97pub(super) fn proven_at(
100 held: &Prepared,
101 config: &RenderConfig,
102 silent: Silent,
103) -> Result<(usize, u64), EngineError> {
104 let rate = f64::from(config.rate);
105 let start = config.horizon.start_secs;
106 let samples = ((silent.max_secs - start) * rate).ceil().max(0.0) as usize;
107 let grid = Grid {
108 start,
109 rate,
110 points: samples / STEP + 1,
111 };
112 let rendered = |id: NodeId, end: f64| heard_alone(&held.tys, id, config, end);
113 let threshold = silent.threshold();
114 let mut level = threshold;
115 let forms = Forms::new(Cow::Borrowed(&held.tys), Cow::Borrowed(config));
116 let mut proofs = 0;
117 loop {
118 proofs += 1;
119 let mut bounds = Bounds::new(&forms, grid.clone(), &rendered, level);
120 let envelope = bounded(&held.tys, held.root, &mut bounds, silent)?;
121 if let Some(j) = envelope.at.iter().position(|v| *v < threshold) {
122 return Ok((j * STEP, proofs));
123 }
124 let last = envelope.at.last().copied().unwrap_or(f64::INFINITY);
125 if !bounds.held_flat || !last.is_finite() {
126 return Err(not_by(held, &envelope, silent));
127 }
128 level *= threshold / last / 2.0;
130 }
131}
132
133pub(crate) struct Kept {
136 heard: RefCell<BTreeMap<(NodeId, u64), Buffer>>,
137 forms: Forms<'static>,
138}
139
140impl Kept {
141 pub(crate) fn new(tys: crate::typing::Typing, config: RenderConfig) -> Kept {
142 Kept {
143 heard: RefCell::default(),
144 forms: Forms::new(Cow::Owned(tys), Cow::Owned(config)),
145 }
146 }
147}
148
149pub(crate) fn bound_from(
151 kept: &Kept,
152 root: NodeId,
153 silent: Silent,
154 live: &dyn Live,
155 now: usize,
156) -> Result<f64, EngineError> {
157 let (tys, config) = (&*kept.forms.tys, &*kept.forms.config);
158 let heard = &kept.heard;
159 let grid = Grid {
160 start: now as f64 / f64::from(config.rate),
161 rate: f64::from(config.rate),
162 points: 1,
163 };
164 let rendered = |id: NodeId, end: f64| {
165 if let Some(buffer) = heard.borrow().get(&(id, end.to_bits())) {
166 return Ok(buffer.clone());
167 }
168 let buffer = heard_alone(tys, id, config, end)?;
169 heard
170 .borrow_mut()
171 .insert((id, end.to_bits()), buffer.clone());
172 Ok(buffer)
173 };
174 let mut bounds = Bounds::new(&kept.forms, grid, &rendered, silent.threshold());
175 bounds.live = Some(live);
176 Ok(bounded(tys, root, &mut bounds, silent)?.at[0])
177}
178
179fn bounded(
181 tys: &crate::typing::Typing,
182 root: NodeId,
183 bounds: &mut Bounds,
184 silent: Silent,
185) -> Result<Envelope, EngineError> {
186 let envelope = match bounds.of(root)? {
187 Ok(envelope) => envelope,
188 Err(Unbounded { node, class }) => {
189 return Err(refusal(
190 tys,
191 root,
192 "engine.no_tail_bound",
193 format!(
194 "`{node}` is {class}, and no bound on its tail is derived yet, so silence \
195 is never proven for it."
196 ),
197 "render it to a stated --to instead, or crop it to a window",
198 ));
199 }
200 };
201 let threshold = silent.threshold();
202 if envelope.floor >= threshold {
203 return Err(refusal(
204 tys,
205 root,
206 "engine.never_silent",
207 format!(
208 "it returns to {} forever, at or above the {}-bit floor of {}.",
209 dbfs(envelope.floor),
210 silent.bits,
211 dbfs(threshold)
212 ),
213 "crop it, or give it a release",
214 ));
215 }
216 Ok(envelope)
217}
218
219fn not_by(held: &Prepared, envelope: &Envelope, silent: Silent) -> EngineError {
220 let last = envelope.at.last().copied().unwrap_or(f64::INFINITY);
221 not_silent_by(&held.tys, held.root, Some(last), silent)
222}
223
224pub(crate) fn not_silent_by(
226 tys: &crate::typing::Typing,
227 root: NodeId,
228 last: Option<f64>,
229 silent: Silent,
230) -> EngineError {
231 let floor = format!(
232 "the {}-bit floor of {}",
233 silent.bits,
234 dbfs(silent.threshold())
235 );
236 let found = match last {
237 Some(last) => format!(
238 "its bound at {}s is {}, not under {floor}",
239 silent.max_secs,
240 dbfs(last)
241 ),
242 None => format!(
243 "no block has ended by {}s to prove it under {floor}",
244 silent.max_secs
245 ),
246 };
247 refusal(
248 tys,
249 root,
250 "engine.not_silent_by",
251 format!("{found}, so silence is not proven by then."),
252 "if it decays, raise --max or lower the bits",
253 )
254}
255
256fn dbfs(v: f64) -> String {
257 match v.is_finite() {
258 true => format!("{:.1} dBFS", 20.0 * v.log10()),
259 false => "unbounded".to_string(),
260 }
261}
262
263fn refusal(
264 tys: &crate::typing::Typing,
265 root: NodeId,
266 code: &str,
267 message: String,
268 help: &str,
269) -> EngineError {
270 EngineError::refused(Diagnostic {
271 code: code.to_string(),
272 message,
273 location: Located::at(tys.name(root), None),
274 help: help.to_string(),
275 })
276}
277
278fn ended(mut config: RenderConfig, samples: usize) -> RenderConfig {
279 let start = config.horizon.start_secs;
280 config.horizon = Horizon::secs(start, start + samples as f64 / f64::from(config.rate));
281 config
282}
283
284fn trimmed(render: &mut Render, samples: usize) {
287 for buffer in render.buffers.values_mut() {
288 if buffer.len() > samples {
289 let origin = buffer.origin_secs;
290 *buffer = Buffer::of_planes(
291 buffer.rate,
292 (0..buffer.width)
293 .map(|c| buffer.plane(c)[..samples].to_vec())
294 .collect(),
295 );
296 buffer.origin_secs = origin;
297 }
298 }
299 render.config = ended(render.config.clone(), samples);
300}
301
302fn heard_alone(
304 tys: &crate::typing::Typing,
305 id: NodeId,
306 config: &RenderConfig,
307 end: f64,
308) -> Result<Buffer, EngineError> {
309 let mut held = Render {
310 root: id,
311 tys: tys.clone(),
312 buffers: BTreeMap::new(),
313 frames: BTreeMap::new(),
314 symbolic: BTreeMap::new(),
315 labels: BTreeMap::new(),
316 traces: Vec::new(),
317 config: RenderConfig {
318 horizon: Horizon::secs(config.horizon.start_secs, end),
319 asks: Vec::new(),
320 ..config.clone()
321 },
322 schedule: Schedule {
323 materialize: Vec::new(),
324 symbolic: Vec::new(),
325 compose: Vec::new(),
326 },
327 bindings: BTreeMap::new(),
328 cache_stats: None,
329 proofs: 0,
330 };
331 for member in dependencies_first(tys, id, &mut BTreeSet::new()) {
332 materialize(&mut held, member, None)?;
333 }
334 held.buffers
335 .remove(&id)
336 .ok_or_else(|| EngineError::UnknownNode(tys.name(id).to_string()))
337}
338
339const SILENT_TAG: u64 = 0x73_69_6c_65_6e_74_00_01;
340
341fn silent_key((identity, scored): (Hash, bool), config: &RenderConfig, silent: Silent) -> Hash {
344 crate::cache::mixed(
345 identity,
346 &[
347 u64::from(config.rate),
348 config.horizon.start_secs.to_bits(),
349 u64::from(silent.bits),
350 silent.max_secs.to_bits(),
351 u64::from(scored),
352 SILENT_TAG,
353 ],
354 )
355}
356
357fn samples_key(key: Hash, samples: usize) -> Hash {
360 crate::cache::mixed(key, &[samples as u64, SILENT_TAG])
361}
362
363fn recalled(
364 cache: Option<&dyn Cache>,
365 key: Hash,
366 held: &Prepared,
367 rate: u32,
368 width: usize,
369) -> Option<(Buffer, Label)> {
370 let cache = cache?;
371 let name = held.tys.name(held.root);
372 let length = cache.load(
373 key,
374 name,
375 Expected::Samples {
376 rate,
377 width: 1,
378 samples: 1,
379 },
380 )?;
381 let samples = length.payload.samples()?.plane(0)[0] as usize;
382 let entry = cache.load(
383 samples_key(key, samples),
384 name,
385 Expected::Samples {
386 rate,
387 width,
388 samples,
389 },
390 )?;
391 Some((entry.payload.samples().cloned()?, entry.label?))
392}
393
394fn remember(
395 cache: &dyn Cache,
396 key: Hash,
397 buffer: &Buffer,
398 label: &Label,
399 cost: std::time::Duration,
400) {
401 let payload = Payload::Samples(Box::new(buffer.clone()));
402 if !cache.worth_storing(cost, payload.bytes(), crate::cache::PayloadKind::Samples) {
403 return;
404 }
405 cache.store(samples_key(key, buffer.len()), &payload, &[], Some(label));
406 let length = Buffer::of_planes(buffer.rate, vec![vec![buffer.len() as f64]]);
407 cache.store(key, &Payload::Samples(Box::new(length)), &[], None);
408}