Skip to main content

cove_ir/
program.rs

1//! A lowered program, and the tables an instruction indexes into.
2//!
3//! A [`Program`] is immutable once lowered. ADR 0008 runs a spawned task on
4//! a thread of its own and a task's body is a lowered function like any
5//! other, so every thread of one run reads this same program rather than a
6//! copy of it — which is why the strings in it are `Arc<str>` and why
7//! nothing here is behind a cell.
8
9use std::collections::BTreeMap;
10use std::sync::Arc;
11
12use cove_diag::Span;
13
14use crate::inst::{Inst, Pc, Slot};
15use crate::layout::{Layout, LayoutId};
16use crate::repr::{RefMap, Repr};
17
18macro_rules! id {
19    ($(#[$doc:meta])* $name:ident, $prefix:literal) => {
20        $(#[$doc])*
21        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
22        pub struct $name(pub u32);
23
24        impl $name {
25            /// The index this id names.
26            pub fn index(self) -> usize {
27                self.0 as usize
28            }
29        }
30
31        impl std::fmt::Display for $name {
32            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33                write!(f, concat!($prefix, "{}"), self.0)
34            }
35        }
36    };
37}
38
39id!(
40    /// Names a [`Function`] in [`Program::functions`].
41    ///
42    /// # Scope and stability
43    ///
44    /// A `FunctionId` is dense — it is a position in one [`Program`]'s
45    /// `functions`, not a name — and it means nothing outside that one
46    /// linked program. It is not stable across an edit to the package that
47    /// produced it: declarations are numbered first and lambdas and generic
48    /// instantiations are appended after them, so adding, removing or moving
49    /// an earlier one renumbers every later one. It is not a stable external
50    /// identity either, in the way a qualified name is — two builds of the
51    /// same source are not guaranteed to number a function the same way, and
52    /// nothing here promises they will.
53    ///
54    /// Nothing may persist a bare `FunctionId` across an artifact boundary,
55    /// because there is no third thing to check it against once it is on
56    /// the far side of one. Every boundary Cove has today avoids the
57    /// question instead of answering it: `cove build` embeds the checked
58    /// source in the generated crate and lowers a fresh [`Program`] when
59    /// that crate is compiled, rather than serialising this one's; the
60    /// trace and replay format keys an entry point by `module` and
61    /// `function` **strings** ([`Program::function_named`] resolves the
62    /// pair against whatever program is current, back into a `FunctionId`
63    /// of *that* run) and never writes the id itself; and the wasm
64    /// playground's boundary is rendered text — [`crate::print`]'s
65    /// listing — not the program that produced it. No id crosses a boundary
66    /// today because nothing here has ever needed one to.
67    FunctionId, "fn"
68);
69id!(
70    /// Names a string in [`Program::strings`].
71    StrId, "str"
72);
73id!(
74    /// Names an argument list in [`Program::args`].
75    ///
76    /// A call's arguments are a static list of [`Arg`]s, held once in the
77    /// program rather than inline in the instruction, so that [`Inst`] stays
78    /// small enough to be worth copying and a repeated call shape costs one
79    /// list rather than one per site.
80    ArgsId, "args"
81);
82id!(
83    /// Names a jump table in [`Program::tables`].
84    TableId, "table"
85);
86id!(
87    /// Names one case of an enum layout: its position in
88    /// [`crate::layout::Shape::Enum::cases`].
89    ///
90    /// It is the number an enum's discriminant word holds, and it is a type
91    /// of its own for the reason [`crate::Repr::Tag`] is: the word is an
92    /// integer and the value is not one. Where the number is written into a
93    /// slot — [`crate::Inst::Tag`] — the id says which case it names, and the
94    /// verifier bounds it against the layout rather than against nothing.
95    CaseId, "case"
96);
97id!(
98    /// Names a host operation in [`Program::host_ops`].
99    HostOpId, "host"
100);
101id!(
102    /// Names a builtin in [`Program::builtins`].
103    BuiltinId, "builtin"
104);
105
106/// One argument of a call: where the value is, and what it is.
107///
108/// A slot alone says where an operand *begins* and never how wide it is. A
109/// scalar is described by the `Repr` of the slot it sits in and a reference
110/// by the header of the object it names, but an inline struct or enum is a
111/// run of words with nothing attached to it at all — a `Point` in a frame is
112/// described by neither. So a callee that is polymorphic over the values it
113/// is handed had no way to read one: `"{Point(x: 1)}"` rendered the first
114/// word, `a == b` on two structs compared the first word, and the operations
115/// that put a whole value into a collection refused rather than store half of
116/// one.
117///
118/// Carrying the layout beside the slot answers all of them at once, and it is
119/// carried for *every* argument rather than for the calls that turned out to
120/// need it. A layout is what an argument is; which callee reads it is not the
121/// argument's business, and one rule the verifier checks everywhere is worth
122/// more than the word this costs at the sites that could have done without.
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
124pub struct Arg {
125    /// The first slot of the value location in the caller's frame.
126    pub slot: Slot,
127    /// The layout of that location, which is what says how wide it is.
128    pub layout: LayoutId,
129}
130
131/// One host operation a program calls: `console.log`, `files.read`,
132/// `files.Writer.writeLine`.
133#[derive(Clone, Debug, PartialEq, Eq)]
134pub struct HostOp {
135    pub module: Arc<str>,
136    pub operation: Arc<str>,
137    /// The resource kind the operation belongs to, for one addressed to a
138    /// handle rather than to the module: `Writer` in
139    /// `files.Writer.writeLine`.
140    ///
141    /// It is what [`Inst::CallResource`] names and what
142    /// [`Inst::CallHost`] does not, so one table holds both and the two
143    /// namings cannot collide: a module's `files.write` and a resource's
144    /// `files.Writer.write` are two entries rather than one.
145    ///
146    /// Nothing dispatches on it. Which resource an operation reaches is the
147    /// business of the handle the receiver names — ADR 0013 gives the host
148    /// the only record of what is open — and this is what the call site
149    /// settled, kept for the disassembly and for a diagnostic that has to
150    /// say what was being called.
151    pub resource: Option<Arc<str>>,
152    /// The layout of the value location the host's answer is written into.
153    ///
154    /// A schema that declared its result `Any` gives a boxed layout;
155    /// anything else gives the layout of the declared type.
156    pub result: LayoutId,
157}
158
159impl HostOp {
160    /// The operation as the source writes it: `console.println`, or
161    /// `files.Writer.writeLine` for one addressed to a resource.
162    pub fn qualified(&self) -> String {
163        match &self.resource {
164            Some(kind) => format!("{}.{kind}.{}", self.module, self.operation),
165            None => format!("{}.{}", self.module, self.operation),
166        }
167    }
168}
169
170/// One builtin a program calls: `Array.length`, `String.split`, `Int.abs`.
171///
172/// A builtin is named rather than numbered because the set of them is the
173/// language reference's, not the IR's: adding one is a runtime change, and
174/// the IR should not have to be renumbered for it.
175#[derive(Clone, Debug, PartialEq, Eq)]
176pub struct Builtin {
177    /// The type the operation belongs to: `Array`, `String`, `Map`, `Int`.
178    pub receiver: Arc<str>,
179    pub operation: Arc<str>,
180    pub result: LayoutId,
181}
182
183/// Where a [`Inst::Switch`] goes.
184#[derive(Clone, Debug, PartialEq, Eq)]
185pub struct Table {
186    /// One target per case index, in order.
187    pub targets: Vec<Pc>,
188    /// Where an index outside `targets` goes.
189    ///
190    /// A `match` the checker proved exhaustive still has one, because the
191    /// value being switched on came out of a heap object and the machine
192    /// does not take the lowering's word for what is in it.
193    pub default: Pc,
194}
195
196/// A capture a closure body reads.
197#[derive(Clone, Debug, PartialEq, Eq)]
198pub struct Capture {
199    pub name: Arc<str>,
200    /// The first slot of the closure frame's value location for it.
201    ///
202    /// Captures follow the parameters, each taking the words its layout
203    /// says. It is written down rather than derived because the machine
204    /// should not have to re-add a run of widths it can read.
205    pub slot: Slot,
206    pub layout: LayoutId,
207}
208
209/// One named binding, and the range of the function's code over which that
210/// name denotes that slot.
211///
212/// A side table, and read for the same reason [`Function::spans`] is: a name
213/// is wanted when a *human* asks what a frame holds — a debugger stopped at a
214/// breakpoint, issue #241 — and never in the dispatch loop, so it belongs
215/// beside the code rather than in it.
216///
217/// It exists because neither half of that question is answerable from the
218/// frame. [`Function::reprs`] says what a slot's *word* holds, for the whole
219/// function, and that is all it says: until this table the only name anywhere
220/// in a lowered program was [`Capture::name`], parameters were positional and
221/// locals were anonymous. So a debugger could say `s7:int = 3`, which is true
222/// of the machine and can be a lie about the program.
223///
224/// # Two locals may share a slot, and that is the point
225///
226/// [`Function::reprs`]' own note says a slot may be reused by a later value
227/// of the same `Repr`, because the lowering hands a dead run to the next
228/// value that asks for that shape. One slot is therefore several source
229/// variables over a function's life, and nothing but this table can tell them
230/// apart. Two locals of one slot have *disjoint* ranges and, usually,
231/// different names.
232///
233/// # Two locals may share a name
234///
235/// Shadowing is recorded, not resolved. `let x = 1; let x = "two"` is two
236/// bindings and both are kept, because the first is still what the frame
237/// holds at every pc before the second — and because resolving here would
238/// make the table disagree with the lowering, whose scope is searched
239/// backwards so that the latest declaration wins. Their ranges may overlap
240/// and their slots differ. A reader keeps the locals whose range contains the
241/// pc and **takes the last match**; [`Function::local_at`] is that rule
242/// written down.
243///
244/// A `break` or a `continue` is not an end of a range. `[from, to)` is an
245/// interval of program counters, every pc inside a scope's body is one the
246/// binding is live at, and the pc a `break` jumps to is outside the interval
247/// already.
248#[derive(Clone, Debug, PartialEq, Eq)]
249pub struct Local {
250    pub name: Arc<str>,
251    pub slot: Slot,
252    pub layout: LayoutId,
253    /// The first pc at which the name is bound.
254    pub from: Pc,
255    /// One past the last. `[from, to)` is a half-open interval, like a
256    /// [`Span`].
257    pub to: Pc,
258}
259
260/// A body that was written elsewhere and expanded into this one.
261///
262/// `[from, to)` is the run of this function's program counters the expansion
263/// occupies — from where the call stood to where its answer landed —
264/// `callee` is whose instructions they are, and `site` is where the call was
265/// written.
266///
267/// `site` is the whole of what an error chain lost. `Machine::call_chain`
268/// walks the live frames and reads each one's call site; an expansion has no
269/// frame, so its call site was not there to read, and an error raised inside
270/// one named where it happened and not where it was called from. One span per
271/// expansion is what puts that back.
272#[derive(Clone, Debug, PartialEq, Eq)]
273pub struct Inlined {
274    pub from: Pc,
275    pub to: Pc,
276    pub callee: FunctionId,
277    pub site: Span,
278    /// The names the expanded body bound, in this function's slots and this
279    /// function's counters.
280    ///
281    /// Here rather than in [`Function::locals`], and that is not tidiness. A
282    /// caller's binding and an expanded body's parameter can be bound at the
283    /// *same* program counter — the caller's `let raised = n + 1` and the
284    /// callee's `n`, when the argument needed no copy — and a reader sorting
285    /// one table by "which expansion contains this counter" cannot tell them
286    /// apart. Which body declared a name is not something a counter answers,
287    /// so it is recorded rather than derived.
288    pub locals: Vec<Local>,
289}
290
291/// One lowered function.
292#[derive(Clone, Debug)]
293pub struct Function {
294    /// The module and name the source declared, for diagnostics and for
295    /// [`Program::function_named`].
296    pub module: Arc<str>,
297    pub name: Arc<str>,
298    /// The layout of each parameter, in declaration order.
299    ///
300    /// Parameters occupy the frame from slot 0 onward, each taking the words
301    /// its layout says: a `(Int, Point, Int)` list occupies slots 0, 1–2 and
302    /// 3. Declaration order, not a permutation into type groups — ADR 0034's
303    /// *"a mixed list such as `(Int, String, Int)` is not permuted into type
304    /// regions"*. There are no type regions to permute into.
305    pub params: Vec<LayoutId>,
306    /// What each slot of the frame holds. `reprs.len()` is the frame size.
307    ///
308    /// A slot's `Repr` is fixed for the whole function; that is what makes
309    /// [`Function::refs`] correct at every program counter. A slot may be
310    /// reused by a later value of the same `Repr`, and a reference slot is
311    /// cleared to null at its last use, so the static map costs no retention
312    /// beyond a value's live range.
313    pub reprs: Vec<Repr>,
314    /// Which slots are references, derived from [`Function::reprs`].
315    pub refs: RefMap,
316    /// The layout of what the function answers.
317    ///
318    /// [`Inst::Return`] names the base slot of the answer in the callee's
319    /// frame and the caller's [`Inst::Call`] names the base slot of the
320    /// destination location in its own; the machine copies this many words
321    /// between them.
322    pub returns: LayoutId,
323    /// The values the enclosing body handed this function, if it is a
324    /// lambda. Empty for a declared function.
325    pub captures: Vec<Capture>,
326    pub code: Vec<Inst>,
327    /// The source span of each instruction, parallel to [`Function::code`].
328    ///
329    /// A parallel array rather than a field of [`Inst`]: a span is read when
330    /// a run fails or a trace is written, and never in the dispatch loop, so
331    /// it should not be in the cache line the loop is reading.
332    pub spans: Vec<Span>,
333    /// What the source called the values in the frame, and where each name
334    /// meant which slot.
335    ///
336    /// In declaration order, which is the order the shadowing rule reads
337    /// them in; see [`Local`]. Not parallel to anything — a function binds as
338    /// many names as it binds — and empty is a legal answer for a body that
339    /// binds none.
340    pub locals: Vec<Local>,
341    /// The bodies this function holds that were written somewhere else.
342    ///
343    /// `lower::inline` expands a call to a small leaf where it is made, and
344    /// the frame that call would have pushed then does not exist. Nothing
345    /// downstream can tell: a run of instructions in the middle of this
346    /// function *is* another function, and every reader that walks frames —
347    /// an error's chain, a backtrace, a profile — sees one frame where there
348    /// were two.
349    ///
350    /// So the expansion writes down what it removed. This is that record, and
351    /// it is [`Local`]'s shape for [`Local`]'s reason: a slot number is not an
352    /// answer to "what did the source call this", and a program counter is not
353    /// an answer to "whose instruction is this".
354    ///
355    /// In the order the expansions were made, which is program-counter order,
356    /// and ranges nest rather than overlap. A reader takes the *last* range
357    /// that contains the pc, which is the innermost body — the same rule
358    /// [`Function::local_at`] follows, for the same reason.
359    pub inlined: Vec<Inlined>,
360    /// Where the declaration itself is, for a diagnostic that is about the
361    /// function rather than about one of its instructions.
362    pub span: Span,
363    /// Whether the body is a task's: `async fn`, or the lambda a `spawn`
364    /// was handed.
365    pub is_async: bool,
366    /// Whether this is a stand-in the lowering left for a declaration it
367    /// did not lower a body for. See `lower::stub`, and `Function::is_stub`.
368    pub stub: bool,
369}
370
371impl Function {
372    /// How many words a call to this function occupies on the stack.
373    pub fn frame_size(&self) -> u32 {
374        self.reprs.len() as u32
375    }
376
377    /// How many parameters the function declares.
378    pub fn arity(&self) -> u32 {
379        self.params.len() as u32
380    }
381
382    /// The first slot of parameter `at`, which is the widths of the ones
383    /// before it.
384    pub fn param_slot(&self, at: usize, layouts: &[Layout]) -> Slot {
385        self.params[..at]
386            .iter()
387            .map(|id| layouts[id.index()].width())
388            .sum()
389    }
390
391    /// How many slots the parameters occupy in total.
392    pub fn param_words(&self, layouts: &[Layout]) -> u32 {
393        self.params
394            .iter()
395            .map(|id| layouts[id.index()].width())
396            .sum()
397    }
398
399    /// What slot `slot` holds.
400    pub fn repr(&self, slot: Slot) -> Option<Repr> {
401        self.reprs.get(slot as usize).copied()
402    }
403
404    /// The span of the instruction at `pc`, or the declaration's own.
405    pub fn span_at(&self, pc: usize) -> Span {
406        self.spans.get(pc).copied().unwrap_or(self.span)
407    }
408
409    /// Which slot `name` denotes at `pc`, if the source bound it there.
410    ///
411    /// The last match wins, because a shadowing declaration is recorded
412    /// beside the one it shadows rather than in place of it: see [`Local`].
413    pub fn local_at(&self, name: &str, pc: Pc) -> Option<&Local> {
414        self.locals
415            .iter()
416            .rev()
417            .find(|local| &*local.name == name && local.from <= pc && pc < local.to)
418    }
419
420    /// The expanded bodies `pc` is inside, innermost last.
421    ///
422    /// A reader that wants one frame's worth of context wants the last of
423    /// them; a reader rebuilding a chain wants all of them, innermost first,
424    /// which is this reversed. Ranges nest, so "contains the pc" and "in the
425    /// order they were made" is enough to order them: an inner expansion is
426    /// always written after the outer one it sits in.
427    pub fn inlined_at(&self, pc: Pc) -> impl Iterator<Item = &Inlined> + '_ {
428        self.inlined
429            .iter()
430            .filter(move |held| held.from <= pc && pc < held.to)
431    }
432
433    /// `module.name`, as a diagnostic writes it.
434    pub fn qualified(&self) -> String {
435        format!("{}.{}", self.module, self.name)
436    }
437
438    /// Whether this is a stand-in rather than a lowered body.
439    ///
440    /// A stub has no body to stop at: its one instruction is a `Return`
441    /// written at the declaration's own span, and it has no parameters and
442    /// no names, because there was no boundary and no scope to bind them
443    /// from. A tool that resolves a source location or a breakpoint against
444    /// a program — a debugger walking [`Function::locals`], a stack trace
445    /// reading [`Function::span_at`] — has to skip a stub rather than answer
446    /// out of it, or it answers a question about a function that was never
447    /// written.
448    ///
449    /// It answers `true` for all three kinds `lower::stub`'s doc comment
450    /// describes, because `stub` is the one place any of them is built and
451    /// this reads back exactly what it recorded. But a program that actually
452    /// runs — the output of [`lower_roots`](crate::lower_roots) or
453    /// [`lower_entry`](crate::lower_entry) once a lowering finishes without
454    /// error — can only hold two of the three: the declaration a slice left
455    /// out, and a generic declaration whose instantiations carry the real
456    /// code beside it. The third kind, a declaration this lowering reported
457    /// a gap about, belongs to a lowering that never got handed back — a gap
458    /// is an error, so the program it would have been part of does not exist
459    /// for a caller of this method to ask about.
460    ///
461    /// This is a stored fact rather than a test of the four fields above,
462    /// because a shape a stub happens to have is not a shape only a stub
463    /// has. `lower::stub`'s own construction is the only place that knows
464    /// *why* the instruction, span, and empty lists are what they are;
465    /// asking a shape test to recover that intent at a distance means the
466    /// day a real body of one instruction is ever written at its
467    /// declaration's own span, the test is wrong and nothing says so.
468    /// Recording the fact the lowering already has costs one field;
469    /// re-deriving it costs a convention two crates now have to keep in
470    /// sync by hand.
471    pub fn is_stub(&self) -> bool {
472        self.stub
473    }
474}
475
476/// A whole lowered package.
477#[derive(Clone, Debug, Default)]
478pub struct Program {
479    pub functions: Vec<Function>,
480    pub layouts: Vec<Layout>,
481    pub strings: Vec<Arc<str>>,
482    pub args: Vec<Vec<Arg>>,
483    pub tables: Vec<Table>,
484    pub host_ops: Vec<HostOp>,
485    pub builtins: Vec<Builtin>,
486    /// The layout every string object shares.
487    ///
488    /// One field rather than a layout in each [`Inst::Str`], because every
489    /// string in a program has the same shape and the machine should not
490    /// have to be told it once per literal. A program that mentions no
491    /// string still declares it: the machine allocates one for a host's
492    /// answer, and a table it has to check for emptiness first is a branch
493    /// on a path that always takes the same side.
494    pub str_layout: LayoutId,
495    /// The layout every byte run under construction shares.
496    ///
497    /// A program-wide constant for the reason [`Program::str_layout`] is one:
498    /// [`Inst::AllocBytes`] should not have to be told this layout per call
499    /// site, and [ADR 0051](../../docs/adr/0051-a-string-is-built-as-a-byte-run.md)
500    /// gives every run the same [`crate::layout::Shape::Bytes`] shape whatever
501    /// string it will become.
502    pub bytes_layout: LayoutId,
503    /// The layout every byte buffer's owner shares.
504    ///
505    /// A program-wide constant for [`Program::bytes_layout`]'s reason, and the
506    /// other half of the pair: an owner and its store are allocated together
507    /// by [`Inst::AllocBuffer`], so neither layout is named at a call site.
508    /// [ADR 0052](../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)
509    /// gives every buffer the same [`crate::layout::Shape::ByteBuffer`] shape
510    /// whatever bytes it will hold, because an owner's two words are a length
511    /// and a reference whatever the store's capacity.
512    pub buffer_layout: LayoutId,
513    /// The layout every [`Inst::Box`] allocates its object as.
514    ///
515    /// A program-wide constant for the same reason [`Program::str_layout`]
516    /// is one: every box has the same *object* shape, and what differs — the
517    /// layout of the value inside it — is in the box's first payload word.
518    /// The machine should not have to search a table for a shape that is
519    /// always the same, and a search that fails has to answer something.
520    pub boxed_layout: LayoutId,
521    /// `module.name` to id, for an entry point named on a command line.
522    pub by_name: BTreeMap<(Arc<str>, Arc<str>), FunctionId>,
523}
524
525impl Program {
526    pub fn function(&self, id: FunctionId) -> &Function {
527        &self.functions[id.index()]
528    }
529
530    pub fn layout(&self, id: LayoutId) -> &Layout {
531        &self.layouts[id.index()]
532    }
533
534    pub fn string(&self, id: StrId) -> &Arc<str> {
535        &self.strings[id.index()]
536    }
537
538    pub fn arg_list(&self, id: ArgsId) -> &[Arg] {
539        &self.args[id.index()]
540    }
541
542    pub fn table(&self, id: TableId) -> &Table {
543        &self.tables[id.index()]
544    }
545
546    pub fn host_op(&self, id: HostOpId) -> &HostOp {
547        &self.host_ops[id.index()]
548    }
549
550    pub fn builtin(&self, id: BuiltinId) -> &Builtin {
551        &self.builtins[id.index()]
552    }
553
554    /// The id of `module.name`, if the program has it.
555    pub fn function_named(&self, module: &str, name: &str) -> Option<FunctionId> {
556        self.by_name
557            .iter()
558            .find(|((m, n), _)| &**m == module && &**n == name)
559            .map(|(_, id)| *id)
560    }
561}