Skip to main content

blue_lang_runtime/
observe.rs

1//! Watching a blue program run: the call stack a runtime error reports, and
2//! the per-definition counters `blue profile`, `blue test --coverage` and
3//! `blue trace` read.
4//!
5//! One mechanism under all of them: tatara-lisp-eval's call hook
6//! (`tatara_lisp_eval::observe`), which every closure activation passes once,
7//! tail calls included. Its unwind arm is always on and costs nothing until an
8//! error, so every runtime error carries the frames it left ([`Fault`]). Its
9//! observer arm is installed only when a tool asks, and [`Recorder`] is the one
10//! observer here: counts, inclusive and exclusive time and allocations
11//! ([`crate::alloc`]), and the trace of one definition. The hook's `enter` may also answer a call without running its
12//! body, which is where a memo table keyed on a definition and its arguments
13//! plugs in (`theory/BLUE-TOOLING.md` T-M) without a second hook.
14//!
15//! Every count is keyed by the DEFINITION the runtime binds (`retsu/first`,
16//! `%root/f`), the key the check stage resolved. That is the key the engine's
17//! per-definition queries carry (`theory/BLUE-ENGINE.md` §3), so when
18//! `def_hash` replaces the name, the counters move with it.
19//!
20//! The tree-walker only: the VM reports neither arm.
21
22use std::collections::HashMap;
23use std::sync::{Arc, Mutex, PoisonError};
24use std::time::{Duration, Instant};
25
26use tatara_lisp::{Span, Spanned};
27use tatara_lisp_eval::{EvalError, Value};
28
29pub use tatara_lisp_eval::observe::{Call, CallObserver, Exit, Trace, TraceFrame};
30
31use crate::alloc::Allocations;
32use crate::uses::ResolvedProgram;
33
34/// A function the program defines at the top level, under its runtime key.
35#[derive(Clone, Debug)]
36pub struct Definition {
37    /// What the runtime binds: `retsu/first`, `%root/f`.
38    pub key: Arc<str>,
39    /// What the author writes: `retsu::first`, `f`.
40    pub name: String,
41    /// The top-level form that defines it.
42    pub top_level: usize,
43    /// The defining form's span, in its own file.
44    pub span: Span,
45    /// Defined in the entry file rather than an imported one.
46    pub entry: bool,
47    /// Where it is defined.
48    pub place: Place,
49}
50
51/// A position as a reader follows it. Each part is absent when it cannot be
52/// justified, never guessed.
53#[derive(Clone, Debug, Default, PartialEq, Eq)]
54pub struct Place {
55    pub file: Option<String>,
56    pub line: Option<usize>,
57    pub column: Option<usize>,
58}
59
60impl Place {
61    fn of(program: &ResolvedProgram, top_level: usize, span: Span) -> Self {
62        let at = program.locate(top_level, span, "");
63        Self {
64            file: at.file_name(),
65            line: at.line_col().map(|(l, _)| l),
66            column: at.line_col().map(|(_, c)| c),
67        }
68    }
69
70    fn json(&self) -> serde_json::Value {
71        serde_json::json!({ "file": self.file, "line": self.line, "column": self.column })
72    }
73}
74
75impl std::fmt::Display for Place {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        f.write_str(self.file.as_deref().unwrap_or("<unknown file>"))?;
78        if let Some(line) = self.line {
79            write!(f, ":{line}")?;
80            if let Some(col) = self.column {
81                write!(f, ":{col}")?;
82            }
83        }
84        Ok(())
85    }
86}
87
88/// Every function a program defines at its top level: the denominator of
89/// coverage, and the table that turns a closure's runtime key back into a
90/// name and a place.
91#[derive(Debug, Default)]
92pub struct Definitions {
93    defs: Vec<Definition>,
94    by_key: HashMap<Arc<str>, usize>,
95    /// `f = fn(x) … end` binds an unnamed lambda: found by its span. `None`
96    /// when two definitions share one (two files, same offsets), so no
97    /// activation is credited to the wrong one.
98    by_lambda: HashMap<Span, Option<usize>>,
99}
100
101impl Definitions {
102    /// Read off the tree the evaluator runs (`erased`, one form per top-level
103    /// form of `program`).
104    #[must_use]
105    pub fn of(program: &ResolvedProgram, erased: &[Spanned]) -> Self {
106        let mut out = Self::default();
107        for (i, form) in erased.iter().enumerate() {
108            let Some(items) = form.as_list() else {
109                continue;
110            };
111            if items.first().and_then(Spanned::as_symbol) != Some("define") || items.len() < 3 {
112                continue;
113            }
114            let (key, lambda) = match items[1].as_list() {
115                Some(head) => match head.first().and_then(Spanned::as_symbol) {
116                    Some(k) => (k, None),
117                    None => continue,
118                },
119                None => match (items[1].as_symbol(), items[2].as_list()) {
120                    (Some(k), Some(value))
121                        if value.first().and_then(Spanned::as_symbol) == Some("lambda") =>
122                    {
123                        (k, Some(items[2].span))
124                    }
125                    _ => continue,
126                },
127            };
128            let key: Arc<str> = Arc::from(key);
129            let index = out.defs.len();
130            out.defs.push(Definition {
131                name: display_key(&key),
132                key: Arc::clone(&key),
133                top_level: i,
134                span: form.span,
135                entry: program.owner_of(i) == Some(ResolvedProgram::ENTRY),
136                place: Place::of(program, i, form.span),
137            });
138            out.by_key.insert(key, index);
139            if let Some(span) = lambda {
140                out.by_lambda
141                    .entry(span)
142                    .and_modify(|slot| *slot = None)
143                    .or_insert(Some(index));
144            }
145        }
146        out
147    }
148
149    pub fn iter(&self) -> impl Iterator<Item = &Definition> {
150        self.defs.iter()
151    }
152
153    #[must_use]
154    pub fn len(&self) -> usize {
155        self.defs.len()
156    }
157
158    #[must_use]
159    pub fn is_empty(&self) -> bool {
160        self.defs.is_empty()
161    }
162
163    #[must_use]
164    pub fn get(&self, index: usize) -> Option<&Definition> {
165        self.defs.get(index)
166    }
167
168    /// The definitions an author's `name` can mean: the exact name
169    /// (`retsu::first`, `f`), else every definition whose own name is it.
170    #[must_use]
171    pub fn named(&self, name: &str) -> Vec<usize> {
172        let exact: Vec<usize> = (0..self.defs.len())
173            .filter(|&i| self.defs[i].name == name || &*self.defs[i].key == name)
174            .collect();
175        if !exact.is_empty() {
176            return exact;
177        }
178        (0..self.defs.len())
179            .filter(|&i| self.defs[i].name.rsplit("::").next() == Some(name))
180            .collect()
181    }
182
183    /// Which definition an activation is a call of: by its name, or for a
184    /// lambda bound at the top level, by its span.
185    fn of_call(&self, name: Option<&Arc<str>>, source: Span) -> Option<usize> {
186        match name {
187            Some(n) => self.by_key.get(n).copied(),
188            None => self.by_lambda.get(&source).copied().flatten(),
189        }
190    }
191
192    fn of_frame(&self, frame: &TraceFrame) -> Option<usize> {
193        self.of_call(frame.name.as_ref(), frame.source)
194    }
195
196    /// A named closure the program did not define: one of the runtime's own
197    /// tatara-lisp builtins. Every program definition runs under a qualified
198    /// key (`%root/f`, `retsu/first`), so a bare name is never the author's.
199    fn is_builtin(&self, frame: &TraceFrame) -> bool {
200        frame.name.as_ref().is_some_and(|n| {
201            !self.by_key.contains_key(n) && blue_lang_syntax::qualified(n).is_none()
202        })
203    }
204
205    /// Where the program's own code was when an error left it: the innermost
206    /// frame that is not a builtin, and the span inside it — the error's own
207    /// span, or the call into the builtin that raised.
208    fn first_own(&self, trace: &Trace, error: Span) -> (Option<usize>, Span) {
209        let frames = &trace.frames;
210        let own = frames.iter().position(|f| !self.is_builtin(f));
211        match own {
212            Some(0) | None => (own, error),
213            Some(k) => (Some(k), frames[k - 1].at),
214        }
215    }
216}
217
218/// A runtime key as the author writes it: `%root/f` is `f`, `retsu/first` is
219/// `retsu::first`, a builtin stays bare.
220#[must_use]
221pub fn display_key(key: &str) -> String {
222    let root = format!(
223        "{}{}",
224        blue_lang_check::names::ROOT_QUALIFIER,
225        blue_lang_syntax::QUALIFIER
226    );
227    if let Some(rest) = key.strip_prefix(&root) {
228        rest.to_string()
229    } else if let Some((pkg, name)) = blue_lang_syntax::qualified(key) {
230        format!("{pkg}::{name}")
231    } else {
232        key.to_string()
233    }
234}
235
236/// One frame of a blue call stack: the definition that was running and where
237/// in it.
238#[derive(Clone, Debug, PartialEq, Eq)]
239pub struct StackFrame {
240    pub function: String,
241    /// `None` for a builtin the runtime defines in tatara-lisp (`first`,
242    /// `map`): its offsets index the runtime's own source, not the program's.
243    pub place: Option<Place>,
244}
245
246/// The frames a runtime error unwound through, innermost first, ending with
247/// the top-level form that started the chain when it was recorded.
248#[derive(Clone, Debug, Default, PartialEq, Eq)]
249pub struct Stack {
250    pub frames: Vec<StackFrame>,
251    /// Activations unwound, recorded or not. Above `frames.len()` when the
252    /// recursion was deeper than `tatara_lisp_eval::observe::TRACE_FRAMES`.
253    pub depth: usize,
254}
255
256/// Distinct lines a rendered stack shows before it summarises the rest.
257pub const STACK_LINES: usize = 24;
258
259impl Stack {
260    #[must_use]
261    pub fn is_empty(&self) -> bool {
262        self.frames.is_empty()
263    }
264
265    /// Read a [`Trace`] against the program that ran. `error` is where the
266    /// innermost frame raised; `top_level` the form that was executing.
267    #[must_use]
268    pub fn of(
269        program: &ResolvedProgram,
270        defs: &Definitions,
271        trace: &Trace,
272        error: Span,
273        top_level: usize,
274    ) -> Self {
275        let frames = &trace.frames;
276        let mut out = Vec::with_capacity(frames.len() + 1);
277        for (k, frame) in frames.iter().enumerate() {
278            let def = defs.of_frame(frame);
279            // A lambda is defined inside whatever named frame is outward of
280            // it, which is the file its body's offsets index.
281            let outer = frames[k + 1..].iter().find_map(|f| defs.of_frame(f));
282            let site = def
283                .or(outer)
284                .and_then(|d| defs.get(d))
285                .map_or(top_level, |d| d.top_level);
286            if defs.is_builtin(frame) {
287                out.push(StackFrame {
288                    function: frame.name.as_deref().map_or_else(String::new, display_key),
289                    place: None,
290                });
291                continue;
292            }
293            let at = if k == 0 { error } else { frames[k - 1].at };
294            let function = match (def.and_then(|d| defs.get(d)), &frame.name) {
295                (Some(d), _) => d.name.clone(),
296                (None, Some(n)) => display_key(n),
297                (None, None) => match outer.and_then(|d| defs.get(d)) {
298                    Some(d) => format!("fn in {}", d.name),
299                    None => "fn".to_string(),
300                },
301            };
302            out.push(StackFrame {
303                function,
304                place: Some(Place::of(program, site, at)),
305            });
306        }
307        if let Some(last) = frames.last() {
308            if trace.depth == frames.len() {
309                out.push(StackFrame {
310                    function: "<top level>".to_string(),
311                    place: Some(Place::of(program, top_level, last.at)),
312                });
313            }
314        }
315        Self {
316            frames: out,
317            depth: trace.depth,
318        }
319    }
320
321    #[must_use]
322    pub fn json(&self) -> serde_json::Value {
323        serde_json::Value::Array(
324            self.frames
325                .iter()
326                .map(|f| {
327                    let mut v = f
328                        .place
329                        .as_ref()
330                        .map_or_else(|| serde_json::json!({ "builtin": true }), Place::json);
331                    v["function"] = serde_json::Value::String(f.function.clone());
332                    v
333                })
334                .collect(),
335        )
336    }
337}
338
339impl std::fmt::Display for Stack {
340    /// `  at f (file:line:col)`, one line per frame, a run of identical frames
341    /// folded into one, at most [`STACK_LINES`] lines.
342    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343        let mut runs: Vec<(&StackFrame, usize)> = Vec::new();
344        for frame in &self.frames {
345            match runs.last_mut() {
346                Some((last, n)) if *last == frame => *n += 1,
347                _ => runs.push((frame, 1)),
348            }
349        }
350        let mut first = true;
351        let mut line = |f: &mut std::fmt::Formatter<'_>, text: String| {
352            let sep = if first { "" } else { "\n" };
353            first = false;
354            write!(f, "{sep}  {text}")
355        };
356        for (shown, (frame, n)) in runs.iter().enumerate() {
357            if shown == STACK_LINES {
358                let rest: usize = runs[shown..].iter().map(|(_, n)| n).sum();
359                line(f, format!("… {rest} more frame(s)"))?;
360                break;
361            }
362            match &frame.place {
363                Some(place) => line(f, format!("at {} ({place})", frame.function))?,
364                None => line(f, format!("at {} (builtin)", frame.function))?,
365            }
366            if *n > 1 {
367                line(f, format!("… repeated {} more time(s)", n - 1))?;
368            }
369        }
370        let unrecorded = self.depth.saturating_sub(self.frames.len());
371        if unrecorded > 0 {
372            line(f, format!("… {unrecorded} outer frame(s) not recorded"))?;
373        }
374        Ok(())
375    }
376}
377
378/// A runtime error: what went wrong, where, and the blue call stack that led
379/// there. [`crate::RunError::Eval`] carries one.
380#[derive(Clone, Debug, Default)]
381pub struct Fault {
382    /// The message, names as the author writes them, without a position.
383    pub message: String,
384    /// Where it was raised.
385    pub place: Place,
386    /// `file:line:col: message`, the one-line form.
387    pub located: String,
388    pub stack: Stack,
389    /// The run ended on a capability breach ([`crate::breach`]).
390    pub breach: bool,
391}
392
393impl Fault {
394    /// An error at `top_level` of `program`, unwound through `trace`.
395    #[must_use]
396    pub fn of(
397        program: &ResolvedProgram,
398        erased: &[Spanned],
399        top_level: usize,
400        e: &EvalError,
401        trace: &Trace,
402    ) -> Self {
403        let defs = Definitions::of(program, erased);
404        let message = crate::pipeline::display_keys(&crate::messages::describe(e, erased));
405        // `span()` is `None` for the arms that genuinely have no position
406        // (`Reader`, `Halted`, `NotImplemented`); synthetic is the honest
407        // stand-in, rendered as a file with no line.
408        let raised = e.span().unwrap_or_else(Span::synthetic);
409        // The innermost frame of the program's own owns the error's offsets,
410        // which is what lets a raise inside a callee from another file name
411        // that file; inside a builtin, the position is the call into it.
412        let (own, at) = defs.first_own(trace, raised);
413        let site = own
414            .and_then(|k| trace.frames[k..].iter().find_map(|f| defs.of_frame(f)))
415            .and_then(|d| defs.get(d))
416            .map_or(top_level, |d| d.top_level);
417        Self {
418            located: program.locate(site, at, &message).to_string(),
419            place: Place::of(program, site, at),
420            stack: Stack::of(program, &defs, trace, raised, top_level),
421            message,
422            breach: crate::is_breach(e),
423        }
424    }
425
426    /// An error raised by forms that carry no source (`pipeline::run_compiled`):
427    /// the message alone, with no place and no stack.
428    #[must_use]
429    pub fn unplaced(e: &EvalError, erased: &[Spanned]) -> Self {
430        let message = crate::pipeline::display_keys(&crate::messages::describe(e, erased));
431        Self {
432            located: message.clone(),
433            message,
434            breach: crate::is_breach(e),
435            ..Self::default()
436        }
437    }
438
439    /// The JSON form: one object, the stack innermost first.
440    #[must_use]
441    pub fn json(&self) -> serde_json::Value {
442        let mut v = self.place.json();
443        v["stage"] = "eval".into();
444        v["severity"] = "error".into();
445        v["message"] = self.message.clone().into();
446        v["stack"] = self.stack.json();
447        v["stack_depth"] = self.stack.depth.into();
448        if self.breach {
449            v["kind"] = crate::BREACH.into();
450        }
451        v
452    }
453}
454
455impl From<String> for Fault {
456    fn from(message: String) -> Self {
457        Self {
458            located: message.clone(),
459            message,
460            ..Self::default()
461        }
462    }
463}
464
465impl std::fmt::Display for Fault {
466    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467        f.write_str(&self.located)?;
468        if !self.stack.is_empty() {
469            write!(f, "\n{}", self.stack)?;
470        }
471        Ok(())
472    }
473}
474
475/// What one definition, or one unnamed function, cost.
476#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
477pub struct Counts {
478    /// Activations, a tail call counting as one.
479    pub calls: u64,
480    /// Time inside it and everything it called, counted once across recursion.
481    pub inclusive: Duration,
482    /// Time inside it alone.
483    pub exclusive: Duration,
484    /// Allocations made inside it and everything it called, counted once
485    /// across recursion. Zero when the binary does not count allocations;
486    /// [`Recorder::counts_allocations`] says which.
487    pub allocated: Allocations,
488    /// Allocations made inside it alone.
489    pub allocated_exclusive: Allocations,
490}
491
492/// A recorded function: a definition, or a lambda no top-level name binds.
493#[derive(Clone, Debug, PartialEq, Eq, Hash)]
494enum Slot {
495    Def(usize),
496    /// Keyed by its runtime name when it has one (a macro's `define`), else
497    /// by its span and the definition it ran inside.
498    Other {
499        name: Option<Arc<str>>,
500        within: Option<usize>,
501        source: Span,
502    },
503}
504
505/// One live activation on the recorder's shadow stack.
506struct Active {
507    slot: Slot,
508    start: Option<Instant>,
509    children: Duration,
510    /// This thread's allocation totals when the activation entered, read
511    /// after the recorder's own bookkeeping for the entry.
512    allocated_at: Option<Allocations>,
513    allocated_children: Allocations,
514    /// The arguments of a traced activation, kept for its exit line.
515    traced: Option<Vec<Value>>,
516}
517
518#[derive(Default)]
519struct State {
520    stack: Vec<Active>,
521    counts: HashMap<Slot, Counts>,
522    live: HashMap<Slot, usize>,
523    trace_depth: usize,
524}
525
526/// One line of `blue trace`: a call of the traced definition, or its exit.
527pub enum TraceEvent<'a> {
528    Call {
529        depth: usize,
530        name: &'a str,
531        args: &'a [Value],
532    },
533    Exit {
534        depth: usize,
535        name: &'a str,
536        args: &'a [Value],
537        exit: Exit<'a>,
538    },
539}
540
541/// Which definitions to trace, and where their lines go.
542pub struct Tracer {
543    pub targets: Vec<usize>,
544    pub sink: Box<dyn Fn(&TraceEvent<'_>) + Send + Sync>,
545}
546
547/// The one observer: per-definition counts, optionally timed, optionally
548/// tracing some definitions.
549pub struct Recorder {
550    defs: Arc<Definitions>,
551    timed: bool,
552    tracer: Option<Tracer>,
553    state: Mutex<State>,
554}
555
556impl Recorder {
557    /// `timed` reads the clock twice per activation; coverage leaves it off.
558    #[must_use]
559    pub fn new(defs: Arc<Definitions>, timed: bool, tracer: Option<Tracer>) -> Self {
560        Self {
561            defs,
562            timed,
563            tracer,
564            state: Mutex::new(State::default()),
565        }
566    }
567
568    fn state(&self) -> std::sync::MutexGuard<'_, State> {
569        self.state.lock().unwrap_or_else(PoisonError::into_inner)
570    }
571
572    fn slot(&self, call: &Call<'_>, stack: &[Active]) -> Slot {
573        if let Some(d) = self.defs.of_call(call.name, call.source) {
574            return Slot::Def(d);
575        }
576        let within = stack.iter().rev().find_map(|a| match a.slot {
577            Slot::Def(d) => Some(d),
578            Slot::Other { within, .. } => within,
579        });
580        Slot::Other {
581            name: call.name.cloned(),
582            within,
583            source: call.source,
584        }
585    }
586
587    fn label(&self, slot: &Slot) -> String {
588        match slot {
589            Slot::Def(d) => self
590                .defs
591                .get(*d)
592                .map_or_else(String::new, |d| d.name.clone()),
593            Slot::Other { name: Some(n), .. } => display_key(n),
594            Slot::Other {
595                name: None, within, ..
596            } => match within.and_then(|d| self.defs.get(d)) {
597                Some(d) => format!("fn in {}", d.name),
598                None => "fn".to_string(),
599            },
600        }
601    }
602
603    /// Every function that ran, with what it cost, and every definition that
604    /// did not run with zero calls: the profile and the coverage in one table.
605    #[must_use]
606    pub fn report(&self) -> Vec<Measured> {
607        let state = self.state();
608        let mut out: Vec<Measured> = self
609            .defs
610            .iter()
611            .enumerate()
612            .map(|(i, d)| Measured {
613                name: d.name.clone(),
614                definition: Some(i),
615                place: d.place.clone(),
616                entry: d.entry,
617                counts: state.counts.get(&Slot::Def(i)).copied().unwrap_or_default(),
618            })
619            .collect();
620        for (slot, counts) in &state.counts {
621            let Slot::Other { within, .. } = slot else {
622                continue;
623            };
624            let place = within
625                .and_then(|d| self.defs.get(d))
626                .map(|d| d.place.clone())
627                .unwrap_or_default();
628            out.push(Measured {
629                name: self.label(slot),
630                definition: None,
631                place,
632                entry: within
633                    .and_then(|d| self.defs.get(d))
634                    .is_some_and(|d| d.entry),
635                counts: *counts,
636            });
637        }
638        out
639    }
640
641    #[must_use]
642    pub fn definitions(&self) -> &Definitions {
643        &self.defs
644    }
645
646    /// Whether the allocation columns of [`Counts`] were measured: true only
647    /// in a binary that installs [`crate::alloc::Counting`]. Elsewhere they
648    /// read zero, which means unmeasured, never "allocated nothing".
649    #[must_use]
650    pub fn counts_allocations(&self) -> bool {
651        crate::alloc::allocations().is_some()
652    }
653}
654
655/// One row of [`Recorder::report`].
656#[derive(Clone, Debug)]
657pub struct Measured {
658    pub name: String,
659    /// Its index in [`Definitions`]; `None` for an unnamed function.
660    pub definition: Option<usize>,
661    pub place: Place,
662    pub entry: bool,
663    pub counts: Counts,
664}
665
666impl CallObserver for Recorder {
667    fn enter(&self, call: &Call<'_>, args: &[Value]) -> Option<Value> {
668        let mut state = self.state();
669        let slot = self.slot(call, &state.stack);
670        state.counts.entry(slot.clone()).or_default().calls += 1;
671        *state.live.entry(slot.clone()).or_default() += 1;
672        let traced = match (&self.tracer, &slot) {
673            (Some(t), Slot::Def(d)) if t.targets.contains(d) => {
674                let depth = state.trace_depth;
675                state.trace_depth += 1;
676                (t.sink)(&TraceEvent::Call {
677                    depth,
678                    name: &self.label(&slot),
679                    args,
680                });
681                Some(args.to_vec())
682            }
683            _ => None,
684        };
685        state.stack.push(Active {
686            slot,
687            start: self.timed.then(Instant::now),
688            children: Duration::ZERO,
689            allocated_at: None,
690            allocated_children: Allocations::default(),
691            traced,
692        });
693        // Last, so the entry's own map and stack growth falls outside the
694        // window the activation is charged for.
695        if let Some(active) = state.stack.last_mut() {
696            active.allocated_at = crate::alloc::allocations();
697        }
698        None
699    }
700
701    fn leave(&self, _call: &Call<'_>, exit: Exit<'_>) {
702        // First, for the same reason `enter` reads it last.
703        let allocated_now = crate::alloc::allocations();
704        let mut state = self.state();
705        let Some(active) = state.stack.pop() else {
706            return;
707        };
708        let elapsed = active.start.map_or(Duration::ZERO, |s| s.elapsed());
709        let allocated = match (allocated_now, active.allocated_at) {
710            (Some(now), Some(at)) => now.since(at),
711            _ => Allocations::default(),
712        };
713        if let Some(parent) = state.stack.last_mut() {
714            parent.children += elapsed;
715            parent.allocated_children += allocated;
716        }
717        let live = state.live.entry(active.slot.clone()).or_default();
718        *live = live.saturating_sub(1);
719        let outermost = *live == 0;
720        let counts = state.counts.entry(active.slot.clone()).or_default();
721        counts.exclusive += elapsed.saturating_sub(active.children);
722        counts.allocated_exclusive += allocated.since(active.allocated_children);
723        if outermost {
724            counts.inclusive += elapsed;
725            counts.allocated += allocated;
726        }
727        if let (Some(t), Some(args)) = (&self.tracer, &active.traced) {
728            state.trace_depth = state.trace_depth.saturating_sub(1);
729            (t.sink)(&TraceEvent::Exit {
730                depth: state.trace_depth,
731                name: &self.label(&active.slot),
732                args,
733                exit,
734            });
735        }
736    }
737}