Skip to main content

cove_runtime/vm/
exec.rs

1//! The machine, and everything one instruction can reach.
2//!
3//! One `Machine` runs one task's frames over one [`Memory`]. It is a
4//! register machine: every instruction names its operands and its
5//! destination by slot, and a slot is `memory[frame_base + slot]`.
6//!
7//! # The loop is not here
8//!
9//! [`encoded::dispatch`] is the loop, and it is the only one. Issue #245's
10//! Phase 5 cut production execution over to
11//! [ADR 0041](../../../../docs/adr/0041-a-slot-number-fits-in-sixteen-bits.md)'s
12//! fixed-width form and deleted the `Inst` loop that had been beside it, so
13//! the pipeline reads:
14//!
15//! ```text
16//! checked AST -> lowering -> cove_ir::Inst -> encoder -> bytecode -> verify once -> this machine
17//! ```
18//!
19//! [`cove_ir::Inst`] is still the *compiler's* vocabulary — what the lowering
20//! builds, what `cove_ir::print` renders, what a test asserts on, and what
21//! [`crate::vm::debug`] shows a debugger as lowered IR — and it is no longer
22//! anything the machine matches on. What is left in this file is the state
23//! an instruction acts on and every operation one can reach: the frames, the
24//! calls, the host boundary, the builtins, the scopes, the tasks and the
25//! collector's rendezvous. The loop reads sixteen bytes and calls into here.
26//!
27//! The cutover was not taken for speed and did not buy any; the commit that
28//! made it says what it cost and what it bought instead.
29//!
30//! # There is no Rust recursion here
31//!
32//! A Cove call pushes a frame onto [`Machine::frames`] and continues the same
33//! loop. Nothing about a call grows the native stack, so how deep a Cove
34//! program may recurse is decided by [`STACK_WORDS`] alone rather than by how
35//! large a Rust stack frame the dispatch loop happens to compile to — which
36//! is a number that changes when an unrelated instruction is added.
37//!
38//! # There is no `Value` here
39//!
40//! Ordinary Cove-to-Cove execution moves words and heap objects. The public
41//! `Value` is built at the boundary — a Host call, an entry's answer, a trace
42//! capture — and nowhere else. There is no operand `Vec<Value>`, no argument
43//! buffer, no spill area and no fallback path, which is what ADR 0034 asks
44//! for and what the predecessor could not say.
45
46use std::sync::{Arc, Mutex};
47use std::thread::{Scope, ScopedJoinHandle};
48use std::time::Duration;
49
50use cove_diag::Span;
51use cove_ir::{
52    ArgsId, ArithOp, BuiltinId, CmpOp, FunctionId, HostOpId, LayoutId, Program, Repr, Shape, Slot,
53    StrId,
54};
55
56use crate::budget::{Cancellation, Meter, Stopped};
57use crate::error::RuntimeError;
58use crate::host::{HostRegistry, Reentry, ResourceHandle};
59use crate::interp::stopped_here;
60use crate::runtime::{Runtime, ENTRY_TASK};
61use crate::task;
62use crate::trace::TraceEvent;
63use crate::vm::builtins::operand::Operand;
64use crate::vm::debug::{halted, Debugger, Resume, Stop};
65use crate::vm::mem::{Collected, Memory, NoSegment, Overflow, Parked, Rooted, Roots};
66use crate::vm::{boundary, builtins, cell};
67use crate::wallclock::Instant;
68// The one import of the public `Value` outside `boundary`, and the one thing
69// ADR 0034 allows it for: a host call's arguments and its answer exist as
70// `Value`s for the length of the call and nowhere else. Nothing here stores
71// one, and the three places it is named are all in transit — the vector
72// handed to the boundary, the callee a way back is offered, and a callback's
73// arguments and answer, which are the same boundary crossed the other way
74// round and are converted by the same file.
75use crate::value::Value;
76
77/// The dispatch loop, and the refusal that keeps it honest.
78///
79/// A **child** module rather than a sibling, and that is still load-bearing:
80/// it reads `Machine`'s private state without widening any of it to the
81/// crate. The loop over the same machine is exactly as privileged as the
82/// machine, and nothing else is.
83pub(crate) mod encoded;
84
85/// How many instructions run between two budget checks.
86///
87/// A budget check reads an atomic and a clock, and doing that per instruction
88/// would cost more than most instructions do. Doing it per *call* would let a
89/// tight arithmetic loop run unbounded. A fixed stride is the arrangement that
90/// bounds both: the run notices a cancellation within a known number of
91/// instructions, whatever it is doing.
92///
93/// It is public because it is the arithmetic of a contract rather than a
94/// tuning knob.
95/// [ADR 0040](../../../../docs/adr/0040-a-bound-outlives-its-backend.md)
96/// states every bound a stop mode promises in terms of it, and
97/// `crates/cove-runtime/tests/responsiveness.rs` measures each of them, so
98/// moving this number moves a stated maximum and costs both.
99pub const SAFEPOINT_STRIDE: u64 = 1024;
100
101/// How many [`crate::vm::builtins::operand::Operand`]s
102/// [`Machine::call_builtin`] holds inline before it spills to a `Vec`.
103///
104/// Sized for an ordinary fixed-arity builtin — a receiver and a couple of
105/// arguments — which is every call except the handful `cove-schema` declares
106/// `variadic: true` (`Vector.of`, `Map.of`, `Set.of`), and those spill
107/// instead of raising this for everyone else's sake.
108const INLINE_OPERANDS: usize = 8;
109
110/// Payload word 0 of a [`Shape::ByteBuffer`] owner: how many of its store's
111/// bytes are value.
112const BUFFER_LEN: u32 = 0;
113
114/// Payload word 1 of a [`Shape::ByteBuffer`] owner: the [`Shape::Bytes`] store
115/// holding them, whose own header length is the capacity.
116const BUFFER_STORE: u32 = 1;
117
118/// The smallest store [`Inst::AllocBuffer`] asks for, and the floor a growth
119/// doubles up from.
120///
121/// [ADR 0052](../../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)
122/// says "twice the capacity from a small floor" and leaves the floor to the
123/// storage unit. `seq.rs`'s `MIN_CAPACITY` is four *elements*; sixteen is the
124/// byte-sized answer to the same question, and the reason it is not four is
125/// that four bytes is less than one word. A byte store packs eight bytes to a
126/// word and costs a header word whatever it holds, so a capacity below eight
127/// buys nothing at all and a capacity of eight buys one growth's worth of
128/// nothing: sixteen is two payload words, which covers the punctuation-sized
129/// appends a formatter makes between the ones that are worth reallocating for.
130const MIN_BUFFER_BYTES: u64 = 16;
131
132/// A live byte buffer: its owner, its store, and how much of the store is
133/// value rather than spare room.
134///
135/// `seq.rs`'s `Growable` for bytes, and the same three-part reading of ADR
136/// 0052's one growable-run discipline — a stable owner, a replaceable store,
137/// and a live prefix `[0, len)` inside a capacity. `len` and `capacity` are
138/// both byte counts, as the owner's word 0 and the store's own header state
139/// them; there is no stride, because the storage unit is the byte.
140struct ByteBuffer {
141    owner: u64,
142    store: u64,
143    len: u32,
144    capacity: u32,
145}
146
147/// One live call.
148///
149/// The top of [`Machine::frames`] is the frame currently executing, not the
150/// caller of it. That costs a write of `pc` before anything that can collect
151/// or fail, and it buys a collector and an error reporter that need no
152/// special case for "and also the one in the local variables".
153struct Frame {
154    function: FunctionId,
155    /// The linear address of slot 0.
156    base: u64,
157    /// Where this frame resumes: the instruction after the call it is
158    /// suspended at, or the one about to run.
159    pc: u32,
160    /// The slot of the *caller's* frame this call's answer is written to.
161    dst: Slot,
162}
163
164/// What one task is holding, read where the collector asks rather than
165/// gathered in advance.
166///
167/// A safepoint runs every [`SAFEPOINT_STRIDE`] instructions and a collection
168/// is rare, so the walk has to cost nothing when the answer is that nothing
169/// is pending — and [`Memory::poll`] answers that with one relaxed load,
170/// *before* it asks for roots. Gathering into a `Vec` first would have paid
171/// for a pass over every reference slot of every live frame on the common
172/// path in order to save nothing on the rare one.
173///
174/// It borrows the machine immutably and reads its own memory, which is why
175/// every reader of it takes `&self`: a collection and a park are both things
176/// a task asks for about itself, and neither changes a frame.
177struct Live<'m, 'a>(&'m Machine<'a>);
178
179impl Roots for Live<'_, '_> {
180    fn each_root(&self, f: &mut dyn FnMut(u64)) {
181        let machine = self.0;
182        let program = machine.program;
183        for frame in &machine.frames {
184            let function = program.function(frame.function);
185            for slot in function.refs.iter() {
186                let word = machine.mem.slot(frame.base, slot);
187                if word != 0 {
188                    f(word);
189                }
190            }
191        }
192        for &addr in &machine.temps {
193            if addr != 0 {
194                f(addr);
195            }
196        }
197        // A cell this task is inside. It is already named by a `Repr::Ref`
198        // slot of the frame the lock region belongs to — the lowering holds
199        // the receiver for the whole region — so this adds nothing to what is
200        // reachable. What it adds is that the claim does not have to be made:
201        // [`Machine::give_cells_back`] *writes* the lock word of every one of
202        // these, on a path taken after the loop has stopped, and a word
203        // written into a run the sweep reclaimed would be a word of whatever
204        // is allocated there next.
205        for &addr in &machine.held {
206            f(addr);
207        }
208        // The scheduler table is a *root provider*, which is the whole of
209        // what keeps it from being the second value store ADR 0034 forbids:
210        // it holds the address of the object a task's answer goes into, and
211        // the object and its words are in the run's one heap like anything
212        // else. Nothing that wanted to dodge a heap representation could be
213        // put here, because an address is all there is room for.
214        for child in &machine.children {
215            if child.answer != 0 {
216                f(child.answer);
217            }
218            if child.closure != 0 {
219                f(child.closure);
220            }
221        }
222    }
223}
224
225/// What a task thread hands back: nothing, or why it stopped.
226///
227/// Nothing, because the value is not carried out — the parent allocated the
228/// object it goes into before the thread existed, and the child wrote its
229/// words there. Handing the words back through the join would have left them
230/// in a Rust `Vec`, which nothing the collector walks names, for as long as
231/// the join took.
232type Outcome = Result<(), RuntimeError>;
233
234/// What a spawned task has done so far.
235///
236/// [`crate::task::TaskState`] is the oracle's, and the four are the same
237/// four: a task ends by finishing, by failing, by being cancelled, or by
238/// breaking an invariant in its own thread — and the last is reported as the
239/// third or the second, exactly as [`crate::task::Task::join`] reports it.
240enum ChildState {
241    /// The body is running on its own thread, which has not been joined.
242    Running,
243    /// The body produced a value, and it is in the answer object.
244    Settled,
245    /// The body raised. Awaiting again raises the same error.
246    Failed(RuntimeError),
247    /// The task's own flag was raised and it stopped at a safepoint rather
248    /// than finishing. Awaiting a cancelled task is an error.
249    Cancelled,
250}
251
252/// One task this machine spawned, and everything about it that is not the
253/// thread.
254///
255/// The thread is not here, and cannot be: a scoped join handle borrows the
256/// scope it was started in, and that is a lifetime one turn of the dispatch
257/// loop owns rather than a field the machine can hold. So the handles live
258/// beside this list, at the same indices, for the length of one
259/// [`Machine::drive`].
260struct Child {
261    /// Trace identity, unique across the run.
262    id: u64,
263    /// Where in its scope's spawn order it is, counting from one.
264    position: usize,
265    /// The name of the scope that owns it.
266    ///
267    /// Both are here for one reason: a diagnostic says *task 2 of scope
268    /// `requests`*, and [`crate::task::describe`] is where both backends read
269    /// that sentence from.
270    scope: Arc<str>,
271    /// The flag the body reads at its own safepoints.
272    cancellation: Cancellation,
273    /// The closure environment the body runs, until it has been joined.
274    ///
275    /// Held as a root, and it has to be. The lowering ends the closure
276    /// temporary's live range at the `spawn` — correctly: the value is a
277    /// temporary and the instruction consumed it — so the moment the
278    /// `Inst::Clear` after the `Inst::Spawn` runs, nothing in the parent's
279    /// frame names the environment. The child has not necessarily read it
280    /// yet: ADR 0008's amendment says a `spawn` orders nothing, so whether
281    /// the thread has run an instruction by then is the operating system's
282    /// answer, and one allocation in the parent could otherwise free the
283    /// object the child is about to enter.
284    ///
285    /// The oracle has no such window because the closure crosses as a
286    /// `Transfer` — a copy the receiving thread owns outright. Here what
287    /// crosses is an address into a heap both tasks share, so the *table*
288    /// holds it, and this is the second thing that makes the scheduler table
289    /// a root provider.
290    ///
291    /// It is dropped at the join rather than at the child's first
292    /// instruction, because that is the earliest moment the parent can know
293    /// the child is done with it — and it retains nothing the child was not
294    /// already retaining, since the captures were copied into the child's
295    /// own frame.
296    closure: u64,
297    /// The object this task's answer is written into.
298    ///
299    /// Allocated by **this** task, before the thread existed, and a root of
300    /// this task from that moment. A child that allocated its own would have
301    /// left it named by nothing the collector walks between its own last
302    /// safepoint and the parent's next one.
303    answer: u64,
304    /// What the words inside that object are.
305    layout: LayoutId,
306    state: ChildState,
307}
308
309impl Child {
310    /// How a diagnostic names this task.
311    fn describe(&self) -> String {
312        task::describe(self.position, &self.scope)
313    }
314}
315
316/// One task scope this machine has entered.
317///
318/// The scope owns every task spawned into it, which is what lets leaving it
319/// wait for or cancel them. It is never removed: a `Repr::Scope` word is an
320/// index, and an index that could be reused would name two scopes over one
321/// run.
322struct ScopeEntry {
323    name: Arc<str>,
324    /// Indices into [`Machine::children`], in spawn order.
325    tasks: Vec<usize>,
326    /// Set once the scope has been left. A handle that outlived its scope can
327    /// no longer spawn into it.
328    closed: bool,
329}
330
331/// One task's execution over one linear memory.
332pub(crate) struct Machine<'a> {
333    program: &'a Program,
334    /// What every thread of this run shares: the trace, and the counter task
335    /// ids are drawn from.
336    ///
337    /// `None` is a machine with no run around it — what this module's own
338    /// tests drive — and it costs exactly two things, both of which are
339    /// reporting rather than execution: nothing is traced, and task ids are
340    /// drawn from a counter of this machine's own. A program still spawns,
341    /// awaits and cancels.
342    runtime: Option<&'a Runtime>,
343    /// The boundary a [`cove_ir::Inst::CallHost`] calls through, if this run
344    /// has one.
345    ///
346    /// `None` is a machine with no host behind it — what a test that runs
347    /// arithmetic drives, and the same state [`crate::host::NoReentry`]
348    /// exists for on the other side of the boundary. A program that reaches
349    /// a host call from one is told what is missing rather than being given a
350    /// registry that answers nothing.
351    hosts: Option<&'a HostRegistry>,
352    mem: Memory,
353    frames: Vec<Frame>,
354    /// The heap address of each [`StrId`], or the refusal
355    /// [`Machine::place_literals`] met trying to build one — held exactly as
356    /// [`Machine::encoded`] holds its own, because both are prepared once,
357    /// before anything runs, by a constructor that itself cannot fail.
358    ///
359    /// Placed by [`Machine::for_run`], once per run, before the first
360    /// instruction executes and therefore before any other heap object can
361    /// exist. [`Machine::for_task`] never places one: it is handed this
362    /// `Arc` and clones it, so every task of a run addresses the same
363    /// objects with no lock between them and no second placement to pay
364    /// for. The objects themselves are never collected — they sit below
365    /// [`Memory::seal_static`]'s floor, which a sweep never walks past — so
366    /// unlike the table this replaces, nothing here roots them: there is
367    /// nothing for a collector to be told. See
368    /// [ADR 0045](../../../../docs/adr/0045-a-literal-is-there-before-the-program-runs.md).
369    literal_addrs: Result<Arc<[u64]>, RuntimeError>,
370    /// The host resources this run has been handed, in the table a
371    /// [`Repr::Host`] word indexes.
372    ///
373    /// [`Repr::Host`]'s own documentation is what fixes the shape — *an index
374    /// into the run's host resource table* — and this is that table. It is
375    /// here and not in the heap because
376    /// [ADR 0031](../../../../docs/adr/0031-a-host-handle-is-not-a-vm-handle.md)
377    /// draws exactly this line: a host resource handle is a name the *host*
378    /// minted for something the host owns, and a heap object is a reference
379    /// into storage this run allocated. Only the second is the VM's. Making a
380    /// resource an object in the traced heap would put a collection in charge
381    /// of a lifetime [ADR 0013](../../../../docs/adr/0013-host-resource-handles.md)
382    /// gives to the host, and would mean sweeping something whose `close` the
383    /// program had not written.
384    ///
385    /// It is not a second value store either, which is the other thing
386    /// ADR 0034 and ADR 0031 forbid. Nothing a Cove program can write down
387    /// may be put in it: an entry is an [`Arc`] of a [`ResourceHandle`] — a
388    /// module, a type name, a number and a flag — and the only two operations
389    /// over it are [`Machine::resource`] and [`Machine::resource_word`],
390    /// neither of which can be handed a Cove value. A value that wanted to
391    /// avoid having a heap representation could not hide here.
392    ///
393    /// **The word is one past the index, so zero is no resource.** Frames are
394    /// zeroed on entry, so a `Host` slot that has not been written yet reads
395    /// zero exactly as a `Ref` slot reads null; a table indexed straight by
396    /// the word would answer an unwritten slot with whichever resource
397    /// happened to be first.
398    ///
399    /// Nothing is ever removed. ADR 0013 says a closed resource's handle
400    /// survives as a name for something that is gone, and that a host never
401    /// reuses an identity — so an entry that outlived its resource is still
402    /// the right answer to give, because the refusal a later call earns is
403    /// the *host's* and can only be reached by handing the host the name.
404    /// What that costs is one name per distinct resource this run was handed,
405    /// which is the size of the table the host is keeping anyway.
406    ///
407    /// It is a field of the machine, which today is the run: a run has one
408    /// machine as it has one [`Memory`]. When a run has task threads this
409    /// moves where the object heap moves, and for the reason ADR 0013 gives
410    /// rather than by analogy — a resource is owned by the *run*, not by the
411    /// task or the scope that opened it, so a handle one task was given is a
412    /// name every task of the run may hold.
413    ///
414    /// Shared by every task of the run, behind a lock, and that is not an
415    /// economy: a task-safe resource **crosses** a task boundary, and what
416    /// crosses is the word. A table of this task's own would make that word
417    /// an index into a list the receiving task does not have — so a handle a
418    /// parent opened would name whatever the child happened to open first,
419    /// or nothing. ADR 0013 says a resource is the *run's*, and this is what
420    /// that sentence costs.
421    resources: Arc<Mutex<Vec<Arc<ResourceHandle>>>>,
422    /// Objects a boundary conversion is holding and no frame names yet.
423    ///
424    /// A frame is a root because a static map says which of its slots are
425    /// references. A half-built object is not: it is reachable only from a
426    /// Rust local, which nothing walks, and the next allocation the
427    /// conversion makes could collect it out from under itself. So the
428    /// conversion says so, explicitly, for exactly as long as that is true —
429    /// [`Machine::push_temp`] to take a root, [`Machine::release_temps`] to
430    /// give every root back that was taken since a mark.
431    ///
432    /// It is a stack rather than a set because the discipline is lexical: a
433    /// conversion that recurses takes a mark on the way in and releases to it
434    /// on the way out, so nothing has to remember which root was whose.
435    temps: Vec<u64>,
436    /// The task scopes this machine has entered, in the order it entered
437    /// them. A `Repr::Scope` word is one past an index into this.
438    ///
439    /// This machine's, not the run's, and that is what the task-safety rule
440    /// buys: neither a `TaskScope` nor a `Task` may cross a task boundary, so
441    /// a word formed here is only ever read here. Two tasks cannot form one
442    /// another's handles, which is the same disjointness by construction that
443    /// keeps two stack segments apart.
444    scopes: Vec<ScopeEntry>,
445    /// The tasks this machine spawned, in spawn order across every scope. A
446    /// `Repr::Task` word is one past an index into this.
447    children: Vec<Child>,
448    /// This task's own cancellation flag, or `None` for the entry, which has
449    /// none.
450    ///
451    /// Separate from the run's, which lives in the [`Meter`] every task
452    /// shares: cancelling one task stops that task, and cancelling the run
453    /// stops everything. [`crate::interp::stopped_here`] is where the two a
454    /// *thread* owns are read, and it is the oracle's own function so that
455    /// neither backend can drift from the other's answer.
456    cancellation: Option<Cancellation>,
457    /// The flags of the bounded host calls this thread is inside, innermost
458    /// last.
459    ///
460    /// [`crate::host::Reentry::call_until`] pushes one: `clock.timeout` bounds
461    /// its body, and *"`stop` bounds this call and everything inside it,
462    /// including a further host call the body makes and any callback that host
463    /// runs in turn"*. So a safepoint reads these beside this task's own flag,
464    /// which is what makes a timeout a timeout rather than a measurement taken
465    /// afterwards. [`crate::interp::stopped_here`] is the oracle's own
466    /// function and asks both, so neither backend can drift from the other's
467    /// answer about which of the two stopped the work.
468    stops: Vec<Cancellation>,
469    /// How many host calls running a Cove callback are stacked on this
470    /// thread.
471    ///
472    /// A Cove call adds no native frame here — that is what the dispatch loop
473    /// is for — but a *reentry* does: the host is a Rust frame, and running
474    /// its callback puts another turn of [`encoded::dispatch`] below it. So
475    /// this is bounded exactly as the oracle bounds it, by
476    /// [`crate::interp::MAX_REENTRY_DEPTH`], and for the same reason.
477    reentry_depth: usize,
478    /// Which task this machine is running, for a trace and for the way back
479    /// a host is offered.
480    task: u64,
481    /// Where the next task id comes from when there is no [`Runtime`] to ask.
482    ///
483    /// Only a test reaches it. A run draws ids from one counter for the whole
484    /// run, because a task id is a trace identity and two tasks spawned at
485    /// the same time on two threads must not share one.
486    next_task: u64,
487    /// Instructions dispatched, exactly.
488    ///
489    /// This is an *observable*: `cove-bench` reports it, so does
490    /// `cove run --profile`, so does the debugger, and
491    /// [`crate::vm::profile`] asserts that its per-opcode totals sum to it.
492    /// It counts opcodes and nothing else, so a `copy-bytes` that moved a
493    /// megabyte is one, the same as an `add.int`.
494    ///
495    /// What a run is *charged* is [`Machine::work`], which is a different
496    /// question and now a different number.
497    instructions: u64,
498    /// Work charged beyond one per instruction.
499    ///
500    /// Only the bulk byte operations of
501    /// [ADR 0052](../../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)
502    /// add to it, one per payload word they move, which is that ADR's
503    /// "charged proportionally to the bytes or words examined".
504    /// [`Machine::work`] is `instructions + bulk_work` and is the coordinate
505    /// fuel, the safepoint schedule and every stop bound are stated in.
506    ///
507    /// It is an *offset* rather than a parallel counter for one measured
508    /// reason: the dispatch loop must keep exactly the one increment and the
509    /// one comparison it already had. Maintaining a second counter beside
510    /// `instructions` in the loop cost 3% on `examples/covefmt` — 4.05s
511    /// against 3.93s, interleaved, four runs an arm twice — which is the same
512    /// order as the 2.4% `docs/VM_ARCHITECTURE.md` measured for a second
513    /// per-instruction branch. Keeping it here means the loop is untouched
514    /// and `next_check` absorbs the offset instead, since it is recomputed
515    /// only when something charges in bulk.
516    bulk_work: u64,
517    /// The instruction count at which the loop next asks a question.
518    ///
519    /// The whole of what a debugger costs the dispatch loop, and it is
520    /// nothing: the loop's one comparison already existed, and this is the
521    /// same comparison against a number that answers *both* questions. With
522    /// no debugger installed it is [`SAFEPOINT_STRIDE`] past the last count
523    /// the run was charged at and the loop behaves exactly as it did while
524    /// every instruction cost one; with one installed it is
525    /// `instructions + 1`, so the machine asks before every instruction and
526    /// the safepoint still fires on its own schedule inside.
527    ///
528    /// `docs/VM_ARCHITECTURE.md`'s "The mechanism that turns a thing off can
529    /// cost more than the thing" is why it is not an `Option` tested per
530    /// instruction: a `bool` guarding the counter measured 2.4% on `arith`,
531    /// and *"the branch costs what the increment costs"*. There is no second
532    /// branch here to pay for, and the substitution was measured to cost
533    /// nothing: the same tree with this comparison put back to the modulo
534    /// measured 82.9 ms on `arith` against 82.7 ms with it. What did cost
535    /// something was writing the question itself into the loop, which is why
536    /// [`Machine::ask`] is a call. [`crate::vm::debug`] has the table.
537    next_check: u64,
538    /// How many of [`Machine::instructions`] have been handed to the run's
539    /// [`Meter`].
540    ///
541    /// The two counters differ by the work this machine has done and not yet
542    /// paid for, and every place that pays hands over exactly that difference
543    /// and sets this to the count it paid up to. There is no second
544    /// accumulator to keep in step with the instruction count, which is what
545    /// makes "the run is charged for every instruction it dispatched" a
546    /// subtraction rather than a claim about the paths somebody remembered.
547    ///
548    /// Three places move it, and between them they cover every way work can
549    /// be done. The periodic safepoint in [`encoded::dispatch`] is the
550    /// ordinary one. [`Machine::charge_at_host_boundary`] is
551    /// [ADR 0030](../../../../docs/adr/0030-a-host-call-asks-the-fuel-limit.md)'s:
552    /// the fuel a run has been charged has to be current before a Host call
553    /// asks whether it may begin. [`Machine::spend_pending_fuel`] is the last
554    /// one, at the end of a run or of a spawned task's thread, because a run
555    /// that raised, ran out of budget, was cancelled or was abandoned by the
556    /// host that bounded it leaves through Rust's `?` rather than through an
557    /// instruction and reaches no further safepoint —
558    /// [ADR 0024](../../../../docs/adr/0024-a-stop-is-a-bound-not-a-point.md)
559    /// says pending fuel is never lost, and this is the counter that makes
560    /// that checkable.
561    charged_work: u64,
562    /// How long this machine has spent inside host calls.
563    ///
564    /// The oracle charges the same measurement against every open timing
565    /// context so that a run can separate its own work from what it spent
566    /// waiting; this machine has one context, which is the run.
567    host_wait: Duration,
568    collected: Collected,
569    /// The `Shared` cells this task is inside, innermost last.
570    ///
571    /// `lock` is two instructions with a call between them, and the release is
572    /// an obligation on every exit path — which the lowering discharges on the
573    /// one it can write. This is the other one: a runtime error is not a jump
574    /// the lowering emits, so a cell a failing task never gave back would be a
575    /// cell no task could ever take again. It is the same division
576    /// [`Machine::stop_all`] is under for a task scope, and it costs a push
577    /// and a pop per `lock`.
578    ///
579    /// A `Vec` rather than a set because the discipline is lexical: two cells
580    /// nest, the refusal is per cell rather than per task, and the lowering
581    /// leaves the regions in the order it entered them.
582    held: Vec<u64>,
583    /// The layout of the value the host call now running was handed back by a
584    /// callback, if it ran one.
585    ///
586    /// This exists for one question the family search in
587    /// [`crate::vm::boundary`] cannot answer. A host answer that crosses in
588    /// at a `Shape::Boxed` position has to be tagged with the family it
589    /// holds, and the tag is what [`cove_ir::Inst::Unbox`] compares against
590    /// the layout
591    /// the checker settled at the use. The search reads the value's own
592    /// description, and a description does not always name one family:
593    /// `Err(Error("no"))` fits `Result<Int, Error>` and
594    /// `Result<http.Response, Error>` equally well, and the two are different
595    /// runs of words. Which one the search returned was then decided by which
596    /// the lowering happened to intern first.
597    ///
598    /// A callback's answer needs no search, because it is a value that just
599    /// left this machine. `clock.timeout` declares `Result<Any, Error>` and
600    /// wraps whatever its body answered, so what goes in the box is the
601    /// callback's return value and the family it belongs to is the callback's
602    /// declared return layout — a static fact, recorded here on the way out
603    /// so that the way back in does not have to guess at it.
604    ///
605    /// Cleared when a host call begins ([`Back::parked`]) and written when a
606    /// callback returns ([`Machine::call_from_host`]), so it names the
607    /// innermost call in progress and never an older one: a host call the
608    /// callback itself makes clears it on the way in and the callback's own
609    /// return writes it afterwards.
610    callback_answer: Option<LayoutId>,
611    /// Where the most recent assertion failed, and the message it produced.
612    ///
613    /// Written only by [`cove_ir::Inst::AssertFailed`], which the failing arm
614    /// of a
615    /// lowered assertion carries. A failed assertion is an ordinary `Err`
616    /// from here on — the machine does not stop, and a program that handles
617    /// it goes on running — so this is a record of what was seen and not a
618    /// state the run is in. A test runner reads it to point at the assertion
619    /// rather than at the test, and keeps the message so that it can tell
620    /// the `Err` it is holding from a later, unrelated one.
621    assertion_failure: Option<(Span, String)>,
622    /// The debugger this run asks before every instruction, if one is
623    /// installed.
624    ///
625    /// It is the *machine* that calls, never the other way round: nothing
626    /// here hands out a suspended machine, and [`Stop`] is a shared borrow
627    /// that lasts one call. That is not a style choice. [`encoded::dispatch`]
628    /// holds a `&'s Scope<'s, 'a>` — the thread scope a `spawn` starts its
629    /// children in — and that borrow cannot outlive [`Machine::drive`], so a
630    /// handle to a paused machine could not be a value a debugger keeps.
631    /// Inverting the call is what makes the borrow expressible.
632    ///
633    /// `Send + Sync`, because a spawned task's machine is handed the same
634    /// reference and asks it from its own thread.
635    debugger: Option<&'a (dyn Debugger + Send + Sync)>,
636    /// **The instructions this machine executes**, or why this program has
637    /// none.
638    ///
639    /// Issue #245's Phase 5. There is one execution form and this is it: the
640    /// program is encoded and verified once, when the machine is built, and
641    /// from then on the loop reads operands out of sixteen bytes without
642    /// asking whether they are in range. [`cove_ir::Inst`] is what the
643    /// *lowering* produces and what a listing and the debugger show; nothing
644    /// below
645    /// [`encoded::dispatch`] matches on one.
646    ///
647    /// A `Result` rather than a refusal raised where it is discovered,
648    /// because a machine is built by an infallible constructor and a program
649    /// with no encoding must fail the same way however the machine was built.
650    /// [`Machine::run`] and [`Machine::enter_closure`] raise it **before a
651    /// frame is pushed**, so such a program has no observable effect at all
652    /// rather than stopping partway through one. A machine that only ever
653    /// answers builtin questions — several of this crate's tests build one —
654    /// never asks, and pays nothing for a program it will not run.
655    ///
656    /// An `Arc` because a run's tasks share one: [`Machine::for_task`] is
657    /// handed the parent's rather than encoding again, which is what makes
658    /// one spawn cost a pointer instead of a second pass over the program.
659    encoded: Result<Arc<cove_ir::bytecode::Encoded>, RuntimeError>,
660    /// [`Machine::call_builtin`]'s scratch buffer of argument words, taken
661    /// out for the duration of one call and put back afterwards.
662    ///
663    /// A builtin call reads every argument's words into one buffer before it
664    /// can hand out an [`crate::vm::builtins::operand::Operand`] pointing
665    /// into it, and a fresh `Vec` for that on every single call is exactly
666    /// the allocation issue #268's stage 1 is about. Keeping one here and
667    /// moving it in and out with [`std::mem::take`], instead of borrowing it
668    /// in place, is what lets the call stay `&mut self` without a second
669    /// borrow of `self` alive at the same time for the body that fills it —
670    /// and it is also what makes the following safe to be wrong about.
671    ///
672    /// **The reentrancy question this answers.** Nothing reachable from
673    /// [`crate::vm::builtins::call`] — not the ~100-arm dispatch in
674    /// `builtins.rs`, nor `seq.rs`, `keyed.rs`, `key.rs`, `text.rs`,
675    /// `scalar.rs`, `make.rs`, or `equal.rs` — calls [`Machine::call_host`],
676    /// [`Machine::call_resource`], [`Machine::call_from_host`], or anything
677    /// else that runs the dispatch loop again: a builtin is a leaf call. The
678    /// AST interpreter's own builtin table
679    /// ([`crate::builtins::Callable`]) says the same thing of itself and
680    /// cites `docs/LINEAR_VM.md` for why, and the VM's higher-order
681    /// operations (`Result.mapError`, `map`, `sorted`) are lowered to
682    /// ordinary Cove-level loops that call back through `Inst::Call`, not
683    /// through `CallBuiltin`, so they never reach here at all. The one
684    /// genuinely reentrant path in this file, [`Machine::call_resource`] /
685    /// [`Machine::call_host`] parking the machine so a host call can run a
686    /// callback back through [`Machine::call_from_host`], is a different
687    /// method with its own local `values: Vec<Value>` and is never invoked
688    /// from inside [`Machine::call_builtin`].
689    ///
690    /// So today, taking this field's `Vec` out and putting it back is a
691    /// no-op around a leaf call. But it costs nothing to be wrong about
692    /// safely: because the buffer is moved out with `mem::take` rather than
693    /// borrowed, a `call_builtin` that somehow nested inside another one
694    /// finds `self.builtin_words` already emptied by the outer call and
695    /// allocates its own `Vec` rather than aliasing or clobbering the outer
696    /// call's words. And the restore keeps the *whole run* allocation-free
697    /// in that case too, not just the outer call: whichever of the outer
698    /// call's buffer and the inner call's buffer has the larger capacity is
699    /// the one left in this field, so the next call — nested or not — still
700    /// finds a buffer large enough not to grow.
701    builtin_words: Vec<u64>,
702    /// The same, for the words a builtin *answers* with.
703    ///
704    /// Every builtin used to build a fresh `Vec` for its answer, including
705    /// the ones that answer a single word: `Array.length` allocated a
706    /// one-element `Vec` and dropped it a few instructions later, once per
707    /// call. On `examples/covefmt` that was **39 ns of an 86 ns call** —
708    /// measured by adding a second such allocation to the path and watching
709    /// the call get 39 ns dearer — which is to say that nearly half of what
710    /// a builtin cost was the container its answer travelled home in.
711    ///
712    /// So a builtin writes into a buffer instead, and this is the one it
713    /// writes into. It is taken out and put back the way `builtin_words` is,
714    /// for the reasons that field's note gives at length; the two are
715    /// separate buffers because a builtin reads its operands out of the
716    /// first while it is filling the second.
717    builtin_answer: Vec<u64>,
718    /// The last case index each enum wrapper resolved to, and for which
719    /// layout.
720    ///
721    /// `make::some` and its three siblings find their case by *name* —
722    /// `Shape::Enum`'s cases are a run and `Layout::case` scans it comparing
723    /// strings — and they do it once per call. That was 11 ns of a 110 ns
724    /// `String.codePointAtByte`, which is a builtin the lexer in
725    /// `examples/covefmt` calls once per byte of the source it reads.
726    ///
727    /// A memo and not a table, because there is nothing to invalidate: a
728    /// [`Program`]'s layouts are fixed before its first instruction runs, so
729    /// a `(layout, name)` pair has one answer for the whole run. And one
730    /// entry per wrapper rather than a map, because the shape of the miss is
731    /// known — a loop calls one builtin with one result layout over and over,
732    /// so the entry it wants is the one it left there.
733    cases: [Option<(LayoutId, u32)>; 4],
734    /// How many words a value of each layout occupies, by [`LayoutId`].
735    ///
736    /// [`Machine::width`] was `program.layout(id).width()` — an index into
737    /// `Program::layouts`, then the length of that `Layout`'s `words` — and
738    /// the dispatch loop asks it fifteen times over, once per instruction
739    /// that names a value location. `Machine::call_builtin` asks it *twice
740    /// per argument*: once to copy the words out of the frame and once to
741    /// slice the buffer back into operands.
742    ///
743    /// Two chases became one index, which measured about 2.5 ns each — 10 ns
744    /// of a 98 ns `String.codePointAtByte`, a builtin the lexer in
745    /// `examples/covefmt` calls once per byte it reads.
746    ///
747    /// Built once, before the first instruction, because a [`Program`]'s
748    /// layouts are fixed by then: there is no invalidation to get wrong and
749    /// no entry that can be missing.
750    widths: Arc<[u32]>,
751}
752
753/// Which of [`Machine::cases`] a wrapper memoises into.
754///
755/// Four constants rather than a hash of the name: the callers are the four
756/// functions in [`crate::vm::builtins::make`] and nothing else, so the set is
757/// closed and naming it costs nothing at run time.
758#[derive(Clone, Copy)]
759pub(crate) enum Wrapper {
760    Some = 0,
761    None = 1,
762    Ok = 2,
763    Err = 3,
764}
765
766impl<'a> Machine<'a> {
767    /// A machine with no host boundary, for a program that calls none.
768    ///
769    /// Production always goes through [`Machine::for_run`], which a `Vm`
770    /// calls with the runtime it is part of; this and [`Machine::with_hosts`]
771    /// are the two narrower forms this crate's own tests build a bare
772    /// `Machine` from when a fixture has no runtime to hand one.
773    #[cfg_attr(not(test), allow(dead_code))]
774    pub(crate) fn new(program: &'a Program, heap_words: usize) -> Machine<'a> {
775        Machine::with_hosts(program, heap_words, None)
776    }
777
778    /// A machine that calls hosts through `hosts`, with nothing above it.
779    ///
780    /// See [`Machine::new`]: reached only from this crate's own tests, which
781    /// use it for a fixture that has hosts to call but no runtime.
782    #[cfg_attr(not(test), allow(dead_code))]
783    pub(crate) fn with_hosts(
784        program: &'a Program,
785        heap_words: usize,
786        hosts: Option<&'a HostRegistry>,
787    ) -> Machine<'a> {
788        Machine::for_run(program, heap_words, hosts, None)
789    }
790
791    /// The entry task of one run.
792    ///
793    /// Places every program literal into the heap before answering — see
794    /// [`Machine::place_literals`] and
795    /// [ADR 0045](../../../../docs/adr/0045-a-literal-is-there-before-the-program-runs.md).
796    /// A failure there is held on [`Machine::literal_addrs`] exactly as a
797    /// failure to encode the program is held on [`Machine::encoded`]: this
798    /// constructor stays infallible, and [`Machine::run`] and
799    /// [`Machine::enter_closure`] are what turn either into a refusal,
800    /// before a frame exists.
801    pub(crate) fn for_run(
802        program: &'a Program,
803        heap_words: usize,
804        hosts: Option<&'a HostRegistry>,
805        runtime: Option<&'a Runtime>,
806    ) -> Machine<'a> {
807        let mut machine = Machine {
808            program,
809            runtime,
810            hosts,
811            mem: Memory::new(heap_words),
812            frames: Vec::new(),
813            // Overwritten below, once the machine that places them exists.
814            literal_addrs: Ok(Arc::from([])),
815            resources: Arc::new(Mutex::new(Vec::new())),
816            temps: Vec::new(),
817            scopes: Vec::new(),
818            children: Vec::new(),
819            cancellation: None,
820            stops: Vec::new(),
821            reentry_depth: 0,
822            task: ENTRY_TASK,
823            next_task: 1,
824            instructions: 0,
825            charged_work: 0,
826            bulk_work: 0,
827            host_wait: Duration::ZERO,
828            collected: Collected::default(),
829            held: Vec::new(),
830            callback_answer: None,
831            assertion_failure: None,
832            debugger: None,
833            next_check: SAFEPOINT_STRIDE,
834            // Encoded and verified here, once, for every machine — because
835            // this is where a run's program arrives and because a refusal
836            // that happened later would happen after a frame was pushed. See
837            // the field.
838            encoded: encoded::prepare(program),
839            builtin_words: Vec::new(),
840            builtin_answer: Vec::new(),
841            cases: [None; 4],
842            widths: program
843                .layouts
844                .iter()
845                .map(|layout| layout.width())
846                .collect(),
847        };
848        machine.literal_addrs = machine.place_literals();
849        machine
850    }
851
852    /// A machine for a spawned task, over a stack segment of its own and the
853    /// run's one heap.
854    ///
855    /// Everything a run owns is shared and everything a task owns is fresh,
856    /// and the split is the whole of ADR 0008 here. Shared: the program, the
857    /// hosts, the trace, the heap, the run's budget, the resource table, and
858    /// — since [ADR 0045](../../../../docs/adr/0045-a-literal-is-there-before-the-program-runs.md)
859    /// — every literal's address. Fresh: the stack segment, the frames, and
860    /// the scheduler table it spawns its own children into.
861    ///
862    /// `literal_addrs` is handed over rather than rebuilt, and that is the
863    /// point: the entry's machine placed every literal, in the run's one
864    /// heap, before this task could have been spawned, so there is nothing
865    /// left for this constructor to allocate, copy or place. A literal in a
866    /// loop this task runs costs one load, exactly as it does in the task
867    /// that spawned it — no lock, because there is nothing left to
868    /// synchronise.
869    #[allow(clippy::too_many_arguments)]
870    fn for_task(
871        program: &'a Program,
872        hosts: Option<&'a HostRegistry>,
873        runtime: Option<&'a Runtime>,
874        resources: Arc<Mutex<Vec<Arc<ResourceHandle>>>>,
875        mem: Memory,
876        cancellation: Cancellation,
877        task: u64,
878        encoded: Arc<cove_ir::bytecode::Encoded>,
879        literal_addrs: Arc<[u64]>,
880        widths: Arc<[u32]>,
881    ) -> Machine<'a> {
882        Machine {
883            program,
884            runtime,
885            hosts,
886            mem,
887            frames: Vec::new(),
888            literal_addrs: Ok(literal_addrs),
889            resources,
890            temps: Vec::new(),
891            scopes: Vec::new(),
892            children: Vec::new(),
893            cancellation: Some(cancellation),
894            stops: Vec::new(),
895            reentry_depth: 0,
896            task,
897            next_task: 1,
898            instructions: 0,
899            charged_work: 0,
900            bulk_work: 0,
901            host_wait: Duration::ZERO,
902            collected: Collected::default(),
903            held: Vec::new(),
904            callback_answer: None,
905            assertion_failure: None,
906            debugger: None,
907            next_check: SAFEPOINT_STRIDE,
908            // The parent's, not a second encoding of the same program: a run
909            // executes one form, and encoding again per spawn would be a
910            // second pass over the whole program for a pointer's worth of
911            // sharing.
912            encoded: Ok(encoded),
913            builtin_words: Vec::new(),
914            builtin_answer: Vec::new(),
915            cases: [None; 4],
916            // The parent's, for the reason `encoded` is: a table derived from
917            // a program the whole run shares is the same table in every task.
918            widths,
919        }
920    }
921
922    /// The family of the value a callback answered during the host call in
923    /// progress, if one ran. See [`Machine::callback_answer`].
924    pub(crate) fn callback_answer(&self) -> Option<LayoutId> {
925        self.callback_answer
926    }
927
928    /// How many instructions this machine has run.
929    pub(crate) fn instructions(&self) -> u64 {
930        self.instructions
931    }
932
933    /// Which task this machine is running.
934    ///
935    /// The same number a trace event carries, because there is only one
936    /// counter: [`crate::trace::Event`] writes this field and so does
937    /// [`crate::vm::debug::Stop::task`], so a debugger and a trace name a
938    /// task the same way instead of each inventing an identity for it.
939    pub(crate) fn task(&self) -> u64 {
940        self.task
941    }
942
943    /// Installs `debugger`, to be asked before every instruction this machine
944    /// runs from here on.
945    ///
946    /// Installed rather than passed, because the question is asked in the
947    /// dispatch loop and the loop takes its arguments once per call. A task
948    /// this machine spawns is handed the same reference, which is why the
949    /// trait is `Send + Sync`.
950    pub(crate) fn watch(&mut self, debugger: Option<&'a (dyn Debugger + Send + Sync)>) {
951        self.debugger = debugger;
952        self.next_check = self.next_question();
953    }
954
955    /// The instructions this machine runs, or the refusal every run of this
956    /// program answers with.
957    ///
958    /// Cloning an `Arc`, because the loop borrows the code while the machine
959    /// is borrowed mutably, and a run's tasks share the one encoding anyway.
960    fn code(&self) -> Result<Arc<cove_ir::bytecode::Encoded>, RuntimeError> {
961        self.encoded.clone()
962    }
963
964    /// The address of every program literal, or the refusal placing them
965    /// met. See [`Machine::literal_addrs`].
966    ///
967    /// Cloning an `Arc`, for the same reason [`Machine::code`] does: a run's
968    /// tasks share the one placement.
969    fn literals(&self) -> Result<Arc<[u64]>, RuntimeError> {
970        self.literal_addrs.clone()
971    }
972
973    /// The instruction count at which the loop next asks its one question.
974    ///
975    /// The two questions folded into one comparison. Without a debugger it is
976    /// [`SAFEPOINT_STRIDE`] past the last count the run was charged at; with
977    /// a debugger it is the very next instruction, and the safepoint's own
978    /// schedule is unchanged underneath it.
979    ///
980    /// This used to be the next *multiple* of [`SAFEPOINT_STRIDE`], and while
981    /// every instruction cost one it was the same number: `charged` is set to
982    /// `instructions` at every safepoint, so a stride past it is the stride's
983    /// next multiple. The two part company only when something charges more
984    /// than one, which is what
985    /// [ADR 0052](../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)
986    /// asks for and what the multiple could not survive — see the safepoint
987    /// condition in [`crate::vm::exec::encoded`].
988    /// What this run has been charged for: one per instruction, plus the
989    /// words the bulk operations moved.
990    #[inline]
991    fn work(&self) -> u64 {
992        self.instructions + self.bulk_work
993    }
994
995    /// Cancellation, fuel and the collector's rendezvous, at `pc`.
996    ///
997    /// Lifted out of [`crate::vm::exec::encoded`]'s loop so that a bulk
998    /// operation can reach one *while it is running*. That is
999    /// [ADR 0052](../../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)'s
1000    /// requirement and
1001    /// [ADR 0040](../../../../docs/adr/0040-a-bound-outlives-its-backend.md)'s
1002    /// arithmetic: a copy that charged for a megabyte only when it had
1003    /// finished copying it would overshoot a fuel or cancellation bound by a
1004    /// megabyte, however promptly the loop polled afterwards.
1005    ///
1006    /// The order is the loop's order and may not be rearranged — cancellation
1007    /// before fuel, because a run that was asked to stop is not out of fuel;
1008    /// then the collector, which must see a frame this caller has already
1009    /// `sync`ed.
1010    fn safepoint(&mut self, budget: &Meter, id: FunctionId, pc: usize) -> Result<(), RuntimeError> {
1011        stopped_here(self.cancellation.as_ref(), &self.stops, self.span(id, pc))?;
1012        let gathered = self.work() - self.charged_work;
1013        self.charged_work = self.work();
1014        if let Err(stopped) = budget.safepoint(gathered) {
1015            return Err(budget.to_runtime_error(stopped).at(self.span(id, pc)));
1016        }
1017        let live = Live(self);
1018        self.mem.poll(&live);
1019        Ok(())
1020    }
1021
1022    #[inline]
1023    fn next_question(&self) -> u64 {
1024        match self.debugger {
1025            Some(_) => self.instructions + 1,
1026            // Instruction coordinates, because that is what the loop
1027            // compares: the count at which `work()` will have reached a
1028            // stride past the last charge. It saturates because a bulk charge
1029            // can already have passed it, and a check that is due now is
1030            // exactly what zero asks for.
1031            None => (self.charged_work + SAFEPOINT_STRIDE).saturating_sub(self.bulk_work),
1032        }
1033    }
1034
1035    /// What every collection so far has done.
1036    pub(crate) fn collected(&self) -> Collected {
1037        self.collected
1038    }
1039
1040    /// Words the heap region occupies, free blocks included.
1041    pub(crate) fn heap_words(&self) -> u64 {
1042        self.mem.heap_words()
1043    }
1044
1045    /// Words handed out over the whole run, reuse counted each time.
1046    pub(crate) fn allocated_words(&self) -> u64 {
1047        self.mem.allocated_words()
1048    }
1049
1050    /// Objects handed out over the whole run, reuse counted each time.
1051    pub(crate) fn allocations(&self) -> u64 {
1052        self.mem.allocations()
1053    }
1054
1055    /// How long this machine has waited on hosts.
1056    pub(crate) fn host_wait(&self) -> Duration {
1057        self.host_wait
1058    }
1059
1060    /// Where the most recent failed assertion was written, and the message
1061    /// it produced, or `None` when none has failed.
1062    pub(crate) fn assertion_failure(&self) -> Option<(Span, &str)> {
1063        self.assertion_failure
1064            .as_ref()
1065            .map(|(span, message)| (*span, message.as_str()))
1066    }
1067
1068    /// Runs `entry` with `args` already in word form, answering the words of
1069    /// its result.
1070    ///
1071    /// The caller converts: this is below the boundary, and nothing here
1072    /// knows what a public `Value` is. `args` is the parameters' words
1073    /// flattened in declaration order — a `(Int, Point, Int)` list is four
1074    /// words — because that is what the frame they are written into is.
1075    pub(crate) fn run(
1076        &mut self,
1077        entry: FunctionId,
1078        args: &[u64],
1079        budget: &Meter,
1080    ) -> Result<Vec<u64>, RuntimeError> {
1081        // Before anything: a program with no encoding is refused here, with
1082        // the stacks and the cells exactly as this call found them. There is
1083        // one execution form, so this is not a fallback — it is the whole of
1084        // what "verified once and then trusted" costs a caller.
1085        let code = self.code()?;
1086        let program = self.program;
1087        let function = program.function(entry);
1088        // And before anything else: a heap that could not hold every
1089        // literal is refused here too, at the entry's own span rather than
1090        // a literal's — the program is what could not be started, not the
1091        // string. See ADR 0045.
1092        self.literals().map_err(|error| error.at(function.span))?;
1093        debug_assert_eq!(
1094            args.len(),
1095            function.param_words(&program.layouts) as usize,
1096            "an entry is called with its parameters' words"
1097        );
1098
1099        // On an empty stack, and that has to be said rather than assumed. A
1100        // machine is built once and called many times — `Vm::invoke_within`
1101        // exists so that one bounded invocation can be stopped without
1102        // damaging the session that made it — and a call that was stopped
1103        // where it stood left its frames standing, because a runtime error is
1104        // not a jump the lowering emits and nothing unwound them. Building
1105        // this call on top of those would give `Inst::Return` a caller to
1106        // resume that belongs to a call that is over: the answer would be
1107        // written into an abandoned frame and the run would continue in it.
1108        //
1109        // The cells go back for the same reason and in the same breath. A
1110        // `lock` region the stopped call was inside was left by no
1111        // `SharedUnlock`, and a cell nobody gives back is a cell no task can
1112        // ever take again.
1113        self.give_cells_back(0);
1114        self.frames.clear();
1115        self.mem.reset_stack();
1116        self.temps.clear();
1117
1118        let base = self
1119            .mem
1120            .push_frame(function.frame_size())
1121            .map_err(|Overflow| self.too_deep(function.span))?;
1122        for (slot, word) in args.iter().enumerate() {
1123            self.mem.set_slot(base, slot as u32, *word);
1124        }
1125        self.frames.push(Frame {
1126            function: entry,
1127            base,
1128            pc: 0,
1129            dst: 0,
1130        });
1131        self.drive(&code, budget)
1132    }
1133
1134    /// Runs the frame already on the stack, inside the thread scope its
1135    /// `spawn`s start their children in.
1136    ///
1137    /// The scope is here rather than around the whole run because it is what
1138    /// bounds a task's threads to a task: nothing this body starts can outlive
1139    /// this call, so the borrow a child holds of the program, the hosts and
1140    /// the run is a borrow the compiler can check rather than one this module
1141    /// has to promise. A child that spawns children of its own opens a scope
1142    /// of its own, nested, and leaves it before it answers.
1143    ///
1144    /// Whatever the body did, no thread leaves here with work to do. A
1145    /// runtime error is not a jump the lowering emits, so there is no
1146    /// `ScopeCancel` on that path and nothing would otherwise cancel the
1147    /// children of a scope the error left — and `std::thread::scope` would
1148    /// then wait for a task that is waiting for nobody. [`Machine::stop_all`]
1149    /// is that path, and on the ordinary one it has nothing to do because
1150    /// every scope was already left where it was written.
1151    ///
1152    /// It is `#[inline(never)]` for what `a78a8ad` measured one phase ago:
1153    /// two large dispatch calls in one closure cost the loop 2.5% on `arith`
1154    /// even though the branch between them was taken once per run. There is
1155    /// one call here now and one loop to call, so the measurement no longer
1156    /// has a second body to be about — but what it established is that this
1157    /// closure's contents are a cost every instruction pays, and the
1158    /// attribute is what keeps that from being re-decided by an inliner.
1159    #[inline(never)]
1160    fn drive(
1161        &mut self,
1162        code: &cove_ir::bytecode::Encoded,
1163        budget: &Meter,
1164    ) -> Result<Vec<u64>, RuntimeError> {
1165        std::thread::scope(|threads| {
1166            let mut running: Vec<Option<ScopedJoinHandle<'_, Outcome>>> = Vec::new();
1167            let answer = encoded::dispatch(self, code, budget, threads, &mut running, 0);
1168            // The frames are not unwound on this path — see the module-level
1169            // note beside `Machine::frames` — so they are still exactly what
1170            // was live when the error was raised. This is the one place that
1171            // reads them for it: every error leaves the machine through here,
1172            // whichever instruction or safepoint raised it, so a chain
1173            // attached at every `.at()` site instead would be the same work
1174            // repeated at every one of them for no error that reaches two.
1175            let answer = answer.map_err(|error| error.with_chain(self.call_chain()));
1176            debug_assert!(
1177                answer.is_err() || !self.anything_running(),
1178                "a body that answered left every scope it opened, so nothing is still running"
1179            );
1180            debug_assert!(
1181                answer.is_err() || self.held.is_empty(),
1182                "a body that answered left every cell it took"
1183            );
1184            self.give_cells_back(0);
1185            self.stop_all(&mut running);
1186            // Last, after the answer is settled and after the children are
1187            // joined, so that what is put back is everything this thread
1188            // dispatched and nothing is added to the run's total once the
1189            // total has been read.
1190            //
1191            // [ADR 0024](../../../../docs/adr/0024-a-stop-is-a-bound-not-a-point.md)
1192            // says pending fuel is never lost, and a loop that lost it would
1193            // report a `fuel_spent` below the instructions it dispatched.
1194            self.spend_pending_fuel(budget);
1195            answer
1196        })
1197    }
1198
1199    /// Hands the run's [`Meter`] whatever this thread has dispatched and not
1200    /// yet paid for, at the end of a run or of a spawned task's thread.
1201    ///
1202    /// The ordinary way out of a body pays on its way: a run long enough to
1203    /// reach a periodic safepoint has handed over every whole stride of it,
1204    /// and a Host call has handed over the part of the stride that preceded
1205    /// it. What pays for nothing is the remainder — the instructions after
1206    /// the last hand-over — and every way a run can end without dispatching
1207    /// another instruction is a way that remainder would be dropped with the
1208    /// stacks: a raised error, an exhausted budget, a cancelled task, a
1209    /// bounded call the host abandoned. Each of those leaves through Rust's
1210    /// `?` rather than through an instruction.
1211    ///
1212    /// The work was really done, so the run is charged for it.
1213    /// [ADR 0024](../../../../docs/adr/0024-a-stop-is-a-bound-not-a-point.md)
1214    /// decides that pending fuel is never lost, and a `fuel_spent` below the
1215    /// instructions the run dispatched is the observable form of losing it.
1216    ///
1217    /// [`Meter::spend`] rather than [`Meter::safepoint`], and the difference
1218    /// is the whole reason this is its own function: this runs after the
1219    /// answer is settled, and a stop raised here would replace the reason the
1220    /// run actually ended. A run that raised would report that it was out of
1221    /// fuel.
1222    ///
1223    /// A spawned task's thread reaches this through its own [`Machine::drive`]
1224    /// and pays into the same accounting, because ADR 0008 draws a task's
1225    /// fuel from the run's budget rather than giving each task one of its
1226    /// own.
1227    fn spend_pending_fuel(&mut self, budget: &Meter) {
1228        let pending = self.work() - self.charged_work;
1229        if pending != 0 {
1230            self.charged_work = self.work();
1231            budget.spend(pending);
1232        }
1233    }
1234
1235    /// Hands over what this thread has dispatched and not yet paid for, and
1236    /// asks the run's accounting whether it may continue — at a Host call,
1237    /// before the call is dispatched.
1238    ///
1239    /// # The contract
1240    ///
1241    /// **No Host call begins once the fuel a run has been charged has reached
1242    /// its limit.**
1243    /// [ADR 0030](../../../../docs/adr/0030-a-host-call-asks-the-fuel-limit.md)
1244    /// decides that, and it is a statement about the bound rather than about
1245    /// the count: what the two backends share is the property, not the number
1246    /// that satisfies it. The oracle satisfies it by holding no pending fuel
1247    /// at all — `Interpreter::charge_safepoint` hands `SAFEPOINT_FUEL` over in
1248    /// the same call that charges it, so its charged total cannot move while
1249    /// a straight line runs. This machine holds pending fuel by construction,
1250    /// because it charges on a fixed instruction stride, so it satisfies it
1251    /// the other way ADR 0030 allows: by flushing here.
1252    ///
1253    /// Without this, a Host call is just another instruction the stride
1254    /// counts, and a straight line of them shorter than one
1255    /// [`SAFEPOINT_STRIDE`] is not stopped at any fuel limit whatever —
1256    /// forty effects under a limit of one, which is the shape ADR 0030 was
1257    /// written to refuse.
1258    ///
1259    /// # Why this is not a safepoint
1260    ///
1261    /// It asks the budget and nothing else. The two flags a thread owns are
1262    /// read by the caller one line above, so repeating them here would be
1263    /// asking a question that has just been answered.
1264    ///
1265    /// The collector is the interesting half. The predecessor's argument for
1266    /// leaving it out was that its arguments had already been drained into a
1267    /// `Vec<Value>` and were therefore rooted by their own references rather
1268    /// than by the walk. **That argument does not hold here, and the
1269    /// conclusion still does.** The arguments this boundary converts are
1270    /// [`Value`]s built by [`crate::vm::boundary::to_value`], which copies
1271    /// out of the heap rather than naming it, and the words they were read
1272    /// from are still in the slots of a frame this machine has not left — so
1273    /// a collection here would be as sound as one anywhere else, and
1274    /// [`Machine::park`] is about to publish exactly those roots for the
1275    /// length of the call. The reason not to collect is therefore the second
1276    /// half of the predecessor's: this machine's collection point is a
1277    /// rendezvous poll, and putting one in front of every Host call would
1278    /// make an unpredictable sweep part of the cost of reaching the outside
1279    /// world, for a reason the budget never asked for.
1280    fn charge_at_host_boundary(&mut self, budget: &Meter, span: Span) -> Result<(), RuntimeError> {
1281        let pending = self.work() - self.charged_work;
1282        self.charged_work = self.work();
1283        if let Err(stopped) = budget.safepoint(pending) {
1284            return Err(budget.to_runtime_error(stopped).at(span));
1285        }
1286        Ok(())
1287    }
1288
1289    /// Runs the body of a spawned closure, which is this machine's whole
1290    /// task.
1291    ///
1292    /// The closure takes no parameters — `scope.spawn { ... }` is written
1293    /// with none and an `async fn`'s handle carries none — so its frame is
1294    /// its captures and its locals. The captures are copied out of the
1295    /// environment exactly as [`cove_ir::Inst::CallClosure`] copies them,
1296    /// because it
1297    /// is the same object read the same way; what differs is only that the
1298    /// caller is a thread rather than an instruction.
1299    fn enter_closure(
1300        &mut self,
1301        object: u64,
1302        budget: &Meter,
1303        span: Span,
1304    ) -> Result<Vec<u64>, RuntimeError> {
1305        // A task machine was handed its parent's encoding and its parent's
1306        // placed literals, so both of these are `Ok`; each is asked rather
1307        // than assumed because the alternative is an `expect` in the one
1308        // place a task's body starts.
1309        let code = self.code()?;
1310        self.literals().map_err(|error| error.at(span))?;
1311        let program = self.program;
1312        let callee = self.callee_of(object)?;
1313        let target = program.function(callee);
1314        if !target.params.is_empty() {
1315            return Err(wrong_arity(target.qualified(), target.params.len(), 0).at(span));
1316        }
1317        let base = self
1318            .mem
1319            .push_frame(target.frame_size())
1320            .map_err(|Overflow| self.too_deep(span))?;
1321        let mut held = 1;
1322        for capture in &target.captures {
1323            let width = program.layout(capture.layout).width();
1324            self.mem.copy_words(
1325                base + capture.slot as u64,
1326                self.mem.payload_addr(object, held),
1327                width,
1328            );
1329            held += width;
1330        }
1331        self.frames.push(Frame {
1332            function: callee,
1333            base,
1334            pc: 0,
1335            dst: 0,
1336        });
1337        self.drive(&code, budget)
1338    }
1339
1340    /// The debugger's question, out of line.
1341    ///
1342    /// `pc` has been synced by the caller, which is what makes the frame it
1343    /// reads truthful, and this runs once per instruction while a debugger is
1344    /// installed and never otherwise.
1345    ///
1346    /// `#[inline(never)]` is a measurement rather than a preference.
1347    /// `docs/VM_ARCHITECTURE.md` has established more than once that a change
1348    /// altering nothing a program executes can still move `arith` by several
1349    /// percent, *"because the dispatch body's footprint and its branch-target
1350    /// alignment are costs every program pays"* — and building the [`Stop`]
1351    /// and the indirect call inside [`encoded::dispatch`] cost 4.3% on
1352    /// `arith` against the same tree with this line, which is more than the
1353    /// per-instruction branch this whole arrangement was shaped to avoid.
1354    /// Out of line, the change measures at or below the base on both `arith`
1355    /// and `field`.
1356    #[inline(never)]
1357    fn ask(&mut self, id: FunctionId, pc: usize) -> Result<(), RuntimeError> {
1358        if let Some(debugger) = self.debugger {
1359            if let Resume::Halt = debugger.at(&Stop::new(self, id, pc)) {
1360                return Err(halted(self.span(id, pc)));
1361            }
1362        }
1363        Ok(())
1364    }
1365
1366    /// Writes the local program counter back into the top frame.
1367    ///
1368    /// Called before anything that reads the frames: a collection, which
1369    /// walks them for roots, and a failure, which reads a span out of one.
1370    fn sync(&mut self, pc: usize) {
1371        if let Some(frame) = self.frames.last_mut() {
1372            frame.pc = pc as u32;
1373        }
1374    }
1375
1376    fn span(&self, id: FunctionId, pc: usize) -> Span {
1377        self.program.function(id).span_at(pc)
1378    }
1379
1380    fn repr(&self, id: FunctionId, slot: Slot) -> Option<Repr> {
1381        self.program.function(id).repr(slot)
1382    }
1383
1384    fn too_deep(&self, span: Span) -> RuntimeError {
1385        self.too_deep_error().at(span)
1386    }
1387
1388    fn too_deep_error(&self) -> RuntimeError {
1389        RuntimeError::new("this call nests too deeply")
1390            .with_rule("A recursion that does not terminate is stopped rather than left to run.")
1391    }
1392
1393    /// Refuses a frame that would take this task past the embedder's
1394    /// [`Limits::max_call_depth`].
1395    ///
1396    /// This is a *budget*, and the difference from
1397    /// [`Machine::too_deep_error`] is the whole reason it is a second check.
1398    /// A frame that would leave this task's stack segment is a stack
1399    /// overflow: a fact about the memory the run was built with, reported as
1400    /// a runtime error and classified as one. `max_call_depth` is a number an
1401    /// embedder chose, reported as a stop, and classified as
1402    /// [`RunOutcome::CallDepth`] — so a tool that reads a trace can tell a
1403    /// program that recursed too far from a limit the embedder set, which is
1404    /// what makes either bound worth acting on. Both stay, and they are asked
1405    /// in the order the oracle asks them: the unconditional limit first, then
1406    /// the configured one.
1407    ///
1408    /// Counted against *this* task's frames rather than against a total the
1409    /// run shares, for the reason [`crate::budget::Budget`] gives for not
1410    /// enforcing this limit itself: ADR 0008 gives each task a stack of its
1411    /// own, and a shared count would stop a shallow task because a sibling
1412    /// was deep.
1413    ///
1414    /// The limit is read off the meter at the call rather than cached in a
1415    /// field, and that is a decision about where the answer lives. A
1416    /// `Machine` holds no [`Meter`] — the run's accounting is passed into
1417    /// [`encoded::dispatch`], which is what lets one machine serve a session
1418    /// of invocations each bounded by a budget of its own. A field would have
1419    /// to be re-bound every time [`crate::Vm::invoke_within`] installed one,
1420    /// and a stale one would enforce the previous request's limit on this
1421    /// request. Reading it here cannot go stale, because it comes from the
1422    /// very meter the safepoints of this run are charging. It costs one
1423    /// pointer chase through an `Arc` to a field that does not change while a
1424    /// run lasts, on a path that is already writing a frame.
1425    ///
1426    /// [`Limits::max_call_depth`]: crate::budget::Limits::max_call_depth
1427    /// [`RunOutcome::CallDepth`]: crate::trace::RunOutcome::CallDepth
1428    fn admit_frame(&self, budget: &Meter, span: Span) -> Result<(), RuntimeError> {
1429        if let Some(limit) = budget.limits().max_call_depth {
1430            if self.frames.len() + 1 > limit {
1431                // The error names the value the limit was configured with, so
1432                // it is built where that value is rather than here.
1433                return Err(budget.to_runtime_error(Stopped::CallDepth).at(span));
1434            }
1435        }
1436        Ok(())
1437    }
1438
1439    /// How many words a value of `layout` occupies.
1440    ///
1441    /// The one question every move in the machine asks now: a value location
1442    /// is a base slot and a width, and this is the width. It is a table read
1443    /// rather than a walk, because [`cove_ir::Layout`] caches the flattened
1444    /// words for exactly the readers that are on this path.
1445    #[inline]
1446    /// The index of `case` in the enum `layout`, remembered.
1447    ///
1448    /// Nothing is searched for. Which `Option` or `Result` a builtin answers
1449    /// is carried by [`cove_ir::Inst::CallBuiltin`] and passed down from
1450    /// `vm::builtins::call`, because the alternative — looking for an enum of
1451    /// that name whose carrying case holds the right payload — cannot tell
1452    /// `Result<String, Error>` from `Result<String, cq.diag.Detail>`. Both are
1453    /// named `Result` and both carry a `String` in `Ok`, and they are two
1454    /// words and four; answering the wrong one is a word run written into a
1455    /// destination sized for the other.
1456    ///
1457    /// What *is* remembered is which index the name resolves to; see
1458    /// [`Machine::cases`]. `family` and `case` are the names a diagnostic uses
1459    /// when `layout` is not the enum it was expected to be.
1460    pub(crate) fn case_index(
1461        &mut self,
1462        layout: LayoutId,
1463        wrapper: Wrapper,
1464        family: &str,
1465        case: &str,
1466    ) -> Result<u32, RuntimeError> {
1467        if let Some((held, index)) = self.cases[wrapper as usize] {
1468            if held == layout {
1469                return Ok(index);
1470            }
1471        }
1472        let index = self
1473            .program
1474            .layouts
1475            .get(layout.index())
1476            .filter(|held| matches!(held.shape, Shape::Enum { .. }))
1477            .and_then(|held| held.case(case))
1478            .ok_or_else(|| crate::vm::builtins::operand::unknown_family(family))?;
1479        self.cases[wrapper as usize] = Some((layout, index));
1480        Ok(index)
1481    }
1482
1483    fn width(&self, layout: LayoutId) -> u32 {
1484        self.widths[layout.index()]
1485    }
1486
1487    /// Allocates, collecting once if the first attempt does not fit.
1488    ///
1489    /// `len` is `i64` rather than `u32` because this is the one place every
1490    /// `Inst::Alloc` operand converges on, and one of its three `Len` forms —
1491    /// `Len::Slot` — is a slot the running program computed, not a count this
1492    /// compiler chose. `cove_ir::bytecode::verify`'s own module doc says why
1493    /// that boundary cannot be trusted ahead of time: *"this must be safe
1494    /// against arbitrary bytes, because a verifier that is only safe against
1495    /// its own encoder is not a verifier"* — and `Len::Count`'s encoded form,
1496    /// `Half::Count`, is one of the two halves that same verifier explicitly
1497    /// does not range-check, because there is no table for a raw count to be
1498    /// an index into. So every caller here, trusted or not, is checked the
1499    /// same way: a negative `len`, a `len` past what the header's length
1500    /// field can hold, and a `count * stride` that would not fit `u32` are
1501    /// all rejected before anything is allocated.
1502    ///
1503    /// They are rejected through the same error an exhausted heap already
1504    /// raises below rather than a new one of their own, because from the
1505    /// machine's point of view a request nothing could ever satisfy is not a
1506    /// different failure than one this run's budget happens not to satisfy
1507    /// today.
1508    fn allocate(&mut self, layout: LayoutId, len: i64) -> Result<u64, RuntimeError> {
1509        let exhausted = || RuntimeError::new("this run has no memory left");
1510        let len = u32::try_from(len).map_err(|_| exhausted())?;
1511        let words = self
1512            .program
1513            .layout(layout)
1514            .try_payload_words(len, &self.program.layouts)
1515            .ok_or_else(exhausted)?;
1516        if let Some(addr) = self.mem.alloc(layout, len, words) {
1517            return Ok(addr);
1518        }
1519        self.collect();
1520        self.mem.alloc(layout, len, words).ok_or_else(exhausted)
1521    }
1522
1523    /// Stops the world and reclaims what nothing this run's tasks hold
1524    /// reaches.
1525    ///
1526    /// This task's own roots are [`Live`]; every other task's are what it
1527    /// published at the safepoint it parked at. The host resource table is
1528    /// among neither and could not be: it holds names rather than addresses,
1529    /// so there is nothing in it for a mark to follow — and a `Repr::Host`
1530    /// word in a frame is not gathered either, because `Function::refs` is
1531    /// `RefMap::of` the `Repr`s and `Repr::Host::is_ref` is false. A
1532    /// `Repr::Task` and a `Repr::Scope` are outside it for the same reason,
1533    /// and what a task's table *does* name is reached through [`Live`].
1534    pub(crate) fn collect(&mut self) {
1535        let done = self.mem.collect(&self.program.layouts, &Live(self));
1536        self.collected = done;
1537    }
1538
1539    /// How many temporary roots are held, for a caller about to take more.
1540    ///
1541    /// The mark to hand back to [`Machine::release_temps`]. Taking it and
1542    /// releasing to it is the whole discipline: a conversion that recurses
1543    /// nests marks, and a conversion that fails releases on the way out
1544    /// because the caller that took the mark is the one that releases it.
1545    pub(crate) fn temps(&self) -> usize {
1546        self.temps.len()
1547    }
1548
1549    /// Holds `addr` as a root until the mark it was taken after is released.
1550    ///
1551    /// What this is for is the window in which an object exists and nothing
1552    /// the collector walks names it: between the allocation of a struct and
1553    /// the write of its last field, the object is reachable only from a Rust
1554    /// local, and building one of those fields can allocate. Without a root
1555    /// here the collector would be right to free it, and the write that
1556    /// followed would land in a free block.
1557    pub(crate) fn push_temp(&mut self, addr: u64) {
1558        self.temps.push(addr);
1559    }
1560
1561    /// Releases every temporary root taken since `mark`.
1562    ///
1563    /// The object is not freed by this; it stops being a root, which is what
1564    /// a root has to do the moment something else names it. Releasing rather
1565    /// than leaving them is what keeps this from becoming the retention the
1566    /// static reference map was careful not to be.
1567    pub(crate) fn release_temps(&mut self, mark: usize) {
1568        self.temps.truncate(mark);
1569    }
1570
1571    /// The resource a [`Repr::Host`] word names, or `None` for a word that
1572    /// names none.
1573    ///
1574    /// `None` is two things, and the caller is what tells them apart: the
1575    /// zero a frame's own zeroing leaves in a slot nothing has written, and a
1576    /// word from somewhere that is not this table. Both are questions about a
1577    /// value crossing rather than about the machine, so both are reported by
1578    /// [`crate::vm::boundary`] and neither is decided here.
1579    pub(crate) fn resource(&self, word: u64) -> Option<Arc<ResourceHandle>> {
1580        self.held_resources()
1581            .get(word.checked_sub(1)? as usize)
1582            .cloned()
1583    }
1584
1585    /// The run's resource table, recovering from a lock a panicking task
1586    /// left poisoned.
1587    ///
1588    /// A table of names is not a state anything recovers *from*: what it held
1589    /// before the panic is exactly what it holds after, because nothing here
1590    /// is ever removed. Refusing every later resource operation of the run
1591    /// because one task panicked would turn a task's failure into the run's,
1592    /// which is the opposite of what a task boundary is for — the same
1593    /// reasoning `Space::allocator` gives about the heap's own lock.
1594    fn held_resources(&self) -> std::sync::MutexGuard<'_, Vec<Arc<ResourceHandle>>> {
1595        self.resources
1596            .lock()
1597            .unwrap_or_else(|held| held.into_inner())
1598    }
1599
1600    /// The word naming `handle`, writing it into the table the first time
1601    /// this run is handed it.
1602    ///
1603    /// Interned rather than appended, so that one resource is one word for
1604    /// the length of a run. ADR 0013 says two handles are equal when they
1605    /// name the same resource, and a table that gave one resource two words
1606    /// would be a table on which comparing the words was not comparing the
1607    /// resources — which is the one thing an untagged word naming a resource
1608    /// has to get right. `task_safe` is not part of the comparison for the
1609    /// same reason it is not part of [`ResourceHandle::names_same`]: it is a
1610    /// fact about the kind, copied onto every handle of it, so two handles
1611    /// naming one resource cannot disagree about it.
1612    ///
1613    /// The scan is linear over the resources this run has been handed. That
1614    /// is the table the host is keeping too, at the size the host keeps it.
1615    pub(crate) fn resource_word(&mut self, handle: &ResourceHandle) -> u64 {
1616        let mut held = self.held_resources();
1617        if let Some(at) = held.iter().position(|kept| kept.names_same(handle)) {
1618            return at as u64 + 1;
1619        }
1620        held.push(Arc::new(handle.clone()));
1621        // One past the index, because a zeroed slot has to mean no resource.
1622        held.len() as u64
1623    }
1624
1625    /// Materialises the arguments, calls the host, and writes its answer
1626    /// back as a word.
1627    ///
1628    /// This follows [`crate::interp::Interpreter::call_host`] rather than
1629    /// inventing an order of its own, because what a host call does is a fact
1630    /// about the language and not about a backend. The registry is what
1631    /// charges [`crate::Budget::charge_host_call`], refuses an ungranted
1632    /// capability, holds the arguments and the answer to the operation's
1633    /// schema, and writes the `HostCall` trace event; a backend that repeated
1634    /// any of that would be a second opinion about a question that already has
1635    /// one. What is left for the machine is the three things only it can do:
1636    /// read the words out as the `Repr`s of the slots they came from, wait,
1637    /// and write the answer back.
1638    ///
1639    /// The run's own cancellation is not checked here. The oracle checks a
1640    /// *task's* flag and the flag of every bounded call its thread is inside,
1641    /// neither of which this machine has yet; the run's flag is read inside
1642    /// the boundary by `charge_host_call`, which is where it is read on every
1643    /// backend.
1644    #[allow(clippy::too_many_arguments)]
1645    fn call_host<'s>(
1646        &mut self,
1647        base: u64,
1648        op: HostOpId,
1649        args: ArgsId,
1650        budget: &Meter,
1651        span: Span,
1652        threads: &'s Scope<'s, 'a>,
1653        running: &mut Vec<Option<ScopedJoinHandle<'s, Outcome>>>,
1654    ) -> Result<Vec<u64>, RuntimeError> {
1655        let program = self.program;
1656        let op = program.host_op(op);
1657        let list = program.arg_list(args);
1658
1659        // An argument names a value location, so each one materialises as
1660        // the whole of what is at it. That is why a struct or an enum
1661        // reaches a host as itself: it used to be boxed on the way in — a
1662        // slot said where an operand began and never how wide it was — and
1663        // the host was handed an erased value where the schema declared a
1664        // concrete one.
1665        let mut values = Vec::with_capacity(list.len());
1666        for arg in list {
1667            let words = self
1668                .mem
1669                .read_words(base + arg.slot as u64, self.width(arg.layout));
1670            values.push(
1671                boundary::to_value(self, arg.layout, &words).map_err(|error| error.at(span))?,
1672            );
1673        }
1674
1675        let hosts = self.hosts.ok_or_else(|| {
1676            RuntimeError::new(format!(
1677                "`{}.{}` cannot be called, because this run has no host boundary",
1678                op.module, op.operation
1679            ))
1680            .at(span)
1681        })?;
1682        stopped_here(self.cancellation.as_ref(), &self.stops, span)?;
1683        self.charge_at_host_boundary(budget, span)?;
1684        let started = Instant::now();
1685        let answer = {
1686            // A task inside a host call is not running Cove and its frames do
1687            // not change, so the snapshot it leaves stays true for the whole
1688            // call — and a collection that waited for it instead would be
1689            // waiting for something outside the run altogether. A callback is
1690            // the exception, and [`Back::call`] is where it says so: the
1691            // moment Cove runs again the snapshot stops being true, so the
1692            // park is dropped for exactly as long as the callback runs.
1693            let mut back = Back::parked(self, budget, span, threads, running);
1694            hosts.call_with(&op.module, &op.operation, values, &mut back)
1695        };
1696        self.host_wait += started.elapsed();
1697        let answer = answer.map_err(|error| error.at(span))?;
1698        let result = op.result;
1699        boundary::from_value(self, result, &answer).map_err(|error| error.at(span))
1700    }
1701
1702    /// The same, addressed to the resource the [`Repr::Host`] word in
1703    /// `receiver` names.
1704    ///
1705    /// Everything [`Machine::call_host`] does, through the one seam that
1706    /// differs: `HostRegistry::call_resource` rather than
1707    /// `HostRegistry::call_with`. The grant, the schema on both sides, the
1708    /// budget and the trace are the registry's on this path too — a resource
1709    /// operation is a Host API call and is charged and recorded as one — and
1710    /// this follows `crate::interp::Interpreter::call_host_resource`, which
1711    /// is the same three lines around the same call.
1712    ///
1713    /// The handle is looked up rather than materialised. ADR 0013 makes it a
1714    /// name the host minted, `Machine::resource` is the table that word
1715    /// indexes, and the registry takes it as the thing being addressed — so
1716    /// it is never one of `args`, and the arguments are what the host is
1717    /// handed.
1718    ///
1719    /// A zero word is refused rather than read through. `docs/LINEAR_VM.md`
1720    /// is explicit that the word is one past the index so that a slot nothing
1721    /// has written names no resource, and that zero *"earns the same refusal
1722    /// a null reference does"* — which is this one, because a `Host` slot
1723    /// read before it was given a handle is the same lowering bug reaching
1724    /// the machine.
1725    #[allow(clippy::too_many_arguments)]
1726    fn call_resource<'s>(
1727        &mut self,
1728        base: u64,
1729        receiver: Slot,
1730        op: HostOpId,
1731        args: ArgsId,
1732        budget: &Meter,
1733        span: Span,
1734        threads: &'s Scope<'s, 'a>,
1735        running: &mut Vec<Option<ScopedJoinHandle<'s, Outcome>>>,
1736    ) -> Result<Vec<u64>, RuntimeError> {
1737        let program = self.program;
1738        let op = program.host_op(op);
1739        let list = program.arg_list(args);
1740
1741        let word = self.mem.slot(base, receiver);
1742        let Some(handle) = self.resource(word) else {
1743            return Err(null_object().at(span));
1744        };
1745
1746        let mut values = Vec::with_capacity(list.len());
1747        for arg in list {
1748            let words = self
1749                .mem
1750                .read_words(base + arg.slot as u64, self.width(arg.layout));
1751            values.push(
1752                boundary::to_value(self, arg.layout, &words).map_err(|error| error.at(span))?,
1753            );
1754        }
1755
1756        let hosts = self.hosts.ok_or_else(|| {
1757            RuntimeError::new(format!(
1758                "`{}` cannot be called, because this run has no host boundary",
1759                op.qualified()
1760            ))
1761            .at(span)
1762        })?;
1763        stopped_here(self.cancellation.as_ref(), &self.stops, span)?;
1764        // A resource operation is a Host API call and is bounded as one, so
1765        // ADR 0030's boundary is here for the reason it is in
1766        // [`Machine::call_host`] and in the same order.
1767        self.charge_at_host_boundary(budget, span)?;
1768        let started = Instant::now();
1769        let answer = {
1770            let mut back = Back::parked(self, budget, span, threads, running);
1771            hosts.call_resource(&handle, &op.operation, values, &mut back)
1772        };
1773        self.host_wait += started.elapsed();
1774        let answer = answer.map_err(|error| error.at(span))?;
1775        let result = op.result;
1776        boundary::from_value(self, result, &answer).map_err(|error| error.at(span))
1777    }
1778
1779    /// Reads the operand words out of the frame and hands them to the
1780    /// builtin.
1781    ///
1782    /// An operand is a value location: the layout the argument names and the
1783    /// words at its slot. Both halves are read here rather than in
1784    /// [`crate::vm::builtins`] for the reason the boundary takes them here
1785    /// too — a word is untagged and where it came from is a fact about this
1786    /// frame, which a builtin has no business knowing about.
1787    ///
1788    /// The words are copied into one buffer and the operands point into it,
1789    /// so a builtin reads a whole `Point` without holding a frame and
1790    /// without the argument list having to promise that consecutive operands
1791    /// are adjacent, which it never could: the lowering places each argument
1792    /// where a run of the right shape was free.
1793    fn call_builtin(
1794        &mut self,
1795        base: u64,
1796        dst: Slot,
1797        builtin: BuiltinId,
1798        args: ArgsId,
1799    ) -> Result<(), RuntimeError> {
1800        let program = self.program;
1801        let list = program.arg_list(args);
1802
1803        // The word buffer is [`Machine::builtin_words`], taken out for this
1804        // call and put back at the end — see the field for why that is safe
1805        // and what it costs if it is ever wrong.
1806        let mut words = std::mem::take(&mut self.builtin_words);
1807        words.clear();
1808        words.reserve(list.len());
1809        for arg in list {
1810            let width = self.width(arg.layout);
1811            for at in 0..width {
1812                words.push(self.mem.slot(base, arg.slot + at));
1813            }
1814        }
1815
1816        // The operands point into `words`, so they cannot themselves live in
1817        // the machine beside it: a field borrowed here would have to stay
1818        // borrowed across `builtins::call(self, ...)`, which takes `&mut
1819        // Machine`. [`INLINE_OPERANDS`] is sized for an ordinary fixed-arity
1820        // call — a receiver and a couple of arguments — and only the
1821        // builtins `cove-schema` declares `variadic: true` (`Vector.of`,
1822        // `Map.of`, `Set.of`) can exceed it, so they spill to a `Vec` sized
1823        // to the call instead of paying for a larger array on every call.
1824        let mut inline: [Operand; INLINE_OPERANDS] = [Operand {
1825            layout: LayoutId(0),
1826            words: &[],
1827        }; INLINE_OPERANDS];
1828        let mut spill: Vec<Operand>;
1829        let operands: &[Operand] = if list.len() <= INLINE_OPERANDS {
1830            let mut offset = 0usize;
1831            for (slot, arg) in inline.iter_mut().zip(list) {
1832                let width = self.width(arg.layout) as usize;
1833                *slot = Operand {
1834                    layout: arg.layout,
1835                    words: &words[offset..offset + width],
1836                };
1837                offset += width;
1838            }
1839            &inline[..list.len()]
1840        } else {
1841            let mut offset = 0usize;
1842            spill = Vec::with_capacity(list.len());
1843            for arg in list {
1844                let width = self.width(arg.layout) as usize;
1845                spill.push(Operand {
1846                    layout: arg.layout,
1847                    words: &words[offset..offset + width],
1848                });
1849                offset += width;
1850            }
1851            &spill
1852        };
1853
1854        let mut out = std::mem::take(&mut self.builtin_answer);
1855        out.clear();
1856        let answered = builtins::call(self, program.builtin(builtin), operands, &mut out);
1857
1858        // Written into the frame here rather than by the dispatch loop,
1859        // because the buffer has to come back: handing the answer out as a
1860        // `Vec` would hand out the allocation with it, and this field would
1861        // find itself empty on the next call and allocate again — which is
1862        // the whole thing it exists not to do.
1863        if answered.is_ok() {
1864            for (at, word) in out.iter().enumerate() {
1865                self.mem.set_slot(base, dst + at as u32, *word);
1866            }
1867        }
1868
1869        // Both buffers may have grown past what was already here — keep
1870        // whichever of each pair has the larger capacity, per the fields'
1871        // doc comments.
1872        if words.capacity() >= self.builtin_words.capacity() {
1873            self.builtin_words = words;
1874        }
1875        if out.capacity() >= self.builtin_answer.capacity() {
1876            self.builtin_answer = out;
1877        }
1878        answered
1879    }
1880
1881    /// Places every entry of [`Program::strings`] into the heap, in
1882    /// [`StrId`] order, before this machine's first instruction can run and
1883    /// therefore before any other object exists. Called once, by
1884    /// [`Machine::for_run`]; [`Machine::for_task`] is handed the `Arc` this
1885    /// builds rather than calling it again. See
1886    /// [ADR 0045](../../../../docs/adr/0045-a-literal-is-there-before-the-program-runs.md).
1887    ///
1888    /// Not [`Machine::allocate`], on purpose: that collects and retries when
1889    /// an allocation does not fit, and every object a collection could find
1890    /// at this point *is* a literal this very call is still placing —
1891    /// nothing else has run yet, so there is nothing else to reclaim.
1892    /// Collecting here would sweep an already-placed literal the moment
1893    /// before [`Memory::seal_static`] could tell the collector to leave it
1894    /// alone, and a later literal reusing the freed run would land on the
1895    /// address an earlier one already handed out. A heap that does not fit
1896    /// them once will not fit them a second time, so this fails immediately
1897    /// instead of trying twice for the same answer.
1898    fn place_literals(&mut self) -> Result<Arc<[u64]>, RuntimeError> {
1899        let exhausted = || RuntimeError::new("this run has no memory left");
1900        let mut addrs = Vec::with_capacity(self.program.strings.len());
1901        for index in 0..self.program.strings.len() {
1902            let bytes = Arc::clone(self.program.string(StrId(index as u32)));
1903            let len = u32::try_from(bytes.len()).map_err(|_| exhausted())?;
1904            let words = self
1905                .program
1906                .layout(self.program.str_layout)
1907                .try_payload_words(len, &self.program.layouts)
1908                .ok_or_else(exhausted)?;
1909            let addr = self
1910                .mem
1911                .alloc(self.program.str_layout, len, words)
1912                .ok_or_else(exhausted)?;
1913            self.write_bytes(addr, bytes.as_bytes());
1914            addrs.push(addr);
1915        }
1916        self.mem.seal_static();
1917        Ok(addrs.into())
1918    }
1919
1920    /// The address of the literal `text` names.
1921    ///
1922    /// Reached only from the dispatch loop's `STR` arm, after
1923    /// [`Machine::run`] or [`Machine::enter_closure`] has already turned a
1924    /// placement failure into a refusal before any frame existed — so by
1925    /// the time an instruction asks, the answer is always there, and this
1926    /// is a load rather than a question.
1927    #[inline]
1928    fn literal_addr(&self, text: StrId) -> u64 {
1929        self.literal_addrs
1930            .as_ref()
1931            .expect("a placement failure is refused before this machine's first frame")
1932            [text.index()]
1933    }
1934
1935    /// The layout a [`cove_ir::Inst::Box`] allocates its object as.
1936    ///
1937    /// The program says, rather than this searching for a `Shape::Boxed`.
1938    /// A search has to answer something when it fails, and the answer it
1939    /// used to give — `LayoutId::FREE` — sized the object by the wrong
1940    /// shape, so a box of a two-word value was allocated one word short and
1941    /// the copy into it ran off the end of the heap.
1942    fn boxed_layout(&self) -> LayoutId {
1943        self.program.boxed_layout
1944    }
1945
1946    /// Checks that `addr` is an object with a payload word `at`.
1947    ///
1948    /// A reference slot carries no layout, so the object is the only thing
1949    /// that can say how wide it is. The lowering computed `at` from the type
1950    /// the checker settled, so this should never refuse — and it is here
1951    /// because "should never" is not "cannot", and reading past an object
1952    /// into whatever follows it would be a silent wrong answer rather than a
1953    /// loud one.
1954    fn checked(&self, addr: u64, at: u32, width: u32) -> Result<(), RuntimeError> {
1955        if addr == 0 {
1956            return Err(null_object());
1957        }
1958        let layout = self.program.layout(self.mem.object_layout(addr));
1959        let words = layout.payload_words(self.mem.object_len(addr), &self.program.layouts);
1960        if at + width > words {
1961            return Err(RuntimeError::new(format!(
1962                "this reads word {at} of a `{}`, which has {words}",
1963                layout.name
1964            )));
1965        }
1966        Ok(())
1967    }
1968
1969    /// The function the closure object at `addr` calls.
1970    ///
1971    /// Three things have to hold before a frame is pushed, and none of them
1972    /// is something a program can get wrong: the object has to be a closure's,
1973    /// the callee it names has to be one this program has, and the captures it
1974    /// holds have to be the ones that callee reads. The checker resolved the
1975    /// callee's type and the verifier holds the slot to `Repr::Ref`, so each
1976    /// of the three is a lowering bug — reported for the reason
1977    /// [`Machine::checked`] is, because the alternative is a frame whose
1978    /// capture slots hold whatever followed the object in the heap.
1979    ///
1980    /// The id comes from the object rather than from the layout, which carries
1981    /// one too. They agree — a layout is one per lowered lambda — and the
1982    /// object's word is the one [`cove_ir::Inst::CallClosure`] is defined in
1983    /// terms of.
1984    pub(crate) fn callee_of(&self, addr: u64) -> Result<FunctionId, RuntimeError> {
1985        if addr == 0 {
1986            return Err(null_object());
1987        }
1988        let program = self.program;
1989        let layout = program.layout(self.mem.object_layout(addr));
1990        let Shape::Closure { captures, .. } = &layout.shape else {
1991            // The oracle's words for a call of something that is not a
1992            // function, with the name the layout carries — which is the name
1993            // the declaration wrote, and so the one a `Value` of this object
1994            // would answer.
1995            return Err(RuntimeError::new(format!(
1996                "`{}` is not callable",
1997                layout.name
1998            )));
1999        };
2000        let word = self.mem.payload(addr, 0);
2001        let callee = u32::try_from(word)
2002            .ok()
2003            .map(FunctionId)
2004            .filter(|id| id.index() < program.functions.len())
2005            .ok_or_else(|| {
2006                RuntimeError::new(format!(
2007                    "this closure names function {word}, which this program has not"
2008                ))
2009            })?;
2010        let target = program.function(callee);
2011        if target.captures.len() != captures.len() {
2012            return Err(RuntimeError::new(format!(
2013                "this closure and `{}` disagree about its captures: {} held, {} read",
2014                target.qualified(),
2015                captures.len(),
2016                target.captures.len()
2017            )));
2018        }
2019        Ok(callee)
2020    }
2021
2022    /// Turns a language-level index into a payload offset, at a stride of
2023    /// `width`.
2024    ///
2025    /// The header's length counts *elements*, not words, so an index is
2026    /// checked against it and then multiplied — which is what makes an
2027    /// `Array<Point>` a run of two-word elements and an out-of-range index on
2028    /// one say the same thing it says on an `Array<Int>`.
2029    fn element(&self, addr: u64, at: i64, width: u32) -> Result<u32, RuntimeError> {
2030        if addr == 0 {
2031            return Err(null_object());
2032        }
2033        let len = self.mem.object_len(addr) as i64;
2034        if at < 0 || at >= len {
2035            return Err(
2036                RuntimeError::new(format!("index {at} is outside a collection of {len}"))
2037                    .with_rule("An index outside a collection is a broken invariant."),
2038            );
2039        }
2040        Ok(at as u32 * width)
2041    }
2042
2043    /// Orders two string objects by their bytes.
2044    fn compare_strings(&self, a: u64, b: u64) -> std::cmp::Ordering {
2045        self.string_bytes(a).cmp(&self.string_bytes(b))
2046    }
2047
2048    /// The bytes of the string object at `addr`.
2049    ///
2050    /// A null address answers the empty string rather than failing: the one
2051    /// caller that can see one is the comparison, and two strings one of
2052    /// which does not exist is a lowering bug the verifier will catch
2053    /// elsewhere, not something to unwind a comparison for.
2054    pub(crate) fn string_bytes(&self, addr: u64) -> Vec<u8> {
2055        if addr == 0 {
2056            return Vec::new();
2057        }
2058        let len = self.mem.object_len(addr) as usize;
2059        let mut out = Vec::with_capacity(len);
2060        for at in 0..len.div_ceil(8) {
2061            let word = self.mem.payload(addr, at as u32);
2062            for byte in 0..8 {
2063                if out.len() == len {
2064                    break;
2065                }
2066                out.push((word >> (byte * 8)) as u8);
2067            }
2068        }
2069        out
2070    }
2071
2072    /// The byte at `at` of the packed run at `addr`.
2073    ///
2074    /// The payload holds eight bytes to a word, least-significant byte first —
2075    /// the inverse of [`Machine::write_bytes`] — so one byte is one payload read
2076    /// and a shift, and no part of the object is copied. The caller owns the
2077    /// bound, as every reader of a packed run here does.
2078    pub(crate) fn byte_of(&self, addr: u64, at: usize) -> u8 {
2079        (self.mem.payload(addr, (at / 8) as u32) >> ((at % 8) * 8)) as u8
2080    }
2081
2082    /// A new string object holding `text`.
2083    ///
2084    /// Unlike [`Machine::place_literals`] this allocates every time, and is
2085    /// never collected out from under it either: a literal is retained
2086    /// because the program named it statically and can name it again; a
2087    /// string that arrived from outside has no such name, and retaining
2088    /// every one a host ever answered would be a leak with a table in front
2089    /// of it.
2090    pub(crate) fn new_string(&mut self, text: &str) -> Result<u64, RuntimeError> {
2091        let addr = self.allocate(self.program.str_layout, text.len() as i64)?;
2092        self.write_bytes(addr, text.as_bytes());
2093        Ok(addr)
2094    }
2095
2096    fn write_bytes(&mut self, addr: u64, bytes: &[u8]) {
2097        for (at, chunk) in bytes.chunks(8).enumerate() {
2098            let mut word = 0u64;
2099            for (byte, value) in chunk.iter().enumerate() {
2100                word |= (*value as u64) << (byte * 8);
2101            }
2102            self.mem.set_payload(addr, at as u32, word);
2103        }
2104    }
2105
2106    /// A new string object of `len` zero bytes, for a caller that will fill
2107    /// it itself.
2108    ///
2109    /// The payload is zero on both of the paths that can answer it — a
2110    /// reused free block is filled and a fresh chunk is committed zeroed —
2111    /// which is what lets a caller write only the bytes it has and leave the
2112    /// tail of the last word alone. That tail matters: `eq.str` compares
2113    /// payload words rather than bytes, so a string whose final word held
2114    /// anything past its length would be unequal to the same text written
2115    /// some other way.
2116    ///
2117    /// Nothing roots the answer, so a caller must fill it without allocating
2118    /// again. That is not a restriction in practice — the point of asking for
2119    /// an exact length is to have counted first.
2120    pub(crate) fn new_string_of(&mut self, len: i64) -> Result<u64, RuntimeError> {
2121        self.allocate(self.program.str_layout, len)
2122    }
2123
2124    // ---- ADR 0052's byte buffer -------------------------------------------
2125
2126    /// [`Inst::AllocBuffer`]: a new, empty byte buffer whose store has room for
2127    /// `capacity` bytes.
2128    ///
2129    /// Two objects, because [ADR 0052](../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)'s
2130    /// growable value is a stable owner over a replaceable store. Neither
2131    /// layout is a choice: the owner is `Program::buffer_layout` and its
2132    /// payload is a fixed two words, and the store is `Program::bytes_layout`
2133    /// with the capacity as its header length.
2134    ///
2135    /// # Which is allocated first, and why nothing is lost
2136    ///
2137    /// Two allocations means a collection may happen between them, and
2138    /// whichever object exists first is reachable from nothing the collector
2139    /// walks — a Rust local is not a root. So the **store is allocated first
2140    /// and held by [`Machine::push_temp`]** for exactly the window in which
2141    /// the owner is allocated, which is the discipline
2142    /// [`crate::vm::boundary`] already uses for a half-built object and the
2143    /// reason `push_temp` exists.
2144    ///
2145    /// The other order was available and is worse. Allocating the owner first
2146    /// is safe only if something names it, which means writing it into the
2147    /// destination slot before the object it owns exists — leaving a buffer
2148    /// with a null store visible to a debugger stopped at the allocation, and
2149    /// making the arm's correctness depend on the frame slot rather than on
2150    /// this method. One temporary root costs a push and a truncate.
2151    ///
2152    /// A negative or oversized `capacity` fails through the same "this run has
2153    /// no memory left" refusal every other allocation does, and it fails
2154    /// before the owner exists rather than leaving one behind.
2155    pub(crate) fn alloc_buffer(&mut self, capacity: i64) -> Result<u64, RuntimeError> {
2156        // A capacity below the floor is raised to it, because a capacity is a
2157        // hint and a small one is still a hint. A *negative* capacity is not
2158        // raised: it is nonsense rather than a small number, and clamping it
2159        // would turn the one arithmetic a caller could not have meant into a
2160        // silent success. `Machine::allocate` already has the answer for a
2161        // length nothing could satisfy.
2162        let capacity = if capacity < 0 {
2163            capacity
2164        } else {
2165            capacity.max(MIN_BUFFER_BYTES as i64)
2166        };
2167        let mark = self.temps();
2168        let store = self.allocate(self.program.bytes_layout, capacity)?;
2169        self.push_temp(store);
2170        // The owner's payload is two words and its header length is zero: a
2171        // fixed-size object's length field says nothing, exactly as a
2172        // `Shape::Vector` header's does not.
2173        let owner = self.allocate(self.program.buffer_layout, 0);
2174        self.release_temps(mark);
2175        let owner = owner?;
2176        // Zeroed by the allocator, so word 0 is already the empty length; it is
2177        // written anyway rather than relied upon, because the one word that
2178        // says how much of the store is value should be set where it is decided.
2179        self.set_payload(owner, BUFFER_LEN, 0);
2180        self.set_payload(owner, BUFFER_STORE, store);
2181        Ok(owner)
2182    }
2183
2184    /// A live buffer at `owner`: its store, its logical length and its
2185    /// capacity.
2186    ///
2187    /// Every one of the three checks is one this machine must make rather than
2188    /// one the verifier could. A slot's `Repr` is `Ref` and nothing static says
2189    /// which family the object behind it belongs to, so reading an arbitrary
2190    /// object's payload word 1 as an address is how a wrong program becomes a
2191    /// write into the middle of the heap. `Inst::CopyBytes` checks its
2192    /// destination's shape for the same reason.
2193    ///
2194    /// A null store is a buffer [`Inst::FinishBuffer`] already consumed. For a
2195    /// checked program that is unreachable — the uniqueness proof is
2196    /// `cove_sema`'s — so reaching it means the proof let one through, and an
2197    /// internal invariant that reports is better than one that reads a null
2198    /// store as an empty buffer. It is `Vector`'s `operand::frozen` in the
2199    /// vocabulary of a buffer.
2200    fn buffer(&self, shown: &str, owner: u64) -> Result<ByteBuffer, RuntimeError> {
2201        if owner == 0 {
2202            return Err(null_object());
2203        }
2204        if !matches!(
2205            self.program.layout(self.mem.object_layout(owner)).shape,
2206            Shape::ByteBuffer
2207        ) {
2208            return Err(RuntimeError::new(format!(
2209                "`{shown}` needs a byte buffer under construction, and this is not one"
2210            )));
2211        }
2212        let store = self.mem.payload(owner, BUFFER_STORE);
2213        if store == 0 {
2214            return Err(RuntimeError::new(format!(
2215                "`{shown}` was called on a byte buffer that `finish()` already consumed"
2216            ))
2217            .with_rule("`finish()` consumes its buffer; the source buffer is no longer usable.")
2218            .with_help("use the `String` that `finish()` returned, or build a new buffer"));
2219        }
2220        let len = self.mem.payload(owner, BUFFER_LEN);
2221        let capacity = self.mem.object_len(store);
2222        if len > u64::from(capacity) {
2223            return Err(RuntimeError::new(format!(
2224                "`{shown}` found a byte buffer of {len} byte(s) in a store of {capacity}"
2225            )));
2226        }
2227        Ok(ByteBuffer {
2228            owner,
2229            store,
2230            len: len as u32,
2231            capacity,
2232        })
2233    }
2234
2235    /// The store of `buffer`, grown if `needed` bytes will not fit in it.
2236    ///
2237    /// The new capacity is `max(needed, capacity * 2, MIN_BUFFER_BYTES)`, which
2238    /// is ADR 0052's "growth uses the existing Vector policy initially" with
2239    /// the one addition a bulk append needs: a doubling that still would not
2240    /// hold the range is not two growths, it is one growth to the length that
2241    /// fits.
2242    ///
2243    /// Arithmetic overflow is rejected before anything is mutated, and it is
2244    /// rejected by the allocator rather than here. `needed` is a `u64` and the
2245    /// doubling is `saturating_mul`, so the only value that can reach
2246    /// [`Machine::allocate`] out of range is one too large to be a header
2247    /// length or too large for `try_payload_words` to size — and both of those
2248    /// answer "this run has no memory left" *before* the owner's store word or
2249    /// its length word is touched. So a refused growth leaves the buffer
2250    /// exactly as it was.
2251    ///
2252    /// The old store is reachable from the owner, which this read out of a
2253    /// frame slot, so the allocation below cannot free it — `seq.rs`'s `grow`
2254    /// makes the same argument for a `Vector`. The new store is unrooted for
2255    /// exactly the copy, which allocates nothing.
2256    ///
2257    /// The copy is the live prefix and nothing else. That is what keeps the
2258    /// spare tail zero: a fresh store is zeroed, and `copy_string_bytes` blends
2259    /// masked bytes rather than whole words, so the bytes of the last partial
2260    /// word above `len` are left as the allocator left them.
2261    fn reserve_bytes(&mut self, buffer: &ByteBuffer, needed: u64) -> Result<u64, RuntimeError> {
2262        if needed <= u64::from(buffer.capacity) {
2263            return Ok(buffer.store);
2264        }
2265        let want = needed
2266            .max(u64::from(buffer.capacity).saturating_mul(2))
2267            .max(MIN_BUFFER_BYTES);
2268        let store = self.allocate(
2269            self.program.bytes_layout,
2270            i64::try_from(want).unwrap_or(i64::MAX),
2271        )?;
2272        self.copy_string_bytes(store, 0, buffer.store, 0, buffer.len as usize);
2273        self.set_payload(buffer.owner, BUFFER_STORE, store);
2274        Ok(store)
2275    }
2276
2277    /// [`Inst::AppendByte`]: one checked byte onto the end of a buffer.
2278    pub(crate) fn append_byte(&mut self, owner: u64, value: i64) -> Result<(), RuntimeError> {
2279        let buffer = self.buffer("appendByte", owner)?;
2280        if !(0..=255).contains(&value) {
2281            return Err(RuntimeError::new(format!(
2282                "`appendByte`'s value is `{value}`, and a byte is 0 to 255"
2283            )));
2284        }
2285        let store = self.reserve_bytes(&buffer, u64::from(buffer.len) + 1)?;
2286        self.put_bytes(store, buffer.len as usize, 1, value as u64);
2287        self.set_payload(buffer.owner, BUFFER_LEN, u64::from(buffer.len) + 1);
2288        Ok(())
2289    }
2290
2291    /// [`Inst::FinishBuffer`]: the buffer's live prefix, validated and
2292    /// relabelled into a `String`, and the owner emptied.
2293    ///
2294    /// The prefix is `[0, len)` and the store may be longer, so only the prefix
2295    /// is validated: the bytes above the logical length are spare room the
2296    /// program never appended and must not be asked to account for.
2297    ///
2298    /// Then ADR 0052's "finishing reuses the store". A `Shape::Bytes` run and a
2299    /// `Shape::Str` object of the same byte length occupy the same number of
2300    /// words, so the store *is* the answer — relabelled down from the capacity
2301    /// to the logical length, with the words in between released as a free
2302    /// block the next sweep folds back in. `spare` is the difference in
2303    /// *payload words* rather than in bytes, because a free block is measured
2304    /// in words; `vector_freeze` computes the same difference in elements
2305    /// times a stride.
2306    ///
2307    /// The tail of the last partial word is zero, which is what makes the
2308    /// answer equal word-for-word to the same text written by
2309    /// [`Machine::new_string`]: allocation zeroes, every append writes only the
2310    /// live prefix, and a growth copies only the live prefix into another
2311    /// zeroed store. `eq.str` compares payload words, so a dirty tail would be
2312    /// a string unequal to itself written another way.
2313    ///
2314    /// Finally the owner is emptied — length zero, store null — because
2315    /// finishing *consumes*, exactly as `Vector.freeze()` empties the vector it
2316    /// consumed.
2317    pub(crate) fn finish_buffer(&mut self, owner: u64) -> Result<u64, RuntimeError> {
2318        let buffer = self.buffer("finishBuffer", owner)?;
2319        let text = self.buffer_bytes(&buffer);
2320        if std::str::from_utf8(&text).is_err() {
2321            return Err(RuntimeError::new("this string's bytes are not valid UTF-8"));
2322        }
2323        let spare = self.payload_words(self.program.bytes_layout, buffer.capacity)
2324            - self.payload_words(self.program.str_layout, buffer.len);
2325        self.relabel(buffer.store, self.program.str_layout, buffer.len, spare);
2326        self.set_payload(buffer.owner, BUFFER_LEN, 0);
2327        self.set_payload(buffer.owner, BUFFER_STORE, 0);
2328        Ok(buffer.store)
2329    }
2330
2331    /// The live prefix of `buffer`, as bytes.
2332    ///
2333    /// [`Machine::string_bytes`]' read bounded by the *owner's* length rather
2334    /// than the store's, which is the whole difference between a buffer and a
2335    /// run: the store's header length is its capacity.
2336    fn buffer_bytes(&self, buffer: &ByteBuffer) -> Vec<u8> {
2337        let len = buffer.len as usize;
2338        let mut out = Vec::with_capacity(len);
2339        for at in 0..len.div_ceil(8) {
2340            let word = self.mem.payload(buffer.store, at as u32);
2341            for byte in 0..8 {
2342                if out.len() == len {
2343                    break;
2344                }
2345                out.push((word >> (byte * 8)) as u8);
2346            }
2347        }
2348        out
2349    }
2350
2351    /// The eight bytes of the string object at `addr` beginning at byte `at`,
2352    /// least-significant byte first, and zero past `len`.
2353    ///
2354    /// One payload read when `at` is word-aligned and two when it is not.
2355    /// The second read is guarded by `len` rather than by the object's
2356    /// payload width because a string's last word is the last word it has:
2357    /// reading past it would read whatever the heap put there next.
2358    fn bytes_word(&self, addr: u64, at: usize, len: usize) -> u64 {
2359        let shift = (at % 8) * 8;
2360        let word = self.mem.payload(addr, (at / 8) as u32) >> shift;
2361        if shift == 0 {
2362            return word;
2363        }
2364        let next = at - (at % 8) + 8;
2365        if next >= len {
2366            return word;
2367        }
2368        word | (self.mem.payload(addr, (next / 8) as u32) << (64 - shift))
2369    }
2370
2371    /// Writes the low `count` bytes of `bytes` into payload word `word` of
2372    /// the object at `addr`, at byte `offset`, leaving the rest of the word
2373    /// as it was.
2374    ///
2375    /// A whole aligned word is one store and no load, which is the case a
2376    /// copy between two strings spends nearly all of its time in.
2377    fn blend(&mut self, addr: u64, word: u32, offset: usize, count: usize, bytes: u64) {
2378        debug_assert!(count > 0 && offset + count <= 8);
2379        if offset == 0 && count == 8 {
2380            self.mem.set_payload(addr, word, bytes);
2381            return;
2382        }
2383        let mask = ((1u64 << (count * 8)) - 1) << (offset * 8);
2384        let held = self.mem.payload(addr, word);
2385        self.mem.set_payload(
2386            addr,
2387            word,
2388            (held & !mask) | ((bytes << (offset * 8)) & mask),
2389        );
2390    }
2391
2392    /// Copies `len` bytes of the string object at `src`, from byte `src_at`,
2393    /// into the one at `dst`, from byte `dst_at`.
2394    ///
2395    /// Eight bytes a turn rather than one. The byte-at-a-time version this
2396    /// replaced read a word and shifted it for every byte it copied, which
2397    /// made a copy of *n* bytes *n* payload reads and *n* stores; this makes
2398    /// it *n*/8 of each when both ends are aligned, and at most twice that
2399    /// when neither is.
2400    ///
2401    /// The caller owns the bounds. Every caller here has already established
2402    /// them — a slice from [`crate::vm::builtins::text`]'s `byte_range`, a
2403    /// join from the lengths it summed to size the answer — and an
2404    /// out-of-range write would be a payload write past the object, which is
2405    /// the one thing this must not be asked to check per byte if it is to be
2406    /// worth writing at all.
2407    pub(crate) fn copy_string_bytes(
2408        &mut self,
2409        dst: u64,
2410        dst_at: usize,
2411        src: u64,
2412        src_at: usize,
2413        len: usize,
2414    ) {
2415        // A range copy answers the source as it *was*, which is `memmove` and
2416        // not `memcpy`. The two only differ when the ranges overlap, which
2417        // they can: `Inst::CopyBytes` admits a `Shape::Bytes` source, so `src`
2418        // and `dst` may be the same run — a builder shifting its own bytes
2419        // along is the obvious use and there is no reason for it to be the one
2420        // shape of copy that corrupts.
2421        //
2422        // Overlap only matters within one object, and only in one direction:
2423        // writing *forward* into a range that begins later than the source
2424        // overwrites bytes the copy has not read yet. Everything else — two
2425        // different objects, or a destination at or before the source — is
2426        // safe read-then-write in ascending order.
2427        if dst == src && dst_at > src_at {
2428            self.copy_bytes_descending(dst, dst_at, src, src_at, len);
2429        } else {
2430            self.copy_bytes_ascending(dst, dst_at, src, src_at, len);
2431        }
2432    }
2433
2434    /// Eight bytes a turn, from the front.
2435    fn copy_bytes_ascending(
2436        &mut self,
2437        dst: u64,
2438        dst_at: usize,
2439        src: u64,
2440        src_at: usize,
2441        len: usize,
2442    ) {
2443        let src_len = self.mem.object_len(src) as usize;
2444        let mut done = 0;
2445        while done < len {
2446            let take = (len - done).min(8);
2447            let bytes = self.bytes_word(src, src_at + done, src_len);
2448            self.put_bytes(dst, dst_at + done, take, bytes);
2449            done += take;
2450        }
2451    }
2452
2453    /// Eight bytes a turn, from the back, for a copy that shifts a run's bytes
2454    /// to a higher offset in itself.
2455    fn copy_bytes_descending(
2456        &mut self,
2457        dst: u64,
2458        dst_at: usize,
2459        src: u64,
2460        src_at: usize,
2461        len: usize,
2462    ) {
2463        let src_len = self.mem.object_len(src) as usize;
2464        let mut done = len;
2465        while done > 0 {
2466            let take = done.min(8);
2467            done -= take;
2468            let bytes = self.bytes_word(src, src_at + done, src_len);
2469            self.put_bytes(dst, dst_at + done, take, bytes);
2470        }
2471    }
2472
2473    /// Writes the low `take` bytes of `bytes` at byte `at` of the run at
2474    /// `dst`, which may straddle two payload words.
2475    fn put_bytes(&mut self, dst: u64, at: usize, take: usize, bytes: u64) {
2476        let word = (at / 8) as u32;
2477        let offset = at % 8;
2478        let first = take.min(8 - offset);
2479        self.blend(dst, word, offset, first, bytes);
2480        if first < take {
2481            self.blend(dst, word + 1, 0, take - first, bytes >> (first * 8));
2482        }
2483    }
2484
2485    /// The program this machine runs.
2486    pub(crate) fn program(&self) -> &'a Program {
2487        self.program
2488    }
2489
2490    /// This task's live calls, innermost first, as
2491    /// [`crate::vm::debug`] projects them: which function, where its words
2492    /// begin, and where it is.
2493    ///
2494    /// `pc` is truthful only after a [`Machine::sync`], which is the
2495    /// dispatch loop's obligation and not this function's — it is the whole
2496    /// of what makes a debug stop a stop rather than a guess.
2497    pub(crate) fn calls(&self) -> Vec<(FunctionId, u64, u32)> {
2498        self.frames
2499            .iter()
2500            .rev()
2501            .map(|frame| (frame.function, frame.base, frame.pc))
2502            .collect()
2503    }
2504
2505    /// The call-site spans [`RuntimeError::with_chain`] wants, innermost
2506    /// first: every live frame above the one that is failing.
2507    ///
2508    /// The innermost frame contributes no call site of its own: its `pc` is
2509    /// the error's own span, already `RuntimeError::span` — read there by
2510    /// whatever `fail!` or `.at()` this error passed through, not here. It
2511    /// does contribute the *expanded bodies* that `pc` sits inside, because
2512    /// each of those is a frame that would have been here and is not; see
2513    /// [`Inlined`](cove_ir::program::Inlined).
2514    ///
2515    /// Every frame above it is suspended at *the instruction after* the call
2516    /// that led one level deeper — [`Frame::pc`] says so, and
2517    /// [`crate::vm::debug::Stop::frame`] reads that same `pc` for the
2518    /// debugger's own backtrace. A resume address is not a call site: the
2519    /// instruction it names is whatever runs next, which is frequently the
2520    /// next statement's rather than anything to do with the call, and a
2521    /// label built from it points at the wrong line as often as the right
2522    /// one. `- 1` is always the call itself — `entered!` only ever syncs a
2523    /// `pc` that has already moved past the instruction it just dispatched —
2524    /// so that is what this reads instead. The debugger's own view is left
2525    /// alone; nothing here changes what a resume address is used to display
2526    /// there.
2527    ///
2528    /// Lazy in the depth, so a bound below [`RuntimeError::with_chain`]'s
2529    /// [`crate::error::MAX_CALL_CHAIN`] never walks past it: nothing here
2530    /// builds a `Vec` sized to the recursion depth on its way to being
2531    /// truncated back down. One frame at a time does build a small one — an
2532    /// expansion's sites come out of `inlined_at` outermost first and a
2533    /// chain wants them the other way — and that one is bounded by how
2534    /// deeply expansions nest at a single program counter, not by how deep
2535    /// the recursion is.
2536    fn call_chain(&self) -> impl Iterator<Item = Span> + '_ {
2537        // The bodies that were expanded into the frame this failed in come
2538        // first, innermost outwards. `lower::inline` removes a frame that
2539        // would otherwise be here, and `Function::inlined` is the record of
2540        // what it removed: without it an error inside an expanded body named
2541        // where it happened and nothing about where it was called from, and
2542        // the oracle — which pushes a real frame — named both.
2543        //
2544        // Read at `frame.pc` and not one before it: `fail!` syncs the failing
2545        // instruction's own program counter into the frame before it raises,
2546        // where every frame above is left at the instruction it will *resume*
2547        // at. The two are one apart and this is the one place both are read.
2548        let innermost = self.frames.last().into_iter().flat_map(|frame| {
2549            let function = self.program.function(frame.function);
2550            let mut held: Vec<Span> = function
2551                .inlined_at(frame.pc)
2552                .map(|held| held.site)
2553                .collect();
2554            held.reverse();
2555            held
2556        });
2557        // Then the frames above it, each read at the call it is waiting on —
2558        // and each of *those* through the same record, because a call may
2559        // stand inside an expanded body too.
2560        let outer = self.frames.iter().rev().skip(1).flat_map(|frame| {
2561            let function = self.program.function(frame.function);
2562            let at = frame.pc.saturating_sub(1);
2563            let mut held: Vec<Span> = function.inlined_at(at).map(|held| held.site).collect();
2564            held.reverse();
2565            held.insert(0, function.span_at(at as usize));
2566            held
2567        });
2568        innermost.chain(outer)
2569    }
2570
2571    /// `words` words of the frame based at `base`, from `at`.
2572    pub(crate) fn frame_run(&self, base: u64, at: u32, words: u32) -> Vec<u64> {
2573        self.mem.read_words(base + at as u64, words)
2574    }
2575
2576    /// Whether `words` words at `addr` are words this run's memory has.
2577    ///
2578    /// The question a lossy reader has to ask and a boundary conversion does
2579    /// not: every address the boundary follows came out of a value location
2580    /// the lowering wrote, and a debugger is handed words by whoever is
2581    /// looking. Reading past the end of a region is what this refuses.
2582    pub(crate) fn readable(&self, addr: u64, words: u32) -> bool {
2583        self.mem.holds(addr, words)
2584    }
2585
2586    /// What the object at `addr` is, for a boundary that has to name it.
2587    pub(crate) fn object_layout(&self, addr: u64) -> LayoutId {
2588        self.mem.object_layout(addr)
2589    }
2590
2591    /// The length field of the object at `addr`: elements, or a string's
2592    /// bytes.
2593    pub(crate) fn object_len(&self, addr: u64) -> u32 {
2594        self.mem.object_len(addr)
2595    }
2596
2597    /// Payload word `at` of the object at `addr`.
2598    pub(crate) fn payload(&self, addr: u64, at: u32) -> u64 {
2599        self.mem.payload(addr, at)
2600    }
2601
2602    /// Writes payload word `at` of the object at `addr`.
2603    pub(crate) fn set_payload(&mut self, addr: u64, at: u32, word: u64) {
2604        self.mem.set_payload(addr, at, word);
2605    }
2606
2607    /// Re-labels the object at `addr`, releasing the `spare` words it gives
2608    /// up. See [`Memory::relabel`].
2609    pub(crate) fn relabel(&mut self, addr: u64, layout: LayoutId, len: u32, spare: u32) {
2610        let payload = self.payload_words(layout, len);
2611        self.mem.relabel(addr, layout, len, payload, spare);
2612    }
2613
2614    /// The `words` payload words of the object at `addr`, from `at`.
2615    ///
2616    /// What a boundary reads when a value is inline in a payload: an array
2617    /// element, a capture, a struct field, the value inside a box. Nothing in
2618    /// ordinary execution calls it — a move inside the machine never leaves
2619    /// the memory.
2620    pub(crate) fn payload_run(&self, addr: u64, at: u32, words: u32) -> Vec<u64> {
2621        self.mem.read_words(self.mem.payload_addr(addr, at), words)
2622    }
2623
2624    /// Writes `words` into the payload of the object at `addr`, from `at`.
2625    pub(crate) fn set_payload_run(&mut self, addr: u64, at: u32, words: &[u64]) {
2626        for (offset, word) in words.iter().enumerate() {
2627            self.mem.set_payload(addr, at + offset as u32, *word);
2628        }
2629    }
2630
2631    /// How many words a value of `layout` occupies, for a caller outside the
2632    /// dispatch loop.
2633    pub(crate) fn words_of(&self, layout: LayoutId) -> u32 {
2634        self.width(layout)
2635    }
2636
2637    /// How many payload words an object of `layout` with header length `len`
2638    /// occupies.
2639    pub(crate) fn payload_words(&self, layout: LayoutId, len: u32) -> u32 {
2640        self.program
2641            .layout(layout)
2642            .payload_words(len, &self.program.layouts)
2643    }
2644
2645    // ---- the scheduler -----------------------------------------------------
2646
2647    /// The scope a `Repr::Scope` word names.
2648    fn scope_at(&self, word: u64, span: Span) -> Result<usize, RuntimeError> {
2649        word.checked_sub(1)
2650            .map(|at| at as usize)
2651            .filter(|at| *at < self.scopes.len())
2652            .ok_or_else(|| no_such_handle("task scope").at(span))
2653    }
2654
2655    /// The task a `Repr::Task` word names.
2656    fn child_at(&self, word: u64, span: Span) -> Result<usize, RuntimeError> {
2657        word.checked_sub(1)
2658            .map(|at| at as usize)
2659            .filter(|at| *at < self.children.len())
2660            .ok_or_else(|| no_such_handle("task").at(span))
2661    }
2662
2663    /// `scope.spawn { ... }`: a thread for the closure, and the handle the
2664    /// scope now owns.
2665    ///
2666    /// This follows `crate::task::spawn_into` step for step, because what a
2667    /// `spawn` decides is a fact about the language rather than about a
2668    /// backend: the scope has to still be open, the run's concurrency limit
2669    /// is charged **before** the task is given an id, an event or a thread,
2670    /// the trace records the spawn before the thread starts so that a task is
2671    /// never seen completing before it was seen spawning, and a place charged
2672    /// for a task that never got a thread goes back.
2673    ///
2674    /// What differs is the two things only this backend can do. The answer's
2675    /// object is allocated here, before the thread exists, so that it is a
2676    /// root of this task from the moment it can hold anything; and the child
2677    /// is handed a [`Memory`] over a stack segment of its own and the run's
2678    /// one heap, which is the whole of issue #240's Q1.
2679    ///
2680    /// It returns once the thread exists and orders nothing else. ADR 0008's
2681    /// amendment refuses a rendezvous here, and so does this.
2682    #[allow(clippy::too_many_arguments)]
2683    fn spawn<'s>(
2684        &mut self,
2685        scope_word: u64,
2686        object: u64,
2687        answer: LayoutId,
2688        budget: &Meter,
2689        span: Span,
2690        threads: &'s Scope<'s, 'a>,
2691        running: &mut Vec<Option<ScopedJoinHandle<'s, Outcome>>>,
2692    ) -> Result<u64, RuntimeError> {
2693        let at = self.scope_at(scope_word, span)?;
2694        if self.scopes[at].closed {
2695            return Err(task::scope_already_left(&self.scopes[at].name, span));
2696        }
2697        if object == 0 {
2698            return Err(null_object().at(span));
2699        }
2700        // Everything above is about the program and is decided the same way
2701        // on both backends. Everything below needs a thread, and
2702        // `task::no_threads_here` says what it means for there not to be one.
2703        if cfg!(target_arch = "wasm32") {
2704            return Err(task::no_threads_here(span));
2705        }
2706
2707        // Charged before this task is given an id, an event or a thread: a
2708        // thread that has started is a resource already taken, which no later
2709        // safepoint could refuse.
2710        if let Some(hosts) = self.hosts {
2711            if let Some(Err(error)) = hosts.with_budget(|held| {
2712                held.charge_task()
2713                    .map_err(|stopped| held.to_runtime_error(stopped))
2714            }) {
2715                return Err(error.at(span));
2716            }
2717        }
2718
2719        match self.launch(at, object, answer, budget, span, threads, running) {
2720            Ok(word) => Ok(word),
2721            Err(error) => {
2722                // A task the machine refused is not a task the run holds, so
2723                // the place charged for it above goes back.
2724                if let Some(hosts) = self.hosts {
2725                    hosts.with_budget(|held| held.release_task());
2726                }
2727                Err(error)
2728            }
2729        }
2730    }
2731
2732    /// Everything after the concurrency limit has been charged.
2733    ///
2734    /// Split out so that every way of failing after the charge gives the
2735    /// place back, in one place rather than at each way out.
2736    #[allow(clippy::too_many_arguments)]
2737    fn launch<'s>(
2738        &mut self,
2739        at: usize,
2740        object: u64,
2741        answer: LayoutId,
2742        budget: &Meter,
2743        span: Span,
2744        threads: &'s Scope<'s, 'a>,
2745        running: &mut Vec<Option<ScopedJoinHandle<'s, Outcome>>>,
2746    ) -> Result<u64, RuntimeError> {
2747        // The segment first, because taking one can wait: a task joining a
2748        // run whose collection has already begun waits it out, and a task
2749        // that waited without publishing its roots would be a task the
2750        // collector waits for while it waits for the collector. So this task
2751        // parks for the length of the wait, exactly as it does around a host
2752        // call and around a join.
2753        let segment = {
2754            let parked = self.mem.blocking(&Live(self));
2755            let taken = self.mem.for_task();
2756            drop(parked);
2757            taken
2758        };
2759        let segment = segment
2760            .map_err(|NoSegment| no_segment_left().at(span).with_rule(crate::budget::RULE))?;
2761
2762        // Then the answer's home, allocated before the thread exists so that
2763        // it is a root of this task from the moment it can hold anything. The
2764        // closure is in a `Repr::Ref` slot of a live frame, so a collection
2765        // here finds it — which is the one thing this allocation could
2766        // otherwise have taken away.
2767        let width = self.width(answer);
2768        let home = self.allocate(self.boxed_layout(), width as i64)?;
2769        self.mem.set_payload(home, 0, answer.0 as u64);
2770
2771        let id = match self.runtime {
2772            Some(runtime) => runtime.next_task_id(),
2773            None => {
2774                self.next_task += 1;
2775                self.next_task - 1
2776            }
2777        };
2778        let scope = self.scopes[at].name.clone();
2779        let position = self.scopes[at].tasks.len() + 1;
2780        // Traced before the thread starts, so a task is never seen completing
2781        // before it was seen spawning.
2782        if let Some(runtime) = self.runtime {
2783            runtime.trace(TraceEvent::TaskSpawned {
2784                id,
2785                parent: (self.task != ENTRY_TASK).then_some(self.task),
2786                scope: scope.to_string(),
2787            });
2788        }
2789
2790        let cancellation = Cancellation::new();
2791        let program = self.program;
2792        let hosts = self.hosts;
2793        let runtime = self.runtime;
2794        let resources = Arc::clone(&self.resources);
2795        let meter = budget.clone();
2796        let flag = cancellation.clone();
2797        // The same debugger, asked from the child's thread. That is what
2798        // `Send + Sync` on the trait buys, and it is why a debugger sees a
2799        // spawned task's instructions rather than only the entry's.
2800        let watcher = self.debugger;
2801        // And the same instructions, handed over rather than encoded again:
2802        // one program is encoded once per run, however many threads run it.
2803        let form = self.code()?;
2804        // And the same literal addresses, for the reason ADR 0045 gives:
2805        // every task of a run addresses the objects the entry placed, so
2806        // this is a clone of the `Arc` rather than a second placement.
2807        let literals = self.literals()?;
2808        // And the same widths, for the reason the field gives: a table
2809        // derived from a program the whole run shares is one table.
2810        let widths = Arc::clone(&self.widths);
2811        let handle = threads.spawn(move || {
2812            run_task(
2813                program, hosts, runtime, resources, segment, meter, flag, id, object, home, span,
2814                watcher, form, literals, widths,
2815            )
2816        });
2817
2818        let index = self.children.len();
2819        self.children.push(Child {
2820            id,
2821            position,
2822            scope,
2823            cancellation,
2824            closure: object,
2825            answer: home,
2826            layout: answer,
2827            state: ChildState::Running,
2828        });
2829        running.push(Some(handle));
2830        self.scopes[at].tasks.push(index);
2831        // One past the index, because a zeroed slot has to mean no task.
2832        Ok(index as u64 + 1)
2833    }
2834
2835    /// The handle a call to an `async fn` answers, around words the call has
2836    /// already produced.
2837    ///
2838    /// [`crate::task::Task::settled`] is the oracle and this is the same
2839    /// thing in a table: a task with no thread, whose value is known before
2840    /// the handle exists. Everything downstream then works without being
2841    /// told which kind it has — [`Machine::join`] returns at once because the
2842    /// state is not `Running`, [`Machine::settle`] reads the answer object
2843    /// the way it reads a spawned task's, and an `Inst::Cancel` does nothing
2844    /// to a task that is not running, exactly as `Task::cancel` does nothing.
2845    ///
2846    /// Three things it deliberately does not do, each because the oracle does
2847    /// not do it either. It takes **no place under the concurrency limit**:
2848    /// nothing was started, and a limit on how many tasks run at once is not
2849    /// a limit on how many `async fn` calls a program makes. It is **not put
2850    /// in any scope**, so leaving a scope neither waits for it nor cancels
2851    /// it — there is nothing left to wait for. And it is **not traced**: it
2852    /// is `id` zero, the identity `crate::task::Task` gives a handle that
2853    /// "never appears in a trace because it never ran as a task".
2854    ///
2855    /// What it costs is one table entry and one object, kept for the rest of
2856    /// the run. That is the price of a `Repr::Task` word being a name rather
2857    /// than an address: the collector reads a static per-slot map and never
2858    /// inspects a word, so the answer object has to be reachable from
2859    /// somewhere the collector walks, and the table is that somewhere. The
2860    /// oracle's `Rc<Task>` is freed when the last handle goes; a handle here
2861    /// can be inside a `Vector<Task<T>>` or a struct field, and no static map
2862    /// can say when the last one died.
2863    fn settled(
2864        &mut self,
2865        words: &[u64],
2866        answer: LayoutId,
2867        running: &mut Vec<Option<ScopedJoinHandle<'_, Outcome>>>,
2868    ) -> Result<u64, RuntimeError> {
2869        // The same object a spawned task's answer goes into, so that
2870        // `Machine::settle` reads one shape rather than two.
2871        let home = self.allocate(self.boxed_layout(), words.len() as i64)?;
2872        self.mem.set_payload(home, 0, answer.0 as u64);
2873        for (at, word) in words.iter().enumerate() {
2874            self.mem.set_payload(home, 1 + at as u32, *word);
2875        }
2876        let index = self.children.len();
2877        self.children.push(Child {
2878            // Position zero and this name are what `crate::task::describe`
2879            // renders as *this task*: a handle with no place in a spawn
2880            // order, because there was no spawn.
2881            id: 0,
2882            position: 0,
2883            scope: Arc::from("this call"),
2884            cancellation: Cancellation::new(),
2885            closure: 0,
2886            answer: home,
2887            layout: answer,
2888            state: ChildState::Settled,
2889        });
2890        // The two lists are one list at two indices, so a task with no thread
2891        // still takes its place in both.
2892        running.push(None);
2893        Ok(index as u64 + 1)
2894    }
2895
2896    /// `await task`: waits for the thread and answers the words its body
2897    /// produced.
2898    ///
2899    /// A body runs at most once and is waited for at most once, so awaiting
2900    /// the same handle twice answers the same value and repeats no effect —
2901    /// which falls out of the state rather than being arranged, exactly as it
2902    /// does in `crate::task::settle`.
2903    fn settle(
2904        &mut self,
2905        word: u64,
2906        answer: LayoutId,
2907        running: &mut [Option<ScopedJoinHandle<'_, Outcome>>],
2908        span: Span,
2909    ) -> Result<Vec<u64>, RuntimeError> {
2910        let at = self.child_at(word, span)?;
2911        // A task blocked on an `await` is standing where a safepoint would
2912        // be, so it is owed the answer one gives: a cancelled task does not
2913        // wait for a sibling it will never read.
2914        stopped_here(self.cancellation.as_ref(), &[], span)?;
2915        self.join(at, running);
2916        match &self.children[at].state {
2917            ChildState::Settled => {
2918                let width = self.width(answer);
2919                let home = self.children[at].answer;
2920                Ok(self.mem.read_words(self.mem.payload_addr(home, 1), width))
2921            }
2922            ChildState::Failed(error) => Err(error.clone()),
2923            ChildState::Cancelled => Err(task::awaiting_a_cancelled(
2924                &self.children[at].describe(),
2925                span,
2926            )),
2927            ChildState::Running => {
2928                unreachable!("joining a task leaves it settled, failed, or cancelled")
2929            }
2930        }
2931    }
2932
2933    /// Waits for one task's thread and records what it produced.
2934    ///
2935    /// This is `crate::task::join` and `crate::task::Task::join` together,
2936    /// and the three things they decide are decided here in the same order: a
2937    /// task that stopped after its own cancellation was requested is
2938    /// *cancelled* rather than failed, because that is the stop the program
2939    /// asked for; the place it held under the concurrency limit goes back at
2940    /// the join rather than on the task's own thread, so that what a `spawn`
2941    /// is refused for does not depend on how quickly a sibling finished; and
2942    /// `TaskCancelled` is traced here, because this is the only place that
2943    /// knows a cancellation stopped work rather than arriving after it.
2944    fn join(&mut self, at: usize, running: &mut [Option<ScopedJoinHandle<'_, Outcome>>]) {
2945        if !matches!(self.children[at].state, ChildState::Running) {
2946            return;
2947        }
2948        let Some(handle) = running[at].take() else {
2949            return;
2950        };
2951        let outcome = {
2952            // Published for the whole wait. A task waiting for a sibling
2953            // cannot reach a safepoint of its own, and a collector that
2954            // waited for it would be waiting for a task that is waiting for a
2955            // task that is waiting for the collector.
2956            let parked = self.mem.blocking(&Live(self));
2957            let outcome = handle.join();
2958            drop(parked);
2959            outcome
2960        };
2961        let outcome = match outcome {
2962            Ok(outcome) => outcome,
2963            // A panic is a broken invariant in the task's own thread. The
2964            // message has already reached stderr; what this task needs is an
2965            // error rather than a value that never arrived.
2966            Err(_) => Err(task::broken_invariant(&self.children[at].describe())),
2967        };
2968        let cancelled = self.children[at].cancellation.is_cancelled();
2969        self.children[at].state = match outcome {
2970            Ok(()) => ChildState::Settled,
2971            Err(_) if cancelled => ChildState::Cancelled,
2972            Err(error) => ChildState::Failed(error),
2973        };
2974        // The body is over, so the environment it was entered through is no
2975        // longer anything's to keep: the captures it held were copied into
2976        // the child's frame before its first instruction.
2977        self.children[at].closure = 0;
2978        if let Some(hosts) = self.hosts {
2979            hosts.with_budget(|held| held.release_task());
2980        }
2981        if matches!(self.children[at].state, ChildState::Cancelled) {
2982            if let Some(runtime) = self.runtime {
2983                runtime.trace(TraceEvent::TaskCancelled {
2984                    id: self.children[at].id,
2985                });
2986            }
2987        }
2988    }
2989
2990    /// Waits for every child of a scope the body reached the end of **that
2991    /// the body did not await**, and answers the first that failed in a way
2992    /// the enclosing function has to pass on.
2993    ///
2994    /// `crate::task::wait_for_children` is the oracle and this is its
2995    /// translation. Waiting is in spawn order, which is an order of
2996    /// *observation* only — the tasks ran at the same time on threads of
2997    /// their own — and a task the program itself cancelled is neither a
2998    /// failure nor a success, because the program asked for that stop.
2999    ///
3000    /// # Why an awaited child is skipped
3001    ///
3002    /// `if !task.is_running() { continue }` is the oracle's first line and it
3003    /// is a decision rather than an optimisation: a child the body awaited
3004    /// has already handed its value to the program, and the program has
3005    /// already done whatever it does with one. Reporting it again here would
3006    /// overwrite the answer the body computed *from* that failure with the
3007    /// failure itself, so
3008    ///
3009    /// ```cove
3010    /// let answer = task.await()
3011    /// match answer { Ok(n) => n, Err(_) => fallback() }
3012    /// ```
3013    ///
3014    /// could not recover from a failed child at all — leaving the scope would
3015    /// throw the recovery away. What is left to wait for is what nothing has
3016    /// read, which is the case the rule exists for: a failure sitting unread
3017    /// in a handle nobody awaited reaches the caller rather than vanishing.
3018    ///
3019    /// A child is "awaited" here for the same reason it is there: joining is
3020    /// what settles a child's state, and [`Machine::settle`] joins. So a
3021    /// state that is no longer [`ChildState::Running`] is exactly a child
3022    /// something has already waited for.
3023    ///
3024    /// `Ok(Some(child))` is a child whose value was `Err(...)`; `Err` is a
3025    /// child that raised, which propagates as itself. Either way the tasks
3026    /// still running are cancelled and waited for before this answers.
3027    fn leave_scope(
3028        &mut self,
3029        word: u64,
3030        running: &mut [Option<ScopedJoinHandle<'_, Outcome>>],
3031        span: Span,
3032    ) -> Result<Option<usize>, RuntimeError> {
3033        let at = self.scope_at(word, span)?;
3034        let mut index = 0;
3035        let mut failure = None;
3036        let mut raised = None;
3037        // Read by index rather than from a snapshot, so a scope that grew
3038        // while it was being left is still waited for to the end.
3039        while let Some(&child) = self.scopes[at].tasks.get(index) {
3040            index += 1;
3041            if !matches!(self.children[child].state, ChildState::Running) {
3042                continue;
3043            }
3044            self.join(child, running);
3045            match &self.children[child].state {
3046                ChildState::Settled => {
3047                    if self.child_failed(child) {
3048                        failure = Some(child);
3049                        break;
3050                    }
3051                }
3052                ChildState::Failed(error) => {
3053                    raised = Some(error.clone());
3054                    break;
3055                }
3056                ChildState::Cancelled | ChildState::Running => {}
3057            }
3058        }
3059        if failure.is_some() || raised.is_some() {
3060            self.cancel_scope(at, running);
3061        }
3062        self.scopes[at].closed = true;
3063        match raised {
3064            Some(error) => Err(error),
3065            None => Ok(failure),
3066        }
3067    }
3068
3069    /// Cancels every running child of a scope and waits for it to stop.
3070    ///
3071    /// Every child is asked first and waited for afterwards, so they stop at
3072    /// the same time rather than one after another —
3073    /// `crate::task::cancel_children`, in the same two passes.
3074    fn cancel_scope(&mut self, at: usize, running: &mut [Option<ScopedJoinHandle<'_, Outcome>>]) {
3075        for &child in &self.scopes[at].tasks {
3076            if matches!(self.children[child].state, ChildState::Running) {
3077                self.children[child].cancellation.cancel();
3078            }
3079        }
3080        let mut index = 0;
3081        while let Some(&child) = self.scopes[at].tasks.get(index) {
3082            index += 1;
3083            self.join(child, running);
3084        }
3085        self.scopes[at].closed = true;
3086    }
3087
3088    /// Whether any task this machine spawned has not been joined.
3089    ///
3090    /// Asked only by a debug assertion, and what it is asserting is the
3091    /// reason the answer's words are safe to carry out of the frame that
3092    /// produced them: [`Machine::stop_all`] can block, and a task that blocks
3093    /// publishes its roots — which no longer name a popped frame. On the path
3094    /// that answers words there is nothing to block for, because every scope
3095    /// was left where it was written and leaving one joins its children.
3096    fn anything_running(&self) -> bool {
3097        self.children
3098            .iter()
3099            .any(|child| matches!(child.state, ChildState::Running))
3100    }
3101
3102    /// Cancels and joins every task still running, whatever scope it is in.
3103    ///
3104    /// The unwind path, and the one exit a scope has that the lowering cannot
3105    /// write: a runtime error is not a jump, so no `ScopeCancel` stands
3106    /// between it and the end of the run. Without this the thread scope would
3107    /// wait for a task nothing had asked to stop.
3108    ///
3109    /// On the ordinary path it has nothing to do, because every scope was
3110    /// left where it was written.
3111    fn stop_all(&mut self, running: &mut [Option<ScopedJoinHandle<'_, Outcome>>]) {
3112        for child in &self.children {
3113            if matches!(child.state, ChildState::Running) {
3114                child.cancellation.cancel();
3115            }
3116        }
3117        for at in 0..self.children.len() {
3118            self.join(at, running);
3119        }
3120    }
3121
3122    /// Gives back every cell this task took above `mark`, innermost first.
3123    ///
3124    /// The unwind path for a `lock`, and the exact analogue of
3125    /// [`Machine::stop_all`]: a runtime error is not a jump, so no
3126    /// `Inst::SharedUnlock` stands between it and the end of the run, and a
3127    /// cell nobody gave back is a cell no task can ever take. On the ordinary
3128    /// path it has nothing to do, because every lock region was left where it
3129    /// was written.
3130    ///
3131    /// Innermost first, because that is the order the regions would have
3132    /// ended in.
3133    fn give_cells_back(&mut self, mark: usize) {
3134        while self.held.len() > mark {
3135            let addr = self.held.pop().expect("the length is above the mark");
3136            cell::unlock(&self.mem, addr);
3137        }
3138    }
3139
3140    /// Whether a settled child's value was `Err(...)`.
3141    ///
3142    /// The answer object holds the layout of what it carries in its first
3143    /// payload word and the value's words after it, so the discriminant is
3144    /// the second. A child whose answer is not an enum with an `Err` case did
3145    /// not fail this way and cannot: `crate::task::failure_of` asks the same
3146    /// question of a materialised value and answers `None` for the same
3147    /// values.
3148    fn child_failed(&self, at: usize) -> bool {
3149        self.err_part(at)
3150            .is_some_and(|(index, _, _)| self.mem.payload(self.children[at].answer, 1) == index)
3151    }
3152
3153    /// Where a child's `Err` payload is in its answer object: the case index,
3154    /// the payload word it begins at, and its layout.
3155    fn err_part(&self, at: usize) -> Option<(u64, u32, LayoutId)> {
3156        let layout = self.program.layout(self.children[at].layout);
3157        let Shape::Enum { cases, .. } = &layout.shape else {
3158            return None;
3159        };
3160        let index = cases.iter().position(|case| &*case.name == "Err")?;
3161        let part = cases[index].parts.first()?;
3162        // Word 0 of the object is the held layout and word 1 is the value's
3163        // discriminant, so the payload region begins at word 2.
3164        Some((index as u64, 2 + part.at, part.layout))
3165    }
3166
3167    /// Copies a failing child's `Err` payload into the location the enclosing
3168    /// function will wrap and return.
3169    ///
3170    /// The two layouts are held to being one. They are not the same fact —
3171    /// one is what the child answered and the other is what the function the
3172    /// scope was written in fails with — and the checker never had to unify
3173    /// them, because the oracle wraps whatever it finds in a `Value::err` and
3174    /// asks nothing. Here a run of words copied at the wrong width is the one
3175    /// fault this backend must not have quietly, so a disagreement is
3176    /// reported rather than truncated.
3177    fn write_child_error(
3178        &mut self,
3179        at: usize,
3180        into: u64,
3181        layout: LayoutId,
3182    ) -> Result<(), RuntimeError> {
3183        let Some((_, word, held)) = self.err_part(at) else {
3184            return Err(RuntimeError::new(
3185                "this task failed with a value that is not an error the enclosing function can \
3186                 answer with",
3187            ));
3188        };
3189        if held != layout {
3190            let found = self.program.layout(held).name.clone();
3191            let wanted = self.program.layout(layout).name.clone();
3192            return Err(RuntimeError::new(format!(
3193                "this task failed with a `{found}`, and the function its scope was written in \
3194                 answers a `{wanted}`"
3195            )));
3196        }
3197        let width = self.width(layout);
3198        let from = self.mem.payload_addr(self.children[at].answer, word);
3199        self.mem.copy_words(into, from, width);
3200        Ok(())
3201    }
3202
3203    /// Publishes this task's roots and stands at a safepoint until the
3204    /// answer is dropped.
3205    ///
3206    /// [`crate::vm::mem::Parked`] rather than the borrowed guard, because
3207    /// the one caller that needs it is [`Back`], which holds this machine
3208    /// mutably and so cannot also hold a guard borrowing its memory.
3209    fn park(&self) -> Parked {
3210        self.mem.park(&Live(self))
3211    }
3212
3213    /// Runs a Cove callable from outside the dispatch loop, which is what a
3214    /// host callback needs and the only thing that does.
3215    ///
3216    /// # The convention
3217    ///
3218    /// **The call opens its frame at the top of the stack region as it
3219    /// stands, and leaves it exactly as it found it.** The callee's frame
3220    /// begins where the deepest live one ends, which is what
3221    /// [`Memory::push_frame`] answers and what a [`cove_ir::Inst::Call`] would
3222    /// have
3223    /// got; the arguments are written into it as the parameters' words, the
3224    /// captures follow them out of the environment object, and the frame is
3225    /// popped by the return that answers. The frame stack grows above the
3226    /// frame the interrupted instruction belongs to and comes back down to
3227    /// it, which is what `floor` means in [`encoded::dispatch`].
3228    ///
3229    /// That the outer frames are *left* rather than unwound is the whole
3230    /// reason this is another turn of the loop rather than a jump: the
3231    /// instruction that made the host call has not finished, and its frame's
3232    /// slots are live — including, in every case that matters, the slot
3233    /// holding the closure that is being called.
3234    ///
3235    /// # What a failure leaves
3236    ///
3237    /// Nothing. A host may catch what a callback failed with and carry on —
3238    /// `clock.timeout` is written to — so the frames, the stack region, the
3239    /// task scopes the callback opened and the tasks it spawned are all put
3240    /// back the way they were found. The outer run has no unwinding because
3241    /// an abandoned frame's slots stay on the stack until the run ends, which
3242    /// is sound only because the run is ending, and that reasoning does not
3243    /// reach here.
3244    ///
3245    /// # What is still accounted
3246    ///
3247    /// Everything the loop accounts, because it is the loop. Fuel is charged
3248    /// every [`SAFEPOINT_STRIDE`] instructions, a frame that would leave this
3249    /// task's stack segment is a stack overflow, and every safepoint the
3250    /// callee reaches asks what a safepoint asks — including
3251    /// [`Machine::stops`], which [`Reentry::call_until`] pushes onto.
3252    ///
3253    /// [`Reentry::call_until`]: crate::host::Reentry::call_until
3254    fn call_from_host<'s>(
3255        &mut self,
3256        callee: &Value,
3257        args: Vec<Value>,
3258        budget: &Meter,
3259        span: Span,
3260        threads: &'s Scope<'s, 'a>,
3261        running: &mut Vec<Option<ScopedJoinHandle<'s, Outcome>>>,
3262    ) -> Result<Value, RuntimeError> {
3263        // Raised for as long as the callback runs and dropped when it
3264        // returns, so a host that runs its callback twice pays for one level
3265        // twice over rather than for two levels at once. What is bounded is
3266        // how many are stacked on this thread, because that is what is
3267        // spending the native stack.
3268        if self.reentry_depth >= crate::interp::MAX_REENTRY_DEPTH {
3269            return Err(crate::interp::reentry_too_deep(span));
3270        }
3271        let (target_id, object) =
3272            boundary::callback_target(self, callee).map_err(|error| error.at(span))?;
3273        let program = self.program;
3274        let target = program.function(target_id);
3275        if args.len() != target.params.len() {
3276            return Err(wrong_arity(target.qualified(), target.params.len(), args.len()).at(span));
3277        }
3278
3279        // A callback's own frame is an ordinary Cove frame and counts as
3280        // one, which is the answer the oracle gives: the one frame a reentry
3281        // adds is enough to cross a limit the same recursion fits under when
3282        // it is called directly.
3283        self.admit_frame(budget, span)?;
3284
3285        let floor = self.frames.len();
3286        let children = self.children.len();
3287        let scopes = self.scopes.len();
3288        let cells = self.held.len();
3289        let base = self
3290            .mem
3291            .push_frame(target.frame_size())
3292            .map_err(|Overflow| self.too_deep(span))?;
3293        // The frame is on the stack *before* an argument is converted, and
3294        // that is what roots the ones already converted: a `Repr::Ref` slot
3295        // of a live frame is a root, `boundary::from_value` allocates, and a
3296        // value built into a Rust vector first would have been named by
3297        // nothing the collector walks while the next one was built.
3298        self.frames.push(Frame {
3299            function: target_id,
3300            base,
3301            pc: 0,
3302            dst: 0,
3303        });
3304        let answer = self.enter_callback(
3305            object, target_id, args, base, floor, budget, threads, running, span,
3306        );
3307        match answer {
3308            Ok(words) => {
3309                // Nothing between here and the conversion allocates, so the
3310                // objects these words name are still the ones they named when
3311                // the frame that produced them was popped — the same reason
3312                // `Machine::run` may carry an answer out of a frame.
3313                let returns = self.program.function(target_id).returns;
3314                let answer =
3315                    boundary::to_value(self, returns, &words).map_err(|error| error.at(span))?;
3316                // The value has left, and its family goes with it: a host
3317                // that wraps this answer in a result it declared `Any` hands
3318                // it straight back, and the box that is built for it there is
3319                // tagged with what it was here. See
3320                // [`Machine::callback_answer`].
3321                self.callback_answer = Some(returns);
3322                Ok(answer)
3323            }
3324            Err(error) => {
3325                self.unwind_to(floor, children, scopes, cells, base, running);
3326                Err(error)
3327            }
3328        }
3329    }
3330
3331    /// The callback's frame, filled and run. See [`Machine::call_from_host`].
3332    #[allow(clippy::too_many_arguments)]
3333    fn enter_callback<'s>(
3334        &mut self,
3335        object: u64,
3336        target_id: FunctionId,
3337        args: Vec<Value>,
3338        base: u64,
3339        floor: usize,
3340        budget: &Meter,
3341        threads: &'s Scope<'s, 'a>,
3342        running: &mut Vec<Option<ScopedJoinHandle<'s, Outcome>>>,
3343        span: Span,
3344    ) -> Result<Vec<u64>, RuntimeError> {
3345        let program = self.program;
3346        let mut at = 0;
3347        for (value, layout) in args.iter().zip(&program.function(target_id).params) {
3348            let layout = *layout;
3349            let words = boundary::from_value(self, layout, value).map_err(|e| e.at(span))?;
3350            self.mem.write_words(base + at as u64, &words);
3351            at += self.width(layout);
3352        }
3353        // The captures, out of the environment object and into the slots
3354        // `Function::captures` names — the same read `Inst::CallClosure`
3355        // makes, of the same object, at the same widths.
3356        let mut held = 1;
3357        for capture in &program.function(target_id).captures {
3358            let width = self.width(capture.layout);
3359            self.mem.copy_words(
3360                base + capture.slot as u64,
3361                self.mem.payload_addr(object, held),
3362                width,
3363            );
3364            held += width;
3365        }
3366        // The one other place a dispatch loop is entered. `Machine::run`'s
3367        // way back in is `drive`, and this is the other; both read the same
3368        // encoding, because a host that calls back into a run must find
3369        // itself in the run it called out of.
3370        let code = self.code()?;
3371        self.reentry_depth += 1;
3372        let answer = encoded::dispatch(self, &code, budget, threads, running, floor);
3373        self.reentry_depth -= 1;
3374        answer
3375    }
3376
3377    /// Puts the frames, the stack region, the scopes and the tasks back the
3378    /// way a failed callback found them.
3379    ///
3380    /// The children first and while their frames still stand: cancelling and
3381    /// joining blocks, a task that blocks publishes its roots, and roots that
3382    /// named frames this had already truncated would be addresses of words
3383    /// nothing owns.
3384    fn unwind_to(
3385        &mut self,
3386        frames: usize,
3387        children: usize,
3388        scopes: usize,
3389        cells: usize,
3390        base: u64,
3391        running: &mut [Option<ScopedJoinHandle<'_, Outcome>>],
3392    ) {
3393        // Before anything is joined or truncated: a host that catches what a
3394        // callback failed with carries on, and a cell the callback took and
3395        // this did not give back would be held for the rest of the run by a
3396        // frame that no longer exists.
3397        self.give_cells_back(cells);
3398        for child in &self.children[children..] {
3399            if matches!(child.state, ChildState::Running) {
3400                child.cancellation.cancel();
3401            }
3402        }
3403        for at in children..self.children.len() {
3404            self.join(at, running);
3405        }
3406        // A scope the callback opened and did not leave is closed here rather
3407        // than at the end of the run, for the reason its children were joined
3408        // here: its threads would otherwise outlive every frame that could
3409        // name them.
3410        for scope in &mut self.scopes[scopes..] {
3411            scope.closed = true;
3412        }
3413        self.frames.truncate(frames);
3414        self.mem.pop_frame(base);
3415    }
3416
3417    /// Whether `rooted` names an object of this run's memory.
3418    pub(crate) fn holds(&self, rooted: &Rooted) -> bool {
3419        self.mem.is_mine(rooted)
3420    }
3421
3422    /// Makes the object at `addr` a root for as long as the answer lives.
3423    ///
3424    /// The one thing a `Value` crossing out of here can need that a frame
3425    /// cannot give it. See [`crate::vm::mem::Rooted`].
3426    pub(crate) fn pin(&self, addr: u64) -> Rooted {
3427        self.mem.pin(addr)
3428    }
3429
3430    /// A new object of `layout` with header length `len`, collecting once if
3431    /// the first attempt does not fit.
3432    /// A new object of `layout` with header length `len`, collecting once if
3433    /// the first attempt does not fit.
3434    ///
3435    /// The payload is zeroed, so a reference field reads as null until it is
3436    /// written — which is what makes a half-built object safe to collect
3437    /// *through* once [`Machine::push_temp`] has made it safe to collect
3438    /// *around*.
3439    pub(crate) fn new_object(&mut self, layout: LayoutId, len: u32) -> Result<u64, RuntimeError> {
3440        self.allocate(layout, len as i64)
3441    }
3442}
3443
3444/// The way back a host is offered while the linear-memory backend runs.
3445///
3446/// A host that was handed a Cove callback calls it through this. The callback
3447/// is an ordinary frame of this machine — [`Machine::call_from_host`] pushes
3448/// it exactly as [`cove_ir::Inst::CallClosure`] would — and the difference from a call
3449/// the loop made is only in who is waiting for it: a Rust frame rather than
3450/// an instruction. So it holds the machine mutably, which is what makes the
3451/// rest of [`Reentry`]'s contract true rather than merely stated. There can
3452/// be one of these per host call and it cannot be moved to another thread, so
3453/// a host cannot use its way back concurrently; it borrows the machine, so a
3454/// host cannot keep it; and every level of nesting is another one further
3455/// down the same native stack, which is what
3456/// [`crate::interp::MAX_REENTRY_DEPTH`] counts.
3457///
3458/// # What re-entry costs here, and why the bound is the oracle's
3459///
3460/// `docs/LINEAR_VM.md` says **a builtin never calls back into Cove**, and the
3461/// reason it gives is the property the loop exists to have: how deep a Cove
3462/// program may nest is decided by the reserved stack region and not by how
3463/// large a Rust frame the interpreter compiled to. A builtin that ran a
3464/// closure itself would put a Rust frame under every Cove frame the closure
3465/// made, so a `map` over a `map` over a `map` would be three Rust frames deep
3466/// before the program did anything — and a builtin has an alternative, which
3467/// is to be lowered to a loop in the IR.
3468///
3469/// A host callback is the other case, and it differs in both halves. The host
3470/// is *already* a Rust frame: it was reached through
3471/// `HostRegistry::dispatch`, which the machine called, and nothing about
3472/// lowering anything would remove it. And the reentry is the language's own —
3473/// ADR 0013 gives the host the resource and the `Reentry` contract gives it
3474/// the callback — so there is no loop to lower it to. `clock.timeout(500ms)
3475/// { .. }` cannot become a `CallClosure` in the caller's body, because what
3476/// decides whether the body runs at all, and what stops it, is on the host's
3477/// side.
3478///
3479/// So the rule holds where it was aimed and does not reach this. What it
3480/// leaves is that one thing is no longer bounded by the stack region: between
3481/// the callback's frame and the frame that called the host sit
3482/// `HostRegistry::dispatch`, however much native stack the host itself uses,
3483/// and one more turn of [`encoded::dispatch`]. That is exactly the situation
3484/// [`crate::interp::MAX_REENTRY_DEPTH`] was calibrated for, in the oracle,
3485/// where its documentation says the depth limit's promise *"holds for Cove
3486/// calling Cove and stops holding exactly where a third party controls the
3487/// multiplier"*. The sentence is true of this backend word for word — it is
3488/// true of it *more* narrowly, because Cove calling Cove costs no native
3489/// stack here at all — so the bound is the same bound and the refusal is the
3490/// oracle's own, from [`crate::interp::reentry_too_deep`].
3491///
3492/// [`Reentry`]: crate::host::Reentry
3493struct Back<'m, 's, 'a> {
3494    machine: &'m mut Machine<'a>,
3495    budget: &'m Meter,
3496    /// Where the host call that is running this was written, so a failure
3497    /// inside it points at the call rather than at nothing.
3498    span: Span,
3499    /// The thread scope a `spawn` inside a callback starts its children in.
3500    ///
3501    /// The *caller's*, not one of this call's own. A callback is a frame of
3502    /// this machine and its tasks are this machine's tasks: they go into
3503    /// [`Machine::children`] at indices `running` is parallel to, and a scope
3504    /// of this call's own would have made those two disagree — a task the
3505    /// outer level spawned would be at an index a nested `running` had no
3506    /// handle at. One scope per task, which is what [`Machine::drive`] opens,
3507    /// is also what bounds every thread of the task to the task.
3508    threads: &'s Scope<'s, 'a>,
3509    running: &'m mut Vec<Option<ScopedJoinHandle<'s, Outcome>>>,
3510    /// The safepoint the calling task stands at while the host runs.
3511    ///
3512    /// Taken here and dropped for exactly as long as a callback runs. A task
3513    /// inside a host call is not running Cove, so the roots it published stay
3514    /// true and a collection need not wait for it; a task running a callback
3515    /// *is* running Cove, its frames change between two instructions, and a
3516    /// snapshot left standing would be telling the collector to trace a frame
3517    /// that has moved.
3518    parked: Option<Parked>,
3519}
3520
3521impl<'m, 's, 'a> Back<'m, 's, 'a> {
3522    /// The way back for one host call, with the calling task parked.
3523    fn parked(
3524        machine: &'m mut Machine<'a>,
3525        budget: &'m Meter,
3526        span: Span,
3527        threads: &'s Scope<'s, 'a>,
3528        running: &'m mut Vec<Option<ScopedJoinHandle<'s, Outcome>>>,
3529    ) -> Back<'m, 's, 'a> {
3530        let parked = machine.park();
3531        // This call has run no callback yet, so nothing it answers may be
3532        // tagged with the family an earlier one left behind.
3533        machine.callback_answer = None;
3534        Back {
3535            machine,
3536            budget,
3537            span,
3538            threads,
3539            running,
3540            parked: Some(parked),
3541        }
3542    }
3543
3544    /// Runs `callee`, off the safepoint and back onto it.
3545    fn run(&mut self, callee: &Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
3546        drop(self.parked.take());
3547        let answer = self.machine.call_from_host(
3548            callee,
3549            args,
3550            self.budget,
3551            self.span,
3552            self.threads,
3553            self.running,
3554        );
3555        // Whatever the callback did, this task is inside a host call again
3556        // and the host may go on to wait, to call again, or to answer.
3557        self.parked = Some(self.machine.park());
3558        answer
3559    }
3560}
3561
3562impl Reentry for Back<'_, '_, '_> {
3563    fn call(&mut self, callee: &Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
3564        self.run(callee, args)
3565    }
3566
3567    /// The same call, with `stop` added to what its safepoints stop on.
3568    ///
3569    /// The flag bounds this call *and everything inside it*, which is why it
3570    /// stands on the machine rather than being handed to the frame: a further
3571    /// host call the body makes, and any callback that host runs in turn, are
3572    /// reached through the same [`Machine::stops`].
3573    fn call_until(
3574        &mut self,
3575        callee: &Value,
3576        args: Vec<Value>,
3577        stop: &Cancellation,
3578    ) -> Result<Value, RuntimeError> {
3579        self.machine.stops.push(stop.clone());
3580        let result = self.run(callee, args);
3581        self.machine.stops.pop();
3582        result
3583    }
3584
3585    /// Everything a safepoint would stop on, asked from outside the loop.
3586    ///
3587    /// A host that is waiting is standing where a safepoint would be, so it
3588    /// is owed the same answer one gets: the calling task's own flag, the
3589    /// flags of the bounded calls this thread is inside, and the run's
3590    /// cancellation. The middle one is what tells a host blocked inside a
3591    /// `clock.timeout` body that something is wrong, and it could not be
3592    /// answered until a callback could run here at all.
3593    fn is_cancelled(&self) -> bool {
3594        self.machine
3595            .cancellation
3596            .as_ref()
3597            .is_some_and(Cancellation::is_cancelled)
3598            || self.machine.stops.iter().any(Cancellation::is_cancelled)
3599            || self.budget.is_cancelled()
3600    }
3601
3602    fn time_left(&self) -> Option<Duration> {
3603        self.budget
3604            .limits()
3605            .deadline
3606            .map(|deadline| deadline.saturating_sub(self.budget.elapsed()))
3607    }
3608
3609    /// The task whose stack this call is standing on, which is the task the
3610    /// boundary records the call against.
3611    fn task(&self) -> u64 {
3612        self.machine.task
3613    }
3614}
3615
3616/// One spawned task's thread, from the closure in to the answer written.
3617///
3618/// `crate::interp::run_task` is the oracle's, and the shape is the same: an
3619/// evaluator of the receiving task's own, the body, and then the trace event
3620/// a finished task writes. What is not here is the conversion. ADR 0008 says
3621/// *"the runtime's `Rc`-based value representation is not `Send`, so the
3622/// values that cross must be converted at the boundary"* — and in this model
3623/// there is nothing to convert, because a crossing value is a run of words in
3624/// a heap both tasks already address. The closure crossed as its address, and
3625/// the answer goes back into an object the parent allocated.
3626///
3627/// A task stopped by its own cancellation did not run to completion, so it is
3628/// **not** traced as completed here; it is traced as cancelled by whoever
3629/// waits for it, which is the only place that knows it stopped rather than
3630/// finished. That is `crate::task::finished`'s rule, kept.
3631#[allow(clippy::too_many_arguments)]
3632fn run_task(
3633    program: &Program,
3634    hosts: Option<&HostRegistry>,
3635    runtime: Option<&Runtime>,
3636    resources: Arc<Mutex<Vec<Arc<ResourceHandle>>>>,
3637    segment: Memory,
3638    budget: Meter,
3639    cancellation: Cancellation,
3640    id: u64,
3641    closure: u64,
3642    answer: u64,
3643    span: Span,
3644    debugger: Option<&(dyn Debugger + Send + Sync)>,
3645    encoded: Arc<cove_ir::bytecode::Encoded>,
3646    literal_addrs: Arc<[u64]>,
3647    widths: Arc<[u32]>,
3648) -> Outcome {
3649    let mut machine = Machine::for_task(
3650        program,
3651        hosts,
3652        runtime,
3653        resources,
3654        segment,
3655        cancellation.clone(),
3656        id,
3657        encoded,
3658        literal_addrs,
3659        widths,
3660    );
3661    machine.watch(debugger);
3662    let started = Instant::now();
3663    let result = machine.enter_closure(closure, &budget, span);
3664    if !(result.is_err() && cancellation.is_cancelled()) {
3665        if let Some(runtime) = runtime {
3666            runtime.trace(TraceEvent::TaskCompleted {
3667                id,
3668                // What the body spent rather than what the clock did: a task
3669                // that waited on a host was not working while it waited, and
3670                // a trace that could not tell the two apart is what ADR 0008
3671                // lists as the thing phase 1 could not validate.
3672                cpu: started.elapsed().saturating_sub(machine.host_wait()),
3673            });
3674        }
3675    }
3676    let words = result?;
3677    // Into the object the parent allocated, whose address it has held as a
3678    // root since before this thread existed. Nothing between here and the
3679    // last frame's `Return` allocates or reaches a safepoint, so no
3680    // collection can run in the window where these words are only in a Rust
3681    // `Vec`.
3682    for (at, word) in words.iter().enumerate() {
3683        machine.mem.set_payload(answer, 1 + at as u32, *word);
3684    }
3685    Ok(())
3686}
3687
3688fn compare(op: CmpOp, ordering: std::cmp::Ordering) -> bool {
3689    use std::cmp::Ordering::*;
3690    match op {
3691        CmpOp::Eq => ordering == Equal,
3692        CmpOp::Ne => ordering != Equal,
3693        CmpOp::Lt => ordering == Less,
3694        CmpOp::Le => ordering != Greater,
3695        CmpOp::Gt => ordering == Greater,
3696        CmpOp::Ge => ordering != Less,
3697    }
3698}
3699
3700/// `Int` arithmetic, with the language's messages.
3701///
3702/// The messages are the interpreter's, word for word, because overflow and
3703/// division by zero are rules of the language rather than of a backend. The
3704/// differential corpus compares them.
3705fn int_arith(op: ArithOp, a: i64, b: i64, duration: bool) -> Result<i64, RuntimeError> {
3706    let named = |what: &'static str| -> &'static str {
3707        if duration {
3708            "duration arithmetic"
3709        } else {
3710            what
3711        }
3712    };
3713    match op {
3714        ArithOp::Add => a
3715            .checked_add(b)
3716            .ok_or_else(|| overflowed(named("addition"))),
3717        ArithOp::Sub => a
3718            .checked_sub(b)
3719            .ok_or_else(|| overflowed(named("subtraction"))),
3720        ArithOp::Mul => a
3721            .checked_mul(b)
3722            .ok_or_else(|| overflowed(named("multiplication"))),
3723        ArithOp::Div => {
3724            if b == 0 {
3725                Err(divided_by_zero("division"))
3726            } else {
3727                a.checked_div(b).ok_or_else(|| overflowed("division"))
3728            }
3729        }
3730        ArithOp::Rem => {
3731            if b == 0 {
3732                Err(divided_by_zero("remainder"))
3733            } else {
3734                a.checked_rem(b).ok_or_else(|| overflowed("remainder"))
3735            }
3736        }
3737    }
3738}
3739
3740fn float_arith(op: ArithOp, a: f64, b: f64) -> f64 {
3741    match op {
3742        ArithOp::Add => a + b,
3743        ArithOp::Sub => a - b,
3744        ArithOp::Mul => a * b,
3745        ArithOp::Div => a / b,
3746        ArithOp::Rem => a % b,
3747    }
3748}
3749
3750fn overflowed(operation: &str) -> RuntimeError {
3751    RuntimeError::new(format!("`Int` {operation} overflowed"))
3752        .with_rule("Integer overflow is a broken invariant, not a wrapped result.")
3753}
3754
3755fn divided_by_zero(operation: &str) -> RuntimeError {
3756    RuntimeError::new(format!("`Int` {operation} by zero"))
3757        .with_rule("Division and remainder by zero are broken invariants.")
3758}
3759
3760/// A call passed a number of arguments the callee does not declare.
3761///
3762/// The verifier checks it, so this is a lowering bug that got past it. It is
3763/// reported rather than assumed because the alternative is a callee whose
3764/// remaining parameters hold whatever the frame was zeroed with — which is a
3765/// silent wrong answer instead of a loud one.
3766fn wrong_arity(callee: String, declared: usize, given: usize) -> RuntimeError {
3767    RuntimeError::new(format!(
3768        "this call passes {given} argument(s) to `{callee}`, which declares {declared}"
3769    ))
3770}
3771
3772/// A reference slot held null where an object was needed.
3773///
3774/// This is not a language-level `nil`: Cove has none. It is a lowering bug
3775/// reaching the machine, reported rather than read through.
3776fn null_object() -> RuntimeError {
3777    RuntimeError::new("this value was read before it was given one")
3778}
3779
3780/// A `lock` taken by a task that already holds the same cell.
3781///
3782/// The oracle's, word for word: `crate::shared::reentrant_lock` is where these
3783/// three sentences are written, and a program refused by one backend and not
3784/// the other in different words would be two languages. What
3785/// [ADR 0037](../../../../docs/adr/0037-a-cycle-through-a-cell-is-an-ordinary-cycle.md)
3786/// removed is the *other* refusal `lock` used to make; this one it kept, and
3787/// gave the reason: locking the same cell twice from one task is a live lock
3788/// state, and no collector can answer one.
3789fn reentrant_lock() -> RuntimeError {
3790    RuntimeError::new("this task already holds this `Shared`, so `lock` would wait for itself")
3791        .with_rule(
3792            "`lock` holds the value for the whole of the closure it is given, so a `lock` on the same `Shared` inside it can never be granted.",
3793        )
3794        .with_help("do the whole read-modify-write in one `lock`")
3795}
3796
3797/// A `Repr::Task` or `Repr::Scope` word that names no entry of this task's
3798/// scheduler table.
3799///
3800/// Zero is what a zeroed frame leaves in a slot nothing has written, which is
3801/// the same lowering bug a null reference is and earns the same refusal.
3802fn no_such_handle(what: &str) -> RuntimeError {
3803    RuntimeError::new(format!("this {what} was read before it was given one"))
3804}
3805
3806/// A `spawn` this run has no stack segment left for.
3807///
3808/// The reserved stack region divides into a fixed number of segments and a
3809/// task owns one, so a run with more tasks executing at once than there are
3810/// segments has nowhere to put the next one's frames. It is reported as what
3811/// it is — a limit on how many tasks may run at once — rather than as a
3812/// second, differently worded ceiling standing beside the one
3813/// `[run.*] max_tasks` configures, because a program that hits either has hit
3814/// the same wall.
3815///
3816/// The tree-walking oracle has no equivalent: it puts a task's frames on a
3817/// thread's own stack and is bounded by what the operating system will give
3818/// it. Every corpus program stays far below both.
3819fn no_segment_left() -> RuntimeError {
3820    RuntimeError::new(
3821        "execution stopped: this run has no stack segment left for another task, so no more          tasks may run at once",
3822    )
3823}
3824
3825#[cfg(test)]
3826pub(crate) mod tests {
3827    use super::*;
3828    use cove_ir::{
3829        Arg, ArgsId, Capture, Compare, Function, Inst, Layout, Len, Num, RefMap, Table, TableId,
3830    };
3831    use std::sync::Arc;
3832
3833    /// Builds a program by hand.
3834    ///
3835    /// The lowering is a separate piece and a separate test suite. What is
3836    /// under test here is the machine, so its programs are written in the IR
3837    /// directly: a failure is then unambiguously the loop's, and a change to
3838    /// the lowering cannot quietly stop exercising an instruction.
3839    ///
3840    /// `pub(crate)` so that the boundary's and the builtins' tests write
3841    /// their fixtures the same way. A hand-written program is the only kind
3842    /// any of them uses, and having one builder is what keeps a fixture from
3843    /// being the thing under test.
3844    #[derive(Default)]
3845    pub(crate) struct Build {
3846        pub(crate) program: Program,
3847    }
3848
3849    impl Build {
3850        pub(crate) fn strings(mut self, texts: &[&str]) -> Build {
3851            self.program.strings = texts.iter().map(|text| Arc::from(*text)).collect();
3852            self
3853        }
3854
3855        /// A family that lives in the heap, so a value of it is one address.
3856        pub(crate) fn layout(&mut self, name: &str, shape: Shape) -> LayoutId {
3857            self.push(Layout::object(name, shape))
3858        }
3859
3860        /// A one-word family: the width-one case of the whole model.
3861        pub(crate) fn word(&mut self, name: &str, repr: Repr) -> LayoutId {
3862            self.push(Layout::word(name, repr))
3863        }
3864
3865        /// A struct, laid out inline from its fields' layouts.
3866        ///
3867        /// The offsets are computed by `cove_ir::struct_layout` rather than
3868        /// written out, because they are not a choice a fixture gets to make:
3869        /// a fixture free to say where a field is could agree with a machine
3870        /// that had it wrong.
3871        pub(crate) fn structure(&mut self, name: &str, fields: &[(&str, LayoutId)]) -> LayoutId {
3872            let named: Vec<(Arc<str>, LayoutId)> = fields
3873                .iter()
3874                .map(|(name, id)| (Arc::from(*name), *id))
3875                .collect();
3876            let (fields, words) = cove_ir::struct_layout(&named, &self.program.layouts);
3877            self.push(Layout::inline(
3878                name,
3879                Shape::Struct {
3880                    fields,
3881                    opaque: false,
3882                },
3883                words,
3884            ))
3885        }
3886
3887        /// An enum, laid out under the payload-agreement rule.
3888        pub(crate) fn enumeration(
3889            &mut self,
3890            name: &str,
3891            cases: &[(&str, Vec<LayoutId>)],
3892        ) -> LayoutId {
3893            let named: Vec<(Arc<str>, Vec<LayoutId>)> = cases
3894                .iter()
3895                .map(|(name, parts)| (Arc::from(*name), parts.clone()))
3896                .collect();
3897            let (cases, payload) = cove_ir::enum_layout(&named, &self.program.layouts);
3898            let mut words = vec![Repr::Int];
3899            words.extend_from_slice(&payload);
3900            self.push(Layout::inline(name, Shape::Enum { cases, payload }, words))
3901        }
3902
3903        /// The layout a `Box` allocates, reserved the way the lowering
3904        /// reserves it: a fixture that had to remember to declare one would
3905        /// be a fixture that could forget, and forgetting sizes the object
3906        /// by the wrong shape.
3907        pub(crate) fn boxed(&mut self) -> LayoutId {
3908            self.seed();
3909            self.program.boxed_layout
3910        }
3911
3912        /// `LayoutId(0)` is the sweeper's free block and `LayoutId(1)` is the
3913        /// box, exactly as `cove_ir::lower` reserves them.
3914        fn seed(&mut self) {
3915            if self.program.layouts.is_empty() {
3916                self.program.layouts.push(Layout::free());
3917                self.program
3918                    .layouts
3919                    .push(Layout::object("Any", Shape::Boxed));
3920                self.program.boxed_layout = LayoutId(1);
3921            }
3922        }
3923
3924        fn push(&mut self, layout: Layout) -> LayoutId {
3925            self.seed();
3926            self.program.layouts.push(layout);
3927            LayoutId(self.program.layouts.len() as u32 - 1)
3928        }
3929
3930        /// An argument list: where each value is, and the layout that says
3931        /// how wide it is.
3932        pub(crate) fn args(&mut self, args: &[(Slot, LayoutId)]) -> ArgsId {
3933            self.program.args.push(
3934                args.iter()
3935                    .map(|(slot, layout)| Arg {
3936                        slot: *slot,
3937                        layout: *layout,
3938                    })
3939                    .collect(),
3940            );
3941            ArgsId(self.program.args.len() as u32 - 1)
3942        }
3943
3944        pub(crate) fn table(&mut self, targets: &[u32], default: u32) -> TableId {
3945            self.program.tables.push(Table {
3946                targets: targets.to_vec(),
3947                default,
3948            });
3949            TableId(self.program.tables.len() as u32 - 1)
3950        }
3951
3952        pub(crate) fn function(
3953            &mut self,
3954            name: &str,
3955            params: &[LayoutId],
3956            reprs: &[Repr],
3957            returns: LayoutId,
3958            code: Vec<Inst>,
3959        ) -> FunctionId {
3960            let nowhere = Span::new(cove_diag::FileId(0), 0, 0);
3961            let spans = vec![nowhere; code.len()];
3962            self.program.functions.push(Function {
3963                module: Arc::from("t"),
3964                name: Arc::from(name),
3965                params: params.to_vec(),
3966                reprs: reprs.to_vec(),
3967                refs: RefMap::of(reprs),
3968                returns,
3969                captures: Vec::<Capture>::new(),
3970                code,
3971                spans,
3972                locals: Vec::new(),
3973                inlined: Vec::new(),
3974                span: nowhere,
3975                is_async: false,
3976                // A function a test builds by hand is a function with a
3977                // body, whatever the body is: nothing here stands in for a
3978                // declaration the lowering left out.
3979                stub: false,
3980            });
3981            let id = FunctionId(self.program.functions.len() as u32 - 1);
3982            self.program
3983                .by_name
3984                .insert((Arc::from("t"), Arc::from(name)), id);
3985            id
3986        }
3987
3988        /// A function that reads captures: what a lowered lambda is.
3989        ///
3990        /// The slot each capture lands in is filled in here rather than
3991        /// written out per fixture, because it is not a choice a fixture gets
3992        /// to make: captures follow the parameters, so the first one begins
3993        /// where the parameters' words end and each one after it follows at
3994        /// its own width, and a fixture free to say otherwise could agree
3995        /// with a machine that had the rule wrong.
3996        pub(crate) fn lambda(
3997            &mut self,
3998            name: &str,
3999            params: &[LayoutId],
4000            reprs: &[Repr],
4001            returns: LayoutId,
4002            captures: &[LayoutId],
4003            code: Vec<Inst>,
4004        ) -> FunctionId {
4005            let mut slot: Slot = params
4006                .iter()
4007                .map(|id| self.program.layout(*id).width())
4008                .sum();
4009            let held: Vec<Capture> = captures
4010                .iter()
4011                .enumerate()
4012                .map(|(at, layout)| {
4013                    let capture = Capture {
4014                        name: Arc::from(format!("c{at}")),
4015                        slot,
4016                        layout: *layout,
4017                    };
4018                    slot += self.program.layout(*layout).width();
4019                    capture
4020                })
4021                .collect();
4022            let id = self.function(name, params, reprs, returns, code);
4023            self.program.functions[id.index()].captures = held;
4024            id
4025        }
4026
4027        /// Checks the program the way the lowering must, so a malformed test
4028        /// fixture fails as a fixture rather than as a machine bug.
4029        pub(crate) fn done(self) -> Program {
4030            cove_ir::verify(&self.program).expect("a hand-written test program is well formed");
4031            self.program
4032        }
4033
4034        /// A program of layouts and strings and no functions.
4035        ///
4036        /// What a boundary or a builtin test needs: both of them convert or
4037        /// read values rather than running code, and a function written only
4038        /// so that a program has one would be a fixture nothing reads.
4039        pub(crate) fn bare(mut self) -> Program {
4040            let str_layout = self.layout("String", Shape::Str);
4041            self.program.str_layout = str_layout;
4042            self.done()
4043        }
4044
4045        /// `String`'s layout, declared and recorded as `Program::str_layout`
4046        /// the way `cove_ir::lower` records it — `Inst::Str` and
4047        /// [ADR 0051](../../../docs/adr/0051-a-string-is-built-as-a-byte-run.md)'s
4048        /// `Inst::FinishString` read the field rather than being told the
4049        /// layout at each call site, so a fixture that only declared the
4050        /// shape without recording it here would allocate strings the
4051        /// dispatch loop could not finish into.
4052        pub(crate) fn string_layout(&mut self) -> LayoutId {
4053            let id = self.layout("String", Shape::Str);
4054            self.program.str_layout = id;
4055            id
4056        }
4057
4058        /// [ADR 0051](../../../docs/adr/0051-a-string-is-built-as-a-byte-run.md)'s
4059        /// byte-run layout, declared and recorded as `Program::bytes_layout`
4060        /// for `string_layout`'s reason: `Inst::AllocBytes` always allocates
4061        /// this field's layout rather than one named in the instruction.
4062        pub(crate) fn bytes_layout(&mut self) -> LayoutId {
4063            let id = self.layout("Bytes", Shape::Bytes);
4064            self.program.bytes_layout = id;
4065            id
4066        }
4067
4068        /// [ADR 0052](../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)'s
4069        /// byte-buffer owner layout, declared and recorded as
4070        /// `Program::buffer_layout` for `bytes_layout`'s reason:
4071        /// `Inst::AllocBuffer` allocates both of the field's layouts rather
4072        /// than any named in the instruction, so a fixture that declared only
4073        /// the shape would allocate owners the dispatch loop could not read.
4074        pub(crate) fn buffer_layout(&mut self) -> LayoutId {
4075            let id = self.layout("ByteBuffer", Shape::ByteBuffer);
4076            self.program.buffer_layout = id;
4077            id
4078        }
4079
4080        /// The one-word layout of `repr`, declared once per fixture.
4081        pub(crate) fn scalar(&mut self, repr: Repr) -> LayoutId {
4082            if let Some(at) = self
4083                .program
4084                .layouts
4085                .iter()
4086                .position(|layout| layout.shape == Shape::Word(repr))
4087            {
4088                return LayoutId(at as u32);
4089            }
4090            self.word(repr.name(), repr)
4091        }
4092    }
4093
4094    pub(crate) fn budget() -> Meter {
4095        crate::budget::Budget::new(crate::budget::Limits::default()).meter()
4096    }
4097
4098    /// The words a run of `entry` answers.
4099    ///
4100    /// A function answers a *value location*, which is a run of words, so
4101    /// this is the general shape of a result and [`run`] is the common case
4102    /// of it. A fixture that answers a `Point` reads two words here rather
4103    /// than one address naming two words somewhere else.
4104    pub(crate) fn run_words(
4105        program: &Program,
4106        entry: FunctionId,
4107        args: &[u64],
4108    ) -> Result<Vec<u64>, RuntimeError> {
4109        Machine::new(program, 1 << 16).run(entry, args, &budget())
4110    }
4111
4112    /// The one word a run of `entry` answers.
4113    ///
4114    /// Most of what is under test here is one word wide — an `Int`, a `Bool`,
4115    /// a reference — and writing `[0]` at every one of those call sites would
4116    /// put the same unchecked index in fifty places. The assertion is what
4117    /// keeps it honest: a fixture whose answer stopped being one word fails
4118    /// here rather than quietly reporting its first word.
4119    fn run(program: &Program, entry: FunctionId, args: &[u64]) -> Result<u64, RuntimeError> {
4120        let words = run_words(program, entry, args)?;
4121        assert_eq!(words.len(), 1, "this fixture answers one word");
4122        Ok(words[0])
4123    }
4124
4125    #[test]
4126    fn a_constant_comes_back() {
4127        let mut build = Build::default();
4128        let int = build.scalar(Repr::Int);
4129        let f = build.function(
4130            "answer",
4131            &[],
4132            &[Repr::Int],
4133            int,
4134            vec![Inst::Int { dst: 0, value: 42 }, Inst::Return { src: 0 }],
4135        );
4136        let program = build.done();
4137        assert_eq!(run(&program, f, &[]).unwrap() as i64, 42);
4138    }
4139
4140    #[test]
4141    fn arithmetic_reads_and_writes_slots() {
4142        let mut build = Build::default();
4143        let int = build.scalar(Repr::Int);
4144        let f = build.function(
4145            "add",
4146            &[int, int],
4147            &[Repr::Int, Repr::Int, Repr::Int],
4148            int,
4149            vec![
4150                Inst::Arith {
4151                    num: Num::Int,
4152                    op: ArithOp::Add,
4153                    dst: 2,
4154                    a: 0,
4155                    b: 1,
4156                },
4157                Inst::Return { src: 2 },
4158            ],
4159        );
4160        let program = build.done();
4161        assert_eq!(run(&program, f, &[3, 4]).unwrap() as i64, 7);
4162    }
4163
4164    /// The messages are the language's, not the backend's; the differential
4165    /// corpus compares them against the tree-walking oracle word for word.
4166    #[test]
4167    fn arithmetic_faults_say_what_the_oracle_says() {
4168        let cases: Vec<(ArithOp, i64, i64, &str)> = vec![
4169            (ArithOp::Div, 1, 0, "`Int` division by zero"),
4170            (ArithOp::Rem, 1, 0, "`Int` remainder by zero"),
4171            (ArithOp::Add, i64::MAX, 1, "`Int` addition overflowed"),
4172            (ArithOp::Mul, i64::MAX, 2, "`Int` multiplication overflowed"),
4173        ];
4174        for (op, a, b, message) in cases {
4175            let mut build = Build::default();
4176            let int = build.scalar(Repr::Int);
4177            let f = build.function(
4178                "fault",
4179                &[int, int],
4180                &[Repr::Int, Repr::Int, Repr::Int],
4181                int,
4182                vec![
4183                    Inst::Arith {
4184                        num: Num::Int,
4185                        op,
4186                        dst: 2,
4187                        a: 0,
4188                        b: 1,
4189                    },
4190                    Inst::Return { src: 2 },
4191                ],
4192            );
4193            let program = build.done();
4194            let error = run(&program, f, &[a as u64, b as u64]).unwrap_err();
4195            assert_eq!(error.message, message);
4196        }
4197    }
4198
4199    /// A `Duration` is nanoseconds and its arithmetic is an integer's, so the
4200    /// only thing that changes is what an overflow is called.
4201    #[test]
4202    fn a_duration_overflow_is_named_a_duration_overflow() {
4203        let mut build = Build::default();
4204        let duration = build.scalar(Repr::Duration);
4205        let f = build.function(
4206            "late",
4207            &[duration, duration],
4208            &[Repr::Duration, Repr::Duration, Repr::Duration],
4209            duration,
4210            vec![
4211                Inst::Arith {
4212                    num: Num::Int,
4213                    op: ArithOp::Add,
4214                    dst: 2,
4215                    a: 0,
4216                    b: 1,
4217                },
4218                Inst::Return { src: 2 },
4219            ],
4220        );
4221        let program = build.done();
4222        let error = run(&program, f, &[i64::MAX as u64, 1]).unwrap_err();
4223        assert_eq!(error.message, "`Int` duration arithmetic overflowed");
4224    }
4225
4226    #[test]
4227    fn a_branch_takes_one_side() {
4228        let mut build = Build::default();
4229        let int = build.scalar(Repr::Int);
4230        // fn abs(n) { if n < 0 { -n } else { n } }
4231        let f = build.function(
4232            "abs",
4233            &[int],
4234            &[Repr::Int, Repr::Int, Repr::Bool],
4235            int,
4236            vec![
4237                Inst::Int { dst: 1, value: 0 },
4238                Inst::Cmp {
4239                    on: Compare::Int,
4240                    op: CmpOp::Lt,
4241                    dst: 2,
4242                    a: 0,
4243                    b: 1,
4244                },
4245                Inst::BranchFalse { cond: 2, to: 4 },
4246                Inst::Neg {
4247                    num: Num::Int,
4248                    dst: 0,
4249                    a: 0,
4250                },
4251                Inst::Return { src: 0 },
4252            ],
4253        );
4254        let program = build.done();
4255        assert_eq!(run(&program, f, &[(-5i64) as u64]).unwrap() as i64, 5);
4256        assert_eq!(run(&program, f, &[5]).unwrap() as i64, 5);
4257    }
4258
4259    #[test]
4260    fn a_loop_runs_to_its_bound() {
4261        let mut build = Build::default();
4262        let int = build.scalar(Repr::Int);
4263        // fn sum(n) { var t = 0; var i = 0; while i < n { t = t + i; i = i + 1 }; t }
4264        let f = build.function(
4265            "sum",
4266            &[int],
4267            &[Repr::Int, Repr::Int, Repr::Int, Repr::Bool, Repr::Int],
4268            int,
4269            vec![
4270                Inst::Int { dst: 1, value: 0 },
4271                Inst::Int { dst: 2, value: 0 },
4272                Inst::Cmp {
4273                    on: Compare::Int,
4274                    op: CmpOp::Lt,
4275                    dst: 3,
4276                    a: 2,
4277                    b: 0,
4278                },
4279                Inst::BranchFalse { cond: 3, to: 8 },
4280                Inst::Arith {
4281                    num: Num::Int,
4282                    op: ArithOp::Add,
4283                    dst: 1,
4284                    a: 1,
4285                    b: 2,
4286                },
4287                Inst::Int { dst: 4, value: 1 },
4288                Inst::Arith {
4289                    num: Num::Int,
4290                    op: ArithOp::Add,
4291                    dst: 2,
4292                    a: 2,
4293                    b: 4,
4294                },
4295                Inst::Jump { to: 2 },
4296                Inst::Return { src: 1 },
4297            ],
4298        );
4299        let program = build.done();
4300        assert_eq!(run(&program, f, &[10]).unwrap() as i64, 45);
4301    }
4302
4303    /// A call writes its arguments straight into the callee's slots and its
4304    /// answer straight into the caller's destination. There is no buffer
4305    /// between the two frames, and this is what says so.
4306    #[test]
4307    fn recursion_nests_frames_and_unwinds_them() {
4308        let mut build = Build::default();
4309        let int = build.scalar(Repr::Int);
4310        let args = build.args(&[(3, int)]);
4311        // fn fact(n) { if n <= 1 { 1 } else { n * fact(n - 1) } }
4312        let f = build.function(
4313            "fact",
4314            &[int],
4315            &[Repr::Int, Repr::Int, Repr::Bool, Repr::Int, Repr::Int],
4316            int,
4317            vec![
4318                Inst::Int { dst: 1, value: 1 },
4319                Inst::Cmp {
4320                    on: Compare::Int,
4321                    op: CmpOp::Le,
4322                    dst: 2,
4323                    a: 0,
4324                    b: 1,
4325                },
4326                Inst::BranchFalse { cond: 2, to: 4 },
4327                Inst::Return { src: 1 },
4328                Inst::Arith {
4329                    num: Num::Int,
4330                    op: ArithOp::Sub,
4331                    dst: 3,
4332                    a: 0,
4333                    b: 1,
4334                },
4335                Inst::Call {
4336                    dst: 4,
4337                    callee: FunctionId(0),
4338                    args,
4339                },
4340                Inst::Arith {
4341                    num: Num::Int,
4342                    op: ArithOp::Mul,
4343                    dst: 4,
4344                    a: 0,
4345                    b: 4,
4346                },
4347                Inst::Return { src: 4 },
4348            ],
4349        );
4350        let program = build.done();
4351        assert_eq!(run(&program, f, &[10]).unwrap() as i64, 3_628_800);
4352    }
4353
4354    /// Depth is bounded by the reserved stack region, not by the Rust stack:
4355    /// a call does not recurse in the dispatch loop, so this returns an error
4356    /// rather than ending the process.
4357    #[test]
4358    fn an_unbounded_recursion_is_stopped() {
4359        let mut build = Build::default();
4360        let int = build.scalar(Repr::Int);
4361        let args = build.args(&[(0, int)]);
4362        let f = build.function(
4363            "forever",
4364            &[int],
4365            &[Repr::Int, Repr::Int],
4366            int,
4367            vec![
4368                Inst::Call {
4369                    dst: 1,
4370                    callee: FunctionId(0),
4371                    args,
4372                },
4373                Inst::Return { src: 1 },
4374            ],
4375        );
4376        let program = build.done();
4377        let error = run(&program, f, &[0]).unwrap_err();
4378        assert_eq!(error.message, "this call nests too deeply");
4379    }
4380
4381    // ---- values that are more than one word ------------------------------
4382
4383    /// `docs/LINEAR_VM.md` §1, in the IR it writes out.
4384    ///
4385    /// ~~~cove
4386    /// struct Point { x: Int, y: Int }
4387    /// var a = Point(x: 1, y: 2)
4388    /// var b = a
4389    /// b.x = 7
4390    /// ~~~
4391    ///
4392    /// `a` is at slots 0–1 and `b` at 2–3, and `b = a` is one `Copy` of two
4393    /// words. `a.x` is slot 0 and nothing touched it — not because a bit said
4394    /// the copy was unshared, but because the copy put `b`'s words where `b`
4395    /// is. There is no sharing bit, no copy-on-write and no write path to
4396    /// unshare; `b.x = 7` writes slot 2 and that is all of it.
4397    ///
4398    /// The answer is the four slots read as one `Pair`, which is the same
4399    /// claim from the other side: a value location is a base slot and a
4400    /// layout, so two adjacent `Point`s *are* a four-word value.
4401    #[test]
4402    fn a_copy_is_the_words_of_the_value() {
4403        let mut build = Build::default();
4404        let int = build.scalar(Repr::Int);
4405        let point = build.structure("Point", &[("x", int), ("y", int)]);
4406        let pair = build.structure("Pair", &[("a", point), ("b", point)]);
4407        let f = build.function(
4408            "copy",
4409            &[],
4410            &[Repr::Int, Repr::Int, Repr::Int, Repr::Int, Repr::Int],
4411            pair,
4412            vec![
4413                Inst::Int { dst: 0, value: 1 },
4414                Inst::Int { dst: 1, value: 2 },
4415                Inst::Copy {
4416                    dst: 2,
4417                    src: 0,
4418                    layout: point,
4419                },
4420                Inst::Int { dst: 4, value: 7 },
4421                // `b.x` is slot 2 + 0: a field of an inline struct is
4422                // arithmetic the lowering did, not an instruction.
4423                Inst::Copy {
4424                    dst: 2,
4425                    src: 4,
4426                    layout: int,
4427                },
4428                Inst::Return { src: 0 },
4429            ],
4430        );
4431        let program = build.done();
4432        assert_eq!(program.layout(point).words, vec![Repr::Int, Repr::Int]);
4433        assert_eq!(run_words(&program, f, &[]).unwrap(), vec![1, 2, 7, 2]);
4434    }
4435
4436    /// `docs/LINEAR_VM.md` §3: `struct Wrapper { p: Point, v: Vector<Int> }`
4437    /// is `[p.x: Int, p.y: Int, v: Ref]`, and a copy copies all three words.
4438    ///
4439    /// Two answers fall out of that one copy and neither needed a policy. The
4440    /// `Point` words become independent, so writing `b.p.x` leaves `a.p.x`
4441    /// alone. The `Vector` address is duplicated, so both wrappers name one
4442    /// vector — which is ADR 0001 verbatim, because a `Vector`'s storage is
4443    /// shared and mutable by the language's own rule rather than by anything
4444    /// the representation decided.
4445    #[test]
4446    fn a_copied_wrapper_separates_its_point_and_shares_its_vector() {
4447        let mut build = Build::default();
4448        let int = build.scalar(Repr::Int);
4449        let point = build.structure("Point", &[("x", int), ("y", int)]);
4450        let vector = build.layout("Vector", Shape::Vector { elem: int });
4451        let wrapper = build.structure("Wrapper", &[("p", point), ("v", vector)]);
4452        let both = build.structure("Both", &[("a", wrapper), ("b", wrapper)]);
4453        let f = build.function(
4454            "wrap",
4455            &[],
4456            &[
4457                Repr::Int,
4458                Repr::Int,
4459                Repr::Ref,
4460                Repr::Int,
4461                Repr::Int,
4462                Repr::Ref,
4463                Repr::Int,
4464            ],
4465            both,
4466            vec![
4467                Inst::Int { dst: 0, value: 1 },
4468                Inst::Int { dst: 1, value: 2 },
4469                Inst::Alloc {
4470                    dst: 2,
4471                    layout: vector,
4472                    len: Len::Fixed,
4473                },
4474                Inst::Copy {
4475                    dst: 3,
4476                    src: 0,
4477                    layout: wrapper,
4478                },
4479                Inst::Int { dst: 6, value: 7 },
4480                Inst::Copy {
4481                    dst: 3,
4482                    src: 6,
4483                    layout: int,
4484                },
4485                Inst::Return { src: 0 },
4486            ],
4487        );
4488        let program = build.done();
4489        assert_eq!(
4490            program.layout(wrapper).words,
4491            vec![Repr::Int, Repr::Int, Repr::Ref]
4492        );
4493        let words = run_words(&program, f, &[]).unwrap();
4494        assert_eq!(words.len(), 6);
4495        assert_eq!(&words[..2], &[1, 2], "`a`'s point is where `a` is");
4496        assert_eq!(&words[3..5], &[7, 2], "`b`'s point is where `b` is");
4497        assert_ne!(words[2], 0, "the vector was allocated");
4498        assert_eq!(words[2], words[5], "and both wrappers name that one vector");
4499    }
4500
4501    /// `docs/LINEAR_VM.md` §5: a parameter takes the words its layout says,
4502    /// from slot 0 onward in declaration order, so a `(Int, Point, Int)` list
4503    /// occupies slots 0, 1–2 and 3. Nothing is permuted into type groups,
4504    /// because there are no type groups.
4505    ///
4506    /// The answer is a `Point` too, and `Return` copies the two words its
4507    /// `Function::returns` describes into the caller's destination location.
4508    /// Neither direction allocates: a struct crosses a call as its words.
4509    #[test]
4510    fn a_struct_is_passed_and_returned_as_its_words() {
4511        let mut build = Build::default();
4512        let int = build.scalar(Repr::Int);
4513        let point = build.structure("Point", &[("x", int), ("y", int)]);
4514        // fn shift(n: Int, p: Point, m: Int) -> Point
4515        let shift = build.function(
4516            "shift",
4517            &[int, point, int],
4518            &[
4519                Repr::Int,
4520                Repr::Int,
4521                Repr::Int,
4522                Repr::Int,
4523                Repr::Int,
4524                Repr::Int,
4525            ],
4526            point,
4527            vec![
4528                Inst::Arith {
4529                    num: Num::Int,
4530                    op: ArithOp::Add,
4531                    dst: 4,
4532                    a: 1,
4533                    b: 0,
4534                },
4535                Inst::Arith {
4536                    num: Num::Int,
4537                    op: ArithOp::Add,
4538                    dst: 5,
4539                    a: 2,
4540                    b: 3,
4541                },
4542                Inst::Return { src: 4 },
4543            ],
4544        );
4545        let args = build.args(&[(0, int), (1, point), (3, int)]);
4546        let main = build.function(
4547            "main",
4548            &[],
4549            &[
4550                Repr::Int,
4551                Repr::Int,
4552                Repr::Int,
4553                Repr::Int,
4554                Repr::Int,
4555                Repr::Int,
4556            ],
4557            point,
4558            vec![
4559                Inst::Int { dst: 0, value: 10 },
4560                Inst::Int { dst: 1, value: 1 },
4561                Inst::Int { dst: 2, value: 2 },
4562                Inst::Int { dst: 3, value: 20 },
4563                Inst::Call {
4564                    dst: 4,
4565                    callee: shift,
4566                    args,
4567                },
4568                Inst::Return { src: 4 },
4569            ],
4570        );
4571        let program = build.done();
4572        let target = program.function(shift);
4573        assert_eq!(target.param_slot(0, &program.layouts), 0);
4574        assert_eq!(target.param_slot(1, &program.layouts), 1);
4575        assert_eq!(target.param_slot(2, &program.layouts), 3);
4576        assert_eq!(target.param_words(&program.layouts), 4);
4577        assert_eq!(run_words(&program, main, &[]).unwrap(), vec![11, 22]);
4578    }
4579
4580    /// An `Array<Point>` is a run of two-word elements rather than a run of
4581    /// addresses, and the stride an element instruction uses is the element
4582    /// layout's width.
4583    ///
4584    /// The header's `len` counts *elements*, so an index is checked against
4585    /// three and then multiplied — which is why writing element 1 through an
4586    /// `AddrOfElem` leaves element 2 alone rather than smearing across it,
4587    /// and why index 3 is refused although the object holds six words.
4588    #[test]
4589    fn an_array_of_points_is_walked_at_a_two_word_stride() {
4590        let mut build = Build::default();
4591        let int = build.scalar(Repr::Int);
4592        let point = build.structure("Point", &[("x", int), ("y", int)]);
4593        let points = build.layout(
4594            "Array",
4595            Shape::Elements {
4596                elem: point,
4597                growable: false,
4598            },
4599        );
4600        let two = build.structure("Two", &[("a", point), ("b", point)]);
4601        let reprs = &[
4602            Repr::Ref,
4603            Repr::Int,
4604            Repr::Int,
4605            Repr::Int,
4606            Repr::Int,
4607            Repr::Int,
4608            Repr::Int,
4609            Repr::Int,
4610            Repr::Addr,
4611        ];
4612        let walk = build.function(
4613            "walk",
4614            &[],
4615            reprs,
4616            two,
4617            vec![
4618                Inst::Alloc {
4619                    dst: 0,
4620                    layout: points,
4621                    len: Len::Count(3),
4622                },
4623                // xs[0] = Point(1, 2)
4624                Inst::Int { dst: 1, value: 0 },
4625                Inst::Int { dst: 2, value: 1 },
4626                Inst::Int { dst: 3, value: 2 },
4627                Inst::StoreElem {
4628                    obj: 0,
4629                    index: 1,
4630                    src: 2,
4631                    layout: point,
4632                },
4633                // xs[1] = Point(3, 4)
4634                Inst::Int { dst: 1, value: 1 },
4635                Inst::Int { dst: 2, value: 3 },
4636                Inst::Int { dst: 3, value: 4 },
4637                Inst::StoreElem {
4638                    obj: 0,
4639                    index: 1,
4640                    src: 2,
4641                    layout: point,
4642                },
4643                // xs[2] = Point(5, 6)
4644                Inst::Int { dst: 1, value: 2 },
4645                Inst::Int { dst: 2, value: 5 },
4646                Inst::Int { dst: 3, value: 6 },
4647                Inst::StoreElem {
4648                    obj: 0,
4649                    index: 1,
4650                    src: 2,
4651                    layout: point,
4652                },
4653                // A place naming element 1, written through: two words at
4654                // one address, with nothing between the address and them.
4655                Inst::Int { dst: 1, value: 1 },
4656                Inst::AddrOfElem {
4657                    dst: 8,
4658                    obj: 0,
4659                    index: 1,
4660                    layout: point,
4661                },
4662                Inst::Int { dst: 2, value: 30 },
4663                Inst::Int { dst: 3, value: 40 },
4664                Inst::Store {
4665                    addr: 8,
4666                    src: 2,
4667                    layout: point,
4668                },
4669                Inst::LoadElem {
4670                    dst: 4,
4671                    obj: 0,
4672                    index: 1,
4673                    layout: point,
4674                },
4675                Inst::Int { dst: 1, value: 2 },
4676                Inst::LoadElem {
4677                    dst: 6,
4678                    obj: 0,
4679                    index: 1,
4680                    layout: point,
4681                },
4682                Inst::Return { src: 4 },
4683            ],
4684        );
4685        let past = build.function(
4686            "past",
4687            &[],
4688            &[Repr::Ref, Repr::Int, Repr::Int, Repr::Int],
4689            int,
4690            vec![
4691                Inst::Alloc {
4692                    dst: 0,
4693                    layout: points,
4694                    len: Len::Count(3),
4695                },
4696                Inst::Int { dst: 1, value: 3 },
4697                Inst::LoadElem {
4698                    dst: 2,
4699                    obj: 0,
4700                    index: 1,
4701                    layout: point,
4702                },
4703                Inst::Return { src: 2 },
4704            ],
4705        );
4706        let program = build.done();
4707        assert_eq!(
4708            run_words(&program, walk, &[]).unwrap(),
4709            vec![30, 40, 5, 6],
4710            "the write through element 1 left element 2 where it was"
4711        );
4712        let error = run(&program, past, &[]).unwrap_err();
4713        assert_eq!(error.message, "index 3 is outside a collection of 3");
4714    }
4715
4716    /// An enum is a discriminant word and a payload region wide enough for
4717    /// every case, and the offsets are assigned so that **every case using a
4718    /// payload word agrees on its `Repr`**.
4719    ///
4720    /// `enum Msg { Text(Cell), Count(Int) }` therefore lays out as
4721    /// `[disc: Int, Ref, Int]`: `Count`'s `Int` cannot share `Text`'s
4722    /// reference word, so it takes a third. Two things follow, and this is
4723    /// both of them. Constructing a case zeroes the region it does not fill,
4724    /// so `Count`'s reference word reads null rather than whatever `Text`
4725    /// left there. And the collector never reads the discriminant to decide
4726    /// what to trace — the region's map is static, which is one fewer thing
4727    /// that can be wrong.
4728    ///
4729    /// The heap holds one cell and not two, so the second allocation is the
4730    /// question: it succeeds when the value was rebuilt as `Count`, because
4731    /// the word naming the first cell was zeroed and nothing reaches it, and
4732    /// it fails when the value is still `Text`, because that same word is
4733    /// traced and the cell is live.
4734    #[test]
4735    fn an_enums_payload_is_retained_by_its_static_map() {
4736        let mut build = Build::default();
4737        let int = build.scalar(Repr::Int);
4738        let cell = build.layout(
4739            "Cell",
4740            Shape::Elements {
4741                elem: int,
4742                growable: false,
4743            },
4744        );
4745        let msg = build.enumeration("Msg", &[("Text", vec![cell]), ("Count", vec![int])]);
4746        let boolean = build.scalar(Repr::Bool);
4747        let f = build.function(
4748            "held",
4749            &[boolean],
4750            &[
4751                Repr::Bool,
4752                // The `Msg` is at slots 1–3: a discriminant, `Text`'s
4753                // reference and `Count`'s integer.
4754                Repr::Int,
4755                Repr::Ref,
4756                Repr::Int,
4757                Repr::Ref,
4758                Repr::Int,
4759            ],
4760            int,
4761            vec![
4762                Inst::Alloc {
4763                    dst: 4,
4764                    layout: cell,
4765                    len: Len::Count(1200),
4766                },
4767                Inst::Int { dst: 1, value: 0 },
4768                Inst::Copy {
4769                    dst: 2,
4770                    src: 4,
4771                    layout: cell,
4772                },
4773                // The enum's payload word is now the only name for the cell.
4774                Inst::Clear {
4775                    slot: 4,
4776                    layout: cell,
4777                },
4778                Inst::BranchFalse { cond: 0, to: 8 },
4779                // Constructing `Count` zeroes the region it does not fill,
4780                // which is what leaves `Text`'s reference word null.
4781                Inst::Clear {
4782                    slot: 1,
4783                    layout: msg,
4784                },
4785                Inst::Int { dst: 1, value: 1 },
4786                Inst::Int { dst: 3, value: 5 },
4787                Inst::Alloc {
4788                    dst: 4,
4789                    layout: cell,
4790                    len: Len::Count(1200),
4791                },
4792                Inst::Int { dst: 5, value: 7 },
4793                Inst::Return { src: 5 },
4794            ],
4795        );
4796        let program = build.done();
4797        assert_eq!(
4798            program.layout(msg).words,
4799            vec![Repr::Int, Repr::Ref, Repr::Int]
4800        );
4801
4802        let mut kept = Machine::new(&program, 2048);
4803        let error = kept.run(f, &[0], &budget()).unwrap_err();
4804        assert_eq!(error.message, "this run has no memory left");
4805
4806        let mut dropped = Machine::new(&program, 2048);
4807        assert_eq!(dropped.run(f, &[1], &budget()).unwrap(), vec![7]);
4808        assert!(
4809            dropped.collected().collections > 0,
4810            "the second cell only fits after the first is reclaimed"
4811        );
4812    }
4813
4814    /// A one-parameter function that allocates `layout` with `Len::Slot(0)`
4815    /// and returns it: the shape issue #269 is about, where the count is a
4816    /// value the *running* program computed rather than one this compiler
4817    /// chose, so the parameter is a slot the caller controls entirely, and
4818    /// `Machine::allocate` is the one place left to check it.
4819    ///
4820    /// `build` and `int` are the caller's: a `LayoutId` only means anything
4821    /// against the layout table it came from, so `layout` and the scalar the
4822    /// parameter is declared with have to be pushed into the same `Build`
4823    /// this finishes.
4824    fn alloc_of_slot_length(
4825        mut build: Build,
4826        int: LayoutId,
4827        layout: LayoutId,
4828    ) -> (Program, FunctionId) {
4829        let f = build.function(
4830            "alloc_len",
4831            &[int],
4832            &[Repr::Int, Repr::Ref],
4833            layout,
4834            vec![
4835                Inst::Alloc {
4836                    dst: 1,
4837                    layout,
4838                    len: Len::Slot(0),
4839                },
4840                Inst::Return { src: 1 },
4841            ],
4842        );
4843        (build.done(), f)
4844    }
4845
4846    /// A negative count is not a large `u32`: `Machine::allocate` reads the
4847    /// slot's bits as the `i64` they are before anything narrows them, so a
4848    /// negative one is caught rather than turned into an allocation of
4849    /// billions of elements.
4850    #[test]
4851    fn a_negative_length_is_rejected_before_it_narrows() {
4852        let mut build = Build::default();
4853        let int = build.scalar(Repr::Int);
4854        let cell = build.layout(
4855            "Cell",
4856            Shape::Elements {
4857                elem: int,
4858                growable: false,
4859            },
4860        );
4861        let (program, f) = alloc_of_slot_length(build, int, cell);
4862        let mut machine = Machine::new(&program, 1 << 16);
4863        let error = machine.run(f, &[(-1i64) as u64], &budget()).unwrap_err();
4864        assert_eq!(error.message, "this run has no memory left");
4865    }
4866
4867    /// A count past `u32::MAX` is not representable in the header's own
4868    /// length field, whatever the element width is.
4869    #[test]
4870    fn a_length_past_u32_max_is_rejected() {
4871        let mut build = Build::default();
4872        let int = build.scalar(Repr::Int);
4873        let cell = build.layout(
4874            "Cell",
4875            Shape::Elements {
4876                elem: int,
4877                growable: false,
4878            },
4879        );
4880        let (program, f) = alloc_of_slot_length(build, int, cell);
4881        let mut machine = Machine::new(&program, 1 << 16);
4882        let error = machine
4883            .run(f, &[u64::from(u32::MAX) + 1], &budget())
4884            .unwrap_err();
4885        assert_eq!(error.message, "this run has no memory left");
4886    }
4887
4888    /// A count that fits `u32` on its own can still make `count * stride`
4889    /// overflow it: `u32::MAX` elements of a two-word `Point` is the case
4890    /// [`cove_ir::Layout::try_payload_words`] exists for, checked in `u64`
4891    /// rather than wrapped in `u32`.
4892    #[test]
4893    fn a_count_times_stride_overflow_is_rejected() {
4894        let mut build = Build::default();
4895        let int = build.scalar(Repr::Int);
4896        let point = build.structure("Point", &[("x", int), ("y", int)]);
4897        let array = build.layout(
4898            "Array",
4899            Shape::Elements {
4900                elem: point,
4901                growable: false,
4902            },
4903        );
4904        let (program, f) = alloc_of_slot_length(build, int, array);
4905        let mut machine = Machine::new(&program, 1 << 16);
4906        let error = machine
4907            .run(f, &[u64::from(u32::MAX)], &budget())
4908            .unwrap_err();
4909        assert_eq!(error.message, "this run has no memory left");
4910    }
4911
4912    /// A count that is entirely in range, and whose payload size does not
4913    /// overflow anything, is still refused once it is larger than this run's
4914    /// own heap budget — the same "this run has no memory left" a
4915    /// `Len::Count` allocation raises, reached this time through a
4916    /// `Len::Slot` the running program computed.
4917    #[test]
4918    fn a_length_beyond_the_heap_budget_is_rejected() {
4919        let mut build = Build::default();
4920        let int = build.scalar(Repr::Int);
4921        let cell = build.layout(
4922            "Cell",
4923            Shape::Elements {
4924                elem: int,
4925                growable: false,
4926            },
4927        );
4928        let (program, f) = alloc_of_slot_length(build, int, cell);
4929        let mut machine = Machine::new(&program, 64);
4930        let error = machine.run(f, &[1_000_000], &budget()).unwrap_err();
4931        assert_eq!(error.message, "this run has no memory left");
4932    }
4933
4934    /// A frame's map is a function of its `Repr`s, and a multiword value
4935    /// contributes its flattened per-word ones.
4936    ///
4937    /// `docs/LINEAR_VM.md` §6: a `Wrapper { p: Point, v: Vector }` at slot 5
4938    /// contributes `Int, Int, Ref`, so slot 7 is a root and 5 and 6 are not.
4939    /// Nothing about the value's *width* reaches the collector — it reads one
4940    /// bit per slot, as it did when every value was one word, and a wide
4941    /// value is simply several slots' worth of bits.
4942    ///
4943    /// The other half is that a slot the map does not name cannot hold a
4944    /// reference at all: the verifier holds every instruction to the `Repr`
4945    /// of the slot it names, so a program that put an address in slot 6 is
4946    /// not a program. That is what makes one static bit per slot sound, and
4947    /// it is why the dynamic half of this test reaches for `Clear` instead —
4948    /// a reference slot the map *does* name, emptied at its last use.
4949    #[test]
4950    fn a_frames_map_covers_a_multiword_value_word_by_word() {
4951        let mut build = Build::default();
4952        let int = build.scalar(Repr::Int);
4953        let cell = build.layout(
4954            "Cell",
4955            Shape::Elements {
4956                elem: int,
4957                growable: false,
4958            },
4959        );
4960        let point = build.structure("Point", &[("x", int), ("y", int)]);
4961        let wrapper = build.structure("Wrapper", &[("p", point), ("v", cell)]);
4962        assert_eq!(
4963            build.program.layout(wrapper).words,
4964            vec![Repr::Int, Repr::Int, Repr::Ref],
4965            "three words: the `Point` inline, and the cell's address"
4966        );
4967
4968        // A `Wrapper` at slots 5-7, a scratch reference at slot 8, and the
4969        // answer at slot 9.
4970        let reprs = vec![
4971            Repr::Int,
4972            Repr::Int,
4973            Repr::Int,
4974            Repr::Int,
4975            Repr::Int,
4976            Repr::Int,
4977            Repr::Int,
4978            Repr::Ref,
4979            Repr::Ref,
4980            Repr::Int,
4981        ];
4982        let f = build.function(
4983            "wrapper",
4984            &[],
4985            &reprs,
4986            int,
4987            vec![
4988                // The wrapper's `v`, which its own slot keeps alive.
4989                Inst::Alloc {
4990                    dst: 7,
4991                    layout: cell,
4992                    len: Len::Count(600),
4993                },
4994                // A second cell, named by a reference slot that is then
4995                // cleared — so the map still reads slot 8, and reads null.
4996                Inst::Alloc {
4997                    dst: 8,
4998                    layout: cell,
4999                    len: Len::Count(600),
5000                },
5001                Inst::Clear {
5002                    slot: 8,
5003                    layout: cell,
5004                },
5005                Inst::Int { dst: 5, value: 1 },
5006                Inst::Int { dst: 6, value: 2 },
5007                // A third cell fits only if the second was reclaimed, and the
5008                // first must survive to be written through afterwards.
5009                Inst::Alloc {
5010                    dst: 8,
5011                    layout: cell,
5012                    len: Len::Count(600),
5013                },
5014                Inst::Int { dst: 9, value: 0 },
5015                Inst::Int {
5016                    dst: 4,
5017                    value: 4242,
5018                },
5019                Inst::StoreElem {
5020                    obj: 7,
5021                    index: 9,
5022                    src: 4,
5023                    layout: int,
5024                },
5025                Inst::LoadElem {
5026                    dst: 9,
5027                    obj: 7,
5028                    index: 9,
5029                    layout: int,
5030                },
5031                Inst::Return { src: 9 },
5032            ],
5033        );
5034        let program = build.done();
5035
5036        // The static half, which is the claim `docs/LINEAR_VM.md` makes.
5037        let refs = &program.function(f).refs;
5038        assert!(!refs.is_ref(5), "the `Point`'s x is not a root");
5039        assert!(!refs.is_ref(6), "the `Point`'s y is not a root");
5040        assert!(refs.is_ref(7), "the vector's address is");
5041        assert_eq!(refs.iter().collect::<Vec<_>>(), vec![7, 8]);
5042
5043        // The dynamic half: two cells fit at a time and three do not, so the
5044        // run only finishes because the cleared slot stopped being a root —
5045        // and it finishes with the wrapper's own cell still there to write.
5046        let mut machine = Machine::new(&program, 1600);
5047        assert_eq!(machine.run(f, &[], &budget()).unwrap(), vec![4242]);
5048        assert!(
5049            machine.collected().collections > 0,
5050            "the third cell only fits after the cleared one is reclaimed"
5051        );
5052    }
5053
5054    // ---- closures ------------------------------------------------------
5055
5056    /// The layout of a lambda that reads `captures`.
5057    fn closure_layout(build: &mut Build, function: FunctionId, captures: &[LayoutId]) -> LayoutId {
5058        build.layout(
5059            "closure",
5060            Shape::Closure {
5061                function,
5062                captures: captures.to_vec(),
5063            },
5064        )
5065    }
5066
5067    /// A closure's frame is a callee's frame with two writes rather than one:
5068    /// the arguments into the words the parameters occupy, and then the
5069    /// captures into the slots `Function::captures` names, which are the ones
5070    /// straight after.
5071    #[test]
5072    fn a_closure_call_copies_the_arguments_then_the_captures() {
5073        let mut build = Build::default();
5074        let int = build.scalar(Repr::Int);
5075        // { it -> it + captured }
5076        let add = build.lambda(
5077            "lambda",
5078            &[int],
5079            &[Repr::Int, Repr::Int, Repr::Int],
5080            int,
5081            &[int],
5082            vec![
5083                Inst::Arith {
5084                    num: Num::Int,
5085                    op: ArithOp::Add,
5086                    dst: 2,
5087                    a: 0,
5088                    b: 1,
5089                },
5090                Inst::Return { src: 2 },
5091            ],
5092        );
5093        let layout = closure_layout(&mut build, add, &[int]);
5094        let args = build.args(&[(3, int)]);
5095        let main = build.function(
5096            "main",
5097            &[],
5098            &[Repr::Ref, Repr::Int, Repr::Int, Repr::Int, Repr::Int],
5099            int,
5100            vec![
5101                Inst::Alloc {
5102                    dst: 0,
5103                    layout,
5104                    len: Len::Fixed,
5105                },
5106                Inst::Int {
5107                    dst: 1,
5108                    value: add.0 as i64,
5109                },
5110                Inst::StoreField {
5111                    obj: 0,
5112                    at: 0,
5113                    src: 1,
5114                    layout: int,
5115                },
5116                Inst::Int { dst: 2, value: 10 },
5117                Inst::StoreField {
5118                    obj: 0,
5119                    at: 1,
5120                    src: 2,
5121                    layout: int,
5122                },
5123                Inst::Int { dst: 3, value: 5 },
5124                Inst::CallClosure {
5125                    dst: 4,
5126                    closure: 0,
5127                    args,
5128                    result: int,
5129                },
5130                Inst::Return { src: 4 },
5131            ],
5132        );
5133        let program = build.done();
5134        assert_eq!(run(&program, main, &[]).unwrap() as i64, 15);
5135    }
5136
5137    /// A capture is copied into a `Repr::Ref` slot of the callee's frame, so
5138    /// it is a root of that frame like any other — which is what makes a
5139    /// closure need no second story for the collector.
5140    ///
5141    /// The captured object is reachable from nowhere else by the time the call
5142    /// happens: the caller cleared its own slot, and it is not a string, so the
5143    /// interned table is not quietly holding it either. The body then allocates
5144    /// until the heap has to be swept several times over before reading the
5145    /// capture back.
5146    #[test]
5147    fn a_capture_survives_a_collection_in_the_callee() {
5148        let mut build = Build::default();
5149        let int = build.scalar(Repr::Int);
5150        let cell = build.layout(
5151            "Cell",
5152            Shape::Elements {
5153                elem: int,
5154                growable: false,
5155            },
5156        );
5157        let body = build.lambda(
5158            "lambda",
5159            &[],
5160            &[
5161                Repr::Ref,
5162                Repr::Int,
5163                Repr::Int,
5164                Repr::Bool,
5165                Repr::Ref,
5166                Repr::Int,
5167                Repr::Int,
5168                Repr::Int,
5169            ],
5170            int,
5171            &[cell],
5172            vec![
5173                Inst::Int { dst: 2, value: 300 },
5174                Inst::Int { dst: 1, value: 0 },
5175                Inst::Cmp {
5176                    on: Compare::Int,
5177                    op: CmpOp::Lt,
5178                    dst: 3,
5179                    a: 1,
5180                    b: 2,
5181                },
5182                Inst::BranchFalse { cond: 3, to: 9 },
5183                Inst::Alloc {
5184                    dst: 4,
5185                    layout: cell,
5186                    len: Len::Count(64),
5187                },
5188                Inst::Clear {
5189                    slot: 4,
5190                    layout: cell,
5191                },
5192                Inst::Int { dst: 5, value: 1 },
5193                Inst::Arith {
5194                    num: Num::Int,
5195                    op: ArithOp::Add,
5196                    dst: 1,
5197                    a: 1,
5198                    b: 5,
5199                },
5200                Inst::Jump { to: 2 },
5201                Inst::Int { dst: 6, value: 0 },
5202                Inst::LoadElem {
5203                    dst: 7,
5204                    obj: 0,
5205                    index: 6,
5206                    layout: int,
5207                },
5208                Inst::Return { src: 7 },
5209            ],
5210        );
5211        let layout = closure_layout(&mut build, body, &[cell]);
5212        let none = build.args(&[]);
5213        let main = build.function(
5214            "main",
5215            &[],
5216            &[
5217                Repr::Ref,
5218                Repr::Ref,
5219                Repr::Int,
5220                Repr::Int,
5221                Repr::Int,
5222                Repr::Int,
5223            ],
5224            int,
5225            vec![
5226                Inst::Alloc {
5227                    dst: 1,
5228                    layout: cell,
5229                    len: Len::Count(1),
5230                },
5231                Inst::Int { dst: 3, value: 0 },
5232                Inst::Int {
5233                    dst: 4,
5234                    value: 4242,
5235                },
5236                Inst::StoreElem {
5237                    obj: 1,
5238                    index: 3,
5239                    src: 4,
5240                    layout: int,
5241                },
5242                Inst::Alloc {
5243                    dst: 0,
5244                    layout,
5245                    len: Len::Fixed,
5246                },
5247                Inst::Int {
5248                    dst: 2,
5249                    value: body.0 as i64,
5250                },
5251                Inst::StoreField {
5252                    obj: 0,
5253                    at: 0,
5254                    src: 2,
5255                    layout: int,
5256                },
5257                Inst::StoreField {
5258                    obj: 0,
5259                    at: 1,
5260                    src: 1,
5261                    layout: cell,
5262                },
5263                Inst::Clear {
5264                    slot: 1,
5265                    layout: cell,
5266                },
5267                Inst::CallClosure {
5268                    dst: 5,
5269                    closure: 0,
5270                    args: none,
5271                    result: int,
5272                },
5273                Inst::Return { src: 5 },
5274            ],
5275        );
5276        let program = build.done();
5277        let mut machine = Machine::new(&program, 4096);
5278        assert_eq!(machine.run(main, &[], &budget()).unwrap(), vec![4242]);
5279        assert!(
5280            machine.collected().collections > 0,
5281            "the body is meant to allocate more than the heap holds"
5282        );
5283    }
5284
5285    /// A closure that calls itself through its own capture nests until the
5286    /// reserved stack region is full, and stops there — with the message any
5287    /// other unbounded recursion gets, because it is the same event. No Rust
5288    /// frame is added per turn, so how deep this goes is `STACK_WORDS` and
5289    /// nothing else.
5290    #[test]
5291    fn a_closure_chain_is_bounded_by_the_stack_region() {
5292        let mut build = Build::default();
5293        let int = build.scalar(Repr::Int);
5294        // What the closure captures is the closure, so the capture's layout
5295        // is one reference word rather than the callee's own `Int`.
5296        let held = build.word("captured", Repr::Ref);
5297        let none = build.args(&[]);
5298        // What it answers is never reached, because it never returns.
5299        let body = build.lambda(
5300            "lambda",
5301            &[],
5302            &[Repr::Ref, Repr::Int],
5303            int,
5304            &[held],
5305            vec![
5306                Inst::CallClosure {
5307                    dst: 1,
5308                    closure: 0,
5309                    args: none,
5310                    result: int,
5311                },
5312                Inst::Return { src: 1 },
5313            ],
5314        );
5315        let layout = closure_layout(&mut build, body, &[held]);
5316        let main = build.function(
5317            "main",
5318            &[],
5319            &[Repr::Ref, Repr::Int, Repr::Int],
5320            int,
5321            vec![
5322                Inst::Alloc {
5323                    dst: 0,
5324                    layout,
5325                    len: Len::Fixed,
5326                },
5327                Inst::Int {
5328                    dst: 1,
5329                    value: body.0 as i64,
5330                },
5331                Inst::StoreField {
5332                    obj: 0,
5333                    at: 0,
5334                    src: 1,
5335                    layout: int,
5336                },
5337                // The closure captures itself, which is the shortest way to
5338                // write a call chain with no bound on it.
5339                Inst::StoreField {
5340                    obj: 0,
5341                    at: 1,
5342                    src: 0,
5343                    layout: held,
5344                },
5345                Inst::CallClosure {
5346                    dst: 2,
5347                    closure: 0,
5348                    args: none,
5349                    result: int,
5350                },
5351                Inst::Return { src: 2 },
5352            ],
5353        );
5354        let program = build.done();
5355        let error = run(&program, main, &[]).unwrap_err();
5356        assert_eq!(error.message, "this call nests too deeply");
5357    }
5358
5359    /// `fn main() { spin() }`, where `spin` is a closure whose body never
5360    /// leaves its loop.
5361    ///
5362    /// The caller is four instructions and the fifth enters the closure, so
5363    /// every safepoint after the first handful is one the closure's own frame
5364    /// is executing at.
5365    fn spinning_closure(build: &mut Build) -> FunctionId {
5366        let int = build.scalar(Repr::Int);
5367        let body = build.lambda(
5368            "lambda",
5369            &[],
5370            &[Repr::Int],
5371            int,
5372            &[],
5373            vec![Inst::Int { dst: 0, value: 0 }, Inst::Jump { to: 0 }],
5374        );
5375        let layout = closure_layout(build, body, &[]);
5376        let none = build.args(&[]);
5377        build.function(
5378            "main",
5379            &[],
5380            &[Repr::Ref, Repr::Int, Repr::Int],
5381            int,
5382            vec![
5383                Inst::Alloc {
5384                    dst: 0,
5385                    layout,
5386                    len: Len::Fixed,
5387                },
5388                Inst::Int {
5389                    dst: 1,
5390                    value: body.0 as i64,
5391                },
5392                Inst::StoreField {
5393                    obj: 0,
5394                    at: 0,
5395                    src: 1,
5396                    layout: int,
5397                },
5398                Inst::CallClosure {
5399                    dst: 2,
5400                    closure: 0,
5401                    args: none,
5402                    result: int,
5403                },
5404                Inst::Return { src: 2 },
5405            ],
5406        )
5407    }
5408
5409    /// The safepoint is a fact about the loop, not about which frame the loop
5410    /// is in: a run spinning inside a closure is cancelled within one stride
5411    /// exactly as one spinning in its entry is.
5412    #[test]
5413    fn a_cancelled_run_stops_at_a_safepoint_inside_a_closure() {
5414        let mut build = Build::default();
5415        let main = spinning_closure(&mut build);
5416        let program = build.done();
5417        let cancellation = Cancellation::new();
5418        let budget = crate::budget::Budget::with_cancellation(
5419            crate::budget::Limits::default(),
5420            cancellation.clone(),
5421        );
5422        cancellation.cancel();
5423        let mut machine = Machine::new(&program, 1 << 12);
5424        let error = machine.run(main, &[], &budget.meter()).unwrap_err();
5425        assert_eq!(error.message, "execution stopped: the run was cancelled");
5426        assert!(machine.instructions() <= SAFEPOINT_STRIDE + 1);
5427    }
5428
5429    /// And fuel is charged at the same points, so a closure cannot spend a
5430    /// run's budget without the run noticing.
5431    #[test]
5432    fn fuel_runs_out_at_a_safepoint_inside_a_closure() {
5433        let mut build = Build::default();
5434        let main = spinning_closure(&mut build);
5435        let program = build.done();
5436        let budget = crate::budget::Budget::new(crate::budget::Limits {
5437            fuel: Some(2 * SAFEPOINT_STRIDE),
5438            ..Default::default()
5439        });
5440        let mut machine = Machine::new(&program, 1 << 12);
5441        let error = machine.run(main, &[], &budget.meter()).unwrap_err();
5442        assert_eq!(
5443            error.message,
5444            format!(
5445                "execution stopped: fuel budget of {} exhausted",
5446                2 * SAFEPOINT_STRIDE
5447            )
5448        );
5449        assert_eq!(machine.instructions(), 2 * SAFEPOINT_STRIDE);
5450    }
5451
5452    /// The callee comes out of a heap object, so the machine checks that the
5453    /// object is one a call can be made through rather than reading its first
5454    /// word as a function id. Nothing a program can write reaches this; a
5455    /// lowering that did would otherwise push a frame for whichever function
5456    /// the object's first word happened to name.
5457    #[test]
5458    fn a_call_through_something_that_is_not_a_closure_is_refused() {
5459        let mut build = Build::default();
5460        let int = build.scalar(Repr::Int);
5461        let point = build.structure("Point", &[("x", int)]);
5462        let none = build.args(&[]);
5463        let main = build.function(
5464            "main",
5465            &[],
5466            &[Repr::Ref, Repr::Int],
5467            int,
5468            vec![
5469                Inst::Alloc {
5470                    dst: 0,
5471                    layout: point,
5472                    len: Len::Fixed,
5473                },
5474                Inst::CallClosure {
5475                    dst: 1,
5476                    closure: 0,
5477                    args: none,
5478                    result: int,
5479                },
5480                Inst::Return { src: 1 },
5481            ],
5482        );
5483        let program = build.done();
5484        let error = run(&program, main, &[]).unwrap_err();
5485        assert_eq!(error.message, "`Point` is not callable");
5486    }
5487
5488    /// A closure object whose captures are not the ones its callee reads is a
5489    /// lowering bug, and copying what it holds would fill the callee's capture
5490    /// slots from whatever follows the object in the heap.
5491    #[test]
5492    fn a_closure_whose_captures_do_not_match_its_callee_is_refused() {
5493        let mut build = Build::default();
5494        let int = build.scalar(Repr::Int);
5495        let none = build.args(&[]);
5496        let body = build.lambda(
5497            "lambda",
5498            &[],
5499            &[Repr::Int, Repr::Int],
5500            int,
5501            &[int, int],
5502            vec![Inst::Return { src: 0 }],
5503        );
5504        // One capture, against a callee that reads two.
5505        let layout = closure_layout(&mut build, body, &[int]);
5506        let main = build.function(
5507            "main",
5508            &[],
5509            &[Repr::Ref, Repr::Int, Repr::Int],
5510            int,
5511            vec![
5512                Inst::Alloc {
5513                    dst: 0,
5514                    layout,
5515                    len: Len::Fixed,
5516                },
5517                Inst::Int {
5518                    dst: 1,
5519                    value: body.0 as i64,
5520                },
5521                Inst::StoreField {
5522                    obj: 0,
5523                    at: 0,
5524                    src: 1,
5525                    layout: int,
5526                },
5527                Inst::CallClosure {
5528                    dst: 2,
5529                    closure: 0,
5530                    args: none,
5531                    result: int,
5532                },
5533                Inst::Return { src: 2 },
5534            ],
5535        );
5536        let program = build.done();
5537        let error = run(&program, main, &[]).unwrap_err();
5538        assert_eq!(
5539            error.message,
5540            "this closure and `t.lambda` disagree about its captures: 1 held, 2 read"
5541        );
5542    }
5543
5544    /// `bump(var total)` adds to the caller's own binding rather than to a
5545    /// copy of it. A place is one word holding the address of that binding,
5546    /// and this is the whole of the mechanism.
5547    #[test]
5548    fn a_place_writes_the_callers_own_slot() {
5549        let mut build = Build::default();
5550        let int = build.scalar(Repr::Int);
5551        let unit = build.scalar(Repr::Unit);
5552        let place = build.scalar(Repr::Addr);
5553        let args = build.args(&[(1, place)]);
5554        let bump = build.function(
5555            "bump",
5556            &[place],
5557            &[Repr::Addr, Repr::Int, Repr::Int, Repr::Unit],
5558            unit,
5559            vec![
5560                Inst::Load {
5561                    dst: 1,
5562                    addr: 0,
5563                    layout: int,
5564                },
5565                Inst::Int { dst: 2, value: 1 },
5566                Inst::Arith {
5567                    num: Num::Int,
5568                    op: ArithOp::Add,
5569                    dst: 1,
5570                    a: 1,
5571                    b: 2,
5572                },
5573                Inst::Store {
5574                    addr: 0,
5575                    src: 1,
5576                    layout: int,
5577                },
5578                Inst::Unit { dst: 3 },
5579                Inst::Return { src: 3 },
5580            ],
5581        );
5582        let caller = build.function(
5583            "main",
5584            &[],
5585            &[Repr::Int, Repr::Addr, Repr::Unit],
5586            int,
5587            vec![
5588                Inst::Int { dst: 0, value: 10 },
5589                Inst::AddrOfSlot { dst: 1, slot: 0 },
5590                Inst::Call {
5591                    dst: 2,
5592                    callee: bump,
5593                    args,
5594                },
5595                Inst::Return { src: 0 },
5596            ],
5597        );
5598        let program = build.done();
5599        assert_eq!(run(&program, caller, &[]).unwrap() as i64, 11);
5600    }
5601
5602    /// A field of a *heap object* is a load and a store; a field of an inline
5603    /// struct is not an instruction at all. This is the first kind, which is
5604    /// what a struct reaches by being the payload of an object.
5605    #[test]
5606    fn an_object_round_trips_through_its_fields() {
5607        let mut build = Build::default();
5608        let int = build.scalar(Repr::Int);
5609        let point = build.structure("Point", &[("x", int), ("y", int)]);
5610        let f = build.function(
5611            "make",
5612            &[],
5613            &[Repr::Ref, Repr::Int, Repr::Int],
5614            int,
5615            vec![
5616                Inst::Alloc {
5617                    dst: 0,
5618                    layout: point,
5619                    len: Len::Fixed,
5620                },
5621                Inst::Int { dst: 1, value: 3 },
5622                Inst::StoreField {
5623                    obj: 0,
5624                    at: 0,
5625                    src: 1,
5626                    layout: int,
5627                },
5628                Inst::Int { dst: 1, value: 4 },
5629                Inst::StoreField {
5630                    obj: 0,
5631                    at: 1,
5632                    src: 1,
5633                    layout: int,
5634                },
5635                Inst::LoadField {
5636                    dst: 1,
5637                    obj: 0,
5638                    at: 0,
5639                    layout: int,
5640                },
5641                Inst::LoadField {
5642                    dst: 2,
5643                    obj: 0,
5644                    at: 1,
5645                    layout: int,
5646                },
5647                Inst::Arith {
5648                    num: Num::Int,
5649                    op: ArithOp::Mul,
5650                    dst: 1,
5651                    a: 1,
5652                    b: 2,
5653                },
5654                Inst::Return { src: 1 },
5655            ],
5656        );
5657        let program = build.done();
5658        assert_eq!(run(&program, f, &[]).unwrap() as i64, 12);
5659    }
5660
5661    /// Reading past an object is a lowering bug, and the machine reports it
5662    /// rather than reading whatever follows the object in the heap.
5663    ///
5664    /// The object reaches the slot it is read out of through a `Copy`, which
5665    /// is what leaves the machine to answer: `cove_ir::verify` refuses this
5666    /// statically wherever it can prove which layout a reference slot holds,
5667    /// and a slot written by a copy holds whatever the source held. Both
5668    /// checks are wanted — the static one catches the bug at lowering time,
5669    /// and this one catches it where the layout is not a static fact.
5670    #[test]
5671    fn a_field_past_the_object_is_refused() {
5672        let mut build = Build::default();
5673        let int = build.scalar(Repr::Int);
5674        let one = build.structure("One", &[("x", int)]);
5675        let held = build.layout("Held", Shape::Word(Repr::Ref));
5676        let f = build.function(
5677            "past",
5678            &[],
5679            &[Repr::Ref, Repr::Int, Repr::Ref],
5680            int,
5681            vec![
5682                Inst::Alloc {
5683                    dst: 0,
5684                    layout: one,
5685                    len: Len::Fixed,
5686                },
5687                Inst::Copy {
5688                    dst: 2,
5689                    src: 0,
5690                    layout: held,
5691                },
5692                Inst::LoadField {
5693                    dst: 1,
5694                    obj: 2,
5695                    at: 3,
5696                    layout: int,
5697                },
5698                Inst::Return { src: 1 },
5699            ],
5700        );
5701        let program = build.done();
5702        let error = run(&program, f, &[]).unwrap_err();
5703        assert!(
5704            error.message.contains("word 3 of a `One`"),
5705            "{}",
5706            error.message
5707        );
5708    }
5709
5710    /// The loop allocates in a loop, clearing the slot each turn. Without
5711    /// `Clear` the frame would hold every object it ever made; with it the
5712    /// heap stays flat, and this is the test that says so.
5713    #[test]
5714    fn clearing_a_slot_lets_the_collector_reclaim() {
5715        let mut build = Build::default();
5716        let int = build.scalar(Repr::Int);
5717        let cell = build.layout(
5718            "Cell",
5719            Shape::Elements {
5720                elem: int,
5721                growable: false,
5722            },
5723        );
5724        let f = build.function(
5725            "churn",
5726            &[int],
5727            &[Repr::Int, Repr::Int, Repr::Bool, Repr::Ref, Repr::Int],
5728            int,
5729            vec![
5730                Inst::Int { dst: 1, value: 0 },
5731                Inst::Cmp {
5732                    on: Compare::Int,
5733                    op: CmpOp::Lt,
5734                    dst: 2,
5735                    a: 1,
5736                    b: 0,
5737                },
5738                Inst::BranchFalse { cond: 2, to: 8 },
5739                Inst::Alloc {
5740                    dst: 3,
5741                    layout: cell,
5742                    len: Len::Count(64),
5743                },
5744                Inst::Clear {
5745                    slot: 3,
5746                    layout: cell,
5747                },
5748                Inst::Int { dst: 4, value: 1 },
5749                Inst::Arith {
5750                    num: Num::Int,
5751                    op: ArithOp::Add,
5752                    dst: 1,
5753                    a: 1,
5754                    b: 4,
5755                },
5756                Inst::Jump { to: 1 },
5757                Inst::Return { src: 1 },
5758            ],
5759        );
5760        let program = build.done();
5761        // A heap far smaller than 4000 objects of 65 words: the run only
5762        // finishes because each turn's object is unreachable by the next.
5763        let mut machine = Machine::new(&program, 4096);
5764        let answer = machine.run(f, &[4000], &budget()).unwrap();
5765        assert_eq!(answer, vec![4000]);
5766        assert!(
5767            machine.collected().collections > 0,
5768            "the run should have had to collect"
5769        );
5770    }
5771
5772    #[test]
5773    fn a_string_literal_is_allocated_once() {
5774        let mut build = Build::default().strings(&["hello"]);
5775        let bool_layout = build.scalar(Repr::Bool);
5776        let str_layout = build.layout("String", Shape::Str);
5777        build.program.str_layout = str_layout;
5778        let f = build.function(
5779            "twice",
5780            &[],
5781            &[Repr::Ref, Repr::Ref, Repr::Bool],
5782            bool_layout,
5783            vec![
5784                Inst::Str {
5785                    dst: 0,
5786                    text: StrId(0),
5787                },
5788                Inst::Str {
5789                    dst: 1,
5790                    text: StrId(0),
5791                },
5792                Inst::Cmp {
5793                    on: Compare::Identity,
5794                    op: CmpOp::Eq,
5795                    dst: 2,
5796                    a: 0,
5797                    b: 1,
5798                },
5799                Inst::Return { src: 2 },
5800            ],
5801        );
5802        let program = build.done();
5803        assert_eq!(run(&program, f, &[]).unwrap(), 1);
5804    }
5805
5806    #[test]
5807    fn strings_compare_by_their_bytes() {
5808        let mut build = Build::default().strings(&["apple", "banana"]);
5809        let bool_layout = build.scalar(Repr::Bool);
5810        let str_layout = build.layout("String", Shape::Str);
5811        build.program.str_layout = str_layout;
5812        let f = build.function(
5813            "order",
5814            &[],
5815            &[Repr::Ref, Repr::Ref, Repr::Bool],
5816            bool_layout,
5817            vec![
5818                Inst::Str {
5819                    dst: 0,
5820                    text: StrId(0),
5821                },
5822                Inst::Str {
5823                    dst: 1,
5824                    text: StrId(1),
5825                },
5826                Inst::Cmp {
5827                    on: Compare::Str,
5828                    op: CmpOp::Lt,
5829                    dst: 2,
5830                    a: 0,
5831                    b: 1,
5832                },
5833                Inst::Return { src: 2 },
5834            ],
5835        );
5836        let program = build.done();
5837        assert_eq!(run(&program, f, &[]).unwrap(), 1);
5838    }
5839
5840    // --- ADR 0045: a literal is there before the program runs -------------
5841
5842    /// A literal in a hot loop allocates nothing: `Inst::Str` is a load of a
5843    /// precomputed address, and `Machine::allocated_words` — the figure that
5844    /// used to grow the first time a loop reached the literal — does not
5845    /// move at all once the run has started.
5846    #[test]
5847    fn a_literal_in_a_loop_allocates_nothing() {
5848        let mut build = Build::default().strings(&["hot"]);
5849        let str_layout = build.layout("String", Shape::Str);
5850        build.program.str_layout = str_layout;
5851        let int = build.scalar(Repr::Int);
5852        // fn turns() -> Int { var i = 0; while i < 1000 { let _s = "hot"; i += 1 }; i }
5853        let f = build.function(
5854            "turns",
5855            &[],
5856            &[Repr::Ref, Repr::Int, Repr::Int, Repr::Bool, Repr::Int],
5857            int,
5858            vec![
5859                Inst::Int {
5860                    dst: 2,
5861                    value: 1000,
5862                },
5863                Inst::Int { dst: 1, value: 0 },
5864                Inst::Cmp {
5865                    on: Compare::Int,
5866                    op: CmpOp::Lt,
5867                    dst: 3,
5868                    a: 1,
5869                    b: 2,
5870                },
5871                Inst::BranchFalse { cond: 3, to: 8 },
5872                Inst::Str {
5873                    dst: 0,
5874                    text: StrId(0),
5875                },
5876                Inst::Int { dst: 4, value: 1 },
5877                Inst::Arith {
5878                    num: Num::Int,
5879                    op: ArithOp::Add,
5880                    dst: 1,
5881                    a: 1,
5882                    b: 4,
5883                },
5884                Inst::Jump { to: 2 },
5885                Inst::Return { src: 1 },
5886            ],
5887        );
5888        let program = build.done();
5889        let mut machine = Machine::new(&program, 1 << 16);
5890        let before = machine.allocated_words();
5891        assert_eq!(machine.run(f, &[], &budget()).unwrap(), vec![1000]);
5892        assert_eq!(
5893            machine.allocated_words(),
5894            before,
5895            "a thousand turns through `str` allocated nothing beyond the literal's own placement"
5896        );
5897        assert_eq!(
5898            machine.collected().collections,
5899            0,
5900            "nothing here ever had reason to collect"
5901        );
5902    }
5903
5904    /// An unused literal is still built: `Machine::place_literals` places
5905    /// every entry of `Program::strings`, not only the ones an entry
5906    /// happens to load. Today an unused literal costs nothing; under ADR
5907    /// 0045 it costs its bytes, which is the regression the ADR names
5908    /// rather than hides.
5909    #[test]
5910    fn an_unused_literal_is_still_built() {
5911        let mut build = Build::default().strings(&["never loaded"]);
5912        let str_layout = build.layout("String", Shape::Str);
5913        build.program.str_layout = str_layout;
5914        let int = build.scalar(Repr::Int);
5915        // `f`'s body never mentions `StrId(0)` — there is no `Inst::Str` in
5916        // it at all — and the literal is placed before `f` can run anyway.
5917        let f = build.function(
5918            "f",
5919            &[],
5920            &[Repr::Int],
5921            int,
5922            vec![Inst::Int { dst: 0, value: 0 }, Inst::Return { src: 0 }],
5923        );
5924        let program = build.done();
5925        let mut machine = Machine::new(&program, 1 << 12);
5926        let addr = machine.literal_addr(StrId(0));
5927        assert_ne!(
5928            addr, 0,
5929            "the literal was placed even though nothing loads it"
5930        );
5931        assert_eq!(machine.string_bytes(addr), b"never loaded");
5932        // And `f` runs exactly as it would if the literal did not exist:
5933        // placing it ahead of time changes nothing this body can observe.
5934        assert_eq!(machine.run(f, &[], &budget()).unwrap(), vec![0]);
5935    }
5936
5937    /// Literals are never collected, rooted or not — the floor's whole job.
5938    /// A sweep begins at `Space::static_end` and never walks below it, so
5939    /// nothing there can be found unmarked and freed, whether or not any
5940    /// frame, slot or temp names it. This machine has none of those at all.
5941    #[test]
5942    fn a_literal_survives_a_collection_that_roots_nothing() {
5943        let mut build = Build::default().strings(&["kept forever"]);
5944        let str_layout = build.layout("String", Shape::Str);
5945        build.program.str_layout = str_layout;
5946        let program = build.done();
5947        let mut machine = Machine::new(&program, 1 << 12);
5948        let addr = machine.literal_addr(StrId(0));
5949        machine.collect();
5950        assert_eq!(
5951            machine.string_bytes(addr),
5952            b"kept forever",
5953            "a collection that roots nothing still leaves the literal untouched"
5954        );
5955        // Surviving and being counted as surviving are two claims, and the
5956        // second is the one a floor can quietly lose: the sweep starts above
5957        // the literal, so nothing marks it and nothing adds it up unless the
5958        // static region is counted whole. `freed + live` is what says how
5959        // much was occupied when the collection began.
5960        let words = 1 + machine
5961            .program
5962            .layout(machine.program.str_layout)
5963            .try_payload_words(b"kept forever".len() as u32, &machine.program.layouts)
5964            .expect("a twelve-byte string has a payload");
5965        assert_eq!(
5966            machine.collected().live_words,
5967            u64::from(words),
5968            "the literal's header and payload are live words, unwalked or not"
5969        );
5970        assert_eq!(machine.collected().freed_words, 0);
5971    }
5972
5973    /// A heap object holding a reference to a literal survives a collection
5974    /// that reclaims everything else, and reads the reference back
5975    /// correctly: tracing *through* a reference to a placed literal already
5976    /// worked before this ADR — `reachable` accepts anything below `bump` —
5977    /// and this is what confirms it still does.
5978    #[test]
5979    fn a_struct_field_holding_a_literal_survives_a_collection() {
5980        let mut build = Build::default().strings(&["held by a field"]);
5981        let str_layout = build.layout("String", Shape::Str);
5982        build.program.str_layout = str_layout;
5983        let holder = build.structure("Holder", &[("text", str_layout)]);
5984        let program = build.done();
5985        let mut machine = Machine::new(&program, 1 << 12);
5986        let literal = machine.literal_addr(StrId(0));
5987
5988        let object = machine.new_object(holder, 0).expect("the heap has room");
5989        machine.set_payload(object, 0, literal);
5990        // The struct's own address is the only root; the literal is
5991        // reachable only by tracing through it.
5992        machine.push_temp(object);
5993
5994        machine.collect();
5995
5996        assert_eq!(machine.mem.payload(object, 0), literal);
5997        assert_eq!(machine.string_bytes(literal), b"held by a field");
5998    }
5999
6000    /// Two tasks of one run see the same address for one literal: the
6001    /// entry places it once, and `Machine::for_task` receives the table
6002    /// rather than building its own — which is the whole of the per-task
6003    /// duplication this ADR removes.
6004    #[test]
6005    fn two_tasks_of_one_run_see_the_same_address_for_a_literal() {
6006        let mut build = Build::default().strings(&["shared"]);
6007        let str_layout = build.layout("String", Shape::Str);
6008        build.program.str_layout = str_layout;
6009        let program = build.done();
6010
6011        let entry = Machine::new(&program, 1 << 12);
6012        let entry_addr = entry.literal_addr(StrId(0));
6013
6014        let segment = entry
6015            .mem
6016            .for_task()
6017            .expect("a fresh space has a segment free");
6018        let task = Machine::for_task(
6019            &program,
6020            None,
6021            None,
6022            Arc::clone(&entry.resources),
6023            segment,
6024            Cancellation::new(),
6025            1,
6026            entry.code().expect("this fixture encodes"),
6027            entry.literals().expect("the literals placed"),
6028            Arc::clone(&entry.widths),
6029        );
6030
6031        assert_eq!(
6032            task.literal_addr(StrId(0)),
6033            entry_addr,
6034            "no second placement, no second address"
6035        );
6036        assert_eq!(task.string_bytes(entry_addr), b"shared");
6037    }
6038
6039    /// The eager-placement failure, end to end: a heap too small for the
6040    /// program's literals fails before the entry runs, at the entry's own
6041    /// span, with the message an exhausted heap already gives, and no fuel
6042    /// charged — this program executed nothing.
6043    #[test]
6044    fn a_heap_too_small_for_its_literals_fails_before_the_entry_runs() {
6045        let mut build = Build::default().strings(&["far too long for the heap this run was given"]);
6046        let str_layout = build.layout("String", Shape::Str);
6047        build.program.str_layout = str_layout;
6048        let int = build.scalar(Repr::Int);
6049        let f = build.function(
6050            "f",
6051            &[],
6052            &[Repr::Int],
6053            int,
6054            vec![Inst::Int { dst: 0, value: 0 }, Inst::Return { src: 0 }],
6055        );
6056        let program = build.done();
6057        // Far fewer words than the literal's own header and payload, so
6058        // placing it is what fails — nothing in `f`'s body ever runs.
6059        let mut machine = Machine::new(&program, 4);
6060        let meter = budget();
6061        let error = machine
6062            .run(f, &[], &meter)
6063            .expect_err("a heap this small cannot hold the literal");
6064        assert_eq!(error.message, "this run has no memory left");
6065        assert_eq!(
6066            error.span,
6067            Some(program.function(f).span),
6068            "the entry's own span, not the literal's"
6069        );
6070        assert_eq!(
6071            meter.fuel_spent(),
6072            0,
6073            "a run that executed nothing is charged nothing"
6074        );
6075    }
6076
6077    /// A box carries the *layout* of what it holds in payload word 0, not a
6078    /// per-word `Repr`: erasure is where a value stops having a static width,
6079    /// so what the box has to record is the thing that says the width.
6080    #[test]
6081    fn a_box_answers_the_layout_it_holds_and_refuses_another() {
6082        let mut build = Build::default();
6083        let int = build.scalar(Repr::Int);
6084        let boolean = build.scalar(Repr::Bool);
6085        build.boxed();
6086        let good = build.function(
6087            "round-trip",
6088            &[int],
6089            &[Repr::Int, Repr::Ref, Repr::Int],
6090            int,
6091            vec![
6092                Inst::Box {
6093                    dst: 1,
6094                    src: 0,
6095                    layout: int,
6096                },
6097                Inst::Unbox {
6098                    dst: 2,
6099                    src: 1,
6100                    layout: int,
6101                },
6102                Inst::Return { src: 2 },
6103            ],
6104        );
6105        let wrong = build.function(
6106            "wrong-type",
6107            &[int],
6108            &[Repr::Int, Repr::Ref, Repr::Bool],
6109            boolean,
6110            vec![
6111                Inst::Box {
6112                    dst: 1,
6113                    src: 0,
6114                    layout: int,
6115                },
6116                Inst::Unbox {
6117                    dst: 2,
6118                    src: 1,
6119                    layout: boolean,
6120                },
6121                Inst::Return { src: 2 },
6122            ],
6123        );
6124        let program = build.done();
6125        assert_eq!(run(&program, good, &[7]).unwrap() as i64, 7);
6126        assert_eq!(
6127            run(&program, wrong, &[7]).unwrap_err().message,
6128            "this value is not of the type it is being read as"
6129        );
6130    }
6131
6132    /// A boxed `Point` is a two-word payload rather than a reference to
6133    /// somewhere else again: the object holds the `LayoutId` in payload word
6134    /// 0 and the value's words after it, and the header's `len` is that
6135    /// value's width, because a `Boxed` layout cannot know it.
6136    ///
6137    /// So an `Unbox` at the wrong layout is refused for the same reason it is
6138    /// on a scalar — the word the box carries is a layout and the layouts do
6139    /// not match — and nothing about the width had to be guessed.
6140    #[test]
6141    fn a_box_holds_a_multiword_value_inline() {
6142        let mut build = Build::default();
6143        let int = build.scalar(Repr::Int);
6144        let point = build.structure("Point", &[("x", int), ("y", int)]);
6145        build.boxed();
6146        let round_trip = build.function(
6147            "round-trip",
6148            &[],
6149            &[Repr::Int, Repr::Int, Repr::Ref, Repr::Int, Repr::Int],
6150            point,
6151            vec![
6152                Inst::Int { dst: 0, value: 3 },
6153                Inst::Int { dst: 1, value: 4 },
6154                Inst::Box {
6155                    dst: 2,
6156                    src: 0,
6157                    layout: point,
6158                },
6159                Inst::Unbox {
6160                    dst: 3,
6161                    src: 2,
6162                    layout: point,
6163                },
6164                Inst::Return { src: 3 },
6165            ],
6166        );
6167        let width = build.function(
6168            "width",
6169            &[],
6170            &[Repr::Int, Repr::Int, Repr::Ref, Repr::Int],
6171            int,
6172            vec![
6173                Inst::Int { dst: 0, value: 3 },
6174                Inst::Int { dst: 1, value: 4 },
6175                Inst::Box {
6176                    dst: 2,
6177                    src: 0,
6178                    layout: point,
6179                },
6180                Inst::Len { dst: 3, obj: 2 },
6181                Inst::Return { src: 3 },
6182            ],
6183        );
6184        let wrong = build.function(
6185            "wrong-layout",
6186            &[],
6187            &[Repr::Int, Repr::Int, Repr::Ref, Repr::Int],
6188            int,
6189            vec![
6190                Inst::Int { dst: 0, value: 3 },
6191                Inst::Int { dst: 1, value: 4 },
6192                Inst::Box {
6193                    dst: 2,
6194                    src: 0,
6195                    layout: point,
6196                },
6197                Inst::Unbox {
6198                    dst: 3,
6199                    src: 2,
6200                    layout: int,
6201                },
6202                Inst::Return { src: 3 },
6203            ],
6204        );
6205        let program = build.done();
6206        assert_eq!(run_words(&program, round_trip, &[]).unwrap(), vec![3, 4]);
6207        assert_eq!(run(&program, width, &[]).unwrap(), 2);
6208        assert_eq!(
6209            run(&program, wrong, &[]).unwrap_err().message,
6210            "this value is not of the type it is being read as"
6211        );
6212    }
6213
6214    #[test]
6215    fn a_switch_picks_a_case_and_falls_to_its_default() {
6216        let mut build = Build::default();
6217        let int = build.scalar(Repr::Int);
6218        let table = build.table(&[3, 5], 7);
6219        let f = build.function(
6220            "pick",
6221            &[int],
6222            &[Repr::Int, Repr::Int],
6223            int,
6224            vec![
6225                Inst::Switch { on: 0, table },
6226                Inst::Int { dst: 1, value: 0 },
6227                Inst::Return { src: 1 },
6228                Inst::Int { dst: 1, value: 10 },
6229                Inst::Return { src: 1 },
6230                Inst::Int { dst: 1, value: 20 },
6231                Inst::Return { src: 1 },
6232                Inst::Int { dst: 1, value: 30 },
6233                Inst::Return { src: 1 },
6234            ],
6235        );
6236        let program = build.done();
6237        assert_eq!(run(&program, f, &[0]).unwrap() as i64, 10);
6238        assert_eq!(run(&program, f, &[1]).unwrap() as i64, 20);
6239        assert_eq!(run(&program, f, &[9]).unwrap() as i64, 30);
6240    }
6241
6242    // ---- the host boundary -------------------------------------------
6243
6244    /// A host with one operation of each kind of argument the boundary has
6245    /// to move: a scalar in and out, and a string in and out.
6246    ///
6247    /// Written here rather than reused from a shipped module because what is
6248    /// under test is the *instruction*: `console.println` would drag in a
6249    /// grant table, an output stream and a schema written for a different
6250    /// purpose, and a failure would take a paragraph to attribute.
6251    struct Probe;
6252
6253    static PROBE_OPS: &[cove_schema::OperationSchema] = &[
6254        cove_schema::OperationSchema {
6255            name: "double",
6256            params: &[cove_schema::HostType::Int],
6257            variadic: false,
6258            result: cove_schema::HostType::Int,
6259            capability: "probe",
6260            effect: cove_schema::Effect::Read,
6261            cancellable: false,
6262            recordable: true,
6263            result_is_task_safe: true,
6264        },
6265        cove_schema::OperationSchema {
6266            name: "shout",
6267            params: &[cove_schema::HostType::String],
6268            variadic: false,
6269            result: cove_schema::HostType::String,
6270            capability: "probe",
6271            effect: cove_schema::Effect::Read,
6272            cancellable: false,
6273            recordable: true,
6274            result_is_task_safe: true,
6275        },
6276    ];
6277
6278    impl crate::host::HostApi for Probe {
6279        fn module_schema(&self) -> cove_schema::ModuleSchema {
6280            cove_schema::ModuleSchema {
6281                name: "probe",
6282                capability: "probe",
6283                operations: PROBE_OPS,
6284                types: &[],
6285                resources: &[],
6286            }
6287        }
6288
6289        fn call(&self, op: &str, args: Vec<Value>) -> Result<Value, RuntimeError> {
6290            match op {
6291                "double" => Ok(Value::int(
6292                    args[0].as_int().expect("the schema holds it") * 2,
6293                )),
6294                "shout" => Ok(Value::string(format!(
6295                    "{}!",
6296                    args[0].as_str().expect("the schema holds it")
6297                ))),
6298                other => Err(RuntimeError::new(format!("no `{other}` here"))),
6299            }
6300        }
6301    }
6302
6303    fn probing(granted: bool) -> crate::host::HostRegistry {
6304        let grants = if granted {
6305            crate::host::Grants::new(["probe"])
6306        } else {
6307            crate::host::Grants::new(Vec::<String>::new())
6308        };
6309        let mut hosts = crate::host::HostRegistry::new(grants);
6310        hosts.register(Box::new(Probe));
6311        hosts
6312    }
6313
6314    /// `fn f(n) { probe.double(n) }`, in the IR.
6315    fn calls_double(build: &mut Build) -> FunctionId {
6316        let int = build.scalar(Repr::Int);
6317        build.program.host_ops.push(cove_ir::HostOp {
6318            resource: None,
6319            module: Arc::from("probe"),
6320            operation: Arc::from("double"),
6321            result: int,
6322        });
6323        let op = cove_ir::HostOpId(build.program.host_ops.len() as u32 - 1);
6324        let args = build.args(&[(0, int)]);
6325        build.function(
6326            "f",
6327            &[int],
6328            &[Repr::Int, Repr::Int],
6329            int,
6330            vec![Inst::CallHost { dst: 1, op, args }, Inst::Return { src: 1 }],
6331        )
6332    }
6333
6334    #[test]
6335    fn a_host_call_moves_a_word_out_and_the_answer_back() {
6336        let mut build = Build::default();
6337        let f = calls_double(&mut build);
6338        let program = build.done();
6339        let hosts = probing(true);
6340        let mut machine = Machine::with_hosts(&program, 1 << 12, Some(&hosts));
6341        assert_eq!(machine.run(f, &[21], &budget()).unwrap(), vec![42]);
6342    }
6343
6344    /// A string argument and a string answer, which is the case that
6345    /// allocates on both sides of the boundary.
6346    #[test]
6347    fn a_host_call_carries_strings_in_and_out() {
6348        let mut build = Build::default().strings(&["hey"]);
6349        let str_layout = build.layout("String", Shape::Str);
6350        build.program.str_layout = str_layout;
6351        build.program.host_ops.push(cove_ir::HostOp {
6352            resource: None,
6353            module: Arc::from("probe"),
6354            operation: Arc::from("shout"),
6355            result: str_layout,
6356        });
6357        let op = cove_ir::HostOpId(0);
6358        let args = build.args(&[(0, str_layout)]);
6359        let f = build.function(
6360            "f",
6361            &[],
6362            &[Repr::Ref, Repr::Ref],
6363            str_layout,
6364            vec![
6365                Inst::Str {
6366                    dst: 0,
6367                    text: StrId(0),
6368                },
6369                Inst::CallHost { dst: 1, op, args },
6370                Inst::Return { src: 1 },
6371            ],
6372        );
6373        let program = build.done();
6374        let hosts = probing(true);
6375        let mut machine = Machine::with_hosts(&program, 1 << 12, Some(&hosts));
6376        let words = machine.run(f, &[], &budget()).unwrap();
6377        assert_eq!(
6378            String::from_utf8(machine.string_bytes(words[0])).unwrap(),
6379            "hey!"
6380        );
6381    }
6382
6383    /// The boundary refuses an ungranted capability, and it is the boundary
6384    /// that does it: the machine passes the call on and reports what came
6385    /// back, classification included.
6386    #[test]
6387    fn an_ungranted_call_is_refused_at_the_boundary() {
6388        let mut build = Build::default();
6389        let f = calls_double(&mut build);
6390        let program = build.done();
6391        let hosts = probing(false);
6392        let mut machine = Machine::with_hosts(&program, 1 << 12, Some(&hosts));
6393        let error = machine.run(f, &[1], &budget()).unwrap_err();
6394        assert!(
6395            error.message.contains("probe"),
6396            "the refusal names the capability: {}",
6397            error.message
6398        );
6399        assert_eq!(error.denied_capability.as_deref(), Some("probe"));
6400        assert_eq!(error.outcome, crate::trace::RunOutcome::HostBoundary);
6401    }
6402
6403    /// The host-call limit is charged inside the boundary, which is where the
6404    /// oracle charges it too — `Budget::charge_host_call`, once per call,
6405    /// before the host is reached.
6406    #[test]
6407    fn a_host_call_is_charged_the_way_the_oracle_charges_it() {
6408        let mut build = Build::default();
6409        let int = build.scalar(Repr::Int);
6410        build.program.host_ops.push(cove_ir::HostOp {
6411            resource: None,
6412            module: Arc::from("probe"),
6413            operation: Arc::from("double"),
6414            result: int,
6415        });
6416        let op = cove_ir::HostOpId(0);
6417        let args = build.args(&[(0, int)]);
6418        let f = build.function(
6419            "f",
6420            &[int],
6421            &[Repr::Int, Repr::Int],
6422            int,
6423            vec![
6424                Inst::CallHost { dst: 1, op, args },
6425                Inst::CallHost { dst: 1, op, args },
6426                Inst::Return { src: 1 },
6427            ],
6428        );
6429        let program = build.done();
6430        let mut hosts = probing(true);
6431        let limits = crate::budget::Limits {
6432            max_host_calls: Some(1),
6433            ..Default::default()
6434        };
6435        let budget = crate::budget::Budget::new(limits);
6436        let meter = budget.meter();
6437        hosts.set_budget(budget);
6438        let mut machine = Machine::with_hosts(&program, 1 << 12, Some(&hosts));
6439        let error = machine.run(f, &[2], &meter).unwrap_err();
6440        assert_eq!(
6441            error.message,
6442            "execution stopped: host-call limit of 1 exceeded"
6443        );
6444        // Two, not one: the boundary counts the call it is about to make
6445        // and then refuses it for being past the limit. That is the shared
6446        // counter doing what it does for every backend, which is the point —
6447        // nothing here keeps a count of its own.
6448        assert_eq!(hosts.with_budget(|budget| budget.host_calls()), Some(2));
6449    }
6450
6451    /// A machine with no host behind it says what is missing rather than
6452    /// answering as if the call had happened.
6453    #[test]
6454    fn a_host_call_with_no_boundary_says_what_is_missing() {
6455        let mut build = Build::default();
6456        let f = calls_double(&mut build);
6457        let program = build.done();
6458        let mut machine = Machine::new(&program, 1 << 12);
6459        let error = machine.run(f, &[1], &budget()).unwrap_err();
6460        assert_eq!(
6461            error.message,
6462            "`probe.double` cannot be called, because this run has no host boundary"
6463        );
6464    }
6465
6466    // ---- host resources ------------------------------------------------
6467
6468    /// A host that issues a resource and takes one back.
6469    ///
6470    /// The two directions a `Repr::Host` word has to move, and nothing else:
6471    /// `open` answers a handle the way `files.open(path)` answers a
6472    /// `files.Reader`, and `read` is handed one back the way
6473    /// `files.read(reader)` is. It counts what it has opened, so two readers
6474    /// are two resources and `read` answering the id says which one arrived.
6475    #[derive(Default)]
6476    struct Vault {
6477        opened: std::sync::atomic::AtomicU64,
6478    }
6479
6480    static VAULT_RESOURCES: &[cove_schema::ResourceSchema] = &[cove_schema::ResourceSchema {
6481        name: "Reader",
6482        task_safe: true,
6483        operations: &[],
6484    }];
6485
6486    static VAULT_OPS: &[cove_schema::OperationSchema] = &[
6487        cove_schema::OperationSchema {
6488            name: "open",
6489            params: &[],
6490            variadic: false,
6491            result: cove_schema::HostType::Named("vault.Reader"),
6492            capability: "vault",
6493            effect: cove_schema::Effect::Read,
6494            cancellable: false,
6495            recordable: true,
6496            result_is_task_safe: true,
6497        },
6498        cove_schema::OperationSchema {
6499            name: "read",
6500            params: &[cove_schema::HostType::Named("vault.Reader")],
6501            variadic: false,
6502            result: cove_schema::HostType::Int,
6503            capability: "vault",
6504            effect: cove_schema::Effect::Read,
6505            cancellable: false,
6506            recordable: true,
6507            result_is_task_safe: true,
6508        },
6509    ];
6510
6511    impl crate::host::HostApi for Vault {
6512        fn module_schema(&self) -> cove_schema::ModuleSchema {
6513            cove_schema::ModuleSchema {
6514                name: "vault",
6515                capability: "vault",
6516                operations: VAULT_OPS,
6517                types: &[],
6518                resources: VAULT_RESOURCES,
6519            }
6520        }
6521
6522        fn call(&self, op: &str, args: Vec<Value>) -> Result<Value, RuntimeError> {
6523            match op {
6524                // Counting upward and never reusing, which is the rule
6525                // ADR 0013 puts on an identity.
6526                "open" => {
6527                    let id = self
6528                        .opened
6529                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
6530                        + 1;
6531                    Ok(Value::from_resource(ResourceHandle::new(
6532                        "vault",
6533                        &VAULT_RESOURCES[0],
6534                        id,
6535                    )))
6536                }
6537                // The host recognises its own resource, which is the whole
6538                // point of the name crossing back: nothing about the word
6539                // reached here.
6540                "read" => Ok(Value::int(
6541                    args[0].resource().expect("the schema holds it").id as i64,
6542                )),
6543                other => Err(RuntimeError::new(format!("no `{other}` here"))),
6544            }
6545        }
6546    }
6547
6548    fn vault() -> crate::host::HostRegistry {
6549        let mut hosts = crate::host::HostRegistry::new(crate::host::Grants::new(["vault"]));
6550        hosts.register(Box::new(Vault::default()));
6551        hosts
6552    }
6553
6554    /// The two host operations, and the one-word family a handle occupies.
6555    fn vault_ops(build: &mut Build) -> (LayoutId, cove_ir::HostOpId, cove_ir::HostOpId) {
6556        let int = build.scalar(Repr::Int);
6557        let reader = build.word("vault.Reader", Repr::Host);
6558        build.program.host_ops.push(cove_ir::HostOp {
6559            resource: None,
6560            module: Arc::from("vault"),
6561            operation: Arc::from("open"),
6562            result: reader,
6563        });
6564        build.program.host_ops.push(cove_ir::HostOp {
6565            resource: None,
6566            module: Arc::from("vault"),
6567            operation: Arc::from("read"),
6568            result: int,
6569        });
6570        let ops = build.program.host_ops.len() as u32;
6571        (
6572            reader,
6573            cove_ir::HostOpId(ops - 2),
6574            cove_ir::HostOpId(ops - 1),
6575        )
6576    }
6577
6578    /// A host operation whose result is a resource writes the word, not a
6579    /// boxed value: the answer is a value location of one `Repr::Host` slot,
6580    /// and `Inst::CallHost` copies its words into the frame exactly as it
6581    /// does for an `Int`. Nothing about the instruction knows a resource is
6582    /// different.
6583    #[test]
6584    fn a_host_call_answering_a_resource_writes_the_word() {
6585        let mut build = Build::default();
6586        let (reader, open, _) = vault_ops(&mut build);
6587        let args = build.args(&[]);
6588        let f = build.function(
6589            "f",
6590            &[],
6591            &[Repr::Host],
6592            reader,
6593            vec![
6594                Inst::CallHost {
6595                    dst: 0,
6596                    op: open,
6597                    args,
6598                },
6599                Inst::Return { src: 0 },
6600            ],
6601        );
6602        let program = build.done();
6603        let hosts = vault();
6604        let mut machine = Machine::with_hosts(&program, 1 << 12, Some(&hosts));
6605
6606        let answer = machine.run(f, &[], &budget()).unwrap();
6607        assert_eq!(
6608            answer.len(),
6609            1,
6610            "a handle is a name, and a name is one word"
6611        );
6612        assert_eq!(
6613            machine
6614                .resource(answer[0])
6615                .map(|handle| handle.to_string())
6616                .as_deref(),
6617            Some("vault.Reader#1"),
6618            "the word indexes the run's table, and the table holds the name"
6619        );
6620        assert_eq!(
6621            machine.allocated_words(),
6622            0,
6623            "a resource is not an object, so nothing was allocated to hold one"
6624        );
6625    }
6626
6627    /// A resource goes back to the host that issued it, by the name it was
6628    /// issued under. The host is what recognises it; the word never left.
6629    #[test]
6630    fn a_resource_goes_back_to_the_host_that_issued_it() {
6631        let mut build = Build::default();
6632        let (reader, open, read) = vault_ops(&mut build);
6633        let int = build.scalar(Repr::Int);
6634        let none = build.args(&[]);
6635        let one = build.args(&[(0, reader)]);
6636        let f = build.function(
6637            "f",
6638            &[],
6639            &[Repr::Host, Repr::Host, Repr::Int],
6640            int,
6641            vec![
6642                Inst::CallHost {
6643                    dst: 0,
6644                    op: open,
6645                    args: none,
6646                },
6647                // A second resource, so that an answer of `1` is the first
6648                // reader rather than whatever the table happened to hold.
6649                Inst::CallHost {
6650                    dst: 1,
6651                    op: open,
6652                    args: none,
6653                },
6654                Inst::CallHost {
6655                    dst: 2,
6656                    op: read,
6657                    args: one,
6658                },
6659                Inst::Return { src: 2 },
6660            ],
6661        );
6662        let program = build.done();
6663        let hosts = vault();
6664        let mut machine = Machine::with_hosts(&program, 1 << 12, Some(&hosts));
6665        assert_eq!(machine.run(f, &[], &budget()).unwrap(), vec![1]);
6666    }
6667
6668    /// A frame holding a resource across a collection keeps it, and the
6669    /// collector never sees it.
6670    ///
6671    /// Both halves are the claim. The static one is that `Function::refs` —
6672    /// which is `RefMap::of` the frame's `Repr`s — does not name the `Host`
6673    /// slot, so the one pass the collector makes over a frame does not read
6674    /// it. The dynamic one is that the run still gets the right resource back
6675    /// afterwards: the word is untouched, the table is not swept, and the
6676    /// handle it indexes is the one the host issued.
6677    #[test]
6678    fn a_resource_in_a_frame_survives_a_collection_and_is_not_a_root() {
6679        let mut build = Build::default();
6680        let (reader, open, read) = vault_ops(&mut build);
6681        let int = build.scalar(Repr::Int);
6682        let cell = build.layout(
6683            "Cell",
6684            Shape::Elements {
6685                elem: int,
6686                growable: false,
6687            },
6688        );
6689        let none = build.args(&[]);
6690        let one = build.args(&[(0, reader)]);
6691        let f = build.function(
6692            "f",
6693            &[],
6694            &[Repr::Host, Repr::Ref, Repr::Ref, Repr::Int],
6695            int,
6696            vec![
6697                Inst::CallHost {
6698                    dst: 0,
6699                    op: open,
6700                    args: none,
6701                },
6702                Inst::Alloc {
6703                    dst: 1,
6704                    layout: cell,
6705                    len: Len::Count(600),
6706                },
6707                // The cell's last use. A second one fits only if this one is
6708                // reclaimed, which is what makes the collection happen with
6709                // the resource live in slot 0.
6710                Inst::Clear {
6711                    slot: 1,
6712                    layout: cell,
6713                },
6714                Inst::Alloc {
6715                    dst: 2,
6716                    layout: cell,
6717                    len: Len::Count(600),
6718                },
6719                Inst::CallHost {
6720                    dst: 3,
6721                    op: read,
6722                    args: one,
6723                },
6724                Inst::Return { src: 3 },
6725            ],
6726        );
6727        let program = build.done();
6728
6729        // The static half: the collector's one question about a slot, asked
6730        // of the map it actually reads.
6731        let refs = &program.function(f).refs;
6732        assert!(!refs.is_ref(0), "a host word is not a root");
6733        assert_eq!(refs.iter().collect::<Vec<_>>(), vec![1, 2]);
6734
6735        let hosts = vault();
6736        let mut machine = Machine::with_hosts(&program, 1000, Some(&hosts));
6737        assert_eq!(machine.run(f, &[], &budget()).unwrap(), vec![1]);
6738        assert!(
6739            machine.collected().collections > 0,
6740            "the second cell only fits after the first is reclaimed"
6741        );
6742    }
6743
6744    /// A run that will not stop on its own is stopped by its budget, and the
6745    /// stride is what bounds how long that takes.
6746    #[test]
6747    fn a_cancelled_run_stops_at_a_safepoint() {
6748        let mut build = Build::default();
6749        let int = build.scalar(Repr::Int);
6750        let f = build.function(
6751            "spin",
6752            &[],
6753            &[Repr::Int],
6754            int,
6755            vec![Inst::Int { dst: 0, value: 0 }, Inst::Jump { to: 0 }],
6756        );
6757        let program = build.done();
6758        let cancellation = Cancellation::new();
6759        let budget = crate::budget::Budget::with_cancellation(
6760            crate::budget::Limits::default(),
6761            cancellation.clone(),
6762        );
6763        cancellation.cancel();
6764        let mut machine = Machine::new(&program, 1 << 12);
6765        assert!(machine.run(f, &[], &budget.meter()).is_err());
6766        assert!(machine.instructions() <= SAFEPOINT_STRIDE + 1);
6767    }
6768    // ---- tasks ---------------------------------------------------------------
6769
6770    /// A `Repr::Scope` slot, a `Repr::Task` slot, and the three scratch words
6771    /// every fixture below wants.
6772    ///
6773    /// Written once because what is under test is the scheduler and not the
6774    /// arithmetic around it: every one of these programs opens a scope,
6775    /// builds a closure environment, spawns it, and leaves the scope, and the
6776    /// only thing that differs is what the body does.
6777    fn counter(build: &mut Build) -> LayoutId {
6778        let int = build.scalar(Repr::Int);
6779        build.structure("Counter", &[("n", int)])
6780    }
6781
6782    /// The instructions that fill a closure environment naming `body` and
6783    /// capturing the object in `held`, into slot `dst`, using `scratch`.
6784    fn close_over(
6785        build: &mut Build,
6786        layout: LayoutId,
6787        body: FunctionId,
6788        dst: Slot,
6789        scratch: Slot,
6790        held: Option<Slot>,
6791    ) -> Vec<Inst> {
6792        let int = build.scalar(Repr::Int);
6793        let word = build.scalar(Repr::Ref);
6794        let mut code = vec![
6795            Inst::Alloc {
6796                dst,
6797                layout,
6798                len: Len::Fixed,
6799            },
6800            Inst::Int {
6801                dst: scratch,
6802                value: body.0 as i64,
6803            },
6804            Inst::StoreField {
6805                obj: dst,
6806                at: 0,
6807                src: scratch,
6808                layout: int,
6809            },
6810        ];
6811        if let Some(src) = held {
6812            code.push(Inst::StoreField {
6813                obj: dst,
6814                at: 1,
6815                src,
6816                layout: word,
6817            });
6818        }
6819        code
6820    }
6821
6822    /// Leaving a scope waits for a task the body never awaited.
6823    ///
6824    /// The Language Card's sentence, measured the only way a machine can
6825    /// measure it: the child spends fifty thousand turns before it writes,
6826    /// and the parent reads the write. A `ScopeLeave` that did not join would
6827    /// read the zero the allocation left.
6828    #[test]
6829    fn leaving_a_scope_waits_for_a_task_the_body_never_awaited() {
6830        let mut build = Build::default().strings(&["tasks"]);
6831        let str_layout = build.layout("String", Shape::Str);
6832        build.program.str_layout = str_layout;
6833        let int = build.scalar(Repr::Int);
6834        let word = build.scalar(Repr::Ref);
6835        let held = counter(&mut build);
6836        let body = build.lambda(
6837            "body",
6838            &[],
6839            &[Repr::Ref, Repr::Int, Repr::Int, Repr::Bool, Repr::Int],
6840            int,
6841            &[word],
6842            vec![
6843                Inst::Int { dst: 1, value: 0 },
6844                Inst::Int {
6845                    dst: 2,
6846                    value: 50_000,
6847                },
6848                Inst::Int { dst: 4, value: 1 },
6849                Inst::Cmp {
6850                    on: Compare::Int,
6851                    op: CmpOp::Lt,
6852                    dst: 3,
6853                    a: 1,
6854                    b: 2,
6855                },
6856                Inst::BranchFalse { cond: 3, to: 7 },
6857                Inst::Arith {
6858                    num: Num::Int,
6859                    op: ArithOp::Add,
6860                    dst: 1,
6861                    a: 1,
6862                    b: 4,
6863                },
6864                Inst::Jump { to: 3 },
6865                Inst::Int { dst: 1, value: 7 },
6866                Inst::StoreField {
6867                    obj: 0,
6868                    at: 0,
6869                    src: 1,
6870                    layout: int,
6871                },
6872                Inst::Return { src: 1 },
6873            ],
6874        );
6875        let environment = closure_layout(&mut build, body, &[word]);
6876        let mut code = vec![
6877            Inst::Alloc {
6878                dst: 0,
6879                layout: held,
6880                len: Len::Fixed,
6881            },
6882            Inst::Int { dst: 6, value: 0 },
6883            Inst::StoreField {
6884                obj: 0,
6885                at: 0,
6886                src: 6,
6887                layout: int,
6888            },
6889            Inst::ScopeEnter {
6890                dst: 1,
6891                name: StrId(0),
6892            },
6893        ];
6894        code.extend(close_over(&mut build, environment, body, 2, 6, Some(0)));
6895        code.extend([
6896            Inst::Spawn {
6897                dst: 3,
6898                scope: 1,
6899                closure: 2,
6900                answer: int,
6901            },
6902            Inst::ScopeLeave {
6903                scope: 1,
6904                failed: 4,
6905                error: 5,
6906                layout: int,
6907            },
6908            Inst::LoadField {
6909                dst: 6,
6910                obj: 0,
6911                at: 0,
6912                layout: int,
6913            },
6914            Inst::Return { src: 6 },
6915        ]);
6916        let main = build.function(
6917            "main",
6918            &[],
6919            &[
6920                Repr::Ref,
6921                Repr::Scope,
6922                Repr::Ref,
6923                Repr::Task,
6924                Repr::Bool,
6925                Repr::Int,
6926                Repr::Int,
6927            ],
6928            int,
6929            code,
6930        );
6931        let program = build.done();
6932        assert_eq!(run(&program, main, &[]).unwrap() as i64, 7);
6933    }
6934
6935    /// A body runs at most once and is waited for at most once, so awaiting
6936    /// the same handle twice answers the same value and repeats no effect.
6937    ///
6938    /// The counter is what says "no effect twice": it is incremented by the
6939    /// body and read after both awaits, and the answer is the product, so a
6940    /// second run would double it.
6941    #[test]
6942    fn awaiting_the_same_handle_twice_runs_the_body_once() {
6943        let mut build = Build::default().strings(&["tasks"]);
6944        let str_layout = build.layout("String", Shape::Str);
6945        build.program.str_layout = str_layout;
6946        let int = build.scalar(Repr::Int);
6947        let word = build.scalar(Repr::Ref);
6948        let held = counter(&mut build);
6949        let body = build.lambda(
6950            "body",
6951            &[],
6952            &[Repr::Ref, Repr::Int, Repr::Int],
6953            int,
6954            &[word],
6955            vec![
6956                Inst::LoadField {
6957                    dst: 1,
6958                    obj: 0,
6959                    at: 0,
6960                    layout: int,
6961                },
6962                Inst::Int { dst: 2, value: 1 },
6963                Inst::Arith {
6964                    num: Num::Int,
6965                    op: ArithOp::Add,
6966                    dst: 1,
6967                    a: 1,
6968                    b: 2,
6969                },
6970                Inst::StoreField {
6971                    obj: 0,
6972                    at: 0,
6973                    src: 1,
6974                    layout: int,
6975                },
6976                Inst::Int { dst: 1, value: 7 },
6977                Inst::Return { src: 1 },
6978            ],
6979        );
6980        let environment = closure_layout(&mut build, body, &[word]);
6981        let mut code = vec![
6982            Inst::Alloc {
6983                dst: 0,
6984                layout: held,
6985                len: Len::Fixed,
6986            },
6987            Inst::Int { dst: 8, value: 0 },
6988            Inst::StoreField {
6989                obj: 0,
6990                at: 0,
6991                src: 8,
6992                layout: int,
6993            },
6994            Inst::ScopeEnter {
6995                dst: 1,
6996                name: StrId(0),
6997            },
6998        ];
6999        code.extend(close_over(&mut build, environment, body, 2, 8, Some(0)));
7000        code.extend([
7001            Inst::Spawn {
7002                dst: 3,
7003                scope: 1,
7004                closure: 2,
7005                answer: int,
7006            },
7007            Inst::Await {
7008                dst: 4,
7009                task: 3,
7010                answer: int,
7011            },
7012            Inst::Await {
7013                dst: 5,
7014                task: 3,
7015                answer: int,
7016            },
7017            Inst::ScopeLeave {
7018                scope: 1,
7019                failed: 6,
7020                error: 7,
7021                layout: int,
7022            },
7023            Inst::LoadField {
7024                dst: 8,
7025                obj: 0,
7026                at: 0,
7027                layout: int,
7028            },
7029            Inst::Arith {
7030                num: Num::Int,
7031                op: ArithOp::Add,
7032                dst: 4,
7033                a: 4,
7034                b: 5,
7035            },
7036            Inst::Arith {
7037                num: Num::Int,
7038                op: ArithOp::Mul,
7039                dst: 8,
7040                a: 8,
7041                b: 4,
7042            },
7043            Inst::Return { src: 8 },
7044        ]);
7045        let main = build.function(
7046            "main",
7047            &[],
7048            &[
7049                Repr::Ref,
7050                Repr::Scope,
7051                Repr::Ref,
7052                Repr::Task,
7053                Repr::Int,
7054                Repr::Int,
7055                Repr::Bool,
7056                Repr::Int,
7057                Repr::Int,
7058            ],
7059            int,
7060            code,
7061        );
7062        let program = build.done();
7063        // One run of the body, and 7 from each of the two awaits.
7064        assert_eq!(run(&program, main, &[]).unwrap() as i64, 14);
7065    }
7066
7067    /// A task the program cancelled has no value to await, in the words the
7068    /// oracle uses.
7069    ///
7070    /// The body cannot end any other way — it is an unbounded loop — so what
7071    /// is being measured is that the flag reached a safepoint and that the
7072    /// join told a stop from a finish.
7073    #[test]
7074    fn awaiting_a_cancelled_task_is_refused() {
7075        let mut build = Build::default().strings(&["tasks"]);
7076        let str_layout = build.layout("String", Shape::Str);
7077        build.program.str_layout = str_layout;
7078        let int = build.scalar(Repr::Int);
7079        let body = build.lambda(
7080            "body",
7081            &[],
7082            &[Repr::Int],
7083            int,
7084            &[],
7085            vec![Inst::Int { dst: 0, value: 0 }, Inst::Jump { to: 0 }],
7086        );
7087        let environment = closure_layout(&mut build, body, &[]);
7088        let mut code = vec![Inst::ScopeEnter {
7089            dst: 0,
7090            name: StrId(0),
7091        }];
7092        code.extend(close_over(&mut build, environment, body, 1, 6, None));
7093        code.extend([
7094            Inst::Spawn {
7095                dst: 2,
7096                scope: 0,
7097                closure: 1,
7098                answer: int,
7099            },
7100            Inst::Cancel { task: 2 },
7101            Inst::Await {
7102                dst: 3,
7103                task: 2,
7104                answer: int,
7105            },
7106            Inst::ScopeLeave {
7107                scope: 0,
7108                failed: 4,
7109                error: 5,
7110                layout: int,
7111            },
7112            Inst::Return { src: 3 },
7113        ]);
7114        let main = build.function(
7115            "main",
7116            &[],
7117            &[
7118                Repr::Scope,
7119                Repr::Ref,
7120                Repr::Task,
7121                Repr::Int,
7122                Repr::Bool,
7123                Repr::Int,
7124                Repr::Int,
7125            ],
7126            int,
7127            code,
7128        );
7129        let program = build.done();
7130        let error = run(&program, main, &[]).unwrap_err();
7131        assert_eq!(
7132            error.message,
7133            "task 1 of scope `tasks` was cancelled, so it has no value to await"
7134        );
7135    }
7136
7137    /// A call to an `async fn` is a call and a handle, and the handle can be
7138    /// awaited twice.
7139    ///
7140    /// No `ScopeEnter`, no `Spawn` and no thread: an `async fn` runs at its
7141    /// call site and `Inst::Settled` is the handle around what it produced.
7142    /// Awaiting twice answers the same words, which falls out of the state
7143    /// rather than being arranged — the same way it does for a spawned task,
7144    /// and the same way `crate::task::settle` gets it.
7145    #[test]
7146    fn a_settled_task_answers_the_call_s_words_however_often_it_is_awaited() {
7147        let mut build = Build::default();
7148        let int = build.scalar(Repr::Int);
7149        let none = build.args(&[]);
7150        let body = build.function(
7151            "body",
7152            &[],
7153            &[Repr::Int],
7154            int,
7155            vec![Inst::Int { dst: 0, value: 7 }, Inst::Return { src: 0 }],
7156        );
7157        let main = build.function(
7158            "main",
7159            &[],
7160            &[Repr::Int, Repr::Task, Repr::Int, Repr::Int],
7161            int,
7162            vec![
7163                Inst::Call {
7164                    dst: 0,
7165                    callee: body,
7166                    args: none,
7167                },
7168                Inst::Settled {
7169                    dst: 1,
7170                    src: 0,
7171                    answer: int,
7172                },
7173                Inst::Await {
7174                    dst: 2,
7175                    task: 1,
7176                    answer: int,
7177                },
7178                Inst::Await {
7179                    dst: 3,
7180                    task: 1,
7181                    answer: int,
7182                },
7183                Inst::Arith {
7184                    num: Num::Int,
7185                    op: ArithOp::Add,
7186                    dst: 2,
7187                    a: 2,
7188                    b: 3,
7189                },
7190                Inst::Return { src: 2 },
7191            ],
7192        );
7193        let program = build.done();
7194        assert_eq!(run(&program, main, &[]).unwrap() as i64, 14);
7195    }
7196
7197    /// Cancelling a settled task does nothing, so awaiting it still answers.
7198    ///
7199    /// `crate::task::Task::cancel` asks a task that is *running* to stop, and
7200    /// a task whose body already ran is not one: cancellation stops work that
7201    /// has not happened, it does not undo work that has. An `async fn`'s
7202    /// handle is the extreme case of that, because its work was over before
7203    /// the handle existed.
7204    #[test]
7205    fn cancelling_a_settled_task_does_not_take_its_value_away() {
7206        let mut build = Build::default();
7207        let int = build.scalar(Repr::Int);
7208        let none = build.args(&[]);
7209        let body = build.function(
7210            "body",
7211            &[],
7212            &[Repr::Int],
7213            int,
7214            vec![Inst::Int { dst: 0, value: 7 }, Inst::Return { src: 0 }],
7215        );
7216        let main = build.function(
7217            "main",
7218            &[],
7219            &[Repr::Int, Repr::Task, Repr::Int],
7220            int,
7221            vec![
7222                Inst::Call {
7223                    dst: 0,
7224                    callee: body,
7225                    args: none,
7226                },
7227                Inst::Settled {
7228                    dst: 1,
7229                    src: 0,
7230                    answer: int,
7231                },
7232                Inst::Cancel { task: 1 },
7233                Inst::Await {
7234                    dst: 2,
7235                    task: 1,
7236                    answer: int,
7237                },
7238                Inst::Return { src: 2 },
7239            ],
7240        );
7241        let program = build.done();
7242        assert_eq!(run(&program, main, &[]).unwrap() as i64, 7);
7243    }
7244
7245    /// A settled task's answer is a root of the task that made it.
7246    ///
7247    /// The words go into a heap object the scheduler table names, and the
7248    /// slot the call left them in is cleared at once — which is what the
7249    /// lowering emits, because the `Inst::Settled` consumed the temporary.
7250    /// So from that instruction onwards the table is the only thing that
7251    /// names the object, and the loop below allocates far more than the heap
7252    /// holds to say so: without the table among the roots the sweep would
7253    /// take the answer and the `await` would read a reclaimed word.
7254    #[test]
7255    fn a_settled_task_s_answer_survives_a_collection() {
7256        let mut build = Build::default().strings(&["kept"]);
7257        let int = build.scalar(Repr::Int);
7258        let str_layout = build.layout("String", Shape::Str);
7259        build.program.str_layout = str_layout;
7260        let cell = build.layout(
7261            "Cell",
7262            Shape::Elements {
7263                elem: int,
7264                growable: false,
7265            },
7266        );
7267        let none = build.args(&[]);
7268        let body = build.function(
7269            "body",
7270            &[],
7271            &[Repr::Ref],
7272            str_layout,
7273            vec![
7274                Inst::Str {
7275                    dst: 0,
7276                    text: StrId(0),
7277                },
7278                Inst::Return { src: 0 },
7279            ],
7280        );
7281        // s0 the call's answer, s1 the handle, s2 the counter, s3 the bound,
7282        // s4 the test, s5 the churn, s6 the step, s7 what the await answers,
7283        // s8 the length that is returned.
7284        let main = build.function(
7285            "main",
7286            &[],
7287            &[
7288                Repr::Ref,
7289                Repr::Task,
7290                Repr::Int,
7291                Repr::Int,
7292                Repr::Bool,
7293                Repr::Ref,
7294                Repr::Int,
7295                Repr::Ref,
7296                Repr::Int,
7297            ],
7298            int,
7299            vec![
7300                Inst::Call {
7301                    dst: 0,
7302                    callee: body,
7303                    args: none,
7304                },
7305                Inst::Settled {
7306                    dst: 1,
7307                    src: 0,
7308                    answer: str_layout,
7309                },
7310                Inst::Clear {
7311                    slot: 0,
7312                    layout: str_layout,
7313                },
7314                Inst::Int { dst: 2, value: 0 },
7315                Inst::Int {
7316                    dst: 3,
7317                    value: 4000,
7318                },
7319                Inst::Int { dst: 6, value: 1 },
7320                Inst::Cmp {
7321                    on: Compare::Int,
7322                    op: CmpOp::Lt,
7323                    dst: 4,
7324                    a: 2,
7325                    b: 3,
7326                },
7327                Inst::BranchFalse { cond: 4, to: 12 },
7328                Inst::Alloc {
7329                    dst: 5,
7330                    layout: cell,
7331                    len: Len::Count(64),
7332                },
7333                Inst::Clear {
7334                    slot: 5,
7335                    layout: cell,
7336                },
7337                Inst::Arith {
7338                    num: Num::Int,
7339                    op: ArithOp::Add,
7340                    dst: 2,
7341                    a: 2,
7342                    b: 6,
7343                },
7344                Inst::Jump { to: 6 },
7345                Inst::Await {
7346                    dst: 7,
7347                    task: 1,
7348                    answer: str_layout,
7349                },
7350                Inst::Len { dst: 8, obj: 7 },
7351                Inst::Return { src: 8 },
7352            ],
7353        );
7354        let program = build.done();
7355        // A heap far smaller than 4000 objects of 65 words, so the run only
7356        // reaches the `await` by collecting several times on the way.
7357        let mut machine = Machine::new(&program, 4096);
7358        // `kept` is four bytes, and it is still four bytes.
7359        assert_eq!(machine.run(main, &[], &budget()).unwrap(), vec![4]);
7360        assert!(
7361            machine.collected().collections > 0,
7362            "the run should have had to collect"
7363        );
7364    }
7365
7366    /// A child that raised propagates as itself out of the scope it was in.
7367    ///
7368    /// Not as a value: a runtime error is not something a Cove expression can
7369    /// hold, so `ScopeLeave` fails with it rather than answering it. That is
7370    /// the difference `crate::task::ChildFailure` draws, kept.
7371    #[test]
7372    fn a_child_that_raises_leaves_the_scope_with_its_own_error() {
7373        let mut build = Build::default().strings(&["tasks", "the child said so"]);
7374        let str_layout = build.layout("String", Shape::Str);
7375        build.program.str_layout = str_layout;
7376        let int = build.scalar(Repr::Int);
7377        let body = build.lambda(
7378            "body",
7379            &[],
7380            &[Repr::Int],
7381            int,
7382            &[],
7383            vec![Inst::Trap { message: StrId(1) }],
7384        );
7385        let environment = closure_layout(&mut build, body, &[]);
7386        let mut code = vec![Inst::ScopeEnter {
7387            dst: 0,
7388            name: StrId(0),
7389        }];
7390        code.extend(close_over(&mut build, environment, body, 1, 5, None));
7391        code.extend([
7392            Inst::Spawn {
7393                dst: 2,
7394                scope: 0,
7395                closure: 1,
7396                answer: int,
7397            },
7398            Inst::ScopeLeave {
7399                scope: 0,
7400                failed: 3,
7401                error: 4,
7402                layout: int,
7403            },
7404            Inst::Return { src: 4 },
7405        ]);
7406        let main = build.function(
7407            "main",
7408            &[],
7409            &[
7410                Repr::Scope,
7411                Repr::Ref,
7412                Repr::Task,
7413                Repr::Bool,
7414                Repr::Int,
7415                Repr::Int,
7416            ],
7417            int,
7418            code,
7419        );
7420        let program = build.done();
7421        let error = run(&program, main, &[]).unwrap_err();
7422        assert_eq!(error.message, "the child said so");
7423    }
7424
7425    /// Two tasks allocating at once over one heap, and an object only the
7426    /// parent's frame names.
7427    ///
7428    /// This is the whole of issue #240's Q1 as a test. The two children churn
7429    /// far past the heap's budget, so the collections are theirs; the parent
7430    /// is parked in a join for all of them, holding one object no child can
7431    /// reach. A collection that read a stale snapshot of the parent's frame,
7432    /// or that did not wait for a task at all, frees it.
7433    #[test]
7434    fn a_collection_a_sibling_ran_keeps_what_the_parent_holds() {
7435        let mut build = Build::default().strings(&["tasks"]);
7436        let str_layout = build.layout("String", Shape::Str);
7437        build.program.str_layout = str_layout;
7438        let int = build.scalar(Repr::Int);
7439        let held = counter(&mut build);
7440        let body = build.lambda(
7441            "body",
7442            &[],
7443            &[Repr::Ref, Repr::Int, Repr::Int, Repr::Bool, Repr::Int],
7444            int,
7445            &[],
7446            vec![
7447                Inst::Int { dst: 1, value: 0 },
7448                Inst::Int {
7449                    dst: 2,
7450                    value: 20_000,
7451                },
7452                Inst::Int { dst: 4, value: 1 },
7453                Inst::Cmp {
7454                    on: Compare::Int,
7455                    op: CmpOp::Lt,
7456                    dst: 3,
7457                    a: 1,
7458                    b: 2,
7459                },
7460                Inst::BranchFalse { cond: 3, to: 8 },
7461                Inst::Alloc {
7462                    dst: 0,
7463                    layout: held,
7464                    len: Len::Fixed,
7465                },
7466                Inst::Arith {
7467                    num: Num::Int,
7468                    op: ArithOp::Add,
7469                    dst: 1,
7470                    a: 1,
7471                    b: 4,
7472                },
7473                Inst::Jump { to: 3 },
7474                Inst::Return { src: 1 },
7475            ],
7476        );
7477        let environment = closure_layout(&mut build, body, &[]);
7478        let mut code = vec![
7479            Inst::Alloc {
7480                dst: 0,
7481                layout: held,
7482                len: Len::Fixed,
7483            },
7484            Inst::Int { dst: 9, value: 42 },
7485            Inst::StoreField {
7486                obj: 0,
7487                at: 0,
7488                src: 9,
7489                layout: int,
7490            },
7491            Inst::ScopeEnter {
7492                dst: 1,
7493                name: StrId(0),
7494            },
7495        ];
7496        code.extend(close_over(&mut build, environment, body, 2, 9, None));
7497        code.extend([
7498            Inst::Spawn {
7499                dst: 3,
7500                scope: 1,
7501                closure: 2,
7502                answer: int,
7503            },
7504            Inst::Spawn {
7505                dst: 4,
7506                scope: 1,
7507                closure: 2,
7508                answer: int,
7509            },
7510            Inst::Await {
7511                dst: 5,
7512                task: 3,
7513                answer: int,
7514            },
7515            Inst::Await {
7516                dst: 6,
7517                task: 4,
7518                answer: int,
7519            },
7520            Inst::ScopeLeave {
7521                scope: 1,
7522                failed: 7,
7523                error: 8,
7524                layout: int,
7525            },
7526            // The two children's turns, plus the word the parent held from
7527            // before the first collection to after the last.
7528            Inst::Arith {
7529                num: Num::Int,
7530                op: ArithOp::Add,
7531                dst: 5,
7532                a: 5,
7533                b: 6,
7534            },
7535            Inst::LoadField {
7536                dst: 9,
7537                obj: 0,
7538                at: 0,
7539                layout: int,
7540            },
7541            Inst::Arith {
7542                num: Num::Int,
7543                op: ArithOp::Add,
7544                dst: 9,
7545                a: 9,
7546                b: 5,
7547            },
7548            Inst::Return { src: 9 },
7549        ]);
7550        let main = build.function(
7551            "main",
7552            &[],
7553            &[
7554                Repr::Ref,
7555                Repr::Scope,
7556                Repr::Ref,
7557                Repr::Task,
7558                Repr::Task,
7559                Repr::Int,
7560                Repr::Int,
7561                Repr::Bool,
7562                Repr::Int,
7563                Repr::Int,
7564            ],
7565            int,
7566            code,
7567        );
7568        let program = build.done();
7569        assert_eq!(run(&program, main, &[]).unwrap() as i64, 42 + 40_000);
7570    }
7571    /// A child whose *value* was `Err(...)` leaves that error where the
7572    /// enclosing function can return it.
7573    ///
7574    /// The other half of what a failing child can be, and the half the corpus
7575    /// does not reach: `scope s { s.spawn { f()? } }` means the failure
7576    /// reaches the caller rather than sitting unread in a handle nobody
7577    /// awaited, and `crate::task::ChildFailure::Returned` is where the oracle
7578    /// says so. Here the answer is a run of words in the object the parent
7579    /// allocated, and what `ScopeLeave` copies out of it is the `Err` case's
7580    /// payload — at the layout the instruction names, which is the
7581    /// *enclosing* function's failure and not the child's answer.
7582    #[test]
7583    fn a_child_whose_value_failed_leaves_the_scope_with_its_payload() {
7584        let mut build = Build::default().strings(&["tasks"]);
7585        let str_layout = build.layout("String", Shape::Str);
7586        build.program.str_layout = str_layout;
7587        let int = build.scalar(Repr::Int);
7588        let answer = build.enumeration("Result", &[("Ok", vec![int]), ("Err", vec![int])]);
7589        let body = build.lambda(
7590            "body",
7591            &[],
7592            &[Repr::Int, Repr::Int],
7593            answer,
7594            &[],
7595            vec![
7596                // `Err(9)`: the case index, then the payload word the case
7597                // was placed at.
7598                Inst::Int { dst: 0, value: 1 },
7599                Inst::Int { dst: 1, value: 9 },
7600                Inst::Return { src: 0 },
7601            ],
7602        );
7603        let environment = closure_layout(&mut build, body, &[]);
7604        let mut code = vec![Inst::ScopeEnter {
7605            dst: 0,
7606            name: StrId(0),
7607        }];
7608        code.extend(close_over(&mut build, environment, body, 1, 5, None));
7609        code.extend([
7610            Inst::Spawn {
7611                dst: 2,
7612                scope: 0,
7613                closure: 1,
7614                answer,
7615            },
7616            Inst::ScopeLeave {
7617                scope: 0,
7618                failed: 3,
7619                error: 4,
7620                layout: int,
7621            },
7622            Inst::Return { src: 4 },
7623        ]);
7624        let main = build.function(
7625            "main",
7626            &[],
7627            &[
7628                Repr::Scope,
7629                Repr::Ref,
7630                Repr::Task,
7631                Repr::Bool,
7632                Repr::Int,
7633                Repr::Int,
7634            ],
7635            int,
7636            code,
7637        );
7638        let program = build.done();
7639        // Zero would be the location as the frame was zeroed, which is what
7640        // a `ScopeLeave` that had not noticed would leave there.
7641        assert_eq!(run(&program, main, &[]).unwrap() as i64, 9);
7642    }
7643
7644    // ---- a host runs a Cove callback ------------------------------------
7645
7646    /// A host that runs the callback it was handed.
7647    ///
7648    /// Three shapes, which are the three the shipped hosts have: `apply`
7649    /// calls once, as `http.Server.handle` does; `twice` calls more than
7650    /// once, as `clock.every` does; and `bounded` bounds the body with a flag
7651    /// it raises first, as `clock.timeout` does, and turns the stop into its
7652    /// own answer rather than passing the error on.
7653    struct Runner;
7654
7655    static RUNNER_OPS: &[cove_schema::OperationSchema] = &[
7656        cove_schema::OperationSchema {
7657            name: "apply",
7658            params: &[cove_schema::HostType::Any],
7659            variadic: false,
7660            result: cove_schema::HostType::Int,
7661            capability: "runner",
7662            effect: cove_schema::Effect::Read,
7663            cancellable: false,
7664            recordable: false,
7665            result_is_task_safe: true,
7666        },
7667        cove_schema::OperationSchema {
7668            name: "twice",
7669            params: &[cove_schema::HostType::Any],
7670            variadic: false,
7671            result: cove_schema::HostType::Int,
7672            capability: "runner",
7673            effect: cove_schema::Effect::Read,
7674            cancellable: false,
7675            recordable: false,
7676            result_is_task_safe: true,
7677        },
7678        cove_schema::OperationSchema {
7679            name: "bounded",
7680            params: &[cove_schema::HostType::Any],
7681            variadic: false,
7682            result: cove_schema::HostType::Int,
7683            capability: "runner",
7684            effect: cove_schema::Effect::Read,
7685            cancellable: true,
7686            recordable: false,
7687            result_is_task_safe: true,
7688        },
7689        cove_schema::OperationSchema {
7690            name: "caught",
7691            params: &[cove_schema::HostType::Any],
7692            variadic: false,
7693            result: cove_schema::HostType::Int,
7694            capability: "runner",
7695            effect: cove_schema::Effect::Read,
7696            cancellable: false,
7697            recordable: false,
7698            result_is_task_safe: true,
7699        },
7700    ];
7701
7702    impl crate::host::HostApi for Runner {
7703        fn module_schema(&self) -> cove_schema::ModuleSchema {
7704            cove_schema::ModuleSchema {
7705                name: "runner",
7706                capability: "runner",
7707                operations: RUNNER_OPS,
7708                types: &[],
7709                resources: &[],
7710            }
7711        }
7712
7713        fn call_with(
7714            &self,
7715            op: &str,
7716            args: Vec<Value>,
7717            back: &mut dyn Reentry,
7718        ) -> Result<Value, RuntimeError> {
7719            match op {
7720                "apply" => back.call(&args[0], Vec::new()),
7721                "twice" => {
7722                    let one = back.call(&args[0], Vec::new())?.as_int().unwrap_or(0);
7723                    let other = back.call(&args[0], Vec::new())?.as_int().unwrap_or(0);
7724                    Ok(Value::int(one + other))
7725                }
7726                // The `clock.timeout` shape: the flag is raised before the
7727                // body runs, so the body stops at its first safepoint and the
7728                // host answers its own bound rather than passing the error on.
7729                "bounded" => {
7730                    let stop = Cancellation::new();
7731                    stop.cancel();
7732                    match back.call_until(&args[0], Vec::new(), &stop) {
7733                        Ok(_) => Ok(Value::int(0)),
7734                        Err(_) if stop.is_cancelled() => Ok(Value::int(-1)),
7735                        Err(error) => Err(error),
7736                    }
7737                }
7738                // A host that catches what a callback failed with and carries
7739                // on, which is what makes restoring the frames this call
7740                // grew a requirement rather than a tidiness.
7741                "caught" => match back.call(&args[0], Vec::new()) {
7742                    Ok(value) => Ok(value),
7743                    Err(_) => Ok(Value::int(-2)),
7744                },
7745                other => Err(RuntimeError::new(format!("no `{other}` here"))),
7746            }
7747        }
7748
7749        fn call(&self, op: &str, _args: Vec<Value>) -> Result<Value, RuntimeError> {
7750            Err(RuntimeError::new(format!("`{op}` needs a way back")))
7751        }
7752    }
7753
7754    fn running() -> crate::host::HostRegistry {
7755        let mut hosts = crate::host::HostRegistry::new(crate::host::Grants::new(["runner"]));
7756        hosts.register(Box::new(Runner));
7757        hosts
7758    }
7759
7760    /// A program whose entry hands `runner.<op>` a closure over `depth`.
7761    ///
7762    /// The lambda is the recursion the reentry bound is about:
7763    ///
7764    /// ~~~text
7765    /// fn step() -> Int {          // captures d
7766    ///   if d == 0 { return 0 }
7767    ///   runner.apply(fn() { ... d - 1 ... }) + 1
7768    /// }
7769    /// ~~~
7770    ///
7771    /// So `step` at `d` makes one host call, which runs `step` at `d - 1`,
7772    /// and the answer counts the levels — which is what makes a wrong bound
7773    /// visible as a wrong number rather than only as a missing error.
7774    fn a_reentering_program(op: &str, depth: i64) -> Program {
7775        let mut build = Build::default();
7776        let int = build.scalar(Repr::Int);
7777        let reference = build.word("Fn", Repr::Ref);
7778        build.program.host_ops.push(cove_ir::HostOp {
7779            resource: None,
7780            module: Arc::from("runner"),
7781            operation: Arc::from(op),
7782            result: int,
7783        });
7784        let call = cove_ir::HostOpId(build.program.host_ops.len() as u32 - 1);
7785        let args = build.args(&[(0, reference)]);
7786        let inner = build.args(&[(3, reference)]);
7787
7788        // The lambda is at the index it will be pushed to, which is what lets
7789        // its own body name it: a closure over `step` is what `step` builds.
7790        let step_id = FunctionId(build.program.functions.len() as u32);
7791        let closure = build.layout(
7792            "closure",
7793            Shape::Closure {
7794                function: step_id,
7795                captures: vec![int],
7796            },
7797        );
7798        let step = build.lambda(
7799            "step",
7800            &[],
7801            &[
7802                Repr::Int,  // 0: the capture, `d`
7803                Repr::Int,  // 1: zero
7804                Repr::Bool, // 2: d != 0
7805                Repr::Ref,  // 3: the closure over d - 1
7806                Repr::Int,  // 4: the callee's id
7807                Repr::Int,  // 5: d - 1
7808                Repr::Int,  // 6: the answer
7809                Repr::Int,  // 7: one
7810            ],
7811            int,
7812            &[int],
7813            vec![
7814                Inst::Int { dst: 1, value: 0 },
7815                Inst::Cmp {
7816                    on: Compare::Int,
7817                    op: CmpOp::Ne,
7818                    dst: 2,
7819                    a: 0,
7820                    b: 1,
7821                },
7822                Inst::BranchFalse { cond: 2, to: 11 },
7823                Inst::Alloc {
7824                    dst: 3,
7825                    layout: closure,
7826                    len: Len::Fixed,
7827                },
7828                Inst::FuncRef {
7829                    dst: 4,
7830                    callee: step_id,
7831                },
7832                Inst::StoreField {
7833                    obj: 3,
7834                    at: 0,
7835                    src: 4,
7836                    layout: int,
7837                },
7838                Inst::Int { dst: 7, value: 1 },
7839                Inst::Arith {
7840                    num: Num::Int,
7841                    op: ArithOp::Sub,
7842                    dst: 5,
7843                    a: 0,
7844                    b: 7,
7845                },
7846                Inst::StoreField {
7847                    obj: 3,
7848                    at: 1,
7849                    src: 5,
7850                    layout: int,
7851                },
7852                Inst::CallHost {
7853                    dst: 6,
7854                    op: call,
7855                    args: inner,
7856                },
7857                Inst::Arith {
7858                    num: Num::Int,
7859                    op: ArithOp::Add,
7860                    dst: 6,
7861                    a: 6,
7862                    b: 7,
7863                },
7864                // The frame is zeroed, so the `d == 0` arm answers the zero
7865                // that is already standing there.
7866                Inst::Return { src: 6 },
7867            ],
7868        );
7869        assert_eq!(step, step_id, "the lambda is where its own body says");
7870
7871        build.function(
7872            "main",
7873            &[],
7874            &[Repr::Ref, Repr::Int, Repr::Int, Repr::Int],
7875            int,
7876            vec![
7877                Inst::Alloc {
7878                    dst: 0,
7879                    layout: closure,
7880                    len: Len::Fixed,
7881                },
7882                Inst::FuncRef {
7883                    dst: 1,
7884                    callee: step_id,
7885                },
7886                Inst::StoreField {
7887                    obj: 0,
7888                    at: 0,
7889                    src: 1,
7890                    layout: int,
7891                },
7892                Inst::Int {
7893                    dst: 2,
7894                    value: depth,
7895                },
7896                Inst::StoreField {
7897                    obj: 0,
7898                    at: 1,
7899                    src: 2,
7900                    layout: int,
7901                },
7902                Inst::CallHost {
7903                    dst: 3,
7904                    op: call,
7905                    args,
7906                },
7907                Inst::Return { src: 3 },
7908            ],
7909        );
7910        build.done()
7911    }
7912
7913    /// The entry of [`a_reentering_program`], run.
7914    fn reentering(op: &str, depth: i64) -> Result<i64, RuntimeError> {
7915        let program = a_reentering_program(op, depth);
7916        let entry = program.functions.len() as u32 - 1;
7917        let hosts = running();
7918        let mut machine = Machine::with_hosts(&program, 1 << 14, Some(&hosts));
7919        machine
7920            .run(FunctionId(entry), &[], &budget())
7921            .map(|words| words[0] as i64)
7922    }
7923
7924    /// A closure reaches a host, and the host runs it.
7925    ///
7926    /// The whole of what was missing. The boundary refused to materialise one
7927    /// and `Back::call` refused on the other side, so a program that wrote
7928    /// `clock.timeout(500ms) { .. }` did not lower at all.
7929    #[test]
7930    fn a_host_runs_the_callback_it_was_handed() {
7931        assert_eq!(reentering("apply", 1).unwrap(), 1);
7932    }
7933
7934    /// A host may call the callback as many times as its operation means, and
7935    /// each one is a call the run pays for in full.
7936    #[test]
7937    fn a_host_may_call_its_callback_more_than_once() {
7938        // Each round answers 1, so a host that ran it twice answers 2 — and
7939        // one that reused a frame instead of opening a second would not.
7940        assert_eq!(reentering("twice", 1).unwrap(), 2);
7941    }
7942
7943    /// A callback that fails leaves the machine exactly as it found it.
7944    ///
7945    /// `clock.timeout` catches what the body failed with and answers its own
7946    /// bound, so the frames the callback grew, the words its frames occupied
7947    /// and the scopes it opened must all be back where they were — the outer
7948    /// run has no unwinding, and the reasoning that makes that sound (the run
7949    /// is ending) does not reach a host that carries on.
7950    #[test]
7951    fn a_failed_callback_leaves_the_frames_it_grew() {
7952        // The body divides by zero at the first level and the host catches
7953        // it; the entry then returns through frames that have to be intact.
7954        let mut build = Build::default();
7955        let int = build.scalar(Repr::Int);
7956        let reference = build.word("Fn", Repr::Ref);
7957        build.program.host_ops.push(cove_ir::HostOp {
7958            resource: None,
7959            module: Arc::from("runner"),
7960            operation: Arc::from("caught"),
7961            result: int,
7962        });
7963        let op = cove_ir::HostOpId(0);
7964        let args = build.args(&[(0, reference)]);
7965        let step_id = FunctionId(build.program.functions.len() as u32);
7966        let closure = build.layout(
7967            "closure",
7968            Shape::Closure {
7969                function: step_id,
7970                captures: vec![],
7971            },
7972        );
7973        let step = build.lambda(
7974            "step",
7975            &[],
7976            &[Repr::Int, Repr::Int, Repr::Int],
7977            int,
7978            &[],
7979            vec![
7980                Inst::Int { dst: 0, value: 1 },
7981                Inst::Int { dst: 1, value: 0 },
7982                Inst::Arith {
7983                    num: Num::Int,
7984                    op: ArithOp::Div,
7985                    dst: 2,
7986                    a: 0,
7987                    b: 1,
7988                },
7989                Inst::Return { src: 2 },
7990            ],
7991        );
7992        assert_eq!(step, step_id);
7993        let main = build.function(
7994            "main",
7995            &[],
7996            &[Repr::Ref, Repr::Int, Repr::Int],
7997            int,
7998            vec![
7999                Inst::Alloc {
8000                    dst: 0,
8001                    layout: closure,
8002                    len: Len::Fixed,
8003                },
8004                Inst::FuncRef {
8005                    dst: 1,
8006                    callee: step_id,
8007                },
8008                Inst::StoreField {
8009                    obj: 0,
8010                    at: 0,
8011                    src: 1,
8012                    layout: int,
8013                },
8014                Inst::CallHost { dst: 2, op, args },
8015                Inst::Return { src: 2 },
8016            ],
8017        );
8018        let program = build.done();
8019        let hosts = running();
8020        let mut machine = Machine::with_hosts(&program, 1 << 13, Some(&hosts));
8021        let before = machine.mem.stack_words();
8022        assert_eq!(
8023            machine.run(main, &[], &budget()).unwrap(),
8024            vec![-2i64 as u64]
8025        );
8026        assert_eq!(
8027            machine.mem.stack_words(),
8028            before,
8029            "the callback's frame went back where it came from"
8030        );
8031        assert!(machine.frames.is_empty());
8032    }
8033
8034    /// A bounded call's flag stops the body at its next safepoint.
8035    ///
8036    /// `Reentry::call_until` says `stop` *"bounds this call and everything
8037    /// inside it"*, and until a callback could run here at all there was
8038    /// nothing for it to bound: `Machine::stops` was `&[]` at every safepoint
8039    /// and at the boundary. The body here is a loop long enough to reach a
8040    /// safepoint, and what stops it is the oracle's own `stopped_here`.
8041    #[test]
8042    fn a_bounded_callback_stops_at_a_safepoint() {
8043        let mut build = Build::default();
8044        let int = build.scalar(Repr::Int);
8045        let reference = build.word("Fn", Repr::Ref);
8046        build.program.host_ops.push(cove_ir::HostOp {
8047            resource: None,
8048            module: Arc::from("runner"),
8049            operation: Arc::from("bounded"),
8050            result: int,
8051        });
8052        let op = cove_ir::HostOpId(0);
8053        let args = build.args(&[(0, reference)]);
8054        let step_id = FunctionId(build.program.functions.len() as u32);
8055        let closure = build.layout(
8056            "closure",
8057            Shape::Closure {
8058                function: step_id,
8059                captures: vec![],
8060            },
8061        );
8062        // `var i = 0; while i < 100000 { i += 1 }; i`
8063        let step = build.lambda(
8064            "step",
8065            &[],
8066            &[Repr::Int, Repr::Int, Repr::Bool, Repr::Int],
8067            int,
8068            &[],
8069            vec![
8070                Inst::Int { dst: 0, value: 0 },
8071                Inst::Int {
8072                    dst: 1,
8073                    value: 100_000,
8074                },
8075                Inst::Int { dst: 3, value: 1 },
8076                Inst::Cmp {
8077                    on: Compare::Int,
8078                    op: CmpOp::Lt,
8079                    dst: 2,
8080                    a: 0,
8081                    b: 1,
8082                },
8083                Inst::BranchFalse { cond: 2, to: 6 },
8084                Inst::Arith {
8085                    num: Num::Int,
8086                    op: ArithOp::Add,
8087                    dst: 0,
8088                    a: 0,
8089                    b: 3,
8090                },
8091                Inst::Jump { to: 3 },
8092                Inst::Return { src: 0 },
8093            ],
8094        );
8095        assert_eq!(step, step_id);
8096        let main = build.function(
8097            "main",
8098            &[],
8099            &[Repr::Ref, Repr::Int, Repr::Int],
8100            int,
8101            vec![
8102                Inst::Alloc {
8103                    dst: 0,
8104                    layout: closure,
8105                    len: Len::Fixed,
8106                },
8107                Inst::FuncRef {
8108                    dst: 1,
8109                    callee: step_id,
8110                },
8111                Inst::StoreField {
8112                    obj: 0,
8113                    at: 0,
8114                    src: 1,
8115                    layout: int,
8116                },
8117                Inst::CallHost { dst: 2, op, args },
8118                Inst::Return { src: 2 },
8119            ],
8120        );
8121        let program = build.done();
8122        let hosts = running();
8123        let mut machine = Machine::with_hosts(&program, 1 << 13, Some(&hosts));
8124        // The host turned the stop into its own answer, which it could only
8125        // do because the body stopped.
8126        assert_eq!(
8127            machine.run(main, &[], &budget()).unwrap(),
8128            vec![-1i64 as u64]
8129        );
8130        // And the flag went with the call that raised it.
8131        assert!(machine.stops.is_empty());
8132    }
8133
8134    // ---- how deep a reentry may nest ------------------------------------
8135
8136    /// Host → Cove → Host → Cove, as deep as the bound allows.
8137    ///
8138    /// This is the case that decides what `docs/LINEAR_VM.md`'s *"a builtin
8139    /// never calls back into Cove"* means here. Cove calling Cove adds no
8140    /// native frame in this backend, so the reserved stack region is the
8141    /// whole of the depth question — but a *host* callback is not Cove
8142    /// calling Cove: the host is already a Rust frame, and running its
8143    /// callback puts `HostRegistry::dispatch`, the host's own frames, and
8144    /// another turn of `encoded::dispatch` under every Cove frame the
8145    /// callback makes. So the bound is the oracle's `MAX_REENTRY_DEPTH`,
8146    /// which exists for exactly that.
8147    #[test]
8148    fn a_reentry_may_nest_up_to_the_bound() {
8149        // One host call per level, `MAX_REENTRY_DEPTH` of them stacked at the
8150        // deepest point, and the answer counts them.
8151        let depth = crate::interp::MAX_REENTRY_DEPTH as i64 - 1;
8152        assert_eq!(reentering("apply", depth).unwrap(), depth);
8153    }
8154
8155    /// And no deeper: the run stops, rather than the process.
8156    #[test]
8157    fn a_reentry_past_the_bound_stops_the_run_rather_than_the_process() {
8158        let depth = crate::interp::MAX_REENTRY_DEPTH as i64;
8159        let error = reentering("apply", depth).unwrap_err();
8160        assert_eq!(
8161            error.message,
8162            format!(
8163                "reentry depth limit of {} reached while a host ran a Cove callback",
8164                crate::interp::MAX_REENTRY_DEPTH
8165            ),
8166            "the refusal is the oracle's own, word for word"
8167        );
8168    }
8169
8170    /// A host that runs its callback twice pays for one level twice over
8171    /// rather than for two levels at once.
8172    #[test]
8173    fn calling_a_callback_twice_is_one_level_twice() {
8174        // Deep enough that two levels at once would be past the bound, and
8175        // `twice` at every level, so a count that did not come back down
8176        // would refuse long before this answers.
8177        let depth = crate::interp::MAX_REENTRY_DEPTH as i64 - 1;
8178        assert!(reentering("twice", depth).is_ok());
8179    }
8180
8181    // ---- cells ---------------------------------------------------------
8182
8183    /// A cell taken and given back leaves nothing held.
8184    ///
8185    /// The pair the lowering emits, on its own, so that a failure here is the
8186    /// machine's arms and not a lowering that emitted the wrong pair.
8187    #[test]
8188    fn a_cell_is_taken_and_given_back_by_the_pair_of_instructions() {
8189        let mut build = Build::default();
8190        let int = build.word("Int", Repr::Int);
8191        let held = build.layout("Shared", Shape::Shared { value: int });
8192        let main = build.function(
8193            "main",
8194            &[],
8195            &[Repr::Int, Repr::Ref],
8196            int,
8197            vec![
8198                Inst::Alloc {
8199                    dst: 1,
8200                    layout: held,
8201                    len: Len::Fixed,
8202                },
8203                Inst::SharedLock { cell: 1 },
8204                Inst::Int { dst: 0, value: 7 },
8205                Inst::StoreField {
8206                    obj: 1,
8207                    at: cove_ir::SHARED_VALUE,
8208                    src: 0,
8209                    layout: int,
8210                },
8211                Inst::SharedUnlock { cell: 1 },
8212                Inst::LoadField {
8213                    dst: 0,
8214                    obj: 1,
8215                    at: cove_ir::SHARED_VALUE,
8216                    layout: int,
8217                },
8218                Inst::Return { src: 0 },
8219            ],
8220        );
8221        let program = build.done();
8222        let mut machine = Machine::new(&program, 1 << 12);
8223        assert_eq!(machine.run(main, &[], &budget()).unwrap(), vec![7]);
8224        assert!(machine.held.is_empty());
8225    }
8226
8227    /// A run that fails inside a `lock` gives back every cell it was holding.
8228    ///
8229    /// The release is an obligation on every exit path, and a runtime error is
8230    /// the one path the lowering cannot write: it is not a jump, so no
8231    /// `Inst::SharedUnlock` stands between it and the end of the run. Without
8232    /// this a cell a failing task never gave back would be a cell no task
8233    /// could ever take — and *no task* is the point, because the heap and the
8234    /// cells in it belong to the run rather than to the task that failed.
8235    ///
8236    /// Two cells, because they nest and the refusal is per cell: giving back
8237    /// only the innermost would leave the other held.
8238    #[test]
8239    fn a_failing_run_gives_back_every_cell_it_held() {
8240        let mut build = Build::default().strings(&["stop"]);
8241        let str_layout = build.layout("String", Shape::Str);
8242        build.program.str_layout = str_layout;
8243        let int = build.word("Int", Repr::Int);
8244        let held = build.layout("Shared", Shape::Shared { value: int });
8245        let main = build.function(
8246            "main",
8247            &[],
8248            &[Repr::Int, Repr::Ref, Repr::Ref],
8249            int,
8250            vec![
8251                Inst::Alloc {
8252                    dst: 1,
8253                    layout: held,
8254                    len: Len::Fixed,
8255                },
8256                Inst::Alloc {
8257                    dst: 2,
8258                    layout: held,
8259                    len: Len::Fixed,
8260                },
8261                Inst::SharedLock { cell: 1 },
8262                Inst::SharedLock { cell: 2 },
8263                Inst::Trap {
8264                    message: cove_ir::StrId(0),
8265                },
8266                Inst::Return { src: 0 },
8267            ],
8268        );
8269        let program = build.done();
8270        let mut machine = Machine::new(&program, 1 << 12);
8271        let error = machine.run(main, &[], &budget()).unwrap_err();
8272        assert_eq!(error.message, "stop");
8273
8274        assert!(machine.held.is_empty());
8275        // The frames are left standing by a failed run, so the two cells are
8276        // still where the fixture put them and can be asked.
8277        let base = machine.frames[0].base;
8278        for slot in [1, 2] {
8279            let addr = machine.mem.slot(base, slot);
8280            assert_eq!(
8281                cell::holder(&machine.mem, addr),
8282                0,
8283                "a cell a failing task took is free again"
8284            );
8285        }
8286    }
8287
8288    /// A task that already holds a cell is refused rather than made to wait,
8289    /// and the refusal does not take the cell.
8290    #[test]
8291    fn a_reentrant_lock_is_refused_and_leaves_the_cell_held_once() {
8292        let mut build = Build::default();
8293        let int = build.word("Int", Repr::Int);
8294        let held = build.layout("Shared", Shape::Shared { value: int });
8295        let main = build.function(
8296            "main",
8297            &[],
8298            &[Repr::Int, Repr::Ref],
8299            int,
8300            vec![
8301                Inst::Alloc {
8302                    dst: 1,
8303                    layout: held,
8304                    len: Len::Fixed,
8305                },
8306                Inst::SharedLock { cell: 1 },
8307                Inst::SharedLock { cell: 1 },
8308                Inst::Return { src: 0 },
8309            ],
8310        );
8311        let program = build.done();
8312        let mut machine = Machine::new(&program, 1 << 12);
8313        let error = machine.run(main, &[], &budget()).unwrap_err();
8314        assert_eq!(
8315            error.message,
8316            "this task already holds this `Shared`, so `lock` would wait for itself"
8317        );
8318        // The refusal did not take the cell a second time, so the unwind gives
8319        // it back exactly once.
8320        assert!(machine.held.is_empty());
8321        let addr = machine.mem.slot(machine.frames[0].base, 1);
8322        assert_eq!(cell::holder(&machine.mem, addr), 0);
8323    }
8324
8325    // --- ADR 0052: the safepoint schedule is work, not a multiple ----------
8326
8327    /// **A stride past the last charge is the stride's next multiple, while
8328    /// every instruction costs one — and stays a stride of work when one does
8329    /// not.**
8330    ///
8331    /// The schedule is contract arithmetic:
8332    /// `docs/adr/0040-a-bound-outlives-its-backend.md` states every stop bound
8333    /// in multiples of [`SAFEPOINT_STRIDE`] and `tests/responsiveness.rs`
8334    /// measures each one, so neither the change from `instructions % S == 0`
8335    /// to a difference nor the move into the `work` coordinate may shift a
8336    /// single count while every instruction still costs one.
8337    /// `crate::vm::debug`'s `the_safepoint_fires_at_the_same_counts_as_it_did_before`
8338    /// proves that end to end through the fuel limit; this proves the
8339    /// arithmetic, including the bulk case that test cannot reach.
8340    #[test]
8341    fn the_next_question_is_a_stride_of_work_past_the_last_charge() {
8342        let program = Build::default().done();
8343        let mut machine = Machine::new(&program, 1 << 12);
8344
8345        // While every instruction costs one, `charged_work` lands on a
8346        // multiple at every safepoint, so the next question is the next
8347        // multiple — which is what the condition used to say in so many words.
8348        for turn in 0..4u64 {
8349            machine.charged_work = turn * SAFEPOINT_STRIDE;
8350            machine.instructions = machine.charged_work + 1;
8351            machine.bulk_work = 0;
8352            assert_eq!(
8353                machine.next_question(),
8354                (turn + 1) * SAFEPOINT_STRIDE,
8355                "with {} charged, the question is the next multiple",
8356                machine.charged_work
8357            );
8358        }
8359
8360        // And when a copy charges for the words it moved, the question is
8361        // still a stride of *work* away rather than a multiple the charge may
8362        // have stepped clean over. `2500` is past `2048` and is not a multiple
8363        // of `1024`: the old rule answered false here and skipped the
8364        // safepoint entirely.
8365        machine.charged_work = 0;
8366        machine.instructions = 500;
8367        machine.bulk_work = 2000;
8368        assert_eq!(machine.work(), 2500);
8369        // Already past a stride of work, so the question is due now.
8370        assert_eq!(machine.next_question(), 0);
8371        assert!(
8372            machine.work() - machine.charged_work >= SAFEPOINT_STRIDE,
8373            "2500 units of work since the last charge is a safepoint"
8374        );
8375        assert!(
8376            !machine.work().is_multiple_of(SAFEPOINT_STRIDE),
8377            "and 2500 is not a multiple of the stride, which is the bug"
8378        );
8379    }
8380}