Skip to main content

HostRegistry

Struct HostRegistry 

Source
pub struct HostRegistry { /* private fields */ }
Expand description

Holds every host module available to a run, and the grants that gate them.

HostRegistry::call is the single choke point through which Cove code reaches external authority, so it is also the right place to observe that authority being exercised: an optional Budget charges every call against the run’s host-call limit before dispatch, and an optional TraceSink records every call, granted or denied, with how long it took.

Implementations§

Source§

impl HostRegistry

Source

pub fn new(grants: Grants) -> Self

Source

pub fn register(&mut self, module: Box<dyn HostApi>)

Source

pub fn grants(&self) -> &Grants

Source

pub fn set_grant_source(&mut self, source: GrantSource)

Records where these grants came from, which is what a refused call tells the reader to change.

Source

pub fn contains(&self, name: &str) -> bool

Source

pub fn set_trace(&mut self, sink: Arc<dyn TraceSink>)

Installs where this registry’s trace events go. Replaces any sink installed earlier; the default is NullSink, which discards everything.

This is the Host API boundary’s own sink, and it carries exactly one event: TraceEvent::HostCall. Everything else a run traces — task lifecycle, a heap’s summary, and the entry’s own TraceEvent::EntryEnter, TraceEvent::EntryExit and TraceEvent::RunEnded — goes through Runtime::with_trace instead, which has a NullSink of its own to install into. An embedding that installs only this one and only measures host calls will not notice; one that expects a full tape from this alone gets an empty one for everything but HostCall, with no error to say so.

Source

pub fn set_budget(&mut self, budget: Budget)

Installs the budget every call is charged against. Replaces any budget installed earlier; the default is no budget, which imposes no host-call limit here (the interpreter’s own safepoints still apply its other limits).

This arranges a registry before anything runs, and what it installs is spent over every run the registry serves. That is exactly right for a cove run, which is one run, and it is what [run.<name>]’s limits come through. It is not what an embedding that invokes one compiled program many times wants, because there the limits of every request would add up over the life of the process: Vm::invoke_within and its three siblings are how a single invocation is bounded instead.

Source

pub fn with_budget<R>(&self, f: impl FnOnce(&Budget) -> R) -> Option<R>

Runs f against the run’s budget, if the host installed one.

This is how a caller reads the counters after a run, how a host call and a spawn are charged, and how a budget is looked at by anything that has no Meter of its own. Every thread of a run reaches the one budget through here, so the lock is held for the charge and nothing else.

It is not how a safepoint charges. That used to be exactly what this was for, and issue #182 measured the mutex at 36% of benches/call against the predecessor’s execute at 46%, because every call and every return is a safepoint. HostRegistry::budget_meter is what a backend takes once per run instead, and Meter is where the argument for it is.

Source

pub fn budget_meter(&self) -> Option<Meter>

The run’s budget in the form a safepoint charges it, or None if the host installed none.

None means no budget at all, which is what an embedder that installed none has, and what it has always meant here: no limit.

This takes the lock once, and it is the last time a run touches it on a per-instruction path: a Meter charges the same accounting over atomics. A backend takes one where a run begins — see Meter for why that is the only place it may be taken — and both of them do.

Source

pub fn irreversible_writes(&self) -> u64

How many calls this run dispatched whose schema declares them Effect::IrreversibleWrite.

This is what reads the effect an operation declares. Cove makes irreversible operations require visible intent, so a run is able to say how many of the things it did cannot be taken back; cove run --stats prints the count. Whether each call actually reached the outside world is the host’s business rather than the registry’s, so a call the host answered with Err is still counted: the registry knows only that a program asked for something irreversible.

Source

pub fn module_schemas(&self) -> Vec<ModuleSchema>

The table every registered module declares itself with.

This is the pairing the checker needs. An embedding registers its hosts here and hands these same values to cove_sema::Compiler, so the program is checked against the descriptions this registry is about to enforce rather than against a second set written out beside them:

let program = Compiler::new()
    .with_host_schemas(hosts.module_schemas())
    .compile(&package)?;

A registry that has two modules registered under one name still dispatches a call to only one of them: every lookup below that finds a module by name — contains, schema_for, host_type, call, call_with, call_resource, module_for_operation — takes the first one registered. So this keeps only the first schema registered under each name too, rather than handing the checker a second description of a module the runtime will never reach through: the list it hands back describes exactly what this registry dispatches to, not everything that was ever registered.

Source

pub fn module_for_operation(&self, op: &str) -> Option<&'static str>

Looks up which host module exposes op, for unqualified use imports.

Source

pub fn schema_for( &self, module: &str, op: &str, ) -> Option<&'static OperationSchema>

The schema of one operation, if the module and the operation both exist.

Source

pub fn host_type(&self, module: &str, name: &str) -> Option<TypeSchema>

The type module.name declares, if the module declares one.

A TypeSchema is Copy, so this hands back the entry itself rather than a borrow of the registry: the interpreter that asks is about to evaluate arguments, which it cannot do while holding one.

Source

pub fn result_is_task_safe(&self, module: &str, op: &str) -> Option<bool>

Whether the value module.op produces may cross a task boundary, or None when no such operation exists.

The Language Card puts this decision in the schema rather than in the value: “Host resources declare task-safety in their Host API schema.”

Source

pub fn call( &self, module: &str, op: &str, args: Vec<Value>, ) -> Result<Value, RuntimeError>

Dispatches a Host API call after checking the grant, the schema, and the budget, tracing the outcome either way.

This is the boundary’s one choke point, and it takes no interpreter: an operation that was handed a Cove callback cannot be reached through it. HostRegistry::call_with is the same dispatch with a way back into the program.

Source

pub fn call_with( &self, module: &str, op: &str, args: Vec<Value>, back: &mut dyn Reentry, ) -> Result<Value, RuntimeError>

Dispatches a Host API call that may run a Cove callback it was given.

Source

pub fn call_resource( &self, handle: &ResourceHandle, op: &str, args: Vec<Value>, back: &mut dyn Reentry, ) -> Result<Value, RuntimeError>

Dispatches an operation on a resource handle, through the same gate every other Host API call passes.

A handle is a name, so nothing here trusts it: the module it names has to exist, the resource kind has to be one that module declares, and the operation has to be one that kind answers. A handle that outlived what it named fails inside the host instead, which is where the only record of what is still open lives.

Auto Trait Implementations§

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.