# 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` 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::()` | 88 bytes | | `size_of::()` | 96 bytes | | `size_of::()` | 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. - Rune 0.14.2's `VmExecution::into_owned` transfers the stack but resets its VM instruction pointer. The invocation wrapper preserves the selected entrypoint with `vm_mut().set_ip(...)`; regression coverage invokes a non-first `on_shutdown` function so it cannot accidentally execute the first handler. ## 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. The source and implementation identity baseline is implemented by `runa-code`; see [the runtime guide](runtime.md) 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.