Skip to main content

cove_ir/
inst.rs

1//! The instructions.
2//!
3//! Every instruction names its operands and its destination by **slot
4//! number**. There is no operand stack: no push, no pop, no stack-effect
5//! table, no discipline to get wrong.
6//!
7//! That is [ADR 0034](../../../docs/adr/0034-one-physical-word-stack.md)'s
8//! *"parameters, locals, temporaries and captures share the one slot
9//! numbering"* taken literally. If a temporary is a slot, then an
10//! instruction that consumes a temporary names a slot, and the thing an
11//! operand stack exists to provide is already there.
12//!
13//! Two things fall out of that, and they are why it is worth choosing:
14//!
15//! - **A frame's roots are a static fact.** A stack machine's set of live
16//!   references changes as operands are pushed and popped, so its reference
17//!   map has to be indexed by program counter. Here the map does not change
18//!   between a function's first instruction and its last, and
19//!   [`crate::RefMap`] is one bit per slot.
20//! - **A call needs no argument buffer.** The callee's frame begins where
21//!   the caller's ends, so [`Inst::Call`] copies the words of argument *i*
22//!   into the run parameter *i* occupies and transfers control. Nothing is
23//!   pushed, permuted, or copied back.
24//!
25//! # The instruction set describes families, not cases
26//!
27//! There is one `LoadField`, not one per value kind that has fields; one
28//! `Arith`, not one per numeric type; one `Alloc`, not one per collection.
29//! A field of an *inline* value needs no instruction at all — it is a slot
30//! offset the lowering computes.
31//! What an object *is* is a question the object answers at run time, from
32//! its own header. Nothing here grows a case because a corpus program was
33//! refused, because nothing here refuses anything.
34//!
35//! The two instructions that carry an immediate — [`Inst::ArithImm`] and
36//! [`Inst::CmpImm`] — are the same rule applied to an operand rather than to
37//! a type. They are not `add.int.imm`, `sub.int.imm`, `lt.int.imm` and eight
38//! more: the operator is a field, as it already is on [`Inst::Arith`] and
39//! [`Inst::Cmp`], so the family stays two however many operators the language
40//! grows. What they say that no other instruction can is that an operand is a
41//! constant, which is a fact the source stated and every other representation
42//! of it throws away.
43
44use crate::layout::LayoutId;
45use crate::{ArgsId, BuiltinId, CaseId, FunctionId, HostOpId, StrId, TableId};
46
47/// A slot in the current frame: `memory[frame_base + slot]`.
48pub type Slot = u32;
49
50/// An index into a function's instructions.
51pub type Pc = u32;
52
53/// Which numeric interpretation an arithmetic or comparison instruction
54/// gives its operand words.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum Num {
57    /// Two's-complement `i64`. Also what a `Duration` is arithmetic on:
58    /// nanoseconds add like integers, and only the boundary cares that the
59    /// answer is called a `Duration`.
60    Int,
61    /// An IEEE-754 double, bit-cast out of the word.
62    Float,
63}
64
65/// What a comparison compares.
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub enum Compare {
68    Int,
69    Float,
70    Bool,
71    /// The bytes of two [`crate::Shape::Str`] objects.
72    Str,
73    /// Two words, as words.
74    ///
75    /// This is `is`: the identity comparison the language reserves for
76    /// shared storage, and it is the one comparison that is allowed to look
77    /// at a reference as bits, because that is what it is asking about.
78    Identity,
79    /// Two case indices, as the integers they are.
80    ///
81    /// [`Inst::Tag`] says a tag is refused by arithmetic, ordering and
82    /// integer comparison, because none of those accepts that `Repr` — and
83    /// that refusal is what keeps a case index from being confused with a
84    /// number. This does not weaken it: it accepts a `Tag` and nothing else,
85    /// so the pairing a tag can take part in is still only with another tag.
86    ///
87    /// What it is for is `Kind.Space == Kind.Word`, which is `1 == 2`. An
88    /// enum with no payload is one word wide and that word is the
89    /// discriminant, so two of them are equal exactly when the two words are.
90    /// Walked instead by `Any.equals`, the same question measured 342 ms
91    /// against 210 for 2,000,000 comparisons — 66 ns of builtin call apiece,
92    /// and 8% of a native profile of a formatter that reads a token's kind in
93    /// every loop it has.
94    Tag,
95}
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub enum ArithOp {
99    Add,
100    Sub,
101    Mul,
102    Div,
103    Rem,
104}
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107pub enum CmpOp {
108    Eq,
109    Ne,
110    Lt,
111    Le,
112    Gt,
113    Ge,
114}
115
116/// A conversion between two scalar representations.
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub enum Convert {
119    /// `Int` to `Float`, as `as`-style widening.
120    IntToFloat,
121    /// `Float` to `Int`, truncating toward zero.
122    FloatToInt,
123}
124
125/// How many elements an [`Inst::Alloc`] asks for.
126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
127pub enum Len {
128    /// A shape whose size the layout already fixes: a struct, an enum, a
129    /// closure, a box.
130    Fixed,
131    /// A count the lowering knew: a literal array's element count, a string
132    /// literal's byte count.
133    Count(u32),
134    /// A count in a slot, as an `Int`.
135    Slot(Slot),
136}
137
138/// One instruction.
139#[derive(Clone, Debug, PartialEq)]
140pub enum Inst {
141    // ---- constants and moves ------------------------------------------
142    /// `dst = ()`
143    Unit { dst: Slot },
144    /// `dst = value`
145    Bool { dst: Slot, value: bool },
146    /// `dst = value`, also how a `Duration` literal reaches a slot.
147    Int { dst: Slot, value: i64 },
148    /// `dst = <callee's dense id>`, as a word.
149    ///
150    /// What a lowered closure's environment is given for its callee field,
151    /// and the only place that value is produced. It is not [`Inst::Int`],
152    /// though the word it writes is the same [`FunctionId`] an `Int` of that
153    /// value would be: a closure's [`crate::layout::Shape::Closure`] already
154    /// carries `function: FunctionId` as a typed fact, and writing the same
155    /// id again through the untyped integer path made a second,
156    /// uninspectable copy of it — one no verifier could tell from an
157    /// ordinary integer, and one that renumbered every golden lowering with
158    /// a closure in it whenever an unrelated function was added or moved,
159    /// because the two facts were spelled a plain number in the listing
160    /// rather than the name that number happened to hold that day.
161    ///
162    /// [`mod@crate::verify`] bounds `callee` against
163    /// [`crate::program::Program::functions`] the way [`Inst::Call`]'s is
164    /// bounded, and, where the destination is then stored into a
165    /// statically-known closure object, checks that `callee` agrees with
166    /// what the object's own layout says — the comparison the two copies
167    /// never had. [`crate::print`] renders it symbolically, by the callee's
168    /// name and not its number, which is what stops the churn: an unrelated
169    /// declaration changing `callee`'s numeric value no longer changes a
170    /// single character of the listing.
171    FuncRef { dst: Slot, callee: FunctionId },
172    /// `dst = <the index of `case` in `layout`>`, as an enum's discriminant.
173    ///
174    /// The one way a discriminant is written. It is not [`Inst::Int`], though
175    /// the word it writes is the number an `Int` of that value would write,
176    /// and the reason is [`Inst::FuncRef`]'s: a case index reached its slot
177    /// through the untyped integer path, where no verifier could tell it from
178    /// an ordinary number and nothing bounded it against the enum it was
179    /// supposed to name.
180    ///
181    /// Its destination is a [`Repr::Tag`](crate::Repr::Tag) word, which is
182    /// what makes the two facts separable at all: a tag is one non-reference
183    /// word, physically an integer, and is refused by arithmetic, ordering
184    /// and integer comparison because none of those accepts that `Repr`.
185    /// [`Inst::Switch`] accepts it, and so do copying and clearing, which
186    /// read a layout rather than a `Repr`.
187    ///
188    /// [`mod@crate::verify`] bounds `case` against `layout`'s own case list
189    /// and refuses a `layout` that is not an enum. [`crate::print`] renders
190    /// it by the case's name, so an unrelated case added before it changes no
191    /// character of a listing that does not mention it.
192    Tag {
193        dst: Slot,
194        layout: LayoutId,
195        case: CaseId,
196    },
197    /// `dst = f64::from_bits(bits)`
198    ///
199    /// The bits rather than the `f64` so that [`Inst`] can be `Eq` and
200    /// `Hash`ed, and so that a NaN in the source survives the IR unchanged.
201    Float { dst: Slot, bits: u64 },
202    /// `dst = <the address of the string object for `text`>`
203    ///
204    /// The object already exists.
205    /// [ADR 0045](../../../docs/adr/0045-a-literal-is-there-before-the-program-runs.md)
206    /// places every program literal in the heap before the run's first
207    /// instruction executes, so this is a load of a precomputed address —
208    /// no branch, no allocation, no copy, whether this is the first turn of
209    /// a loop or the millionth.
210    Str { dst: Slot, text: StrId },
211    /// `dst = src`, for the words `layout` describes.
212    ///
213    /// This is ADR 0001's field-wise shallow copy, and it is one operation
214    /// because a value's words are where the value is. Copying a
215    /// `Wrapper { p: Point, v: Vector }` copies three words: the `Point`
216    /// becomes independent because its words were copied, and the `Vector`
217    /// stays shared because what was copied is its address. Both answers
218    /// fall out of the same copy and neither needs a policy.
219    ///
220    /// There is no sharing bit, no copy-on-write and no unsharing of a write
221    /// path. Those were needed only while every struct was one address, and
222    /// they existed to conceal an alias the representation had created.
223    ///
224    /// `let` and `var` lower to the same thing: ADR 0001 says they do not
225    /// change expression semantics, and Cove has no move semantics. A
226    /// lowering may elide a copy whose source is a fresh temporary, but that
227    /// is an optimisation — correctness never depends on proving uniqueness,
228    /// and a lowering that cannot tell emits the copy.
229    Copy {
230        dst: Slot,
231        src: Slot,
232        layout: LayoutId,
233    },
234    /// Zeroes the words `layout` describes at `slot`.
235    ///
236    /// A slot whose value is dead. The lowering emits one at the end of the
237    /// scope a binding belonged to, and at a temporary's last use, for every
238    /// slot whose [`Repr`](crate::Repr) is [`Ref`](crate::Repr::Ref) or
239    /// [`Addr`](crate::Repr::Addr).
240    ///
241    /// This is what keeps a static reference map from turning into a leak.
242    /// The map says which slots the collector *reads*; it cannot say when
243    /// the value in one stopped being needed, because that is a fact about a
244    /// program point and the map is a fact about a function. Clearing the
245    /// slot moves the answer into the data: a dead reference slot holds
246    /// null, the collector reads null, and the object is unreachable at the
247    /// next collection rather than at the next return.
248    ///
249    /// It costs one store on a path that was going to leave the value behind
250    /// anyway, and it is emitted only where the slot would otherwise retain
251    /// something — never for a scalar, and never where the slot is about to
252    /// be overwritten.
253    Clear { slot: Slot, layout: LayoutId },
254
255    // ---- scalar operations --------------------------------------------
256    /// `dst = -a`
257    Neg { num: Num, dst: Slot, a: Slot },
258    /// `dst = a op b`
259    Arith {
260        num: Num,
261        op: ArithOp,
262        dst: Slot,
263        a: Slot,
264        b: Slot,
265    },
266    /// `dst = a op b`, answering a `Bool`.
267    Cmp {
268        on: Compare,
269        op: CmpOp,
270        dst: Slot,
271        a: Slot,
272        b: Slot,
273    },
274    /// `dst = a op value`, on `Int` words, where `value` was written in the
275    /// source.
276    ///
277    /// The same arithmetic [`Inst::Arith`] does — the same overflow, the same
278    /// division and remainder by zero, the same `Duration` naming — with the
279    /// right operand in the instruction instead of in a slot. It exists
280    /// because the alternative is worse than a wasted word: the literals of a
281    /// loop condition are materialised by instructions the back edge jumps
282    /// over, so `while i < 2000000` executed an [`Inst::Int`] two million
283    /// times to write a constant into a temporary that nothing else ever
284    /// read.
285    ///
286    /// **Two variants and not sixteen.** `op` is a field here exactly as it
287    /// is on [`Inst::Arith`], so an operator added to the language costs no
288    /// instruction, and this pair covers the eleven that exist.
289    ///
290    /// **The immediate is on the right, and only on the right.** `a - 1` and
291    /// `1 - a` are different questions and `a % 7` and `7 % a` more so, so a
292    /// left-hand immediate would be a second family rather than a mirror of
293    /// this one; a commutative operator's lowering puts the literal on the
294    /// right instead. There is no float immediate for the same reason there
295    /// is no left one — a second family, for a form no benchmark asked for.
296    ///
297    /// `Num` is absent because there is only [`Num::Int`] to name: `value` is
298    /// an `i64`, and a `Duration`'s word is nanoseconds, which is an `i64`.
299    ArithImm {
300        op: ArithOp,
301        dst: Slot,
302        a: Slot,
303        value: i64,
304    },
305    /// `dst = a op value`, comparing `Int` words, answering a `Bool`.
306    ///
307    /// [`Inst::ArithImm`]'s other half, and `Compare` is absent for the
308    /// reason `Num` is absent there: the operand is an `Int` word, so the
309    /// comparison is [`Compare::Int`].
310    CmpImm {
311        op: CmpOp,
312        dst: Slot,
313        a: Slot,
314        value: i64,
315    },
316    /// `dst = !a`
317    Not { dst: Slot, a: Slot },
318    /// `dst = <a, converted>`
319    Convert { to: Convert, dst: Slot, a: Slot },
320
321    // ---- control flow --------------------------------------------------
322    /// Continue at `to`.
323    Jump { to: Pc },
324    /// Continue at `to` when `cond` is false; otherwise fall through.
325    ///
326    /// One conditional branch rather than two: `&&`, `||`, `if` and `while`
327    /// all lower through it, and the lowering inverts the condition rather
328    /// than the instruction set carrying both polarities.
329    BranchFalse { cond: Slot, to: Pc },
330    /// Continue at the entry of `table` selected by the `Int` in `on`.
331    ///
332    /// This is how a `match` over an enum's cases dispatches: `on` is the
333    /// case index read out of the object, and the table has one target per
334    /// case plus a default.
335    Switch { on: Slot, table: TableId },
336    /// Leave the function, answering the value at `src`.
337    ///
338    /// `src` is the *first* slot of that value location, and how many words
339    /// follow it is [`crate::Function::returns`] — which is why a listing
340    /// writes that layout *on* the slot, and the whole run with it:
341    /// `return s0..s2:Result`.
342    Return { src: Slot },
343
344    // ---- calls ----------------------------------------------------------
345    /// `dst = callee(args...)`
346    ///
347    /// The machine writes `args[i]` into the callee's slot `i` and gives it
348    /// a frame beginning at the end of this one. Nothing else happens: the
349    /// argument list is static, the destination is declared, and there is no
350    /// buffer between the two frames.
351    Call {
352        dst: Slot,
353        callee: FunctionId,
354        args: ArgsId,
355    },
356    /// `dst = closure(args...)`, where `closure` holds a reference to a
357    /// [`crate::Shape::Closure`] object.
358    ///
359    /// The callee is the function id in the object's first payload word, and
360    /// its captures are copied into the slots after the parameters.
361    ///
362    /// `result` is the layout of what the call answers, and it is the one
363    /// operand here that is not read off the object at run time. Which body
364    /// this enters is a run-time fact; how wide its answer is, is not. The
365    /// checker settles a call through a value against the callee's *function
366    /// type*, so the answer's type — and with it the run of words the
367    /// destination has to be — is as static as any other call's.
368    ///
369    /// It is carried rather than looked up because there is nowhere to look:
370    /// every other call names a callee the program declares — a
371    /// [`FunctionId`], a [`crate::HostOpId`], a [`crate::BuiltinId`] — and
372    /// the answer's layout is read from that declaration. A closure call
373    /// names a word in a slot. Without this field the destination's width
374    /// was known to the checker, thrown away by the lowering, and then
375    /// unavailable to everything downstream: `crate::verify` could ask only
376    /// that `dst` was a slot at all, the encoded verifier the same, and a
377    /// listing had to print the head word's `Repr` where every other call
378    /// prints the run. A two-word answer written into the last slot of a
379    /// frame was checked by nothing.
380    CallClosure {
381        dst: Slot,
382        closure: Slot,
383        args: ArgsId,
384        result: LayoutId,
385    },
386    /// `dst = <host op>(args...)`
387    ///
388    /// This is a boundary: the arguments are materialised into public
389    /// public `Value`s, the host answers one, and the answer
390    /// is written back into a word. It is the only place in ordinary
391    /// execution where a `Value` exists.
392    CallHost {
393        dst: Slot,
394        op: HostOpId,
395        args: ArgsId,
396    },
397    /// `dst = <host op>(*receiver, args...)`, addressed to the resource
398    /// the [`Repr::Host`](crate::Repr::Host) word in `receiver` names.
399    ///
400    /// The same boundary [`Inst::CallHost`] is, reached the other way a
401    /// callee can be found. `Call` and `CallClosure` are already that pair
402    /// on this side of the boundary — a callee named statically, and a
403    /// callee in a slot — and a host resource's operations are the same
404    /// distinction one boundary further out: ADR 0013 gives the *host* the
405    /// table of what is open, so `files.Writer.writeLine` is dispatched on
406    /// the handle and not on the module the source wrote in front of it.
407    ///
408    /// The receiver is an operand of its own rather than `args[0]`, and that
409    /// is the difference that decides there are two instructions here rather
410    /// than a flag on one. An [`crate::Arg`] is a value location the
411    /// boundary *materialises*, and the registry does not take the handle as
412    /// an argument — `HostRegistry::call_resource` takes it as the thing
413    /// being addressed and hands the host only what follows. So putting it
414    /// in the list would mean materialising a name into a `Value` in order
415    /// to take it apart again, and the argument list would no longer be the
416    /// arguments.
417    CallResource {
418        dst: Slot,
419        receiver: Slot,
420        op: HostOpId,
421        args: ArgsId,
422    },
423    /// `dst = <builtin>(args...)`
424    ///
425    /// A builtin operates on words and heap objects directly. It is not a
426    /// boundary and it does not materialise anything.
427    CallBuiltin {
428        dst: Slot,
429        builtin: BuiltinId,
430        args: ArgsId,
431    },
432
433    // ---- the heap --------------------------------------------------------
434    /// `dst = <a new object of `layout`>`
435    ///
436    /// The payload is zeroed, so a reference field of a half-built object is
437    /// null rather than garbage if a collection happens before it is
438    /// filled in.
439    Alloc {
440        dst: Slot,
441        layout: LayoutId,
442        len: Len,
443    },
444    /// `dst = <the value at payload word `at` of `obj`>`
445    ///
446    /// One instruction for every fixed-position read there is: a struct
447    /// field, an enum's case index (`at == 0`) or payload word, a closure's
448    /// capture. The lowering computes `at` from the layout it knows
449    /// statically; the machine bounds-checks it against the layout the
450    /// object names, because a reference slot carries no layout of its own.
451    LoadField {
452        dst: Slot,
453        obj: Slot,
454        at: u32,
455        layout: LayoutId,
456    },
457    /// `<payload word `at` of `obj`> = src`
458    StoreField {
459        obj: Slot,
460        at: u32,
461        src: Slot,
462        layout: LayoutId,
463    },
464    /// `dst = obj[index]`, for an object whose elements are `layout` wide.
465    ///
466    /// The stride is the element layout's width, so an `Array<Point>` is a
467    /// run of two-word elements rather than a run of addresses.
468    LoadElem {
469        dst: Slot,
470        obj: Slot,
471        index: Slot,
472        layout: LayoutId,
473    },
474    /// `obj[index] = src`
475    StoreElem {
476        obj: Slot,
477        index: Slot,
478        src: Slot,
479        layout: LayoutId,
480    },
481    /// `dst = <byte `at` of the string `obj`>`, as an `Int` in `0..=255`.
482    ///
483    /// The one instruction that reaches *inside* a word. Everything else here
484    /// addresses a value location or a payload word, because a word is what a
485    /// frame and a heap object are made of — but a `String`'s payload is
486    /// bytes, eight to a word, and the only shape that reads one is this.
487    ///
488    /// It is an instruction and not a builtin, and the difference is the
489    /// whole reason it exists. `String.byteAt` as a `call-builtin` measured
490    /// 58 ns of which 48 ns was *being a builtin call* — the operands copied
491    /// into a buffer, the operand array built, the dispatch by two strings,
492    /// the answer written back — for work that is one payload word, a shift
493    /// and a mask. `benches/builtincall` is where those two numbers are.
494    ///
495    /// `at` is bounds-checked against the receiver's byte length, and an
496    /// offset outside it stops the run. That is `String.sliceBytes`'s rule
497    /// and not `Array.get`'s: a byte offset out of range is one this type
498    /// never handed out, where an index out of range is arithmetic a caller
499    /// did about a sequence it can count. Answering an `Option` here would
500    /// also be answering it eight times per word of a lexer's inner loop,
501    /// and the wrapper was measured at more than the read.
502    ByteAt { dst: Slot, obj: Slot, at: Slot },
503    /// `dst = <a new, zeroed byte run of `len` bytes>`.
504    ///
505    /// [ADR 0051](../../../docs/adr/0051-a-string-is-built-as-a-byte-run.md)'s
506    /// allocation. It always allocates [`crate::Program::bytes_layout`] —
507    /// the one shape every run under construction shares — so unlike
508    /// [`Inst::Alloc`] it carries no [`LayoutId`] of its own, for
509    /// [`Inst::Str`]'s reason: a program-wide constant should not have to be
510    /// named at every call site that always means the same one.
511    ///
512    /// The payload is zeroed exactly as [`Inst::Alloc`]'s is, so a run that
513    /// is collected before it is filled walks safely — not because a
514    /// half-written byte is meaningful, but because [`crate::Shape::Bytes`] holds no
515    /// references for the collector to chase either way.
516    ///
517    /// `len` is a byte count and a run-time value, because the whole point
518    /// of ADR 0051's construction is a length computed by summing the pieces
519    /// a `join` was given — a fixed length would have made this
520    /// [`Inst::Alloc`] with a [`Len::Count`] instead. A negative or oversized
521    /// `len` fails through the same "this run has no memory left" refusal
522    /// every other allocation does.
523    AllocBytes { dst: Slot, len: Slot },
524    /// `bytes[at] = value`, one checked byte of a run under construction.
525    ///
526    /// The scalar half of [ADR 0051](../../../docs/adr/0051-a-string-is-built-as-a-byte-run.md)'s
527    /// two write primitives, and deliberately the smaller one: it exists for
528    /// a delimiter or an encoded scalar a lowering writes one at a time, not
529    /// as how a `join` is expected to move text. Copying more than a
530    /// handful of bytes through this would replace one native copy with as
531    /// many dispatches as there are bytes, which is exactly the shape
532    /// [`Inst::CopyBytes`] exists to avoid.
533    ///
534    /// `bytes` must name a live [`crate::Shape::Bytes`] object — writing into a
535    /// `String` is refused, because a `String`'s bytes are the invariant
536    /// [`Inst::FinishString`] exists to establish and never to reopen.
537    /// `at` is bounds-checked against the run's declared length the same way
538    /// [`Inst::ByteAt`]'s is, and `value` must be a byte, `0..=255`: neither
539    /// bound is optional here the way it would be reading back a value this
540    /// run already produced, because this is the instruction that puts an
541    /// arbitrary integer into memory another instruction will one day read
542    /// back and trust.
543    WriteByte { bytes: Slot, at: Slot, value: Slot },
544    /// A bulk range copy into a run under construction: `dst[dst_at
545    /// .. dst_at+len] = src[src_at .. src_at+len]`.
546    ///
547    /// This is [ADR 0051](../../../docs/adr/0051-a-string-is-built-as-a-byte-run.md)'s
548    /// principal instruction — the one a `join` or a fused `sliceBytes`
549    /// lowers to instead of `sliceBytes -> Vector.push -> join`'s hidden
550    /// allocations — and the reason it exists at all is that a byte loop
551    /// over [`Inst::WriteByte`] would multiply dispatch by the number of
552    /// bytes moved, which ADR 0051's "why a byte loop in IR is not enough"
553    /// rejects. One instruction, one native run copy.
554    ///
555    /// # What it costs, and what it is charged
556    ///
557    /// One dispatch and one unit of work per payload word moved, which is
558    /// [ADR 0052](../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)'s
559    /// "charged proportionally to the bytes or words examined". A word is the
560    /// unit because a word is what the memory moves.
561    ///
562    /// The charge is not folded into the count of instructions dispatched.
563    /// That number is a public observable — the debugger, the trace, the
564    /// profile and `cove-bench` all report it, and `crate::vm::profile`'s own
565    /// test asserts its per-opcode totals sum to it — so weighted work has a
566    /// coordinate of its own.
567    ///
568    /// The copy is made in bounded chunks with a safepoint between them, and
569    /// that is not a refinement of the charge but the thing that makes it
570    /// sound. A charge taken only *after* an arbitrarily large copy would let
571    /// one instruction run arbitrarily far past a fuel or cancellation bound
572    /// before anything looked, which
573    /// [ADR 0040](../../../docs/adr/0040-a-bound-outlives-its-backend.md)'s
574    /// `S + T` forbids. One chunk is one stride of work, so a stopped run gets
575    /// no further than a stride past the bound whatever length it was given.
576    ///
577    /// A collection may therefore happen with the destination half written.
578    /// That is safe for the reason ADR 0051 gave for the run's payload holding
579    /// no references, and rooted for a second one: the caller has already
580    /// `sync`ed, and both objects are named by frame slots this instruction
581    /// read them out of, so the walk finds them where it finds every other
582    /// live reference.
583    ///
584    /// [`Inst::AllocBytes`] and [`Inst::FinishString`] are **not** charged
585    /// this way and not chunked. Their bulk work is inside the allocator's
586    /// zeroing and inside one `from_utf8` over a copy of the run, neither of
587    /// which this could interrupt, and charging an operation that cannot be
588    /// interrupted only makes its overshoot visible rather than bounded. They
589    /// remain one unit each, which is what an ordinary [`Inst::Alloc`] of a
590    /// large `Array` has always been.
591    ///
592    /// `src` may be a `String` **or** another [`crate::Shape::Bytes`] run — a fused
593    /// slice copies straight out of the run that produced it, without
594    /// finishing it as a `String` first — but `dst` must always be a
595    /// [`crate::Shape::Bytes`] run under construction: writing into a `String` is
596    /// refused for [`Inst::WriteByte`]'s reason. Bounds are checked against
597    /// both objects' declared lengths rather than left to whatever the
598    /// native copy routine happens to do with an out-of-range range.
599    ///
600    /// # Why five operands live behind an [`ArgsId`]
601    ///
602    /// An encoded instruction has room for three slot-sized operands and a
603    /// payload, and this needs five: `dst`, `dst_at`, `src`, `src_at` and
604    /// `len`. Rather than spend a fifth [`Inst`] variant or a second
605    /// instruction pair to carry the overflow, this reuses the machinery a
606    /// call's argument list already is — [`ArgsId`] names a row of
607    /// [`crate::Program::args`], and a call already demonstrates that an
608    /// arity larger than three operands is a solved problem in this format.
609    /// The row holds exactly five [`crate::Arg`]s, in the order `dst`,
610    /// `dst_at`, `src`, `src_at`, `len`, and carries each one's layout the
611    /// same way a call's arguments do, so the verifier checks them by the
612    /// same rule rather than by a new one.
613    CopyBytes { args: ArgsId },
614    /// `dst = <the run at `bytes`, validated and turned into an immutable
615    /// String, in place>`.
616    ///
617    /// The instruction [ADR 0051](../../../docs/adr/0051-a-string-is-built-as-a-byte-run.md)
618    /// closes construction with. `bytes` must name a live [`crate::Shape::Bytes`]
619    /// run; its packed payload is read and checked as UTF-8 exactly once,
620    /// because a run assembled from [`Inst::WriteByte`] and [`Inst::CopyBytes`]
621    /// may hold anything a byte can hold, and ADR 0051 refuses to skip that
622    /// check for an arbitrary run. Invalid UTF-8 fails with the same error a
623    /// source-level string operation already raises for it.
624    ///
625    /// On success the run becomes the answer **without copying its
626    /// payload**: a [`crate::Shape::Bytes`] object and a [`crate::Shape::Str`] object of
627    /// the same byte length occupy the same number of words, so finishing is
628    /// a re-label of the object's header — its layout changes from
629    /// [`crate::Program::bytes_layout`] to [`crate::Program::str_layout`] and
630    /// its `len` does not change at all — rather than an allocation and a
631    /// copy. Not copying the payload is the whole performance argument this
632    /// ADR makes: every byte a `join` moves is moved once, by
633    /// [`Inst::CopyBytes`], and finishing moves none of them again.
634    FinishString { dst: Slot, bytes: Slot },
635    /// `dst = <a new, empty byte buffer whose store has room for `capacity`
636    /// bytes>`.
637    ///
638    /// [ADR 0052](../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)'s
639    /// allocation, and the first of the four instructions that replace
640    /// [`Inst::AllocBytes`] wherever the final length is not known before the
641    /// writes. ADR 0051's fixed run is enough when it *is* known; it is not
642    /// enough for `examples/covefmt`, whose three hot joins are filled by
643    /// data-dependent loops and whose largest is a `var out` parameter passed
644    /// through recursive calls.
645    ///
646    /// Two objects are allocated, because that is what a stable owner is: the
647    /// owner is [`crate::Program::buffer_layout`], two payload words holding a
648    /// logical length and a reference; the store is
649    /// [`crate::Program::bytes_layout`], the same packed run ADR 0051 already
650    /// has, whose *header* length is the capacity. Neither layout is named
651    /// here, for [`Inst::AllocBytes`]'s reason: both are program-wide
652    /// constants, and a call site that always means the same one should not
653    /// have to say so.
654    ///
655    /// `capacity` is a **hint** and not a bound. Exceeding it grows the store
656    /// rather than failing, so a tuning estimate cannot change what a program
657    /// answers — which is the whole of ADR 0052's "capacity is not an Array
658    /// length". A capacity below the runtime's own floor is raised to it, and a
659    /// negative or oversized one fails through the same "this run has no memory
660    /// left" refusal every other allocation does.
661    AllocBuffer { dst: Slot, capacity: Slot },
662    /// `buffer.append(value)`, one checked byte onto the end of a buffer.
663    ///
664    /// The scalar half of ADR 0052's append pair, and [`Inst::WriteByte`]'s
665    /// counterpart for a growable run — with the one difference that makes a
666    /// buffer a buffer: there is no offset. A write goes at the logical length
667    /// and the logical length becomes one more, so a caller never names a
668    /// position and can never leave a hole below one.
669    ///
670    /// It exists for a delimiter or an encoded scalar a lowering emits one at a
671    /// time, not as how text is expected to move: copying a run of bytes
672    /// through this would be as many dispatches as there are bytes, which is
673    /// what [`Inst::AppendBytes`] is for.
674    ///
675    /// `buffer` must name a live owner and `value` must be a byte, `0..=255`,
676    /// for [`Inst::WriteByte`]'s reason — this is an instruction that puts an
677    /// arbitrary integer into memory that [`Inst::FinishBuffer`] will later
678    /// read back and validate. Nothing is bounds-checked against the capacity,
679    /// because there is no bound to check: a full store grows.
680    AppendByte { buffer: Slot, value: Slot },
681    /// A bulk range append: `buffer.append(src[from .. to])`.
682    ///
683    /// ADR 0052's principal instruction, and [`Inst::CopyBytes`]'s growable
684    /// counterpart. One dispatch moves the whole range, for the reason ADR
685    /// 0051 gave when it refused a byte loop in IR: a loop of
686    /// [`Inst::AppendByte`] would multiply dispatch by the number of bytes.
687    ///
688    /// `src` may be a `String` **or** a [`crate::Shape::Bytes`] run, which is
689    /// what lets a fused slice copy straight out of the run or the string that
690    /// produced it. The ADR's own example is the optimisation this enables:
691    /// `sliceBytes(source, from, to) -> append` becomes one checked append
692    /// from that source range and never materialises the slice.
693    ///
694    /// Where `src` is a `String`, `from` and `to` are checked to be character
695    /// boundaries and not merely in range — the same check, in the same words,
696    /// that `String.sliceBytes` makes. ADR 0052 requires it: "`appendSlice`
697    /// checks the same bounds and UTF-8 boundaries as `String.sliceBytes`".
698    /// Without it a program could assemble a run of valid pieces that is not
699    /// valid UTF-8, and discover it only at [`Inst::FinishBuffer`], where the
700    /// offset that did it is long gone. A [`crate::Shape::Bytes`] source is
701    /// held to no such rule, because a run under construction is not claiming
702    /// to be text.
703    ///
704    /// # What it costs, and what it is charged
705    ///
706    /// [`Inst::CopyBytes`]'s answer, unchanged: one unit of work per payload
707    /// word moved, in bounded chunks with a safepoint between them, so a
708    /// stopped run gets no further than a stride past the bound whatever length
709    /// it was given. Growth is charged as the allocation it is.
710    ///
711    /// The store is grown **once, up front**, for the whole range rather than
712    /// per chunk. That is not only cheaper: a growth part way through would
713    /// have to copy a prefix that the chunks before it had already written, and
714    /// the one allocation before the first chunk is what keeps the copy a copy.
715    ///
716    /// # Why four operands live behind an [`ArgsId`]
717    ///
718    /// [`Inst::CopyBytes`]'s reason at one fewer operand: an encoded
719    /// instruction has room for three slot-sized operands and this needs four —
720    /// `buffer`, `src`, `from` and `to`. Rather than spend a second instruction
721    /// to carry the overflow, this reuses the machinery a call's argument list
722    /// already is. The row holds exactly four [`crate::Arg`]s in the order
723    /// `buffer`, `src`, `from`, `to`, and carries each one's layout the way a
724    /// call's arguments do, so the verifier checks them by the same rule.
725    AppendBytes { args: ArgsId },
726    /// `dst = <the buffer at `buffer`, consumed, its store validated and
727    /// relabelled into an immutable String>`.
728    ///
729    /// ADR 0052's finish, and [`Inst::FinishString`]'s counterpart for a
730    /// growable run. The bytes are read and checked as UTF-8 exactly once,
731    /// because a run assembled from [`Inst::AppendByte`] may hold anything a
732    /// byte can hold, and invalid UTF-8 fails with the same error a
733    /// source-level string operation already raises for it.
734    ///
735    /// What is validated and what is answered is the **live prefix**
736    /// `[0, length)`. A store is as long as the last growth made it, and ADR
737    /// 0052's "finishing reuses the store" is what happens to the rest: the
738    /// store is relabelled from [`crate::Program::bytes_layout`] to
739    /// [`crate::Program::str_layout`] with the *logical* length, and the words
740    /// between the two lengths become a free block the next sweep folds back
741    /// in. Nothing is copied, which is the same O(1) transition
742    /// `Vector.freeze()` already makes for elements.
743    ///
744    /// The owner is then emptied — length zero, store null — exactly as
745    /// `Vector.freeze()` empties a vector, because finishing *consumes*. That
746    /// the consumed buffer has no second live holder is
747    /// [`cove_sema`](../../../crates/cove-sema/src/unique.rs)'s conservative
748    /// local uniqueness proof and not something this machine can answer; what
749    /// the machine keeps is the liveness check, so a buffer used after a finish
750    /// is refused rather than read as an empty one.
751    ///
752    /// [`crate::Shape::Bytes`] cannot cross a Cove call and neither can the
753    /// owner cross the Host boundary, but the owner *can* cross a call, which
754    /// is the whole reason it is a value rather than a raw run: the formatter's
755    /// `fn emit(node: Tree, var out: StringBuilder)` needs to pass a partly
756    /// built string down a recursion.
757    FinishBuffer { dst: Slot, buffer: Slot },
758    /// `dst = <obj's header length>`: an element count, or a string's bytes.
759    Len { dst: Slot, obj: Slot },
760    /// `dst = <the [`LayoutId`] in obj's header>`, as an `Int`.
761    ///
762    /// The other half of the header word [`Inst::Len`] reads, and it is here
763    /// for the same reason: *what an object is* is a question the object
764    /// answers at run time, from its own header, and a `Ref` slot carries no
765    /// layout of its own.
766    ///
767    /// It exists because a dispatch has to ask it. A `dyn Trait` value's
768    /// implementation is decided by the type behind it, and nothing static
769    /// says which that is; the object's header does. Reading it into a slot
770    /// turns "which implementation" into an ordinary [`Inst::Switch`] over a
771    /// table the lowering builds from the trait's declared conformances,
772    /// which is why there is no dispatch instruction — one general question
773    /// about an object, answered with the control flow that is already here.
774    LayoutOf { dst: Slot, obj: Slot },
775
776    // ---- places ----------------------------------------------------------
777    /// `dst = &frame[slot]`
778    ///
779    /// A place is one word. There is no place object, no place stack and no
780    /// table of places; a `var` parameter is an ordinary slot whose
781    /// [`Repr`](crate::Repr) is [`Addr`](crate::Repr::Addr).
782    AddrOfSlot { dst: Slot, slot: Slot },
783    /// `dst = &<payload word `at` of `obj`>`
784    ///
785    /// The lowering keeps `obj` in a live reference slot for exactly the
786    /// address's live range, and clears that slot with [`Inst::Clear`] when
787    /// the address dies — not unconditionally for the rest of the frame,
788    /// which would retain the object across everything a long-running body
789    /// does afterwards. The collector therefore needs no interior-pointer
790    /// logic, and the heap does not move, so the address stays correct
791    /// across a collection for as long as it is live and no longer.
792    AddrOfField { dst: Slot, obj: Slot, at: u32 },
793    /// `dst = &obj[index]`, at a stride of `layout`'s width.
794    AddrOfElem {
795        dst: Slot,
796        obj: Slot,
797        index: Slot,
798        layout: LayoutId,
799    },
800    /// `dst = addr + at`, a static word offset into the value at `addr`.
801    ///
802    /// The one place instruction whose operand is itself a place, and what
803    /// makes a place composable. A place is the address of the *first* word
804    /// of a value location, so without this a `var` parameter could only name
805    /// the whole of what it was given: `p.y = 1` through a `var p: Point` had
806    /// to load both words, write one and store both back — observationally
807    /// the same on one thread, but not what the address was for — and
808    /// `f(var p.y)` could not be lowered at all, because there was no way to
809    /// form the address to pass.
810    ///
811    /// `at` is a word offset within the value the address names, computed by
812    /// the lowering from the layout the checker settled. It is the same
813    /// arithmetic a field of an inline struct is, done to an address instead
814    /// of to a slot number, and the answer is again the address of the first
815    /// word of a value location — so it goes back through [`Inst::Load`],
816    /// [`Inst::Store`] or another of these with no second rule about what an
817    /// address is.
818    ///
819    /// Nothing checks `at` against the value's extent, because a frame does
820    /// not record one: what an address names is a fact about the instruction
821    /// that formed it, and [`mod@crate::verify`] says the same of
822    /// [`Inst::Switch`]'s operand for the same reason.
823    AddrOfPart { dst: Slot, addr: Slot, at: u32 },
824    /// `dst = *addr`, for the words `layout` describes.
825    Load {
826        dst: Slot,
827        addr: Slot,
828        layout: LayoutId,
829    },
830    /// `*addr = src`, for the words `layout` describes.
831    ///
832    /// A nested write through a `var` parameter updates the destination words
833    /// in place. There is nothing between the address and the words, which is
834    /// what a place being an address of the *first word* of a value location
835    /// buys.
836    Store {
837        addr: Slot,
838        src: Slot,
839        layout: LayoutId,
840    },
841
842    // ---- erasure ----------------------------------------------------------
843    /// `dst = <a box holding the words of `src`, tagged `layout`>`
844    ///
845    /// What a value becomes when its static type is not known: `dyn Trait`,
846    /// a Host result a schema declared `Any`, an expression the checker
847    /// declined to type. One word in the slot either way.
848    Box {
849        dst: Slot,
850        src: Slot,
851        layout: LayoutId,
852    },
853    /// `dst = <the value inside the box in `src`>`, trapping if its tag is
854    /// not `layout`.
855    Unbox {
856        dst: Slot,
857        src: Slot,
858        layout: LayoutId,
859    },
860
861    // ---- tasks -------------------------------------------------------------
862    /// `dst = <a new task scope, open>`
863    ///
864    /// `scope name { ... }` binds one of these, and everything the Language
865    /// Card says about a scope is a fact about the two instructions that
866    /// leave it rather than about this one: *concurrent work belongs to a
867    /// task scope, and leaving the scope waits for or cancels its child
868    /// tasks.*
869    ///
870    /// `name` is what the source bound it to. It is carried because a
871    /// diagnostic quotes it — *task 2 of scope `requests`* — and by the time
872    /// a scope is a word there is nothing else left that knows.
873    ScopeEnter { dst: Slot, name: StrId },
874    /// Leave the scope in `scope` the way the body reached its end: wait for
875    /// every child, and say whether one of them failed in a way the
876    /// enclosing function has to pass on.
877    ///
878    /// `failed` is a `Bool`. When it is true, `error` holds the `Err`
879    /// payload of the first child whose value was one, at `layout` — and the
880    /// lowering wraps it in the enclosing function's own `Err` and returns
881    /// it, which is exactly what `?` would have done. A child that *raised*
882    /// is not that: a runtime error is not a value, so this instruction
883    /// fails with it and the two ways a child can end stay two things.
884    ///
885    /// A discriminated outcome rather than an instruction carrying control
886    /// flow, because where the failure goes is a fact about the function the
887    /// scope was written in — which `Err` to build, and what to return — and
888    /// the lowering is what holds those.
889    ScopeLeave {
890        scope: Slot,
891        failed: Slot,
892        error: Slot,
893        layout: LayoutId,
894    },
895    /// Cancel every child of the scope in `scope` and wait for it to stop.
896    ///
897    /// What an *early* exit from a scope's body reaches: a `return`, a `?`,
898    /// a `break` or a `continue` that leaves it. Leaving a scope waits for
899    /// or cancels its children whichever way it is left, so this is an
900    /// obligation on every exit path exactly as [`Inst::Clear`] is, and the
901    /// lowering emits one per open scope the jump leaves.
902    ///
903    /// It answers nothing. A scope being left early is already leaving with
904    /// something to say, and a child's failure discovered on the way out
905    /// would replace it with an unrelated one.
906    ScopeCancel { scope: Slot },
907    /// `dst = scope.spawn(closure)`, on a thread of its own.
908    ///
909    /// `answer` is the layout of the value the body produces, and it is here
910    /// because the answer needs somewhere to be *before* the thread exists:
911    /// the machine allocates an object of that width and records its address
912    /// in the scope's table, so the answer is an object in the run's one heap
913    /// and a root of this task from the moment it can hold anything. Handing
914    /// the words back through the thread instead would leave them in no
915    /// store the collector walks for as long as the join took.
916    ///
917    /// This returns once the thread exists and orders nothing else. ADR
918    /// 0008's amendment is explicit that whether the child has run an
919    /// instruction by the time the next one here does is the operating
920    /// system's answer.
921    Spawn {
922        dst: Slot,
923        scope: Slot,
924        closure: Slot,
925        answer: LayoutId,
926    },
927    /// `dst = await task`, for the words `answer` describes.
928    ///
929    /// Waits for the task's thread and answers the value its body produced.
930    /// A body runs at most once and is waited for at most once, so awaiting
931    /// the same handle twice answers the same value and repeats no effect.
932    Await {
933        dst: Slot,
934        task: Slot,
935        answer: LayoutId,
936    },
937    /// `task.cancel()`: ask the task to stop at its next safepoint.
938    ///
939    /// Asking is all it does. Whether the task stopped or had already
940    /// finished is known only where something waits for it, which is why
941    /// `TaskCancelled` is traced at the join and not here.
942    Cancel { task: Slot },
943    /// `dst = <a task already settled with the words at `src`>`.
944    ///
945    /// What a **call to an `async fn`** answers. The body ran at the call
946    /// site, on this task's stack, as [`Inst::Call`]; this is the handle the
947    /// call hands back, and there is no thread anywhere in it.
948    ///
949    /// That is the oracle's reading rather than an invention here.
950    /// `Interpreter::call_target` runs the body and wraps what it produced in
951    /// `crate::task::Task::settled`, and `crate::task::Task::settled`'s own
952    /// documentation says why: ADR 0008 gives a thread to `spawn`, which is
953    /// where the language says concurrency begins, so nothing may depend on
954    /// *when* an `async fn` body ran — only on the value an `await` produces.
955    /// A call that is never awaited has still run.
956    ///
957    /// So this task belongs to no scope and nothing joins it. It is
958    /// `position` zero of `crate::task::describe`, which is the case that
959    /// spelling exists for: *this task*, with no place in a spawn order to
960    /// name. The words are copied into an object of the same shape a
961    /// spawned task's answer goes into, because an `await` reads the two the
962    /// same way and a second arrangement would be a second thing to get
963    /// right.
964    Settled {
965        dst: Slot,
966        src: Slot,
967        answer: LayoutId,
968    },
969
970    // ---- cells ---------------------------------------------------------------
971    /// Take the [`crate::Shape::Shared`] cell in `cell`, waiting for whoever
972    /// holds it.
973    ///
974    /// ADR 0008 makes `lock` the whole of a `Shared`'s access: there is no
975    /// `get` and no `set`, so a read-modify-write cannot be written as two
976    /// operations that race. What that means here is an ordinary
977    /// [`Inst::CallClosure`] between this and [`Inst::SharedUnlock`], with the
978    /// address of the cell's value as the closure's argument — the same shape
979    /// `map` is lowered to, and for the same reason `docs/LINEAR_VM.md` gives:
980    /// **a builtin never calls back into Cove**. A builtin that ran the
981    /// closure itself would put a Rust frame under every Cove frame it made.
982    ///
983    /// So `lock` is *two* instructions rather than one that calls, and what
984    /// the second one costs is an obligation: **the release belongs to every
985    /// exit path**, exactly as [`Inst::Clear`] and [`Inst::ScopeCancel`] do.
986    /// The lowering emits it on the path that finished, and a runtime error —
987    /// which is not a jump the lowering can emit — is the machine's to answer,
988    /// once, for every cell the task was holding.
989    ///
990    /// A task that asks for a cell it already holds is refused rather than
991    /// made to wait, and that rule is untouched by
992    /// [ADR 0037](../../../docs/adr/0037-a-cycle-through-a-cell-is-an-ordinary-cycle.md):
993    /// waiting would be waiting for itself, and no collector can answer a live
994    /// lock state. What the ADR did remove is the *other* refusal — a closure
995    /// that leaves the cell holding a handle to itself is an ordinary
996    /// object-graph cycle now, collected when it becomes unreachable, so
997    /// nothing here inspects what the closure left.
998    SharedLock { cell: Slot },
999    /// Give the cell in `cell` back, publishing everything written while it
1000    /// was held.
1001    ///
1002    /// The lock word *is* the publication: it is taken with `Acquire` and
1003    /// released with `Release`, and every other word of the machine's memory
1004    /// is relaxed and is allowed to be. Acquiring a cell therefore makes
1005    /// visible not only its own words but every object the previous holder
1006    /// allocated and stored into them.
1007    SharedUnlock { cell: Slot },
1008
1009    // ---- failure ----------------------------------------------------------
1010    /// Fail the run with `message`.
1011    ///
1012    /// This is what an exhausted `match` and a failed `Unbox` reach. It is
1013    /// not a refusal to run the program: the program ran, and this is what
1014    /// it did.
1015    Trap { message: StrId },
1016
1017    /// Record that an assertion failed here, carrying the `String` in
1018    /// `message`.
1019    ///
1020    /// The one instruction that writes nothing a program can read. An
1021    /// assertion is lowered rather than performed — see this crate's
1022    /// `lower::assertions` — so by the time the failing arm runs, the
1023    /// `Err(Error("assertion failed: ..."))` is an ordinary value and the
1024    /// only thing left that the machine knows and the value does not is
1025    /// *where it was written*. A test runner points at the assertion the way
1026    /// every other error points at source, and this is how it is told.
1027    ///
1028    /// The span is the instruction's own, which is the assertion call's, so
1029    /// nothing has to be threaded through the program to carry it. The
1030    /// message is a slot rather than a [`StrId`] because `assertEqual`
1031    /// renders the two values it compared and that string is built at run
1032    /// time; a runner compares it against the `Err` it is holding, so that a
1033    /// later unrelated failure is not reported at this assertion.
1034    AssertFailed { message: Slot },
1035}