110 lines
5.7 KiB
Markdown
110 lines
5.7 KiB
Markdown
|
|
# Rune feasibility: Phase 0
|
||
|
|
|
||
|
|
## Scope and reproduction
|
||
|
|
|
||
|
|
This experiment pins Rune to **0.14.2**. It is not an actor runtime or a
|
||
|
|
production scheduler. Run from the repository root:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
cargo run -p runa
|
||
|
|
mise run pre-commit
|
||
|
|
```
|
||
|
|
|
||
|
|
The binary compiles [feasibility.rn](../examples/feasibility.rn) once and reports
|
||
|
|
the results. Tests live in [runa-rune](../crates/runa-rune/src/lib.rs).
|
||
|
|
Compilation emits source diagnostics; compilation and runtime failures reach
|
||
|
|
the caller and cause the binary to exit unsuccessfully.
|
||
|
|
|
||
|
|
## Verified results
|
||
|
|
|
||
|
|
| Probe | Result |
|
||
|
|
| --- | --- |
|
||
|
|
| Rune function | `add(20, 22)` returns 42 |
|
||
|
|
| Native Rust function | Rune calls `native_double(21)` and returns 42 |
|
||
|
|
| Async suspension | The first poll returns `Pending`; the second returns 42 |
|
||
|
|
| Finite instruction budget | Counting to 100 exceeds 10 instructions; the same execution resumes with a larger budget and returns 100 |
|
||
|
|
| CPU-bound runaway | `loop {}` returns control after exhausting a 100-instruction budget |
|
||
|
|
| Frame inspection | Instruction stepping observes an active nested call frame |
|
||
|
|
| Instruction stepping | Addition completes within 100 calls to `VmExecution::step` |
|
||
|
|
| Compiled-code reuse | Two VMs reference the same `Arc<Unit>` but execute with independent stacks |
|
||
|
|
| Cross-thread reuse | Shared `Unit` and `RuntimeContext` move to a worker which constructs its own VM and returns 3 |
|
||
|
|
| Cancellation | Dropping a pending execution permits subsequent use of its VM |
|
||
|
|
| Failure paths | Invalid Rune source and division by zero produce errors |
|
||
|
|
|
||
|
|
The async native function is deliberately deterministic: it returns `Pending`
|
||
|
|
once, requests a wake, and completes on its next poll. The experiment manually
|
||
|
|
polls it, using a no-op waker; it does not implement an executor or prove
|
||
|
|
integration with real I/O, timers, or a multithreaded async scheduler.
|
||
|
|
|
||
|
|
## Measurements
|
||
|
|
|
||
|
|
One Linux x86-64 debug-build run on 2026-11-01 measured:
|
||
|
|
|
||
|
|
| Measurement | Observed value |
|
||
|
|
| --- | --- |
|
||
|
|
| Context creation, module registration, source compilation, runtime construction | 60.54 ms |
|
||
|
|
| One empty VM construction | 7.71 microseconds |
|
||
|
|
| `size_of::<Vm>()` | 88 bytes |
|
||
|
|
| `size_of::<RuntimeContext>()` | 96 bytes |
|
||
|
|
| `size_of::<Unit>()` | 224 bytes |
|
||
|
|
|
||
|
|
These are observations, not performance guarantees. The sizes are **shallow
|
||
|
|
Rust object sizes**, excluding heap allocations, bytecode, stack capacity,
|
||
|
|
native modules, reference-counted values, and allocator overhead. Total
|
||
|
|
per-actor memory and loaded-context heap cost remain unmeasured; do not use
|
||
|
|
these numbers to claim actor capacity. Timings vary with build mode and host.
|
||
|
|
|
||
|
|
## API findings and limitations
|
||
|
|
|
||
|
|
The implementation was checked against the downloaded 0.14.2 source:
|
||
|
|
|
||
|
|
- `Context`, `Module::function(...).build()`, `Sources`, `Diagnostics`, and
|
||
|
|
`rune::prepare` support embedding and native registration.
|
||
|
|
- `VmExecution::async_complete` supports asynchronous invocation. A suspended
|
||
|
|
invocation remains live; awaiting is not a state commit.
|
||
|
|
- `runtime::budget::with` enforces VM instruction accounting. A finite
|
||
|
|
execution can resume after exhaustion without rewriting its frames.
|
||
|
|
- Budgeting does **not** interrupt blocking native callbacks or impose a
|
||
|
|
wall-clock deadline. Native operations need explicit cancellation and
|
||
|
|
resource limits. No preemptive scheduling or hostile-code sandbox is claimed.
|
||
|
|
- Rune's budget-halt error enums are private in this version. The experiment
|
||
|
|
checks its exact pinned diagnostic, ``Halted for unexpected reason `limited` ``,
|
||
|
|
and rejects any other error. This is a feasibility probe, not a recommended
|
||
|
|
long-term scheduler API; avoid depending on display strings in the kernel.
|
||
|
|
- `VmExecution::step`, `Vm::ip`, `Vm::call_frames`, `Vm::stack`, and
|
||
|
|
`Unit::debug_info` expose building blocks for debugging. Only stepping and
|
||
|
|
frame presence are tested here. Source breakpoints, variable inspection,
|
||
|
|
and source-level stepping are not implemented.
|
||
|
|
- Sharing immutable context and compiled code works with thread-local VMs.
|
||
|
|
The test does not move an active VM, execution, or Rune value between threads.
|
||
|
|
- Dropping an execution cancels this controlled local future. Cancellation of
|
||
|
|
external I/O and rollback of native side effects are not established.
|
||
|
|
|
||
|
|
## Proposed Phase 1 direction
|
||
|
|
|
||
|
|
The [worker-local execution decision](decisions/0001-worker-local-execution.md)
|
||
|
|
now implements this direction using owned asynchronous instruction stepping,
|
||
|
|
typed slice outcomes, and wake-driven scheduling. Follow-up tests cover budgets
|
||
|
|
before/after a genuinely pending operation, wake/resume, cancellation cleanup,
|
||
|
|
and pure-Rune runaway fairness. See [the runtime guide](runtime.md).
|
||
|
|
|
||
|
|
Use immutable compiled units and native contexts shared by independent actor
|
||
|
|
execution stacks. Keep Rune values and active execution on their owning worker
|
||
|
|
until their cross-thread contracts are explicitly tested.
|
||
|
|
|
||
|
|
Let the Rust runtime own actor IDs, mailboxes, committed state, and sequential
|
||
|
|
invocation scheduling. Keep mutable state explicit at the handler boundary;
|
||
|
|
never use shared VM stacks as actor state. Continue to reserve messages until
|
||
|
|
their transition completes, including across awaits.
|
||
|
|
|
||
|
|
The original follow-up requirement was to define a typed slice outcome
|
||
|
|
(`Completed`, `Awaiting`, `BudgetExhausted`, `Failed`) at the Rune adapter
|
||
|
|
boundary. Instruction stepping now supplies that boundary without parsing
|
||
|
|
errors. An upstream bulk-slicing API remains a possible performance improvement;
|
||
|
|
native calls still do not have a guaranteed bounded execution time.
|
||
|
|
|
||
|
|
Phase 0's basic running-example criterion is met, but heap profiling, real
|
||
|
|
async I/O cancellation, active execution
|
||
|
|
thread-safety, and richer debugger mapping remain open. Resolve these as
|
||
|
|
explicit follow-up experiments before promising production runtime guarantees.
|