Verdict: 20 of 28 proposals are high-value and should be pursued. 8 deferred.
Status update (2026-07-11): This document was written 2026-06-25 as a pre-implementation evaluation. Much of Phase 1 has since shipped. All speedup/throughput figures quoted below ("10-100x", "~30%", "2-4x", "10x", "10-20%") are proposal-era design estimates, not measurements — the repository has no benchmark harness (no
benches/, no criterion), so no number in this file should be cited as a measured result. Read the status table and the "Track 1 as built" section before treating any recommendation below as current.
| # | Proposal | Status | Where / notes |
|---|---|---|---|
| 1.1 | Cranelift JIT backend | Shipped | src/jit/ (~7,900 lines, cranelift 0.132), tiered into VM::step at src/vm.rs:1170-1216 |
| 1.2 | Type guard stripping | Shipped | src/jit/typed_compiler.rs emits guard-stripped CLIF; the live tiering path (jit::tiered_execute_step_typed, called from VM::step) recovers register types at tier-up via typed_compiler::infer_reg_types (conservative bytecode must-analysis) and compiles hot regions through the typed path when types are provable, falling back to scalar otherwise. Typed IDiv/IMod/FCmpEq always use runtime helpers to match interpreter semantics exactly |
| 1.3 | Linear scan regalloc | Deferred | Still correct — Cranelift's own regalloc is used |
| 1.4 | MLIR dialect | Deferred | No MLIR in tree |
| 1.5 | SIMD auto-vectorization | Shipped | src/jit/simd_analyzer.rs + src/jit/simd_compiler.rs (I64x2/F64x2/I32x4/F32x4) |
| 2.1 | Lock-free MPSC mailboxes | Shipped differently | src/runtime/mailbox.rs uses an unbounded crossbeam::queue::SegQueue, not the ArrayQueue recommended below — a bounded queue would force blocking or message drops, violating BEAM never-drop semantics. crossbeam's epoch-based reclamation (the Risk Register's ABA mitigation) comes with SegQueue |
| 2.2 | Dual-region actor heaps | Shipped differently — LOS + grow-on-demand | No bumpalo dependency — src/runtime/heap.rs is a hand-rolled bump allocator with size-class free lists, a large-object space (allocations over the 256-byte Huge threshold individually std::alloc'd, exact-size free-list reuse, released on reset()/Drop), and grow-on-demand chaining: bump-block exhaustion chains a fresh 64KB block instead of failing, objects never move, all blocks released on reset()/Drop (2026-07-12). No nursery/tenured split |
| 2.3 | mimalloc global allocator | Shipped | mimalloc = "0.1" in Cargo.toml; #[global_allocator] in src/main.rs |
| 2.4 | Static escape analysis | Reverted | src/escape_analysis.rs was added and later removed; no escape analysis in the tree today |
| 2.5 | Actor arenas | Deferred | Not present |
| 2.6 | Cache-locality scheduling | Not started | Scheduler remains single-threaded cooperative |
| 3.1 | rkyv zero-copy serialization | Not started | No rkyv dep; wire format is hand-rolled big-endian in src/runtime/network.rs |
| 3.2 | Delta-state CRDT replication | Shipped | All 8 CRDTs expose delta_since(base); CrdtManager::generate_delta_sync_ops ships first-seen entries full and changed entries as deltas over Packet::CrdtDeltaSync (type 7, CrdtDeltaOp), applied via apply_delta_op (merge-only). Runtime::sync_crdts ships deltas every round except round 1 and every 16th round thereafter, which ship full CrdtSync state as the repair path |
| 3.3 | io_uring / RDMA | Deferred | Not present |
| 3.4 | Native Raft | Deferred | No Raft code |
| 3.5 | Content-addressable bytecode | Deferred | Not present |
| 4.1 | Evidence-passing style | Not started | Effects remain runtime handler-stack based (Handle/Perform/Resume/Unwind) |
| 4.2 | Linear moves for iso | Partial | LinearIso capability in src/types.rs with at-most-once consumption enforcement wired into CapabilityAnalyzer (src/effect_checker.rs, 2026-07-12): second use of a consumed binding is a CapError, sends/captures consume, conservative branch merge; exactly-once must-use is a documented follow-up. Capabilities are still erased at runtime, so no runtime move mechanism as proposed |
| 4.3 | Typestate analysis | Not started | Not present |
| 4.4 | Implicit effect returns | Not found | No corresponding transform in the current effect checker / HIR lowering |
| 5.1 | Unify actor/agent primitives | Partial | v0.9 AI runtime exists (src/ai/: OpenAI/Ollama providers, memory, pipelines, debates, supervisor) but agents are not actors with capability llm |
| 5.2 | Agent-aware supervision | Partial | src/ai/supervisor.rs (agent supervisor teams) exists alongside the actor Supervisor |
| 5.3 | Wasmtime sandboxed tools | Not started | No wasmtime dep |
| 5.4 | Agent telemetry monitors | Partial | src/ai/usage.rs tracks token usage and pricing; no general telemetry monitor |
| 6.1 | LSP inlay hints | Shipped | src/lsp/mod.rs implements inlay hints (typechecker-backed with regex fallback) |
| 6.2 | Deterministic simulation testing | Not started | No DST harness |
| 6.3 | Causal profiling | Deferred | Not present |
| 6.4 | Actor topology dashboard | Not started | Not present |
Memory-management changes (2026-07-12, outside the 28 proposals):
- Intra-actor reclamation shipped.
plan_drops(liveness analysis insrc/mir_codegen.rs) emitsOpCode::Dropat conservative last-use/redefinition/branch-exit points; theDrophandler clears the register (idempotent);ArrStore/RecS/FieldSwrite barriers retain stored pointers and release overwritten slots;OrcaGc::free_objectreleases slot references on container free. Actor heaps no longer accumulate garbage until actor exit. This covers much of proposal 2.4's goal ("make ORCA GC effectively disappear") via liveness + barriers rather than escape analysis. - Refcounts downgraded to plain integers.
OrcaHeadercounts andGcStatsare no longer atomic — all heap/GC access runs on the single scheduler thread (network/LLM/Python threads never touch heaps; verified). The single-thread invariant is documented in SAFETY comments. - Heap growth shipped — see row 2.2 above.
| Criterion | Assessment |
|---|---|
| Impact | Eliminates interpreter overhead (10-100x speedup for hot loops) |
| Feasibility | Cranelift is mature (wasmtime, rustc) |
| Priority | P0 — Foundation for everything else |
| Effort | 4-6 weeks |
Recommendation: DO FIRST. Highest-leverage single change. JIT can tier: interpreter for cold code, JIT for hot loops.
Status (2026-07-11): SHIPPED. See "Track 1 as built" below for the tiering mechanics that actually landed.
| Criterion | Assessment |
|---|---|
| Impact | Eliminates ~30% of runtime overhead in numeric loops |
| Priority | P0 — Bundle with 1.1 |
| Effort | 1-2 weeks |
Recommendation: Bundle with 1.1. Free once Cranelift backend exists.
Status (2026-07-12): SHIPPED. The VM's tiering entry point (jit::tiered_execute_step_typed) recovers register types at tier-up via typed_compiler::infer_reg_types (a conservative must-analysis over the enclosing function's bytecode) and compiles hot regions through the guard-stripped path when types are provable, with scalar fallback otherwise. Typed IDiv/IMod/FCmpEq route through runtime helpers to stay bit-identical with the interpreter. The "~30%" figure remains the module's own design estimate, unmeasured.
Recommendation: DEFER. Cranelift's regalloc2 is already excellent.
Recommendation: DEFER to Year 3+. Architecturally elegant but premature.
Recommendation: Pursue after 1.1. High-value for AI inference, text processing.
Status (2026-07-11): SHIPPED for element-wise array loops (details below).
Track 1 Summary: Do 1.1 + 1.2 first. Defer 1.3. Research 1.4. Do 1.5 after 1.1.
The JIT backend lives in src/jit/ (~7,900 lines across 7 files, cranelift 0.132) and is wired into the interpreter loop at src/vm.rs:1170-1216.
VMholdsjit_session: Option<JitSession>(src/vm.rs:695). A session builds the CraneliftJITModulefor the host ISA with theenable_simdflag set, and registers 31nulang_*runtime helpers as importable symbols.- Before each interpreted instruction, the VM snapshots the current frame's 256 registers into a
[u64; 256]array and callsjit::tiered_execute_step_typed. If the result is notTieredAction::Interpret, the array is copied back into the frame andpcis advanced by the compiled region's length. - Hotness is tracked in a global
Mutex<HashMap<(usize, usize), u64>>keyed by(module_idx, offset)so identical offsets in different modules do not share counts.HOT_THRESHOLD = 1000(src/jit/mod.rs:55): a region compiles on its 1000th interpreted hit. find_compilable_regionscans at most 500 instructions from the hot offset and stops at the first unsupported opcode and beforeRet/RetVal/Jmp/JmpT/JmpF/Halt. Regions are therefore straight-line only — branches and returns stay interpreted (loop bodies still compile, since the back-edge jump terminates the region). Regions shorter than 3 instructions are not compiled (region_len >= 3).- Compiled functions are cached per
(module_idx, offset); a region is compiled at most once per session.
pub type JitFunctionPtr = extern "C" fn(*mut u64, *const u64);Argument 1 is the 256-entry register file (read/write), argument 2 the module's constant pool pre-converted to raw NaN-boxed bits (constants_to_jit_bits, src/vm.rs:485). JIT function pointers are obtained by transmuting the finalized *const u8; bytecode must not be mutated while JIT code is executing.
MVP opcode subset (50 opcodes): Nop/Halt, Const0/1/2/M1/ConstU, Load/Store/Move/Swap/Dup, integer and float arithmetic, integer/float compares, Not/And/Or, Jmp/JmpT/JmpF, IToF/FToI, DbgPrint, Ret/RetVal, ArrLoad. Everything else (actors, effects, FFI, Python, strings) forces interpretation.
Arithmetic lowers to calls to 31 #[no_mangle] extern "C" helpers in src/jit/runtime.rs. These are NaN-tag aware via the canonical layout in src/value_layout.rs (sext48, tag_int, PAYLOAD_MASK). Integer idiv/imod by zero return nil instead of trapping; float fdiv is raw IEEE-754. fcmp_eq compares with f64::EPSILON tolerance.
Given TypeMetadata (register → KnownType::{Int, Float, Bool, Unknown}), the typed compiler emits direct CLIF (iadd, fadd, …) instead of helper calls, stripping NaN-tag manipulation for statically known operands and falling back to helpers for Unknown. Live: the tiering entry point is jit::tiered_execute_step_typed (called from VM::step, src/vm.rs:1170-1216); at tier-up it runs typed_compiler::infer_reg_types — a conservative forward must-analysis over the enclosing function's bytecode — and compiles the hot region through the guard-stripped path when at least one register type is provable, falling back to the scalar compile_region on absent/empty metadata or compile error. Typed IDiv/IMod/FCmpEq always emit runtime-helper calls (never raw sdiv/srem/fcmp) to stay bit-identical with the interpreter (div-by-zero → nil, epsilon float equality).
JitSession::compile_region_simd runs analyze_region first. The analyzer recognizes three loop shapes — ElementWiseBinop (c[i] = a[i] + b[i]), ElementWiseUnary (b[i] = -a[i]), ElementWiseCmp (c[i] = a[i] < b[i]) — requiring an ArrLoad→arithmetic→ArrStore chain on a single induction variable that steps by 1, a determinable trip count (ArrLen bound or constant), a uniform element type, no calls in the body, and no control flow beyond the back-edge.
The SIMD compiler emits 128-bit vectors — I64x2/F64x2 (2-wide) and I32x4/F32x4 (4-wide) — as a scalar prefix loop, a SIMD body, and a scalar epilogue (SimdWidth::Width8/I16x8 is reserved, not implemented). Compilation requires a compile-time trip-count hint and host support (is_simd_supported(): SSE2 on x86_64, always true on aarch64); otherwise it falls back to the typed scalar compiler. On a SIMD compile error the session falls back to the plain scalar compile_region.
71 #[test]s under src/jit/ (35 in tests.rs, 3 in compiler.rs, 15 in simd_analyzer.rs, 10 in simd_compiler.rs, 8 in typed_compiler.rs) plus 2 VM-level regression tests in src/vm.rs (test_jit_hot_loop_matches_interpreter, test_jit_hot_loop_with_early_exit_branch) and a typed-path test in src/integration_tests.rs (test_jit_typed_guard_stripping_hot_function) that assert JIT-compiled hot loops produce exactly the interpreter's result. There is no performance benchmark harness anywhere in the repository — all speedup numbers in this document are estimates.
No whole-function or ahead-of-time compilation; no inlining; no deoptimization or on-stack replacement beyond re-entering the interpreter at region boundaries; no JIT support for actor, effect, FFI, or Python opcodes; no control flow across region boundaries; no 8/16-bit SIMD widths; no recorded benchmark numbers.
| Criterion | Assessment |
|---|---|
| Impact | Eliminates mutex contention (up to 10x throughput improvement) |
| Priority | P0 — Critical for multi-core scaling |
| Effort | 2-3 weeks |
Recommendation: DO IMMEDIATELY. Highest-ROI change in Track 2. Use crossbeam::queue::ArrayQueue.
Status (2026-07-11): SHIPPED with a different structure. src/runtime/mailbox.rs uses an unbounded crossbeam::queue::SegQueue, not ArrayQueue: a bounded queue forces a choice between blocking senders and dropping messages, both of which violate BEAM never-drop semantics. Push always succeeds; reclamation uses crossbeam's epoch-based GC.
Recommendation: DO. Natural evolution of the existing per-actor heap.
Status (2026-07-12): LOS SHIPPED. There is no bumpalo dependency — src/runtime/heap.rs is a hand-rolled bump allocator with per-size-class intrusive free lists and ORCA headers, plus a large-object space: allocations over the 256-byte Huge threshold are individually std::alloc'd outside the 64KB backing block, with exact-size free-list reuse and release on reset()/Drop. No nursery/tenured split.
Recommendation: DO RIGHT NOW. Literally a one-line change. Instant 10-20% win.
Status (2026-07-11): SHIPPED. mimalloc = "0.1" is the #[global_allocator] in src/main.rs. The "10-20%" figure was never measured.
Recommendation: DO after 1.1. Key to making ORCA GC effectively disappear.
Status (2026-07-11): REVERTED. src/escape_analysis.rs was implemented and subsequently removed (tests and references cleaned up); no escape analysis exists in the tree today.
Recommendation: DEFER. Power-user feature; covered by 2.2 and 2.4.
Recommendation: Pursue after 2.1. Important for Intel/ARM hybrid CPUs.
Track 2 Summary: Do 2.1, 2.3 immediately. Do 2.2, 2.4 next. Defer 2.5. Do 2.6 after 2.1.
Recommendation: DO. Eliminates 50%+ of distributed message latency.
Recommendation: DO. 10-100x bandwidth reduction for large CRDTs.
Status (2026-07-12): SHIPPED (delta-state). All 8 CRDTs expose delta_since(base); CrdtManager::generate_delta_sync_ops ships deltas over Packet::CrdtDeltaSync (type 7) via sync_crdts_delta in src/runtime/distributed.rs, with full-state CrdtSync retained as the join/repair path. Op-based (CmRDT) replication was not implemented.
Recommendation: DEFER. Platform-specific; significant complexity increase.
Recommendation: DEFER to Phase 4. CRDTs cover 80% of distributed state needs.
Recommendation: DEFER to Year 3+. Unison-style paradigm shift; enormous complexity.
Track 3 Summary: Do 3.1, 3.2. Defer 3.3, 3.4, 3.5.
Recommendation: DO after 1.1. Key optimization for algebraic effects performance.
Recommendation: DO IMMEDIATELY. Type system change for zero-cost actor messaging.
Recommendation: Pursue after core type system stabilization. Major differentiator.
Recommendation: DO. Trivial AST transformation; good ergonomics payoff.
Track 4 Summary: Do 4.2 immediately. Do 4.1 after Cranelift. Do 4.3 after type system stabilization. Do 4.4 anytime.
Recommendation: DO. Aligns with v2.0 spec: agents become actors with capability llm.
Recommendation: DO. Essential for production AI systems. LLM APIs are unreliable.
Recommendation: DO IMMEDIATELY. Security requirement, not a feature.
Recommendation: DO after core AI runtime. Important for cost management.
Track 5 Summary: Do 5.1, 5.3 immediately. Do 5.2 next. Do 5.4 after core AI runtime.
Recommendation: DO IMMEDIATELY. Most impactful developer experience feature.
Recommendation: DO. How FoundationDB achieved legendary reliability.
Recommendation: DEFER. Research tool; profilers + DST cover most needs.
Recommendation: DO as a side project. Low-risk, high-fun, great for demos.
Track 6 Summary: Do 6.1 immediately. Do 6.2 next. Do 6.4 as side project. Defer 6.3.
| # | Proposal | Effort | Depends On |
|---|---|---|---|
| 2.3 | mimalloc global allocator | 1 day | — |
| 2.1 | Lock-free CAS MPSC mailboxes | 3 weeks | — |
| 1.1 | Cranelift JIT Backend | 6 weeks | — |
| 1.2 | Type guard stripping / unboxing | 2 weeks | 1.1 |
| 4.2 | Linear type moves for iso | 3 weeks | — |
| 6.1 | LSP Inlay Hints | 4 weeks | — |
| # | Proposal | Effort | Depends On |
|---|---|---|---|
| 2.2 | Dual-region actor heaps | 3 weeks | — |
| 2.4 | Static escape analysis | 4 weeks | 1.1 |
| 2.6 | Cache-locality scheduling | 5 weeks | 2.1 |
| 4.1 | Evidence-passing style | 5 weeks | 1.1 |
| 1.5 | SIMD auto-vectorization | 4 weeks | 1.1 |
| 6.2 | Deterministic Simulation Testing | 7 weeks | — |
| # | Proposal | Effort | Depends On |
|---|---|---|---|
| 3.1 | rkyv zero-copy serialization | 3 weeks | — |
| 3.2 | Delta-state CRDT replication | 5 weeks | 3.1 |
| 5.1 | Unify actor/agent primitives | 2 weeks | — |
| 5.3 | Wasmtime sandboxed execution | 4 weeks | — |
| 5.2 | Agent-aware supervision | 3 weeks | 5.1 |
| 4.3 | Typestate analysis | 7 weeks | 4.2 |
| 6.4 | TUI topology dashboard | 3 weeks | — |
| # | Proposal | Effort | Depends On |
|---|---|---|---|
| 3.4 | Native Raft consensus | 10 weeks | 3.2 |
| 4.4 | Implicit effect returns | 1 week | — |
| 5.4 | Agent telemetry monitors | 3 weeks | 5.2 |
| 1.3 | Linear scan regalloc (if needed) | 4 weeks | 1.1 |
| 2.5 | Actor arenas (if needed) | 4 weeks | 2.2 |
- 1.3: Linear scan regalloc (use Cranelift's default)
- 1.4: MLIR dialect pipeline
- 2.5: Actor arenas
- 3.3: io_uring / RDMA
- 3.5: Content-addressable bytecode
- 6.3: Causal profiling
Cranelift JIT (1.1, 6w) → Type guard stripping (1.2, 2w) → Evidence-passing (4.1, 5w) → Escape analysis (2.4, 4w)
Total: 17 weeks for full native-speed + zero-cost effects + stack allocation.
Parallel tracks:
- Type system: Linear moves (4.2) can proceed in parallel with Cranelift (1.1)
- Memory: Lock-free mailboxes (2.1) + mimalloc (2.3) are independent
- Distributed: rkyv (3.1) + delta CRDTs (3.2) are independent of native compilation
- Observability: LSP (6.1) is independent; DST (6.2) needs stable runtime
Total timeline to Phase 3 completion: ~28 weeks (7 months) with 2 engineers.
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| Cranelift JIT has codegen bugs | Medium | High | Maintain interpreter as fallback |
| Lock-free mailbox ABA bug | Low | Critical | Use crossbeam::epoch |
| Escape analysis unsoundness | Medium | Critical | Conservative analysis; fall back to heap |
| rkyv endianness across platforms | Medium | High | Standardize little-endian wire format |
| LSP performance on large files | Medium | Medium | Incremental type checking; caching |
| Wasmtime sandbox escape | Low | Critical | Keep Wasmtime updated; minimal WASI |
20 of 28 proposals should be pursued. The deferred 8 are premature optimizations, research projects, or platform-specific. The 20 recommended proposals form a coherent, phased roadmap that transforms Nulang from an interpreted research language into a production-grade, native-speed distributed actor platform.
Sequence matters: JIT first, then memory, then distributed, then AI, then advanced type system.