Skip to main content

cove_runtime/vm/
debug.rs

1//! Stopping the machine, and looking at it.
2//!
3//! Issue #241's first half: the machine side of a debugger, with no session,
4//! no commands and no policy in it. What is here is a place to stand — the
5//! machine asks a [`Debugger`] before every instruction while one is
6//! installed, and honours [`Resume::Go`] or [`Resume::Halt`] — and a way to
7//! look, which is a set of owned snapshots this module builds and hands out.
8//!
9//! # The machine calls the debugger, and never the other way round
10//!
11//! There is no "suspended machine" here to hold, and there could not be. The
12//! dispatch loop runs inside [`Machine::drive`]'s `std::thread::scope`, and
13//! it holds a `&'s Scope<'s, 'a>` — the borrow a `spawn` starts its children
14//! in — which by construction cannot outlive that call. A handle to a paused
15//! machine would be a value carrying that borrow out of the scope that
16//! created it, which is exactly what the scope exists to refuse.
17//!
18//! So the call is inverted. The machine reaches a stop, builds a [`Stop`]
19//! that borrows it for the length of one call, and asks. Everything a
20//! debugger wants to keep, it copies out of that call — which is why every
21//! view below is owned, and why none of them borrows the machine.
22//!
23//! # A stop costs the loop nothing when nobody is stopping
24//!
25//! The loop's one per-instruction comparison already existed, as
26//! `instructions % SAFEPOINT_STRIDE == 0`. `Machine::next_question` folds
27//! the debugger's question into that same comparison rather than adding a
28//! second: with no debugger installed the next check is the next multiple of
29//! [`SAFEPOINT_STRIDE`](crate::vm::exec::SAFEPOINT_STRIDE) and the loop does
30//! what it always did; with one installed it is the very next instruction.
31//! `docs/VM_ARCHITECTURE.md` measured what a second per-instruction branch
32//! costs — 2.4% on `arith` for a `bool` guarding the counter — and that is
33//! the price this arrangement does not pay.
34//!
35//! It was measured rather than argued, by `scripts/vm-time.sh`: fifteen runs
36//! of one benchmark, medians of `execute=`, three interleaved rounds, every
37//! build from the same working tree so that nothing but the named line
38//! differs. On `arith`, against a base of that tree with this change removed:
39//!
40//! | build | median |
41//! | --- | ---: |
42//! | base | 80.7 ms |
43//! | the fold, with the question written out in the loop | 85.0 ms |
44//! | the fold, with the question in `Machine::ask` | 79.4 ms |
45//! | base plus the two fields, loop untouched (a control) | 79.6 ms |
46//!
47//! Two things came out of it, and only the second was expected. **The
48//! comparison is free**: the same tree with the loop's condition put back to
49//! `instructions % SAFEPOINT_STRIDE == 0` measured 82.9 ms against the fold's
50//! 82.7 ms, indistinguishable. **Where the question is written is not**:
51//! building the [`Stop`] and making the indirect call inside the dispatch
52//! body cost 4.3%, which is more than the branch this whole arrangement was
53//! shaped to avoid, and moving it behind `#[inline(never)]` recovered all of
54//! it. The control is the reason that is reported as a code-layout effect
55//! rather than as work: adding the same two fields and reading neither of
56//! them moved `arith` by 1.1% on its own. The shipped shape measures 1.5%
57//! *below* the base on `arith` and 1.6% below it on `field`, which is to say
58//! inside that band and not outside it.
59//!
60//! The safepoint's own schedule does not move by one instruction either.
61//! [ADR 0040](../../../../docs/adr/0040-a-bound-outlives-its-backend.md)
62//! states every stop mode's bound in multiples of the stride, and
63//! `crates/cove-runtime/tests/responsiveness.rs` measures each of them.
64//! `the_safepoint_fires_at_the_same_counts_as_it_did_before`, below, is that
65//! rule pinned at the instruction rather than left to the bounds.
66//!
67//! # What may be handed out
68//!
69//! `crates/cove-runtime/tests/representation_is_private.rs` is the arbiter,
70//! and it decided the shape of everything below: no slot, no layout id, no
71//! frame base, no word of VM storage leaves in a public signature. A frame
72//! word is named by its *position* ([`Local::at`]), a family by its *name*
73//! ([`Object::name`]), and a value by what it *renders as* — a `String` this
74//! crate produced, not a piece of the representation that produced it.
75//!
76//! One raw word does cross, in one direction only: [`Stop::object`] takes a
77//! word a [`Word`] view already showed and answers what it names, for the VM
78//! development the sketch asks for. It is not a handle in the sense
79//! ADR 0031 forbids — nothing roots it, nothing stores it, it is not valid
80//! after the run, and a word that names no object answers `None` rather than
81//! misbehaving.
82//!
83//! [`Local::words`] widens *which* words a [`Word`] view can show and not
84//! what one is: the words a name covers, in the same projection as the words
85//! no name covers, so that a named reference can be followed the way an
86//! unnamed one already could. The argument above is unchanged by it, which
87//! is the test of whether it belonged here.
88//!
89//! One number that is not a representation crosses too. [`Stop::task`] is
90//! the id `crate::trace` writes on its events, and it is here because
91//! everything else a [`Stop`] answers — the count, the depth, the frames —
92//! belongs to one task and nothing said which. It names a task; it is not a
93//! way to reach one.
94
95use cove_diag::Span;
96use cove_ir::{print, FunctionId, Pc};
97
98use crate::error::RuntimeError;
99use crate::trace::RunOutcome;
100use crate::vm::exec::Machine;
101use crate::vm::render;
102
103/// What a debugger says when the machine asks.
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105pub enum Resume {
106    /// Run the instruction the stop is at, and go on.
107    Go,
108    /// End the run here, as a stop and not as a failure of the program.
109    Halt,
110}
111
112/// Something that watches a run, one instruction at a time.
113///
114/// The machine's whole contract with it is [`Debugger::at`]: while a debugger
115/// is installed the machine asks before every instruction, and honours the
116/// answer. **Every policy is the implementor's** — a breakpoint is an `at`
117/// that answers `Go` until the pc is one it is looking for, a step is one
118/// that counts, a `finish` is one that watches the depth — because a policy
119/// in the loop is a policy that cannot be changed without touching the loop.
120///
121/// `Send + Sync` because a spawned task's machine is handed the same
122/// reference and asks it from that task's own thread. A debugger that keeps
123/// state therefore keeps it behind a lock or an atomic, as any two threads
124/// sharing anything must.
125pub trait Debugger: Send + Sync {
126    /// Called before each instruction while this debugger is installed.
127    fn at(&self, stop: &Stop<'_>) -> Resume;
128}
129
130/// One instruction's worth of standing still.
131///
132/// It borrows the machine for the length of the call and nothing longer,
133/// which is the whole of why the call is inverted. Every method answers with
134/// an owned snapshot, so a debugger keeps what it asks for and holds nothing
135/// of the machine.
136///
137/// The frame's `pc` is truthful here: the dispatch loop syncs it before
138/// asking, which it does not do between instructions, and a view built
139/// anywhere else would name the instruction after the one being stopped at.
140pub struct Stop<'m> {
141    machine: &'m Machine<'m>,
142    function: FunctionId,
143    pc: usize,
144}
145
146/// One frame the debugger shows, which is not always one the machine pushed.
147///
148/// `function` and `base` say where the *words* are; `pc` says which of that
149/// function's instructions this frame is at; `named` says whose body those
150/// instructions are, which is `function` itself unless `lower::inline`
151/// expanded one here; `span` is where this frame's own call was written; and
152/// `locals` is the table of names to read at that pc, which for an expanded
153/// body is the one the expansion recorded rather than the function's own.
154struct Frame<'p> {
155    function: FunctionId,
156    base: u64,
157    pc: Pc,
158    named: FunctionId,
159    span: Span,
160    locals: &'p [cove_ir::Local],
161}
162
163impl<'m> Stop<'m> {
164    /// The stop the dispatch loop is at, with `pc` already synced.
165    pub(crate) fn new(machine: &'m Machine<'m>, function: FunctionId, pc: usize) -> Stop<'m> {
166        Stop {
167            machine,
168            function,
169            pc,
170        }
171    }
172
173    /// How many instructions this task has run, this one included.
174    ///
175    /// The count [`crate::Vm::instructions`] reports, read at the moment the
176    /// instruction about to run was counted. It is *this task's*, and
177    /// [`Stop::task`] is what says whose.
178    pub fn instructions(&self) -> u64 {
179        self.machine.instructions()
180    }
181
182    /// Words this task's heap has handed out, reuse counted each time.
183    ///
184    /// Read at the moment the instruction about to run was counted, so the
185    /// difference between two consecutive stops is what the instruction
186    /// between them allocated. That is how [`crate::vm::profile::Profiler`]
187    /// attributes a heap to the code that asked for it: there is no seam
188    /// inside `Memory::alloc` that knows which instruction it is serving, and
189    /// a difference needs none.
190    pub fn allocated_words(&self) -> u64 {
191        self.machine.allocated_words()
192    }
193
194    /// Objects this task's heap has handed out, reuse counted each time.
195    ///
196    /// Beside [`Stop::allocated_words`] because allocating often and
197    /// allocating large are different faults with different repairs.
198    pub fn allocations(&self) -> u64 {
199        self.machine.allocations()
200    }
201
202    /// Which task this stop is in.
203    ///
204    /// Everything else a `Stop` answers is one task's — the count, the
205    /// depth, the frames — because a spawned task runs on a machine of its
206    /// own, and until this was here a debugger could not tell two of them
207    /// apart. A policy stated in frame depth is then a policy that a second
208    /// task can satisfy by accident: a `step` asked in the entry, finished
209    /// by an instruction of a task the entry spawned.
210    ///
211    /// The number is [`crate::ENTRY_TASK`] for the entry and a spawned
212    /// task's own id otherwise, which is to say it is the number
213    /// `crate::trace`'s events carry under `task`. That is deliberate and it
214    /// is the whole of the choice here: a debugger and a trace of the same
215    /// run must name the same task the same way, or a person holding both
216    /// has to work out the correspondence themselves.
217    ///
218    /// It is opaque. Two stops with the same id are the same task and two
219    /// with different ids are not; nothing else about the number is
220    /// promised, and no ordering of it means anything.
221    pub fn task(&self) -> u64 {
222        self.machine.task()
223    }
224
225    /// `module.name` of the function this stop is in.
226    ///
227    /// The function the *source* would say it is in, which is not always the
228    /// one whose frame the machine pushed: an instruction inside a body
229    /// `lower::inline` expanded belongs to the body that was written, and a
230    /// session that named the caller would tell a person their breakpoint had
231    /// stopped somewhere they had not asked about. `Function::inlined` is what
232    /// says otherwise, and [`Stop::function_id`] is deliberately the other
233    /// answer.
234    ///
235    /// A `String`, built here, because that is what a session prints and a
236    /// name is what a person reads. A caller that will look the function up
237    /// again — a profiler counting instructions, above all — wants
238    /// [`Stop::function_id`] instead: this allocates, and a debugger that
239    /// stops at every instruction would allocate at every instruction.
240    pub fn function(&self) -> String {
241        let program = self.machine.program();
242        let named = program
243            .function(self.function)
244            .inlined_at(self.pc as Pc)
245            .last()
246            .map_or(self.function, |held| held.callee);
247        program.function(named).qualified()
248    }
249
250    /// Which function this stop is in, as the program names it.
251    ///
252    /// The identity rather than the name: two stops in one function answer
253    /// the same id, and an id indexes `Program::functions` — so a caller can
254    /// hold one per stop without holding a string per stop.
255    ///
256    /// It is the *machine's* answer and not the source's: an instruction of an
257    /// expanded body reports the function whose frame and whose code hold it,
258    /// where [`Stop::function`] reports the body that was written. A profiler
259    /// keys a count by this and prints `function+pc`, and a pc is a counter of
260    /// the function this names; naming the callee there would make the pair
261    /// disagree.
262    pub fn function_id(&self) -> FunctionId {
263        self.function
264    }
265
266    /// Which instruction of that function is about to run.
267    ///
268    /// A counter of the function whose *code* holds it, which for an
269    /// instruction inside an expanded body is the caller's rather than the
270    /// body's: `lower::inline` wrote the body there and there is no other
271    /// numbering. So a stop reported as `m.inner` at pc 1 is not `m.inner`'s
272    /// second instruction, it is `m.inner`'s first, standing at `m.outer`'s
273    /// counter 1. [`Stop::code`] numbers the same way, which is what keeps a
274    /// listing and the pc beside it agreeing.
275    pub fn pc(&self) -> u32 {
276        self.pc as u32
277    }
278
279    /// Where that instruction was written.
280    pub fn span(&self) -> Span {
281        self.machine
282            .program()
283            .function(self.function)
284            .span_at(self.pc)
285    }
286
287    /// How many calls are live, this one included.
288    ///
289    /// An expanded body counts. `lower::inline` writes a small leaf's
290    /// instructions into its caller's code and pushes no frame for them, and
291    /// a debugger that counted only the frames the machine pushed would say a
292    /// stop inside such a body was a stop in the caller — `finish` would run
293    /// past the body it was asked to finish, `next` would step over nothing,
294    /// and a backtrace would be one name short. `Function::inlined` is what
295    /// says otherwise, and this is the one place the count comes from.
296    ///
297    /// Counted rather than built, because `State::wanted` asks this at every
298    /// stop of a stepping session and the frames themselves are only wanted
299    /// when something is shown.
300    pub fn depth(&self) -> usize {
301        let program = self.machine.program();
302        self.machine
303            .calls()
304            .iter()
305            .enumerate()
306            .map(|(at, (id, _, pc))| {
307                let function = program.function(*id);
308                1 + function.inlined_at(Self::shown(at, *pc)).count()
309            })
310            .sum()
311    }
312
313    /// Every frame the debugger shows, innermost first — which is not every
314    /// frame the machine pushed.
315    ///
316    /// One real frame becomes one shown frame per expanded body its pc is
317    /// inside, plus itself. Each of them reads the *same* words of the *same*
318    /// frame, because that is where an expansion put them; what differs is
319    /// whose code the pc belongs to, which name to print, and where the call
320    /// below it was written.
321    ///
322    /// The pcs walk outwards the way the ranges nest. The innermost shown
323    /// frame is at the pc the machine is at; each one further out is at the
324    /// first counter of the body below it, which is where that body's call
325    /// stood before it was expanded — the exact analogue of a suspended real
326    /// frame being shown at its call rather than at its resume address.
327    fn shown_frames(&self) -> Vec<Frame<'m>> {
328        let program = self.machine.program();
329        let mut held = Vec::new();
330        for (at, (id, base, pc)) in self.machine.calls().iter().enumerate() {
331            let function = program.function(*id);
332            let pc = Self::shown(at, *pc);
333            let ranges: Vec<_> = function.inlined_at(pc).collect();
334            // Innermost first: the deepest range's callee is stopped at `pc`,
335            // and every range further out is stopped where the range inside
336            // it begins.
337            let mut here = pc;
338            let mut span = function.span_at(here as usize);
339            for range in ranges.iter().rev() {
340                held.push(Frame {
341                    function: *id,
342                    base: *base,
343                    pc: here,
344                    named: range.callee,
345                    span,
346                    locals: &range.locals,
347                });
348                here = range.from;
349                span = range.site;
350            }
351            held.push(Frame {
352                function: *id,
353                base: *base,
354                pc: here,
355                named: *id,
356                span,
357                locals: &function.locals,
358            });
359        }
360        held
361    }
362
363    /// Every live call, innermost first.
364    ///
365    /// This renders every local of every frame — and every word of every
366    /// local — so a debugger that stops at every instruction and takes a
367    /// whole backtrace at each one is doing real work per instruction.
368    /// [`Stop::frame`] is the same view of one call, for a session that only
369    /// shows what it was asked for.
370    pub fn backtrace(&self) -> Vec<Call> {
371        // Built once and walked, rather than `frame(at)` per level: an
372        // expanded body is not a frame the machine holds, so a shown frame
373        // has to be worked out rather than indexed, and asking for the `at`th
374        // of them works out the first `at` on the way.
375        self.shown_frames().iter().map(|f| self.call(f)).collect()
376    }
377
378    /// The call `at` levels out from this one, or `None` past the outermost.
379    ///
380    /// A frame that is not the innermost is suspended at *the instruction
381    /// after* the call that led one level deeper, and that resume address is
382    /// not where the frame *is*. The instruction it names is whatever runs
383    /// next — frequently the next statement's, or the `return` the body ends
384    /// with — so a line built from it points away from the call as often as
385    /// at it, and a name the call site had in scope may already have gone out
386    /// of it. `- 1` is always the call itself, because a `pc` is only ever
387    /// synced after the instruction it dispatched.
388    ///
389    /// This is the rule `Machine::calls`'s own documentation states for the
390    /// error chain, which had made the same choice for the same reason and
391    /// left this view alone. Issue #302 is what settled that the two should
392    /// agree: with the copy after a call gone, the resume address in a
393    /// one-expression body *is* the `return`, and a suspended frame answered
394    /// that its caller's locals were out of scope.
395    ///
396    /// The innermost frame keeps its own `pc`. It is not suspended at a
397    /// resume address — the machine is about to execute the instruction it
398    /// names — so there is nothing to look back past.
399    pub fn frame(&self, at: usize) -> Option<Call> {
400        Some(self.call(&self.shown_frames().into_iter().nth(at)?))
401    }
402
403    /// The pc a frame is *shown* at, out of the pc the machine holds for it.
404    ///
405    /// They are the same for the innermost frame and differ by one for every
406    /// other, for the reason [`Stop::frame`] gives. It is one function so
407    /// that the backtrace and the disassembly cannot answer differently.
408    fn shown(at: usize, pc: Pc) -> Pc {
409        match at {
410            0 => pc,
411            _ => pc.saturating_sub(1),
412        }
413    }
414
415    /// What the word `at` names, if it names an object of this run's heap.
416    ///
417    /// The one place a raw word crosses, and it crosses inward: what comes
418    /// back is a rendered snapshot. It is for the view VM development wants —
419    /// a [`Word`] showed an address, and this says what is there — and it
420    /// promises nothing about what a word means. A word that is not an
421    /// object this memory holds answers `None`.
422    pub fn object(&self, at: u64) -> Option<Object> {
423        let (name, fields) = render::parts(self.machine, at)?;
424        Some(Object {
425            name,
426            fields: fields
427                .into_iter()
428                .map(|(name, value)| Field { name, value })
429                .collect(),
430        })
431    }
432
433    /// The instructions around frame `at`'s pc, `reach` either side of it,
434    /// or nothing past the outermost frame.
435    ///
436    /// The disassembly a session shows beside a stop.
437    /// [`cove_ir::print::one`] renders each, which is the same rendering
438    /// `cove ir` prints, so a debugger and a dump do not disagree about what
439    /// an instruction is called.
440    ///
441    /// `at` names a frame the way [`Stop::frame`] names one, and for the
442    /// same reason: a session that lets a person select a frame has to be
443    /// able to show that frame's code, and one that could only ever
444    /// disassemble the innermost would answer `frame 2` with frame 0's
445    /// instructions. It is a parameter here rather than a method on [`Call`]
446    /// because a `Call` is an owned snapshot: giving it a disassembly would
447    /// mean rendering every instruction of every live function at every
448    /// stop, and a backtrace is already the expensive view.
449    ///
450    /// The pc it reads is the one [`Stop::frame`] reports, which for a
451    /// suspended frame is the call it is waiting on rather than the resume
452    /// address after it. The two panes of a session are one view: a
453    /// backtrace naming a line and a disassembly marking a different
454    /// instruction would be the debugger disagreeing with itself.
455    pub fn code(&self, at: usize, reach: usize) -> Vec<Line> {
456        let Some(frame) = self.shown_frames().into_iter().nth(at) else {
457            return Vec::new();
458        };
459        let frame_pc = frame.pc as usize;
460        let program = self.machine.program();
461        let function = program.function(frame.function);
462        // Both ends saturate. Only `from` did at first, which reads as a
463        // decision and was an oversight: on a 32-bit target — which
464        // `wasm32-unknown-unknown` is — a `reach` of `u32::MAX` made
465        // `pc + reach + 1` wrap to zero, and the answer became the empty
466        // range `0..pc` rather than the whole function. `cargo test` on a
467        // 64-bit host cannot see it. A caller asking for "all of it" by
468        // naming a very large reach is the obvious way to ask, so this
469        // answers it here rather than leaving each caller to know the width
470        // of a `usize` on the target it will run on.
471        let from = frame_pc.saturating_sub(reach);
472        let to = frame_pc
473            .saturating_add(reach)
474            .saturating_add(1)
475            .min(function.code.len());
476        (from..to)
477            .map(|pc| Line {
478                pc: pc as u32,
479                text: print::one(program, function, &function.code[pc]),
480                span: function.span_at(pc),
481                current: pc == frame_pc,
482            })
483            .collect()
484    }
485
486    /// One frame, projected.
487    fn call(&self, frame: &Frame) -> Call {
488        let (base, pc) = (frame.base, frame.pc);
489        let program = self.machine.program();
490        let function = program.function(frame.function);
491        let mut named = vec![false; function.frame_size() as usize];
492        let mut locals = Vec::new();
493        for local in frame.locals {
494            if !(local.from <= pc && pc < local.to) {
495                continue;
496            }
497            let width = program.layout(local.layout).width();
498            let words = self.machine.frame_run(base, local.slot, width);
499            for word in local.slot..(local.slot + width).min(function.frame_size()) {
500                named[word as usize] = true;
501            }
502            locals.push(Local {
503                name: local.name.to_string(),
504                value: render::lossy(self.machine, local.layout, &words),
505                at: local.slot,
506                width,
507                words: words
508                    .iter()
509                    .enumerate()
510                    .map(|(offset, raw)| self.word(function, local.slot + offset as u32, *raw))
511                    .collect(),
512            });
513        }
514        // What no name covers, read as the frame itself describes it. This is
515        // the VM development view: a compiler temporary, a slot whose live
516        // range has ended, a word the lowering wrote and no source name ever
517        // held.
518        let words = named
519            .iter()
520            .enumerate()
521            .filter(|(_, named)| !**named)
522            .map(|(at, _)| {
523                let at = at as u32;
524                let raw = self.machine.frame_run(base, at, 1)[0];
525                self.word(function, at, raw)
526            })
527            .collect();
528        Call {
529            function: program.function(frame.named).qualified(),
530            within: function.qualified(),
531            pc,
532            span: frame.span,
533            locals,
534            words,
535        }
536    }
537
538    /// One word of a frame, read as the frame itself says it should be.
539    ///
540    /// The same projection whether a name covers the word or nothing does,
541    /// which is what lets [`Local::words`] and [`Call::words`] be one type:
542    /// a position, what the frame says is in it, the word, and that word
543    /// rendered. A frame that says nothing about a word — one past the
544    /// declared frame, which the lowering does not produce — is read as an
545    /// `Int`, because a raw word shown as a number is the least a reader can
546    /// be told and it is still true.
547    fn word(&self, function: &cove_ir::Function, at: u32, raw: u64) -> Word {
548        let repr = function.repr(at).unwrap_or(cove_ir::Repr::Int);
549        Word {
550            at,
551            holds: repr.name(),
552            raw,
553            value: render::raw(self.machine, repr, raw),
554        }
555    }
556}
557
558/// One live call, as a debugger sees it.
559#[derive(Clone, Debug)]
560pub struct Call {
561    function: String,
562    within: String,
563    pc: Pc,
564    span: Span,
565    locals: Vec<Local>,
566    words: Vec<Word>,
567}
568
569impl Call {
570    /// `module.name` of the function running here.
571    ///
572    /// The body that was *written*, which for a frame `lower::inline`
573    /// expanded is the leaf and not the function that holds its
574    /// instructions. [`Call::within`] is that other answer.
575    pub fn function(&self) -> &str {
576        &self.function
577    }
578
579    /// `module.name` of the function whose code [`Call::pc`] is a counter of.
580    ///
581    /// The same as [`Call::function`] for a frame the machine pushed, and the
582    /// *caller* for one that is an expanded body: the expansion wrote the
583    /// leaf's instructions into the caller and there is no other numbering
584    /// for them.
585    ///
586    /// A reader showing a disassembly needs both, and showing one under the
587    /// other's name is the mistake this exists to stop — a pane titled
588    /// `playground.twice` holding `playground.main`'s four instructions,
589    /// which is what a table keyed on [`Call::function`] alone produced.
590    pub fn within(&self) -> &str {
591        &self.within
592    }
593
594    /// Where in it this call is: the instruction about to run for the
595    /// innermost call, and the one to return to for every other.
596    ///
597    /// A counter of [`Call::within`], not of [`Call::function`].
598    pub fn pc(&self) -> Pc {
599        self.pc
600    }
601
602    /// Where that instruction was written.
603    pub fn span(&self) -> Span {
604        self.span
605    }
606
607    /// The names the source bound that are in scope at this pc, in
608    /// declaration order.
609    ///
610    /// One name may appear twice. Shadowing is *recorded* rather than
611    /// resolved — see [`cove_ir::Local`] — so `let x = 1; let x = x + 41` is
612    /// two live bindings of two words, and both are here. [`Call::local`] is
613    /// what chooses between them.
614    pub fn locals(&self) -> &[Local] {
615        &self.locals
616    }
617
618    /// The frame's own words that no name in scope covers.
619    ///
620    /// For a frame that is an expanded body, that includes every word the
621    /// *caller* holds. They are in the same physical frame — an expansion
622    /// appends the callee's run to the caller's rather than pushing one — and
623    /// they are not names this body bound, which is exactly what this reports:
624    /// a word no name in scope covers, whoever else may have a name for it.
625    pub fn words(&self) -> &[Word] {
626        &self.words
627    }
628
629    /// The local called `name` that the source means at this pc, if one is
630    /// in scope here.
631    ///
632    /// **The last match wins**, which is
633    /// [`cove_ir::Function::local_at`]'s rule and is the rule because the
634    /// lowering resolves a name by searching its scope backwards. Two
635    /// bindings of one name are live at once and the later one is what the
636    /// source at this pc means; taking the first match would answer with the
637    /// *shadowed* binding, which is a debugger that is wrong about the value
638    /// of a name exactly where a person is most likely to ask.
639    ///
640    /// The earlier binding is not hidden — it is still in [`Call::locals`],
641    /// where a reader can see both — because it is still in the frame, and a
642    /// view of the machine that quietly dropped a word would be a worse
643    /// lie than a view that shows two.
644    pub fn local(&self, name: &str) -> Option<&Local> {
645        self.locals.iter().rev().find(|local| local.name == name)
646    }
647}
648
649/// One name the source bound, and what it holds.
650#[derive(Clone, Debug)]
651pub struct Local {
652    name: String,
653    value: String,
654    at: u32,
655    width: u32,
656    words: Vec<Word>,
657}
658
659impl Local {
660    /// What the source called it.
661    pub fn name(&self) -> &str {
662        &self.name
663    }
664
665    /// What it holds, rendered.
666    ///
667    /// Always an answer, which is the whole difference from a boundary
668    /// crossing. A value that could not be
669    /// read carries a marker saying which way it could not — `<reclaimed>`,
670    /// `<cycle>`, `<case 7 of 3>` — rather than being absent.
671    pub fn value(&self) -> &str {
672        &self.value
673    }
674
675    /// Which word of the frame it begins at.
676    ///
677    /// A position, not a name of anything this crate owns: it is what lets a
678    /// session say *the same word as that one* without being handed the type
679    /// the machine indexes frames by.
680    pub fn at(&self) -> u32 {
681        self.at
682    }
683
684    /// How many words it occupies. A value is a run of words, so a name
685    /// covers `width` of them from [`Local::at`], and [`Local::words`] is
686    /// that run.
687    pub fn width(&self) -> u32 {
688        self.width
689    }
690
691    /// The words the name covers, read as the frame says they should be.
692    ///
693    /// [`Local::value`] is what the name holds *rendered*, and that is where
694    /// a reader stops. This is where a debugger does not: a name bound to a
695    /// vector renders as its elements and holds a reference, and until this
696    /// was here there was no way to get from the name to the reference —
697    /// [`Stop::object`] follows a word, and [`Call::words`] by construction
698    /// excludes every word a name covers. So `print xs` could show a vector
699    /// and nothing could then look at the object, which is a hole in a
700    /// debugger rather than a missing convenience.
701    ///
702    /// It is the same [`Word`] view [`Call::words`] hands out, and
703    /// deliberately: a word of a frame is a word of a frame whether a name
704    /// covers it or not, and a second shape for the same thing would be a
705    /// second thing to keep true. `words().len()` is [`Local::width`], and
706    /// the first of them is at [`Local::at`].
707    pub fn words(&self) -> &[Word] {
708        &self.words
709    }
710}
711
712/// One word of a frame.
713///
714/// [`Call::words`] hands out the ones no name in scope covers, which is what
715/// the view exists for; [`Local::words`] hands out the ones a name does. The
716/// projection is the same either way — a position, what the frame says is in
717/// it, the word, and that word rendered — because whether a name happens to
718/// cover a word does not change what the word is.
719#[derive(Clone, Debug)]
720pub struct Word {
721    at: u32,
722    holds: &'static str,
723    raw: u64,
724    value: String,
725}
726
727impl Word {
728    /// Which word of the frame it is.
729    pub fn at(&self) -> u32 {
730        self.at
731    }
732
733    /// What the frame says is in it — `Int`, `Ref`, `Duration`.
734    pub fn holds(&self) -> &'static str {
735        self.holds
736    }
737
738    /// The word itself.
739    pub fn raw(&self) -> u64 {
740        self.raw
741    }
742
743    /// The word read as what the frame says it is. A reference renders as
744    /// the address it is and is not followed; [`Stop::object`] is what
745    /// follows one.
746    pub fn value(&self) -> &str {
747        &self.value
748    }
749}
750
751/// One object of the run's heap, rendered.
752#[derive(Clone, Debug)]
753pub struct Object {
754    name: String,
755    fields: Vec<Field>,
756}
757
758impl Object {
759    /// What the family it belongs to is called.
760    pub fn name(&self) -> &str {
761        &self.name
762    }
763
764    /// Its parts, named the way its family names them: a struct's fields by
765    /// their source names, a run of elements by its indices, an enum by
766    /// `case` and then its payload's positions.
767    pub fn fields(&self) -> &[Field] {
768        &self.fields
769    }
770}
771
772/// One named part of an object.
773#[derive(Clone, Debug)]
774pub struct Field {
775    name: String,
776    value: String,
777}
778
779impl Field {
780    /// What the part is called.
781    pub fn name(&self) -> &str {
782        &self.name
783    }
784
785    /// What it holds, rendered.
786    pub fn value(&self) -> &str {
787        &self.value
788    }
789}
790
791/// One line of disassembly.
792#[derive(Clone, Debug)]
793pub struct Line {
794    pc: Pc,
795    text: String,
796    span: Span,
797    current: bool,
798}
799
800impl Line {
801    /// Which instruction of the function it is.
802    pub fn pc(&self) -> Pc {
803        self.pc
804    }
805
806    /// The instruction, as `cove ir` prints it.
807    pub fn text(&self) -> &str {
808        &self.text
809    }
810
811    /// Where it was written.
812    pub fn span(&self) -> Span {
813        self.span
814    }
815
816    /// Whether it is the one the stop is at.
817    pub fn current(&self) -> bool {
818        self.current
819    }
820}
821
822/// The error a run ends with when a debugger answers [`Resume::Halt`].
823///
824/// A stop and not a failure of the program's work, which is why it is
825/// classified as [`RunOutcome::Debugger`] and not as an invariant a program
826/// broke. `every_stop_mode_is_reported_as_itself_on_both_backends` in
827/// `crates/cove-runtime/tests/responsiveness.rs` is the shape being kept:
828/// each way a run can stop reports itself and not something else.
829pub(crate) fn halted(span: Span) -> RuntimeError {
830    RuntimeError::new("a debugger stopped this run")
831        .at(span)
832        .with_rule("A debugger may halt a run at any instruction, and the run ends there.")
833        .with_outcome(RunOutcome::Debugger)
834}
835
836#[cfg(test)]
837pub(crate) mod tests {
838    use std::collections::BTreeMap;
839    use std::path::PathBuf;
840    use std::sync::atomic::{AtomicU64, Ordering};
841    use std::sync::{Arc, Mutex};
842
843    use cove_diag::SourceMap;
844    use cove_sema::config::Config;
845    use cove_sema::package::{Module, Package, Unit};
846    use cove_sema::resolve::Program as Checked;
847
848    use super::*;
849    use crate::budget::{Budget, Limits};
850    use crate::host::{Grants, HostRegistry};
851    use crate::runtime::Runtime;
852    use crate::vm::exec::SAFEPOINT_STRIDE;
853    use crate::vm::Vm;
854
855    /// A program that runs for longer than two safepoint strides.
856    const LOOP: &str = "
857export fn main() -> Int {
858  var total = 0
859  var i = 0
860  while i < 2000 {
861    total = total + i
862    i = i + 1
863  }
864  total
865}
866";
867
868    /// Three functions, a name bound in each, and — since `lower::inline`
869    /// expands a small leaf and then repeats — one frame at run time, with
870    /// `inner` written into `outer` and `outer` into `main`. That is what
871    /// these cases want: what a debugger says about a stop that has no frame
872    /// of its own is exactly the question.
873    const NESTED: &str = "
874fn inner(a: Int) -> Int {
875  let doubled = a * 2
876  doubled
877}
878
879fn outer(b: Int) -> Int {
880  let raised = b + 1
881  inner(raised)
882}
883
884export fn main() -> Int {
885  outer(20)
886}
887";
888
889    /// One name bound twice in one frame, so that two bindings of it are
890    /// live at the same time.
891    const SHADOWED: &str = "
892export fn main() -> Int {
893  let total = 0
894  let total = total + 20
895  total
896}
897";
898
899    /// A name bound to something on the heap, for following a reference the
900    /// frame holds by the name that holds it.
901    const ARRAY: &str = "
902export fn main() -> Int {
903  let items = [10, 20, 30]
904  items.length()
905}
906";
907
908    /// Two tasks, so that two machines ask the same debugger.
909    const SPAWNED: &str = "
910export fn main() -> Int {
911  var total = 0
912  scope tasks {
913    let first = tasks.spawn { 42 }
914    total = await first
915  }
916  total
917}
918";
919
920    /// Everything one run needs, held together so that the borrows it takes
921    /// of each other outlive the [`Vm`] built on them.
922    pub(crate) struct World {
923        hosts: Arc<HostRegistry>,
924        runtime: Runtime,
925        program: cove_ir::Program,
926    }
927
928    impl World {
929        pub(crate) fn new(source: &str) -> World {
930            let (sources, checked) = checked(source);
931            let program = lowered(&sources, &checked);
932            let hosts = Arc::new(HostRegistry::new(Grants::new(Vec::<&str>::new())));
933            let runtime = Runtime::new(checked, sources, hosts.clone());
934            World {
935                hosts,
936                runtime,
937                program,
938            }
939        }
940
941        /// A run nothing is watching.
942        pub(crate) fn plain(&self) -> Vm<'_> {
943            Vm::new(&self.runtime, &self.hosts, &self.program)
944        }
945
946        /// A run `debugger` is watching.
947        pub(crate) fn watched<'w>(&'w self, debugger: &'w dyn Debugger) -> Vm<'w> {
948            Vm::debugged(&self.runtime, &self.hosts, &self.program, debugger)
949        }
950    }
951
952    /// Parses, resolves and checks one module called `m`.
953    fn checked(source: &str) -> (Arc<SourceMap>, Arc<Checked>) {
954        let mut sources = SourceMap::new();
955        let file = sources.add("m/main.cove", source.to_string());
956        let ast = cove_syntax::parse_file(&sources, file).expect("the fixture parses");
957        let mut modules = BTreeMap::from([(
958            "m".to_string(),
959            Module {
960                name: "m".to_string(),
961                dir: PathBuf::from("m"),
962                units: vec![Unit {
963                    file,
964                    path: PathBuf::from("m/main.cove"),
965                    ast,
966                }],
967            },
968        )]);
969        for (name, module) in cove_sema::stdlib::attach(&mut sources).expect("stdlib parses") {
970            modules.insert(name, module);
971        }
972        let package = Package {
973            root: PathBuf::from("."),
974            config: Config::default(),
975            modules,
976        };
977        let program = cove_sema::Compiler::new()
978            .compile(&package)
979            .expect("the fixture checks");
980        (Arc::new(sources), Arc::new(program))
981    }
982
983    fn lowered(sources: &SourceMap, checked: &Checked) -> cove_ir::Program {
984        cove_ir::lower(checked, sources, &cove_schema::HostSchemas::new())
985            .expect("the fixture lowers")
986    }
987
988    /// A debugger that writes down what it was shown and always says go.
989    #[derive(Default)]
990    struct Seen(Mutex<Vec<u64>>);
991
992    impl Debugger for Seen {
993        fn at(&self, stop: &Stop<'_>) -> Resume {
994            self.0.lock().expect("a lock").push(stop.instructions());
995            Resume::Go
996        }
997    }
998
999    /// A debugger that halts once the run has got somewhere.
1000    struct HaltAt(u64);
1001
1002    impl Debugger for HaltAt {
1003        fn at(&self, stop: &Stop<'_>) -> Resume {
1004            match stop.instructions() >= self.0 {
1005                true => Resume::Halt,
1006                false => Resume::Go,
1007            }
1008        }
1009    }
1010
1011    /// A debugger that takes one backtrace, the first time it is deep enough.
1012    #[derive(Default)]
1013    struct Deepest {
1014        depth: AtomicU64,
1015        taken: Mutex<Option<Vec<Call>>>,
1016    }
1017
1018    impl Debugger for Deepest {
1019        fn at(&self, stop: &Stop<'_>) -> Resume {
1020            let depth = stop.depth() as u64;
1021            if depth > self.depth.load(Ordering::SeqCst) {
1022                self.depth.store(depth, Ordering::SeqCst);
1023                *self.taken.lock().expect("a lock") = Some(stop.backtrace());
1024            }
1025            Resume::Go
1026        }
1027    }
1028
1029    /// **A debugger is asked before every instruction, in order, and the
1030    /// count it is shown is the run's own.**
1031    ///
1032    /// The whole of what the machine promises: not "often enough", not "at
1033    /// every call" — every instruction, once, counted the way
1034    /// [`Vm::instructions`] counts.
1035    #[test]
1036    fn a_debugger_is_asked_once_before_every_instruction_in_order() {
1037        let world = World::new(NESTED);
1038        let seen = Seen::default();
1039        let mut vm = world.watched(&seen);
1040        let answer = vm.invoke("m", "main", Vec::new()).expect("the run answers");
1041
1042        assert_eq!(format!("{answer}"), "42");
1043        let counts = seen.0.lock().expect("a lock").clone();
1044        assert_eq!(
1045            counts.len() as u64,
1046            vm.instructions(),
1047            "one question per instruction the run dispatched"
1048        );
1049        let expected: Vec<u64> = (1..=vm.instructions()).collect();
1050        assert_eq!(counts, expected, "in order, and each one once");
1051    }
1052
1053    /// **A debugger that halts ends the run there, and the run reports
1054    /// itself as the debugger's stop.**
1055    ///
1056    /// Its own outcome, the way every other stop mode has one: a run
1057    /// somebody was stepping through did not break an invariant and did not
1058    /// run out of anything.
1059    #[test]
1060    fn halting_ends_the_run_and_is_reported_as_the_debugger_s_own_stop() {
1061        let world = World::new(LOOP);
1062        let halt = HaltAt(500);
1063        let mut vm = world.watched(&halt);
1064        let error = vm
1065            .invoke("m", "main", Vec::new())
1066            .expect_err("a halted run does not answer");
1067
1068        assert_eq!(error.outcome, crate::trace::RunOutcome::Debugger);
1069        assert_eq!(
1070            vm.instructions(),
1071            500,
1072            "the instruction it halted at was counted and not run"
1073        );
1074    }
1075
1076    /// **A backtrace names the functions it is standing in, innermost
1077    /// first, and a local of an outer frame is found by the name the source
1078    /// gave it.**
1079    ///
1080    /// This is the projection the whole of part 3 exists for, and none of
1081    /// what it answers is a piece of the machine: a qualified name, a pc, and
1082    /// a rendered value.
1083    #[test]
1084    fn a_backtrace_names_its_calls_and_finds_a_local_of_an_outer_frame() {
1085        let world = World::new(NESTED);
1086        let deepest = Deepest::default();
1087        let mut vm = world.watched(&deepest);
1088        vm.invoke("m", "main", Vec::new()).expect("the run answers");
1089
1090        let frames = deepest
1091            .taken
1092            .lock()
1093            .expect("a lock")
1094            .clone()
1095            .expect("the run stopped somewhere");
1096        let names: Vec<&str> = frames.iter().map(Call::function).collect();
1097        assert_eq!(names, vec!["m.inner", "m.outer", "m.main"]);
1098
1099        let outer = &frames[1];
1100        let b = outer.local("b").expect("`outer`'s parameter is in scope");
1101        assert_eq!(b.value(), "20");
1102        assert_eq!(b.width(), 1, "an `Int` is one word");
1103        let raised = outer.local("raised").expect("its `let` is in scope too");
1104        assert_eq!(raised.value(), "21");
1105        assert_ne!(
1106            b.at(),
1107            raised.at(),
1108            "two names in scope at once are two positions"
1109        );
1110    }
1111
1112    /// **A stop can say where it is, and read the instruction it is at.**
1113    ///
1114    /// The other half of what a session shows beside a frame: the pc, and
1115    /// the disassembly around it, rendered the way `cove ir` renders it.
1116    #[test]
1117    fn a_stop_reads_the_instruction_it_is_about_to_run() {
1118        /// Keeps the first stop inside `m.inner`.
1119        #[derive(Default)]
1120        struct First(Mutex<Option<(String, u32, Vec<Line>)>>);
1121
1122        impl Debugger for First {
1123            fn at(&self, stop: &Stop<'_>) -> Resume {
1124                let mut held = self.0.lock().expect("a lock");
1125                if held.is_none() && stop.function() == "m.inner" {
1126                    *held = Some((stop.function(), stop.pc(), stop.code(0, 2)));
1127                }
1128                Resume::Go
1129            }
1130        }
1131
1132        let world = World::new(NESTED);
1133        let first = First::default();
1134        let mut vm = world.watched(&first);
1135        vm.invoke("m", "main", Vec::new()).expect("the run answers");
1136
1137        let held = first.0.lock().expect("a lock").clone();
1138        let (function, pc, code) = held.expect("the run entered `m.inner`");
1139        assert_eq!(function, "m.inner");
1140        // Not the callee's zero, because `m.inner` is a small leaf and
1141        // `lower::inline` wrote its body into `m.outer` — and then wrote
1142        // `m.outer`, which had become a leaf itself, into `m.main`. There is
1143        // no frame of its own for a counter to be zero of, and the counter
1144        // this reports is one of `m.main`'s. [`Stop::function`] names the
1145        // innermost body that was written and [`Stop::pc`] numbers the code
1146        // that holds it, which are two answers on purpose — and what this
1147        // case is about is that they agree with the listing, which is the
1148        // next assertion.
1149        assert_eq!(pc, 2, "`m.inner`'s body begins at `m.main`'s counter 2");
1150        let current: Vec<&Line> = code.iter().filter(|line| line.current()).collect();
1151        assert_eq!(current.len(), 1, "exactly one line is the one stopped at");
1152        assert_eq!(current[0].pc(), pc);
1153        assert!(
1154            !current[0].text().is_empty(),
1155            "an instruction renders as something"
1156        );
1157    }
1158
1159    /// **A debugger changes what a run does not at all.**
1160    ///
1161    /// Same answer, same instruction count, watched or not. A machine whose
1162    /// work depended on whether anybody was looking would be a debugger that
1163    /// could not be trusted about the run it was shown.
1164    #[test]
1165    fn watching_a_run_changes_neither_its_answer_nor_its_work() {
1166        let world = World::new(LOOP);
1167        let mut plain = world.plain();
1168        let alone = plain.invoke("m", "main", Vec::new()).expect("it answers");
1169        let instructions = plain.instructions();
1170
1171        let seen = Seen::default();
1172        let mut watched = world.watched(&seen);
1173        let watched_answer = watched.invoke("m", "main", Vec::new()).expect("it answers");
1174
1175        assert_eq!(format!("{alone}"), format!("{watched_answer}"));
1176        assert_eq!(instructions, watched.instructions());
1177    }
1178
1179    /// **The safepoint fires at exactly the instruction counts it fired at
1180    /// before, whether a debugger is installed or not.**
1181    ///
1182    /// `SAFEPOINT_STRIDE` is contract arithmetic:
1183    /// `docs/adr/0040-a-bound-outlives-its-backend.md` states every stop
1184    /// mode's bound in multiples of it and `tests/responsiveness.rs`
1185    /// measures each one, so folding the debugger's question into the
1186    /// loop's one comparison may not move the schedule by a single
1187    /// instruction.
1188    ///
1189    /// The instrument is the fuel limit, because a safepoint is the only
1190    /// place fuel is charged: a run whose budget cannot survive its first
1191    /// charge stops at exactly the instruction the first safepoint is at,
1192    /// and one that can survive that but not the second stops at the second.
1193    #[test]
1194    fn the_safepoint_fires_at_the_same_counts_as_it_did_before() {
1195        let world = World::new(LOOP);
1196        for (fuel, expected) in [
1197            (1, SAFEPOINT_STRIDE),
1198            (SAFEPOINT_STRIDE + 1, 2 * SAFEPOINT_STRIDE),
1199        ] {
1200            let limits = Limits {
1201                fuel: Some(fuel),
1202                ..Limits::default()
1203            };
1204            let mut vm = world.plain();
1205            let error = vm
1206                .run_entry_within(Budget::new(limits.clone()), "m", "main", Vec::new())
1207                .expect_err("a run out of fuel does not answer");
1208            assert_eq!(error.outcome, crate::trace::RunOutcome::Fuel);
1209            assert_eq!(
1210                vm.instructions(),
1211                expected,
1212                "unwatched, under a fuel limit of {fuel}"
1213            );
1214
1215            let seen = Seen::default();
1216            let mut vm = world.watched(&seen);
1217            let error = vm
1218                .run_entry_within(Budget::new(limits), "m", "main", Vec::new())
1219                .expect_err("a run out of fuel does not answer");
1220            assert_eq!(error.outcome, crate::trace::RunOutcome::Fuel);
1221            assert_eq!(
1222                vm.instructions(),
1223                expected,
1224                "watched, under a fuel limit of {fuel}"
1225            );
1226        }
1227    }
1228
1229    /// **A stop reads the frame the machine is really in, not the one it was
1230    /// in an instruction ago.**
1231    ///
1232    /// `frame.pc` is a local of the dispatch loop between safepoints and is
1233    /// only made truthful by `Machine::sync`. A debugger asked before the
1234    /// sync would report the pc of the previous instruction, which is the
1235    /// one bug in this whole arrangement that would not show up as a crash.
1236    #[test]
1237    fn the_pc_a_stop_reports_is_the_one_the_top_frame_holds() {
1238        /// Records the pc twice: as the stop says it, and as the innermost
1239        /// frame of the backtrace says it.
1240        #[derive(Default)]
1241        struct Both(Mutex<Vec<(u32, u32)>>);
1242
1243        impl Debugger for Both {
1244            fn at(&self, stop: &Stop<'_>) -> Resume {
1245                let innermost = stop.frame(0).expect("a stop is inside a call");
1246                self.0
1247                    .lock()
1248                    .expect("a lock")
1249                    .push((stop.pc(), innermost.pc()));
1250                Resume::Go
1251            }
1252        }
1253
1254        let world = World::new(NESTED);
1255        let both = Both::default();
1256        let mut vm = world.watched(&both);
1257        vm.invoke("m", "main", Vec::new()).expect("the run answers");
1258
1259        let seen = both.0.lock().expect("a lock").clone();
1260        assert!(!seen.is_empty(), "the run stopped at least once");
1261        for (stop, frame) in seen {
1262            assert_eq!(stop, frame, "the frame's pc was synced before the question");
1263        }
1264    }
1265
1266    /// **A name bound twice in one frame is answered by the later binding,
1267    /// and the earlier one is still there to be seen.**
1268    ///
1269    /// `cove_ir::Function::local_at` is the rule and the reason: shadowing
1270    /// is recorded rather than resolved, so `let total = 0; let total =
1271    /// total + 20` is two live bindings of two words, and the lowering
1272    /// resolves a name by searching its scope *backwards*. A view that took
1273    /// the first match would answer `total` with the value the source
1274    /// stopped meaning one line earlier — wrong, and wrong silently, since
1275    /// both answers are numbers.
1276    ///
1277    /// The method is pinned here and not only in `cove debug`'s own suite
1278    /// because every reader of a stop calls it: the CLI worked around the
1279    /// first-match rule by reading `Call::locals` backwards itself, and the
1280    /// next reader would have had to know to do the same.
1281    #[test]
1282    fn a_shadowed_name_is_answered_by_the_binding_the_source_means() {
1283        /// Keeps the last frame in which `total` was bound twice at once.
1284        #[derive(Default)]
1285        struct Shadow(Mutex<Option<Call>>);
1286
1287        impl Debugger for Shadow {
1288            fn at(&self, stop: &Stop<'_>) -> Resume {
1289                if let Some(call) = stop.frame(0) {
1290                    let bound = call
1291                        .locals()
1292                        .iter()
1293                        .filter(|local| local.name() == "total")
1294                        .count();
1295                    if bound == 2 {
1296                        *self.0.lock().expect("a lock") = Some(call);
1297                    }
1298                }
1299                Resume::Go
1300            }
1301        }
1302
1303        let world = World::new(SHADOWED);
1304        let shadow = Shadow::default();
1305        let mut vm = world.watched(&shadow);
1306        let answer = vm.invoke("m", "main", Vec::new()).expect("the run answers");
1307        assert_eq!(format!("{answer}"), "20");
1308
1309        let call = shadow
1310            .0
1311            .lock()
1312            .expect("a lock")
1313            .clone()
1314            .expect("both bindings of `total` were live at once");
1315        let both: Vec<&Local> = call
1316            .locals()
1317            .iter()
1318            .filter(|local| local.name() == "total")
1319            .collect();
1320        assert_eq!(both.len(), 2, "shadowing is recorded, not resolved");
1321        assert_ne!(both[0].at(), both[1].at(), "two bindings are two words");
1322        assert_eq!(both[0].value(), "0", "the shadowed binding is still there");
1323
1324        let meant = call.local("total").expect("`total` is in scope");
1325        assert_eq!(
1326            meant.at(),
1327            both[1].at(),
1328            "the later declaration is the one the source means"
1329        );
1330        assert_eq!(meant.value(), "20");
1331    }
1332
1333    /// **A stop says which task it is in, and the id is the one a trace
1334    /// writes.**
1335    ///
1336    /// Everything else a stop answers is one task's — the instruction count,
1337    /// the depth, the frames — because a spawned task runs on a machine of
1338    /// its own. Without this, a policy stated in frame depth can be
1339    /// satisfied by a task nobody was stepping in, and a count can be read
1340    /// as the run's when it is one thread's.
1341    ///
1342    /// The count is checked per task rather than the id alone, because the
1343    /// id would be worth nothing if the things it names were not really that
1344    /// task's: each machine counts from one, so the first stop of every task
1345    /// is its own first instruction.
1346    #[test]
1347    fn a_stop_names_the_task_it_is_in_and_a_spawned_task_is_not_the_entry() {
1348        /// The lowest instruction count seen in each task.
1349        #[derive(Default)]
1350        struct Whose(Mutex<BTreeMap<u64, u64>>);
1351
1352        impl Debugger for Whose {
1353            fn at(&self, stop: &Stop<'_>) -> Resume {
1354                let mut seen = self.0.lock().expect("a lock");
1355                let first = seen.entry(stop.task()).or_insert(u64::MAX);
1356                *first = (*first).min(stop.instructions());
1357                Resume::Go
1358            }
1359        }
1360
1361        let world = World::new(SPAWNED);
1362        let whose = Whose::default();
1363        let mut vm = world.watched(&whose);
1364        let answer = vm.invoke("m", "main", Vec::new()).expect("the run answers");
1365        assert_eq!(format!("{answer}"), "42");
1366
1367        let seen = whose.0.lock().expect("a lock").clone();
1368        assert_eq!(
1369            seen.len(),
1370            2,
1371            "the entry and the task it spawned are two tasks, not one"
1372        );
1373        assert!(
1374            seen.contains_key(&crate::runtime::ENTRY_TASK),
1375            "the entry names itself the way a trace names it"
1376        );
1377        for (task, first) in seen {
1378            assert_eq!(first, 1, "task {task} counts its own instructions from one");
1379        }
1380    }
1381
1382    /// **A reach that would overflow answers the whole function, on any
1383    /// pointer width.**
1384    ///
1385    /// Asking for "all of it" by naming a very large reach is the obvious
1386    /// way to ask, and it has no other spelling. Only `from` saturated at
1387    /// first, which reads as a decision and was an oversight: on a 32-bit
1388    /// target `pc + reach + 1` wraps, and the answer becomes an *empty*
1389    /// listing rather than a full one — a failure that a 64-bit `cargo test`
1390    /// cannot reach and that `crates/cove-wasm` met in a browser.
1391    #[test]
1392    fn a_reach_that_would_overflow_answers_the_whole_function() {
1393        #[derive(Default)]
1394        struct Whole(Mutex<Option<(Vec<Line>, Vec<Line>)>>);
1395
1396        impl Debugger for Whole {
1397            fn at(&self, stop: &Stop<'_>) -> Resume {
1398                let mut held = self.0.lock().expect("a lock");
1399                if held.is_none() && stop.function() == "m.inner" {
1400                    *held = Some((stop.code(0, usize::MAX), stop.code(0, usize::MAX / 2)));
1401                }
1402                Resume::Go
1403            }
1404        }
1405
1406        let world = World::new(NESTED);
1407        let whole = Whole::default();
1408        let mut vm = world.watched(&whole);
1409        vm.invoke("m", "main", Vec::new()).expect("the run answers");
1410
1411        let held = whole.0.lock().expect("a lock").clone();
1412        let (widest, half) = held.expect("the run entered `m.inner`");
1413        assert!(
1414            !widest.is_empty(),
1415            "the widest reach there is answered nothing"
1416        );
1417        assert_eq!(
1418            widest.len(),
1419            half.len(),
1420            "two reaches past the end of the function answered different listings"
1421        );
1422        assert_eq!(
1423            widest.first().expect("a line").pc(),
1424            0,
1425            "the whole function starts at its first instruction"
1426        );
1427    }
1428
1429    /// **A disassembly is of the frame it was asked for, and the outermost
1430    /// is the last one there is.**
1431    ///
1432    /// A session that lets a person select a frame has to be able to show
1433    /// that frame's code. Reading the stopping function's would answer
1434    /// `frame 2` with frame 0's instructions — a listing that looks right,
1435    /// is wrong, and says nothing about which frame it is of.
1436    ///
1437    /// Frame 2 and not frame 1 for the "two listings are two" half of it.
1438    /// Frame 1 is `m.outer` and frame 0 is `m.inner` expanded *into*
1439    /// `m.outer`, so the two are one instruction stream and a disassembly of
1440    /// either shows the same instructions with the same one marked. There is
1441    /// nothing else they could honestly show — what an expansion removed was
1442    /// the frame, not the code — so the case needs a frame the machine
1443    /// actually pushed, and `m.main` is one.
1444    #[test]
1445    fn a_disassembly_is_of_the_frame_it_was_asked_for() {
1446        /// The first stop inside `m.inner`: the innermost frame's code, the
1447        /// outermost's, that frame's own pc, and a frame that is not there.
1448        #[derive(Default)]
1449        #[allow(clippy::type_complexity)]
1450        struct Frames(Mutex<Option<(Vec<Line>, Vec<Line>, Pc, Vec<Line>)>>);
1451
1452        impl Debugger for Frames {
1453            fn at(&self, stop: &Stop<'_>) -> Resume {
1454                let mut held = self.0.lock().expect("a lock");
1455                if held.is_none() && stop.function() == "m.inner" {
1456                    let caller = stop.frame(2).expect("`m.outer` was called from `m.main`");
1457                    *held = Some((
1458                        stop.code(0, 2),
1459                        stop.code(2, 2),
1460                        caller.pc(),
1461                        stop.code(9, 2),
1462                    ));
1463                }
1464                Resume::Go
1465            }
1466        }
1467
1468        let world = World::new(NESTED);
1469        let frames = Frames::default();
1470        let mut vm = world.watched(&frames);
1471        vm.invoke("m", "main", Vec::new()).expect("the run answers");
1472
1473        let held = frames.0.lock().expect("a lock").clone();
1474        let (innermost, outer, caller_pc, past) = held.expect("the run entered `m.inner`");
1475        let marked = |lines: &[Line]| {
1476            let found: Vec<Line> = lines
1477                .iter()
1478                .filter(|line| line.current())
1479                .cloned()
1480                .collect();
1481            assert_eq!(found.len(), 1, "exactly one line is the one stopped at");
1482            found[0].clone()
1483        };
1484
1485        assert_eq!(
1486            marked(&innermost).pc(),
1487            2,
1488            "`m.inner`'s body begins at `m.main`'s counter 2"
1489        );
1490        assert_eq!(
1491            marked(&outer).pc(),
1492            caller_pc,
1493            "the caller is shown at the pc it will return to"
1494        );
1495        assert_ne!(
1496            marked(&outer).text(),
1497            marked(&innermost).text(),
1498            "two frames of two functions are two instructions"
1499        );
1500        assert!(past.is_empty(), "there is no ninth frame to disassemble");
1501    }
1502
1503    /// **A name hands over the words it covers, so a reference a name holds
1504    /// can be followed into the heap.**
1505    ///
1506    /// `Stop::object` follows a word, and `Call::words` by construction
1507    /// shows only the words no name covers. Between the two there was a hole
1508    /// exactly where a debugger is most used: a name bound to a vector
1509    /// rendered as its elements, and nothing could then ask what the object
1510    /// was. The word is the same `Word` view an unnamed word gets, because a
1511    /// word of a frame does not change shape by being named.
1512    #[test]
1513    fn a_local_hands_over_its_word_so_a_named_reference_can_be_followed() {
1514        /// The first stop at which `items` holds something.
1515        #[derive(Default)]
1516        #[allow(clippy::type_complexity)]
1517        struct Follow(Mutex<Option<(u32, u32, u32, String, Option<Object>)>>);
1518
1519        impl Debugger for Follow {
1520            fn at(&self, stop: &Stop<'_>) -> Resume {
1521                let mut held = self.0.lock().expect("a lock");
1522                if held.is_some() {
1523                    return Resume::Go;
1524                }
1525                let Some(call) = stop.frame(0) else {
1526                    return Resume::Go;
1527                };
1528                let Some(items) = call.local("items") else {
1529                    return Resume::Go;
1530                };
1531                let [word] = items.words() else {
1532                    return Resume::Go;
1533                };
1534                if word.raw() == 0 {
1535                    return Resume::Go;
1536                }
1537                *held = Some((
1538                    items.at(),
1539                    items.width(),
1540                    word.at(),
1541                    word.holds().to_string(),
1542                    stop.object(word.raw()),
1543                ));
1544                Resume::Go
1545            }
1546        }
1547
1548        let world = World::new(ARRAY);
1549        let follow = Follow::default();
1550        let mut vm = world.watched(&follow);
1551        let answer = vm.invoke("m", "main", Vec::new()).expect("the run answers");
1552        assert_eq!(format!("{answer}"), "3");
1553
1554        let held = follow.0.lock().expect("a lock").clone();
1555        let (at, width, word_at, holds, object) = held.expect("`items` held a reference");
1556        assert_eq!(width, 1, "a reference is one word");
1557        assert_eq!(word_at, at, "the name's first word is the name's position");
1558        assert_eq!(holds, "ref", "the frame says the word is a reference");
1559
1560        let object = object.expect("the word names an object of this run's heap");
1561        let values: Vec<&str> = object.fields().iter().map(Field::value).collect();
1562        assert_eq!(
1563            values,
1564            vec!["10", "20", "30"],
1565            "the object `items` names is the array the source wrote"
1566        );
1567    }
1568
1569    /// **A run of a program with no debugger is the run it always was.**
1570    ///
1571    /// The regression this whole part is about: `Vm::new`'s signature is
1572    /// unchanged, and so is what it produces.
1573    #[test]
1574    fn an_unwatched_run_answers_what_it_always_did() {
1575        let world = World::new(NESTED);
1576        let mut vm = world.plain();
1577        let answer = vm.invoke("m", "main", Vec::new()).expect("it answers");
1578        assert_eq!(format!("{answer}"), "42");
1579        assert!(vm.instructions() > 0);
1580    }
1581}