5.8 KiB
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:
cargo run -p runa
mise run pre-commit
The binary compiles feasibility.rn once and reports the results. Tests live in runa-rune. 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, andrune::preparesupport embedding and native registration.VmExecution::async_completesupports asynchronous invocation. A suspended invocation remains live; awaiting is not a state commit.runtime::budget::withenforces 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, andUnit::debug_infoexpose 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
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. The source and implementation identity baseline
is implemented by runa-code; see the runtime guide for its
current exact-source and cache-key scope.
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.