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
impl HostRegistry
pub fn new(grants: Grants) -> Self
pub fn register(&mut self, module: Box<dyn HostApi>)
pub fn grants(&self) -> &Grants
Sourcepub fn set_grant_source(&mut self, source: GrantSource)
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.
pub fn contains(&self, name: &str) -> bool
Sourcepub fn set_trace(&mut self, sink: Arc<dyn TraceSink>)
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.
Sourcepub fn set_budget(&mut self, budget: Budget)
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.
Sourcepub fn with_budget<R>(&self, f: impl FnOnce(&Budget) -> R) -> Option<R>
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.
Sourcepub fn budget_meter(&self) -> Option<Meter>
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.
Sourcepub fn irreversible_writes(&self) -> u64
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.
Sourcepub fn module_schemas(&self) -> Vec<ModuleSchema>
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.
Sourcepub fn module_for_operation(&self, op: &str) -> Option<&'static str>
pub fn module_for_operation(&self, op: &str) -> Option<&'static str>
Looks up which host module exposes op, for unqualified use imports.
Sourcepub fn schema_for(
&self,
module: &str,
op: &str,
) -> Option<&'static OperationSchema>
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.
Sourcepub fn host_type(&self, module: &str, name: &str) -> Option<TypeSchema>
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.
Sourcepub fn result_is_task_safe(&self, module: &str, op: &str) -> Option<bool>
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.”
Sourcepub fn call(
&self,
module: &str,
op: &str,
args: Vec<Value>,
) -> Result<Value, RuntimeError>
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.
Sourcepub fn call_with(
&self,
module: &str,
op: &str,
args: Vec<Value>,
back: &mut dyn Reentry,
) -> Result<Value, RuntimeError>
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.
Sourcepub fn call_resource(
&self,
handle: &ResourceHandle,
op: &str,
args: Vec<Value>,
back: &mut dyn Reentry,
) -> Result<Value, RuntimeError>
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.