cove_runtime/vm/mod.rs
1//! The execution backend [ADR 0034](../../../../docs/adr/0034-one-physical-word-stack.md)
2//! decided on, and since the cutover, the only one: this is the production
3//! path an embedder runs a Cove program on.
4//!
5//! [`docs/LINEAR_VM.md`](../../../../docs/LINEAR_VM.md) is the design. It was
6//! written as a clean-room replacement rather than a renovation: nothing here
7//! was derived from the backend it replaced, and at the cutover that backend
8//! was deleted. `lvm` and `cove-lir` were transitional spellings, worn while
9//! the predecessor still held these names; it is gone, and this module and
10//! `cove-ir` have taken them.
11//!
12//! Two things leave this module: [`Vm`], the type an embedder holds, and
13//! [`exec::SAFEPOINT_STRIDE`], a number a test asserts a bound against.
14//! Nothing else does, because what a caller can name is the whole of what
15//! this boundary decides. A word, a layout, a [`mem::Memory`] and a
16//! [`exec::Machine`] are the representation, and a representation that leaves
17//! the crate is one that cannot be changed without changing somebody else's
18//! code.
19//!
20//! # What a run owns, and what each of its tasks owns
21//!
22//! Issue #240's Q1 answers this and [`docs/LINEAR_VM.md`](../../../../docs/LINEAR_VM.md)'s
23//! "Ownership" section writes it out. Five things, and which of them is a
24//! value store is the whole of what ADR 0034 cares about:
25//!
26//! | | belongs to | where it is | a value store? |
27//! |---|---|---|---|
28//! | the object heap | the **run** | [`mem::Space`], one per run, behind an `Arc` | **yes**, and the only one |
29//! | a stack segment | a **task** | `[k * SEGMENT_WORDS, (k+1) * SEGMENT_WORDS)` of the same address space | yes, and it is part of the same one |
30//! | a `Shared` cell | the **run**'s heap | an ordinary object; its lock is one of its words ([`cell`]) | no — it *is* an object in the heap |
31//! | a host resource | the **host** | a table of names, shared by every task | no — see ADR 0031 |
32//! | a `Task`, a `TaskScope` | the **scheduler** | a table of control state, one per task | no |
33//!
34//! The last row is the one that has to be argued rather than asserted, because
35//! a `Task` is a value a Cove program writes down. Its word is **one past an
36//! index into a scheduler table**, the way a `Repr::Host` word is, and the
37//! entry it names holds a task id, a scope name and position, a
38//! `Cancellation`, and the *address* of the heap object holding its answer.
39//! Every one of those but the last is scheduler bookkeeping that no Cove value
40//! could be hidden in. The last is an address, which makes the table a **root
41//! provider** and not a second store: the answer's words are in the run's
42//! heap, in an object the spawning task allocated before the thread existed,
43//! and the table names one the way `Machine::literal_addrs` names a literal's
44//! address: an index into the run's own metadata, not a second store.
45//! Nothing that wanted to dodge a heap representation could be put there,
46//! which is the test ADR 0034 actually applies.
47//!
48//! # The scheduler's table is a *task's*, and the host's is the *run's*
49//!
50//! The two look alike — a word one past an index into a table of names — and
51//! they are owned differently, for a reason each states about itself rather
52//! than by analogy.
53//!
54//! A `Task` and a `TaskScope` may not cross a task boundary; the task-safety
55//! rule says so, and `cove_sema`'s `task_safe_offender` is where it is
56//! enforced. So a word formed in one task is only ever read in that task, the
57//! tables are disjoint by construction, and there is nothing to share. That is
58//! the same arithmetic that keeps two stack segments apart, and it is why
59//! [`exec::Machine`] holds its own.
60//!
61//! A **host resource** does cross: ADR 0013 gives the host the record of what
62//! is open, a resource declares its own task-safety in its schema, and a
63//! task-safe one is copied into a spawned closure like any other value — as
64//! its word. A table of one task's own would make that word an index into a
65//! list the receiving task does not have. So the resource table is the run's,
66//! behind a lock, and one resource is one word for the length of the run,
67//! which is what ADR 0013's *"two handles are equal when they name the same
68//! resource"* costs once there is more than one thread.
69//!
70//! There used to be a single `#![allow(dead_code)]` here, covering every
71//! submodule beneath it. Its own comment was honest about what it was *for*
72//! — several items below are reached only from their own `#[cfg(test)]`
73//! code, and one line in one place meant removing it was a single edit whose
74//! failure would list exactly what was still unused. What it did not say was
75//! that it covered far more than those items, because a module-wide allow
76//! does not distinguish "reached only by a test" from "reached by nothing at
77//! all": [ADR 0043](../../../../docs/adr/0043-a-method-moves-if-it-is-total-and-takes-no-closure.md)'s
78//! third migration condition is checked by deleting a builtin's dispatch arm
79//! and asking clippy whether the implementation it leaves behind is now
80//! unreachable, and inside this module clippy could never answer, allow or
81//! no. It was removed for that reason (issue #274).
82//!
83//! What replaces it is one `#[cfg_attr(not(test), allow(dead_code))]` per
84//! item that is genuinely reached only from this crate's own tests, each
85//! with a comment saying so beside it. That form says under `cargo test`
86//! exactly what the broad allow said all the time — nothing, because the
87//! item is used — and only turns the lint off for the build that has no
88//! caller, which is the build the check above runs against. A handful of
89//! items had no caller at all, not even a test; those were deleted rather
90//! than annotated, which is what removing the broad allow was for.
91
92use std::rc::Rc;
93
94use cove_diag::Span;
95use cove_ir::{Function, FunctionId, Program};
96
97use crate::budget::{Budget, Limits, Meter};
98use crate::error::RuntimeError;
99use crate::host::HostRegistry;
100use crate::runtime::Runtime;
101use crate::trace::{RunOutcome, Timing, TraceEvent};
102use crate::vm::debug::Debugger;
103use crate::vm::exec::Machine;
104// The public `Value` reaches this file for the one reason ADR 0034 allows it
105// to reach any of them: this is a boundary. An entry's arguments and its
106// answer are what a host hands in and reads back, and they are `Value`s on
107// both sides of that line. Nothing here stores one — every value named below
108// is on its way into [`boundary::from_value`] or out of
109// [`boundary::to_value`].
110use crate::value::Value;
111
112pub(crate) mod boundary;
113pub(crate) mod builtins;
114pub(crate) mod cell;
115pub(crate) mod debug;
116#[cfg(test)]
117mod differential;
118#[cfg(test)]
119mod erasure;
120pub(crate) mod exec;
121pub(crate) mod mem;
122pub mod profile;
123pub(crate) mod render;
124
125/// The words a run's heap region may grow to, for every [`Vm`] [`Vm::new`]
126/// builds. [`Vm::with_heap_words`] is the one way to build a run over a
127/// different budget, and its own doc comment says who that is for.
128///
129/// Four mebiwords, thirty-two mebibytes. Reserved is not committed: the
130/// backing store grows on demand, so a program that allocates nothing pays
131/// nothing, and what the number buys is a run that fails with "this run has no
132/// memory left" rather than taking the machine down with it. Like
133/// [`mem::STACK_WORDS`] it is an implementation choice and not a language
134/// fact.
135const DEFAULT_HEAP_WORDS: usize = 1 << 22;
136
137/// One run of a lowered program.
138///
139/// This is the type above the machine: it holds the program, the memory the
140/// run executes over, and the two things that make a run a run rather than a
141/// dispatch loop — the boundary a `Value` crosses, and the accounting a
142/// safepoint charges. The dispatch loop underneath knows nothing about any
143/// of them.
144///
145/// The two ways in are the two the language has, and they are the same two
146/// [`crate::interp::Interpreter`] offers. [`Vm::run_entry`] is how a
147/// *command* speaks to a program: the arguments are process arguments, which
148/// are strings. [`Vm::invoke`] is how an *application* does: the arguments
149/// are values the host built, held to the types the checker resolved before
150/// the first instruction runs. Everything below the two is one path.
151pub struct Vm<'a> {
152 runtime: &'a Runtime,
153 hosts: &'a HostRegistry,
154 program: &'a Program,
155 machine: Machine<'a>,
156 /// The run's accounting, in the handle a safepoint charges through.
157 ///
158 /// Taken once, where the run begins, for the reason [`Meter`] gives. A
159 /// registry with no budget installed answers `None`, which has always
160 /// meant no limit; a meter over default [`Limits`] is that, written down.
161 budget: Meter,
162}
163
164impl<'a> Vm<'a> {
165 /// A run of `program`, over `runtime`'s checked program and `hosts`.
166 ///
167 /// `program` is **encoded and verified here**, once, into the fixed-width
168 /// form [ADR 0041](../../../../docs/adr/0041-a-slot-number-fits-in-sixteen-bits.md)
169 /// decides and issue #245's Phase 5 made the only one a run executes.
170 /// There is no second representation to choose and no flag that selects
171 /// one: `Inst` is what the lowering produced and what a listing and the
172 /// debugger show, and what runs is sixteen bytes per instruction whose
173 /// operands were checked before the first of them ran.
174 ///
175 /// This stays infallible, and what that costs is stated where it is
176 /// paid. A program with no encoding — one whose frame is wider than a
177 /// sixteen-bit slot names, which `cove_ir::lower` already refuses with a
178 /// diagnostic — is refused by [`Vm::run_entry`] and [`Vm::invoke`]
179 /// before a frame is pushed, rather than by this constructor. The
180 /// alternative was a `Result` at every call site for a failure the
181 /// compiler in front of it has already made impossible.
182 ///
183 /// The heap budget is this module's `DEFAULT_HEAP_WORDS`. [`Vm::with_heap_words`]
184 /// is the constructor for a caller that needs a different one.
185 pub fn new(runtime: &'a Runtime, hosts: &'a HostRegistry, program: &'a Program) -> Vm<'a> {
186 Vm::with_heap_words(runtime, hosts, program, DEFAULT_HEAP_WORDS)
187 }
188
189 /// The same run, over a heap that may grow only to `heap_words` words
190 /// rather than `DEFAULT_HEAP_WORDS`.
191 ///
192 /// This is deliberately not a [`Limits`] field. [ADR 0011](../../../../docs/adr/0011-garbage-collection.md)'s
193 /// amendment retracted `Limits::max_memory` because a number that bounds
194 /// only what one collector's table can see is not a memory ceiling; it
195 /// is that instrument's readout wearing a ceiling's name. Nothing about
196 /// the linear-memory backend changes that argument for an *embedder*: its
197 /// heap is a fuller account of a run's Cove-owned values than the old
198 /// per-task heap ever was, per ADR 0034, but a Host's own allocations,
199 /// open resources and each task's stack region still sit outside it, so
200 /// naming `heap_words` beside `fuel` and `max_host_calls` would still
201 /// promise a bound this number cannot back.
202 ///
203 /// What this constructor is for is what `mem::STACK_WORDS` already is
204 /// — an implementation choice a test may need to name to provoke the
205 /// behaviour it bounds, not a knob an embedder is invited to reach for.
206 /// Prefer [`Vm::new`] unless the caller is deliberately forcing a small
207 /// heap so a collection has something to be tested against.
208 pub fn with_heap_words(
209 runtime: &'a Runtime,
210 hosts: &'a HostRegistry,
211 program: &'a Program,
212 heap_words: usize,
213 ) -> Vm<'a> {
214 Vm {
215 runtime,
216 hosts,
217 program,
218 machine: Machine::for_run(program, heap_words, Some(hosts), Some(runtime)),
219 budget: meter_of(hosts),
220 }
221 }
222
223 /// The same run, watched by `debugger`.
224 ///
225 /// A second constructor rather than a parameter on [`Vm::new`], for the
226 /// reason the heap budget is not one either: no existing caller has a
227 /// debugger to name, and a parameter every caller passes `None` to is a
228 /// question every caller is asked and none of them answers.
229 ///
230 /// What it costs the run is stated where it is paid, in
231 /// the debugger's own module: the machine asks before **every** instruction
232 /// for as long as the debugger is installed, so a debugged run is slower
233 /// by whatever the debugger does per instruction. A run built with
234 /// [`Vm::new`] is unchanged — the loop's comparison is the same one it
235 /// was, against the next safepoint.
236 pub fn debugged(
237 runtime: &'a Runtime,
238 hosts: &'a HostRegistry,
239 program: &'a Program,
240 debugger: &'a dyn Debugger,
241 ) -> Vm<'a> {
242 let mut vm = Vm::new(runtime, hosts, program);
243 vm.machine.watch(Some(debugger));
244 vm
245 }
246
247 /// Runs `module.name` with the process arguments `args`.
248 ///
249 /// An entry takes either no parameters or one `Array<String>`, and that
250 /// rule is the language's rather than a backend's — the oracle refuses
251 /// the third shape in these words, at this span.
252 pub fn run_entry(
253 &mut self,
254 module: &str,
255 name: &str,
256 args: Vec<Rc<str>>,
257 ) -> Result<Value, RuntimeError> {
258 let outcome = self.enter(module, name, args);
259 self.ended(outcome)
260 }
261
262 /// Calls `module.name` with values the host built.
263 ///
264 /// The arguments are held to what the checker resolved about the
265 /// declaration — the shape it has to be callable at all, the count, and
266 /// each value's type followed as deeply as the type goes — before
267 /// anything runs. That check is the crate's own `invoke`, shared with the
268 /// oracle so that a host that gets it wrong reads one answer and not one
269 /// per backend.
270 ///
271 /// One refusal belongs to this backend rather than to the language, and
272 /// it is about the *lowering* rather than about the program.
273 /// [`cove_ir::lower_entry`] lowers what one entry can reach and nothing
274 /// else, so a run built for one entry cannot invoke a function no path
275 /// from that entry leads to. Saying the package does not declare it would
276 /// be false and would send an embedder to the wrong file, so this says
277 /// which of the two is missing and what to lower instead.
278 pub fn invoke(
279 &mut self,
280 module: &str,
281 name: &str,
282 args: Vec<Value>,
283 ) -> Result<Value, RuntimeError> {
284 let outcome = self.invoke_checked(module, name, args);
285 self.ended(outcome)
286 }
287
288 /// [`Vm::run_entry`], bounded by `budget` and by nothing else.
289 ///
290 /// The command-shaped way in, bounded the way [`Vm::invoke_within`]
291 /// bounds the application-shaped one. Issue #152 is why both exist: an
292 /// application that runs somebody else's Cove wants the *request*
293 /// bounded, not the session, and a session is built once and invoked
294 /// many times.
295 pub fn run_entry_within(
296 &mut self,
297 budget: Budget,
298 module: &str,
299 name: &str,
300 args: Vec<Rc<str>>,
301 ) -> Result<Value, RuntimeError> {
302 self.hosts.begin_run(budget);
303 self.bind_budget();
304 let outcome = self.enter(module, name, args);
305 self.ended(outcome)
306 }
307
308 /// [`Vm::invoke`], bounded by `budget` and by nothing else.
309 ///
310 /// The check runs before the budget is installed, so a call refused for a
311 /// wrong argument spends none of the budget it was handed and leaves
312 /// whatever bounded this backend where it was.
313 pub fn invoke_within(
314 &mut self,
315 budget: Budget,
316 module: &str,
317 name: &str,
318 args: Vec<Value>,
319 ) -> Result<Value, RuntimeError> {
320 let outcome = self.checked_within(budget, module, name, args);
321 self.ended(outcome)
322 }
323
324 /// The check, the budget, and then the call.
325 fn checked_within(
326 &mut self,
327 budget: Budget,
328 module: &str,
329 name: &str,
330 args: Vec<Value>,
331 ) -> Result<Value, RuntimeError> {
332 crate::invoke::check(self.runtime.program(), module, name, &args)?;
333 self.hosts.begin_run(budget);
334 self.bind_budget();
335 let id = self.lowered(module, name)?;
336 self.enter_with(module, name, id, args)
337 }
338
339 /// Re-reads the meter after the registry was given a new budget.
340 ///
341 /// The handle is taken once where a run begins, so installing a budget
342 /// for one invocation has to be followed by taking the handle again;
343 /// otherwise the safepoints would go on charging the budget the session
344 /// was built over.
345 fn bind_budget(&mut self) {
346 self.budget = meter_of(self.hosts);
347 }
348
349 /// How many instructions this run has executed.
350 pub fn instructions(&self) -> u64 {
351 self.machine.instructions()
352 }
353
354 /// Words the heap region occupies, free blocks included.
355 pub fn heap_words(&self) -> u64 {
356 self.machine.heap_words()
357 }
358
359 /// Words handed out over the whole run, reuse counted each time.
360 pub fn allocated_words(&self) -> u64 {
361 self.machine.allocated_words()
362 }
363
364 /// How many collections this run's heap has done.
365 ///
366 /// [`Vm::live_words`] is `None` exactly when this is `0`: a heap that has
367 /// never collected has nothing that measured what is live.
368 pub fn collections(&self) -> u64 {
369 self.machine.collected().collections
370 }
371
372 /// Words the most recent collection found alive, or `None` if the heap
373 /// has never collected.
374 ///
375 /// [Issue #248](https://github.com/myuon/cove/issues/248) is why this
376 /// exists as its own accessor rather than only inside the trace's
377 /// `heap_summary` event: `Runtime::heap_stats` is filled in only by the
378 /// tree-walking backend (see its doc comment), so a `Vm` embedder asking
379 /// "does this run still hold what an early invocation allocated" has
380 /// nothing else public to read. `heap_words` and `allocated_words`
381 /// answer capacity and a monotonic total; this is the one that answers
382 /// what is live right now, as of the last sweep.
383 pub fn live_words(&self) -> Option<u64> {
384 let collected = self.machine.collected();
385 (collected.collections > 0).then_some(collected.live_words)
386 }
387
388 /// Where the most recent failed assertion was written, together with the
389 /// message it produced, or `None` when no assertion has failed.
390 ///
391 /// The same answer [`crate::interp::Interpreter::assertion_failure`]
392 /// gives, and it is here for the same caller: a test runner points at
393 /// the assertion the way every other error points at source. An
394 /// assertion that failed and was then handled inside the program is
395 /// still recorded, which is why the message is part of the answer — a
396 /// caller reports at this span only when the failure it is holding is
397 /// this one.
398 pub fn assertion_failure(&self) -> Option<(Span, &str)> {
399 self.machine.assertion_failure()
400 }
401
402 /// The process arguments as the one value an entry may take them as.
403 fn enter(
404 &mut self,
405 module: &str,
406 name: &str,
407 args: Vec<Rc<str>>,
408 ) -> Result<Value, RuntimeError> {
409 let id = self.lookup(module, name)?;
410 let function = self.program.function(id);
411 let arguments = match function.arity() {
412 0 => Vec::new(),
413 1 => vec![Value::array(args.into_iter().map(Value::string))],
414 other => {
415 return Err(RuntimeError::new(format!(
416 "entry `{module}.{name}` declares {other} parameters"
417 ))
418 .at(function.span)
419 .with_rule(
420 "An entry function takes either no parameters or one `Array<String>` of process arguments.",
421 )
422 .with_help(format!(
423 "write `fn {name}()` or `fn {name}(args: Array<String>)`"
424 )));
425 }
426 };
427 self.enter_with(module, name, id, arguments)
428 }
429
430 /// The check, and then the call.
431 fn invoke_checked(
432 &mut self,
433 module: &str,
434 name: &str,
435 args: Vec<Value>,
436 ) -> Result<Value, RuntimeError> {
437 crate::invoke::check(self.runtime.program(), module, name, &args)?;
438 let id = self.lowered(module, name)?;
439 self.enter_with(module, name, id, args)
440 }
441
442 /// The call itself, from the arguments in to the answer out.
443 ///
444 /// The one seam. [`Vm::run_entry`] reaches it having turned the process
445 /// arguments into the array an entry declares, and [`Vm::invoke`]
446 /// reaches it having held a host's own values to what the checker
447 /// resolved; nothing below this line knows which of the two happened.
448 fn enter_with(
449 &mut self,
450 module: &str,
451 name: &str,
452 id: FunctionId,
453 args: Vec<Value>,
454 ) -> Result<Value, RuntimeError> {
455 let function = self.program.function(id);
456 let span = function.span;
457 let returns = function.returns;
458
459 self.runtime.trace(TraceEvent::EntryEnter {
460 module: module.to_string(),
461 function: name.to_string(),
462 });
463 // Started here and not in `run_entry`, because what this measures is
464 // the entry: the argument conversion is the entry's own boundary
465 // crossing and the run is what follows it.
466 let timing = Timing::start();
467 let waited = self.machine.host_wait();
468
469 let outcome = self
470 .words_of(function, &args)
471 .map_err(|e| e.at(span))
472 .and_then(|words| self.machine.run(id, &words, &self.budget))
473 .and_then(|answer| {
474 boundary::to_value(&self.machine, returns, &answer).map_err(|e| e.at(span))
475 });
476
477 // Both events on every path, the way the oracle writes them: an entry
478 // that failed still entered and still left, and a run that failed
479 // still allocated. A trace that recorded the exit only for a run that
480 // answered would be a trace whose shape depended on the answer.
481 self.runtime.trace(TraceEvent::EntryExit {
482 module: module.to_string(),
483 function: name.to_string(),
484 cpu: timing
485 .elapsed()
486 .saturating_sub(self.machine.host_wait().saturating_sub(waited)),
487 wait: self.machine.host_wait().saturating_sub(waited),
488 });
489 self.summarize_heap();
490 outcome
491 }
492
493 /// What this run's memory did, recorded once as the run ends.
494 ///
495 /// The word half of the event and none of the object half. Issue #240
496 /// decided that `heap_summary` does not choose between the two — an
497 /// inline struct is words here and no object at all on the oracle, so
498 /// neither family's figures can be derived from the other's — and the
499 /// rule that follows is that a machine leaves `None` in what it does not
500 /// count rather than a zero that reads as a measurement.
501 ///
502 /// `live_words` is one of those. It is what the last collection found
503 /// alive, so a run that never collected has nothing that measured it, and
504 /// the figure is absent rather than nought. `capacity_words` is not: the
505 /// heap region occupies what it occupies whether anything has been swept
506 /// or not.
507 ///
508 /// There is no pause here, and that is the same rule again. This
509 /// collector does not time itself yet, and a zero would say it stopped
510 /// the world for no time at all.
511 fn summarize_heap(&self) {
512 self.runtime.trace(TraceEvent::HeapSummary {
513 collections: self.collections(),
514 object_count: None,
515 allocated_bytes: None,
516 live_bytes: None,
517 peak_bytes: None,
518 pause: None,
519 allocated_words: Some(self.allocated_words()),
520 capacity_words: Some(self.heap_words()),
521 live_words: self.live_words(),
522 });
523 }
524
525 /// The arguments in word form.
526 ///
527 /// Each conversion allocates and an allocation can collect, so an
528 /// argument already converted is held as a temporary root until the frame
529 /// that will own it exists. The roots are released here rather than after
530 /// the run because nothing between this line and the write of the entry's
531 /// frame allocates: [`Machine::run`] reserves stack words and copies the
532 /// arguments into them, and the frame is a root from that moment on.
533 /// Holding them for the length of the run instead would retain the
534 /// entry's arguments past the point the lowering cleared their slots,
535 /// which is exactly the retention the static reference map was careful
536 /// not to be.
537 fn words_of(&mut self, function: &Function, args: &[Value]) -> Result<Vec<u64>, RuntimeError> {
538 let params = function.params.clone();
539 let mark = self.machine.temps();
540 let mut words = Vec::with_capacity(args.len());
541 let mut failed = None;
542 for (layout, value) in params.iter().zip(args) {
543 // Each argument's own words, in declaration order, because that
544 // is what the callee's frame is: parameters occupy it from slot 0
545 // at their own widths, and a `(Int, Point, Int)` list is four
546 // words rather than three slots.
547 //
548 // `from_value` releases its own temporary roots when it returns,
549 // so every reference among the words is re-taken here and held
550 // until the frame that will own it exists.
551 match boundary::from_value(&mut self.machine, *layout, value) {
552 Ok(written) => {
553 let reprs = self.program.layout(*layout).words.clone();
554 for (repr, word) in reprs.iter().zip(&written) {
555 if repr.is_ref() && *word != 0 {
556 self.machine.push_temp(*word);
557 }
558 }
559 words.extend_from_slice(&written);
560 }
561 Err(error) => {
562 failed = Some(error);
563 break;
564 }
565 }
566 }
567 self.machine.release_temps(mark);
568 match failed {
569 Some(error) => Err(error),
570 None => Ok(words),
571 }
572 }
573
574 fn lookup(&self, module: &str, name: &str) -> Result<FunctionId, RuntimeError> {
575 self.program.function_named(module, name).ok_or_else(|| {
576 RuntimeError::new(format!("this package does not declare `{module}.{name}`"))
577 })
578 }
579
580 /// The same lookup, for a caller that has already established the package
581 /// declares the function.
582 ///
583 /// [`crate::invoke::check`] has passed by the time this runs, so the
584 /// package *does* declare it and the reader should not be told it does
585 /// not. What is missing is the lowering, and the remedy is the caller's.
586 fn lowered(&self, module: &str, name: &str) -> Result<FunctionId, RuntimeError> {
587 self.program.function_named(module, name).ok_or_else(|| {
588 RuntimeError::new(format!(
589 "this run's lowering does not include `{module}.{name}`"
590 ))
591 .with_rule(
592 "A run executes the functions one entry can reach, because that is what `lower_entry` lowers.",
593 )
594 .with_help(format!(
595 "lower it too, by naming `{module}.{name}` as a root, and build the run on that program"
596 ))
597 })
598 }
599
600 /// Writes the run's terminal event, whichever way in produced it.
601 ///
602 /// Every path into a program passes through here, which is what makes
603 /// "every run has one" true rather than a claim about the paths somebody
604 /// remembered. An entry that answers `Err` is the program saying what it
605 /// was written to say: a failure of the program's work and not of the
606 /// run, which is why it is its own outcome rather than one more kind of
607 /// stop.
608 fn ended(&self, outcome: Result<Value, RuntimeError>) -> Result<Value, RuntimeError> {
609 let (classification, message) = match &outcome {
610 Ok(value) if value.is_err() => (
611 RunOutcome::Error,
612 crate::interp::returned_error_message(value),
613 ),
614 Ok(_) => (RunOutcome::Success, None),
615 Err(error) => (error.outcome, Some(error.message.clone())),
616 };
617 self.runtime.trace(TraceEvent::RunEnded {
618 outcome: classification,
619 message,
620 });
621 outcome
622 }
623}
624
625/// The meter a run charges through, over `hosts`'s budget or over none.
626///
627/// A registry with no budget installed answers `None`, which has always meant
628/// no limit; a meter over default [`Limits`] is that, written down.
629fn meter_of(hosts: &HostRegistry) -> Meter {
630 hosts
631 .budget_meter()
632 .unwrap_or_else(|| Budget::new(Limits::default()).meter())
633}