arora-engine 7.0.0

The Arora engine: loads and executes Arora modules (wasm and native hosts).
# Arora Engine

Welcome to the core of the project!

## WebAssembly executors

The Arora engine runs WASM modules — advertised with
[a custom module description format (or schema)](https://github.com/semio-ai/arora-sdk) —
through pluggable [`Executor`](src/executor/mod.rs) implementations, chosen
by the `executor.name` of a module's header. A WebAssembly guest runs
natively, on [`wasmtime`](https://docs.wasmtime.dev/) (the default
`wasmtime-host` feature), and in the browser, on its native `WebAssembly`
runtime, when `arora` itself is compiled to `wasm32-unknown-unknown` (the
JS-facing wrapper is [`arora-web`](https://github.com/semio-ai/arora-sdk)).
It is a **`wasm-component`**: a component of the
  `arora:module` world ([`wit/arora-module.wit`](wit/arora-module.wit)), as
  Rust and C++ build one for `wasm32-wasip2`. It exports `functions` and
  `dispatch`, and may import the world's `host` interface and the WASI 0.2
  interfaces `wasi:cli`, `wasi:io`, `wasi:clocks`, `wasi:random` and
  `wasi:filesystem`, with no directory preopened — no sockets
  ([`component_layout`](src/component_layout.rs)).
  Hosted by [`component::ComponentExecutor`](src/executor/component/mod.rs)
  and [`browser::BrowserComponentExecutor`](src/executor/browser/component.rs),
  built on a [`SharedLoader`](src/executor/browser/mod.rs), which stages a
  component compiled asynchronously. A component that traps runs no more calls
  until it is reloaded.

A core module, as a guest built for `wasm32-wasip1` is, has no executor: its
header names `wasm`, which no engine registers, and a device's module
directory naming it is refused with the rebuild it needs.

A guest that exports `_initialize` — a WASI reactor, which a C or C++ guest
is when linked with `-mexec-model=reactor` — has it run once, when it loads,
before any other export: its component's initializer calls it. It runs the
guest's static constructors.

### Compiling a module once

[`Engine::load_module`](src/engine.rs) compiles the module's executable for
the engine that loads it. A module that several engines load — one per
device, say — compiles once as a [`CompiledModule`](src/compiled.rs), which
each engine loads with `Engine::load_compiled_module`: it instantiates the
compiled code, with an instance (memory, globals) of its own, and compiles
nothing. Natively, every component executor in the process runs on one
wasmtime engine, the one a compiled module belongs to; in the browser, the
`WebAssembly.Module` of the component's guest core module instantiates
anywhere.

## Native Executor

A [`native::NativeExecutor`](src/executor/native.rs) is available and is
capable of loading native dynamic libraries, and call the functions
declared in the module description. Beware of figuring out the right
dynamic library extension for the host platform. Gated behind the
default `native-host` feature; disabled on `wasm32-*` targets.

## Calling Module Functions

Once modules are loaded — either via [`Engine::load_module`](src/engine.rs)
directly, or via the [`load::load_module_from_parts`](src/load.rs)
convenience that takes a parsed `Header` plus the executable bytes and
also returns the module's function IDs —
their functions can be called using engine's
implementation of the [`CallBridge` trait](src/call.rs).
`arora_call` takes a `Call` description holding generic `Value`s as an input,
and returns a `CallResult` holding the return value
and the mutated parameters.
The engine also advertises a function called `arora_dispatch`
in the module's runtime context, so that they can call one another.
It is recommended to use the bindings generated by
[`arora-module-cli`](../arora-module-authoring/cli/readme.md),
rather than calling these functions directly.

The host of the engine (the process loading this library)
can also register native functions using [`arora_register_callable`](src/call.rs).
An `u64` identifier is attributed to each registered function,
so that they can be called later using `arora_call_indirect` from the host side,
or from any module via `arora_dispatch_indirect`.

> [Behavior Trees]https://github.com/semio-ai/arora-sdk typically use this mechanism
> to wrap the various node functions into uniform tick functions.

### Arguments checked against the header

Before a call reaches a guest module, the engine checks its arguments against
the parameters the module's header declares for the function: an argument
naming a parameter the function does not declare, or carrying a value not of
its parameter's type, fails the call with `DispatchError::Argument`
(`CallError::Generic` through `arora_call`), and the guest never reads it. This
holds for every guest, whatever built it, and for calls between guests, which
reach the engine's `dispatch` too. Without it, a C++ guest reads a value of
another type as absent and answers as if it had not been given.

The check reads each argument's type tags where they lie in its buffer and
decodes nothing, so an argument costs the host no memory beyond its buffer.
What passes, in the forms callers send:

- `unit` for any parameter: callers pass it for an argument they leave unset,
  and a guest reads it as absent;
- any value for a parameter of a dynamic value type (`KEY_VALUE_ID`,
  `ARRAY_VALUE_ID`), which a guest takes as a `Value`;
- a present optional argument as an option or as its element;
- an array as its typed array, or as a generic array whose every element is of
  the element type. A Rust guest generated from a `module.yaml` reads a generic
  array of records; a `#[module]` guest refuses a generic array, and a C++
  guest reads one as absent.

Left to the guest: an argument that is not an Arora Buffer or not a structure,
a parameter the call leaves out, and the fields of a record-typed argument,
which is checked by its record's id, the one thing a header names of it.

### Time and memory a guest may spend

On wasmtime, a call to a guest module ends by a deadline, and its instance's
linear memory is capped ([`limits`](src/limits.rs)):

- **Time.** During a device's step, set with `Engine::set_step_budget`, the
  deadline is the step's end, so one step's guest calls cannot together
  outlast it. Outside a step it is `DEFAULT_CALL_BUDGET`, one second. A
  module's own `GuestLimits::call_budget`, set with `Engine::set_guest_limits`
  or for every module with `EngineBuilder::with_guest_limits`, tightens it, or
  sets it outside a step. A guest's call to another guest ends by its
  caller's deadline, and a module called back while its call is under way
  ends by that call's deadline, or its caller's if earlier.
  A call still running at its deadline is interrupted at the first epoch
  tick past it, a millisecond apart, never before: it fails with
  `DispatchError::Trap`, naming the module, the function and the budget. The
  module then refuses its calls until it is reloaded, since the interrupted
  call can have left its state half updated. A caller whose callee ran past
  the caller's deadline is interrupted once the call returns. A call with no
  time left before its deadline is not made, and its module, never entered,
  serves its later calls.
- **Memory.** Each linear memory of a guest's instance holds at most
  `GuestLimits::memory` bytes, 256 MiB by default; a component can hold
  several, each under the cap. A `memory.grow` past it
  returns -1 to the guest, whose allocator then reports the allocation
  failed.

In the browser a guest runs on the JS thread, which nothing interrupts: the
browser executors bound neither a call's time nor, beyond the maximum its
module declares, its memory.

### Call Mechanism

When a call is performed, its description is serialized into a raw buffer,
using the [Arora Buffers library](../arora-buffers/readme.md),
copied into the module's memory
(allocated using `arora_buffer_alloc` that must be exported by the modules),
before the function is called with a pointer to the memory segment.
The module is in charge of deserializing it
and serializing the result into a new buffer,
that the engine will free using `arora_buffer_free`.