Skip to main content

Interpreter

Struct Interpreter 

Source
pub struct Interpreter<'a> {
    pub program: &'a Program,
    pub sources: &'a SourceMap,
    pub hosts: &'a HostRegistry,
    /* private fields */
}
Expand description

Executes a resolved program.

One interpreter runs one body on one thread: the entry, or the body of a spawned task. Everything shared with the rest of the run is reached through the Runtime it borrows, which is what a spawn hands to the thread it starts.

§Ownership of the run’s crate::budget::Budget

The Budget is owned by the HostRegistry this interpreter borrows, not by Interpreter itself: a host installs it once with HostRegistry::set_budget, and every task thread charges that one budget at its own safepoints, through a crate::budget::Meter taken from it where the run begins. ADR 0008 draws a task’s fuel from the run’s budget, so there is exactly one authoritative count of what the run spent, whichever thread spent it. Call depth is the exception and is counted here, because a task has a stack of its own.

Fields§

§program: &'a Program§sources: &'a SourceMap§hosts: &'a HostRegistry

Implementations§

Source§

impl<'a> Interpreter<'a>

Source

pub fn new(runtime: &'a Runtime) -> Self

An interpreter for the entry of runtime’s run.

The run’s budget is bound here, which is the one lock this takes and the last one a safepoint of this interpreter will be behind. It is sound to bind it this early because a budget cannot be installed once an interpreter exists: HostRegistry::set_budget needs &mut HostRegistry and this borrows the registry shared for 'a. The one other way a budget is installed is HostRegistry::begin_run, which is reached only through Interpreter::invoke_within and its siblings, each of which rebinds.

Source

pub fn heap_stats(&self) -> HeapStats

What this run’s heaps have done so far: allocation, collections, live heap, peak live heap, and total pause.

The counters come from every heap retired so far, folded into the Runtime as each task’s thread ended. The live figures come from this interpreter’s own heap, which at the end of a run is the only one left: every task’s heap went with its thread, and summing what those last measured would report memory that no longer exists.

Source

pub fn allocate_vector(&mut self, elements: Vec<Value>) -> Value

Allocates growable vector storage in this task’s heap.

Every Vector a Cove program can reach is created here, which is what makes the heap’s table of objects complete.

Source

pub fn collect(&mut self) -> Collection

Marks and sweeps this task’s heap, and records what it did.

The interpreter calls this at safepoints; a host may call it directly to observe the heap at a chosen moment.

Source

pub fn assertion_failure(&self) -> Option<(Span, &str)>

Where the most recent failed assertion was written, together with the message it produced, or None when no assertion has failed.

A caller compares the message against the error it is reporting: an assertion that failed and was then handled inside the program is not the reason a later error was returned.

Source

pub fn run_entry( &mut self, module: &str, name: &str, args: Vec<Rc<str>>, ) -> Result<Value, RuntimeError>

Calls the host-selected entry function, and records how the run came out.

args are the process arguments; they are passed as an Array<String> when the entry declares a parameter for them.

Every path a command takes into a Cove program passes through here — cove run, cove test, cove generate, cove replay, and a cove build binary — because a command has strings to hand over and nothing else. A host that has a value instead calls Interpreter::invoke, which is the same run with a different way in.

It wraps Interpreter::enter rather than living inside it so that a run that never reached its entry — one that named a function this package does not declare, say — still ends with an event saying so.

§Run this on a thread with at least STACK_SIZE bytes

The interpreter is a recursive tree walker, so a Cove program spends native stack as it nests calls, and MAX_CALL_DEPTH stops it before that stack runs out. What “before” means depends on how much stack there is. The runtime sizes every thread it creates itself, so a spawned task and everything the toolchain runs are covered; a thread an embedder created is the one it cannot size, and on it the limit is only as good as the stack underneath.

So an embedder calls this from inside on_cove_stack, building the interpreter there too. A Value is Rc-based and cannot cross a thread boundary in either direction, so the whole run happens inside the closure and only what the embedder wants to keep comes back:

let failure: Option<String> = cove_runtime::on_cove_stack(|| {
    Interpreter::new(&runtime)
        .run_entry("app", "main", Vec::new())
        .err()
        .map(|error| error.message)
})
.map_err(|e| format!("no thread to run Cove on: {e}"))?;

An embedder that would rather manage the thread itself gives it .stack_size(cove_runtime::STACK_SIZE) and builds the interpreter inside it, which is the same arrangement by hand.

On a smaller stack than that, a deep enough Cove program ends the process with a stack overflow instead of returning the depth limit as an error. That is a boundary of what this runtime can promise rather than a bug in it: the size of a thread somebody else created is not something the interpreter can read or change.

Source

pub fn invoke( &mut self, module: &str, name: &str, args: Vec<Value>, ) -> Result<Value, RuntimeError>

Calls module.name with the arguments args, and records how the run came out.

This is the other public way into a Cove program, and the one Interpreter::run_entry is not: an entry takes the process arguments, which are strings, so run_entry is how a command speaks to a program and this is how an application does. A rule engine’s evaluate(pr: PullRequest) -> Decision is invoked here with a Value the host built, and answers the Decision the host reads — which the entry’s result already allowed, so this is the way in catching up with the way out. See issue #150.

Vm::invoke takes the same three things and answers the same way, exactly as the two run_entrys do.

§What holds the arguments to anything

Everything the checker settled, and nothing else. Before the first instruction runs:

  • the declaration must be one a host can call at all — no type parameter, no var parameter, no variadic one;
  • args must be exactly as long as the declared parameter list, a parameter with a default included;
  • each value must be one its declared type admits, followed as deeply as the type goes, with a nominal type checked by the name the value carries.

A capability is not checked here, because an invocation grants nothing: what the called function may reach is what the HostRegistry it runs against was granted, exactly as for any other run.

Every one of those refusals is a RuntimeError carrying the rule it broke, the span of the parameter it was about, and the signature the checker resolved.

§Run this on a thread with at least STACK_SIZE bytes

For the reason Interpreter::run_entry gives, and in the same way.

Source

pub fn invoke_within( &mut self, budget: Budget, module: &str, name: &str, args: Vec<Value>, ) -> Result<Value, RuntimeError>

The same call, bounded by budget and by nothing else.

§What a budget belongs to

A Budget used to belong to the HostRegistry: set_budget needs &mut HostRegistry, a backend holds the registry by shared reference for as long as it exists, and so every limit it carried — fuel, the deadline, max_host_calls, max_tasks — was spent over the whole life of the backend. For a cove run that is exactly right, because a run is one invocation and [run.<name>]’s limits bound it. For an embedding it is not: compile-once/invoke-many is the point, an application wants to bound one request, and the only way to get that was to build a registry, a Runtime and a backend per request — which is 168 allocations of table-building against a request’s own 237, and is the thing compiling once was for not doing.

A budget belongs to an invocation. It still lives on the registry, because ADR 0008 draws a spawned task’s fuel from the run’s budget and a task thread reaches the budget through the Arc<Runtime> it carries; a task’s charges are still the invocation’s. What this changes is when it is put there and how long it stands: budget is installed as this call is entered, bounds everything the invocation and its tasks do, and is left behind afterwards holding what the invocation spent — the same state a finished cove run leaves and reads its --stats out of. The next invoke_within replaces it.

The deadline runs from here, not from wherever budget was built. A budget built to bound an invocation that has not begun would otherwise spend it waiting for its turn. Every count starts at zero for the same reason. A Cancellation is the one thing not reset: a caller that wants to stop this invocation from another thread builds the budget with Budget::with_cancellation and keeps the handle, and a flag already raised stays raised.

§Why this takes &mut self and there is no way to install a budget

that does not

ADR 0024 states each way a run can be stopped as a bound that holds over the run, in that backend’s own fuel. A budget that could be replaced while the run it bounds was executing would make every one of those bounds a claim about something that had changed underneath it, and the ADR’s argument would have to be revisited to say what a bound even meant. So the registry has no public way to install one: this and its three siblings are the only doors, each takes &mut self on the backend, and a backend running an invocation is mutably borrowed for its whole duration. The shape is what forbids it rather than a rule in a comment.

Everything Interpreter::invoke says about what holds the arguments holds here unchanged, and so does the refusal: the argument check runs before the budget is installed, so a call refused for a wrong argument spends none of it.

Source

pub fn run_entry_within( &mut self, budget: Budget, module: &str, name: &str, args: Vec<Rc<str>>, ) -> Result<Value, RuntimeError>

Interpreter::run_entry, bounded by budget and by nothing else.

The command-shaped way in, bounded the way Interpreter::invoke_within bounds the application-shaped one, and that method’s documentation is the description of both.

Trait Implementations§

Source§

impl Callable for Interpreter<'_>

Source§

fn call_value( &mut self, callee: &Value, args: &mut Vec<Value>, span: Span, ) -> Result<Value, RuntimeError>

The caller’s vector is drained rather than consumed, so that a higher-order builtin can hand the same one down for every element it walks. See crate::builtins::Callable::call_value for why, and builtins::walk_with for what pays for it.

One vector is still built per call here, because a slot carries a label and a span beside its value and the interpreter binds parameters out of that shape. It is one of the several this backend builds per call — an Env, the parameter names, the label assignment — rather than the only one, unlike the linear-memory backend, whose calling convention needs no vector at all: an argument already lives in the slot a frame reserved for it before the call began.

Source§

fn allocate_vector(&mut self, elements: Vec<Value>) -> Value

Allocates growable vector storage in the running task’s heap. Read more
Source§

fn snapshot(&mut self, value: &Value, span: Span) -> Result<Value, RuntimeError>

The independent copy Snapshot makes of one value. Read more
Source§

fn arity(&self, callee: &Value) -> Option<usize>

The number of parameters callee declares, when it is a closure.

Auto Trait Implementations§

§

impl<'a> !Freeze for Interpreter<'a>

§

impl<'a> !RefUnwindSafe for Interpreter<'a>

§

impl<'a> !Send for Interpreter<'a>

§

impl<'a> !Sync for Interpreter<'a>

§

impl<'a> !UnwindSafe for Interpreter<'a>

§

impl<'a> Unpin for Interpreter<'a>

§

impl<'a> UnsafeUnpin for Interpreter<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.