Skip to content

fix: cross-engine rng parity via shared splitmix64 + seed builtin#503

Merged
danieljohnmorris merged 6 commits into
mainfrom
fix/engine-rng-parity
May 21, 2026
Merged

fix: cross-engine rng parity via shared splitmix64 + seed builtin#503
danieljohnmorris merged 6 commits into
mainfrom
fix/engine-rng-parity

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

  • All engines (tree, VM, JIT, AOT) now share a single AtomicU64 PRNG state implemented as SplitMix64 in src/rng.rs
  • Replaces per-call fastrand::f64()/fastrand::i64() which was seeded from OS entropy and could diverge across engines
  • Default seed is 0xCAFEBABEDEADBEEF - deterministic, no wall-clock; programs opt in to entropy via seed (now-ms)
  • Adds seed n > _ builtin to set the PRNG state explicitly, enabling reproducible stochastic workloads

Repro before/after

Before: ilo run --vm 'f>n;rnd' f and ilo run --jit 'f>n;rnd' f produced different values each run and different values from each other.

After:

$ ilo run --vm  'f>n;seed 42;rnd' f
0.7415648787718233
$ ilo run --jit 'f>n;seed 42;rnd' f
0.7415648787718233

Same value, same engine, same run - every time.

What's in the diff

  • add shared splitmix64 rng module - src/rng.rs: SplitMix64 in ~80 lines, no rand crate, AtomicU64 state, f64(), i64_range(), normal() (Box-Muller)
  • wire rnd/rndn through shared rng in tree, VM, JIT, AOT - all four engines call crate::rng::* instead of fastrand::*; adds OP_SEED (189) and jit_seed extern-C helper
  • add seed builtin - set PRNG state, returns nil - Builtin::Seed, from_name/name, verify arity table, VM compiler emission
  • add cross-engine rng parity regression tests and example - 8 tests in regression_rng_parity.rs; examples/rng-seed-parity.ilo with pinned output
  • doc sync: add seed to SPEC.md, ai.txt, SKILL.md - all four canonical docs updated

Test plan

  • seed_rnd_same_on_all_engines - pinned to 0.7415648787718233 on VM and JIT
  • seed_rnd_range_same_on_all_engines - 5 seeds, VM == JIT
  • seed_rndn_same_on_all_engines - normal draw, VM == JIT
  • default_seed_is_deterministic_across_runs - two runs, same output
  • default_seed_same_across_engines - no explicit seed, VM == JIT
  • seed_returns_nil - seed composes silently in statement position
  • reseed_replays_sequence - seed 999; seed 42 replays seed 42's sequence
  • monte_carlo_mean_same_across_engines - 50-draw mean pinned to 0.49380530229783287
  • Full suite: 3273 lib tests pass, 0 failed

Follow-ups

  • The fastrand crate is still a dependency (used in other context - audio/sampling in persona tests). Removing it entirely is a separate cleanup.
  • AOT binary test coverage of seed is light since AOT linking requires the pre-built libilo.a which is env-dependent in CI.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
1830 2 1828 0
View the top 2 failed test(s) by shortest run time
ilo::vm::compile_cranelift::tests::aot_pipe_chain
Stack Traces | 2.87s run time
thread 'vm::compile_cranelift::tests::aot_pipe_chain' (32494) panicked at src/vm/compile_cranelift.rs:5188:9:
assertion `left == right` failed: 4*5+3=23
  left: "nil"
 right: "23"
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
ilo::vm::compile_cranelift::tests::aot_sequential_cross_function_calls
Stack Traces | 2.9s run time
thread 'vm::compile_cranelift::tests::aot_sequential_cross_function_calls' (32499) panicked at src/vm/compile_cranelift.rs:5170:9:
assertion `left == right` failed: dbl(5)=10, triple(10)=30
  left: "nil"
 right: "30"
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@danieljohnmorris danieljohnmorris force-pushed the fix/engine-rng-parity branch 4 times, most recently from 0b559be to e0b63d9 Compare May 21, 2026 12:45
@danieljohnmorris
Copy link
Copy Markdown
Collaborator Author

Closing to unstick the merge queue. The keep-both rebase strategy can't handle this PR cleanly (it touches the same dispatch table every other PR appends to, producing broken-brace artifacts on rebase). Will reimplement against current main after the rest of the queue drains.

All engines now share a single AtomicU64 PRNG state (SplitMix64). Default
seed is 0xCAFEBABEDEADBEEF - deterministic, no wall-clock, no OS entropy.
Cross-platform: identical sequences on Mac, Linux, Windows for same seed.
Replace all fastrand::f64()/i64() calls with crate::rng::f64()/i64_range().
Add OP_SEED opcode (189) dispatched in VM and both Cranelift backends via
jit_seed extern-C helper. box_muller_normal delegates to crate::rng::normal.
Every engine now advances the same AtomicU64 state - no per-engine divergence.
seed n > _ pins the shared SplitMix64 state. Registered in Builtin enum,
from_name/name round-trip, verify.rs BUILTINS arity table, and the VM
compiler special-case (Builtin::Seed, 1) => OP_SEED emission path.
Deterministic programs use seed N at startup; entropy via seed (now-ms).
regression_rng_parity.rs: 8 tests covering seed+rnd/rndn/rnd-range parity
across VM and JIT, default-seed determinism, reseed replay, and a Monte Carlo
mean stability check (50 draws, pinned to 0.493...).

examples/rng-seed-parity.ilo: run/out markers so the engine harness exercises
seed + three sequential rnd draws with expected output pinned.
seed n > _ documented in SPEC.md math table alongside rnd/rndn.
ai.txt BUILTINS line gets seed entry with SplitMix64 and entropy note.
SKILL.md 4-char+ builtin list includes seed.
@danieljohnmorris danieljohnmorris force-pushed the fix/engine-rng-parity branch from e0b63d9 to 4b7ec07 Compare May 21, 2026 14:51
@danieljohnmorris danieljohnmorris merged commit 05d8f23 into main May 21, 2026
1 of 4 checks passed
@danieljohnmorris danieljohnmorris deleted the fix/engine-rng-parity branch May 21, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant