Skip to main content

shape_runtime/intrinsics/
random.rs

1//! Random number generation intrinsics — full migration to typed marshal layer.
2//!
3//! Per the intrinsics-typed-CC migration's per-file table, all 5 random
4//! intrinsics (`random`, `random_int`, `random_seed`, `random_normal`,
5//! `random_array`) migrate to `register_typed_fn_N` typed entries via
6//! [`create_random_intrinsics_module`].
7//!
8//! Thread-local `ChaCha8Rng` state is observably-stateful FFI behavior at
9//! the runtime layer; the marshal-API surface treats each intrinsic as
10//! pure-from-thread-local (each call mutates RNG, returns f64/Unit/array).
11//! `with_rng` stays `pub` for shape-vm to share the same RNG state when
12//! delegating random/distribution/stochastic intrinsics to the runtime.
13//!
14//! Provides high-quality PRNG using ChaCha8 for reproducibility and performance.
15//! Thread-local state ensures zero contention in parallel contexts.
16
17use crate::marshal::{register_typed_fn_0, register_typed_fn_1, register_typed_fn_2};
18use crate::module_exports::ModuleExports;
19use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
20use rand::{Rng, SeedableRng};
21use rand_chacha::ChaCha8Rng;
22use std::cell::RefCell;
23
24thread_local! {
25    static RNG: RefCell<ChaCha8Rng> = RefCell::new(ChaCha8Rng::from_entropy());
26}
27
28/// Access the shared thread-local RNG.
29///
30/// Public so that shape-vm can share the same RNG state when delegating
31/// random/distribution/stochastic intrinsics to the runtime.
32pub fn with_rng<F, R>(f: F) -> R
33where
34    F: FnOnce(&mut ChaCha8Rng) -> R,
35{
36    RNG.with(|rng| f(&mut *rng.borrow_mut()))
37}
38
39// ───────────────────── Module factory (5 typed entries) ─────────────────────
40
41/// Create the random intrinsics module with all 5 typed-marshal entry points.
42pub fn create_random_intrinsics_module() -> ModuleExports {
43    let mut module = ModuleExports::new("std::core::intrinsics::random");
44    module.description =
45        "Random number generation intrinsics (ChaCha8 thread-local PRNG)".to_string();
46
47    register_typed_fn_0::<_>(
48        &mut module,
49        "__intrinsic_random",
50        "Generate random f64 in [0, 1)",
51        ConcreteType::Number,
52        |_ctx| {
53            let value = with_rng(|rng| rng.r#gen::<f64>());
54            Ok(TypedReturn::Concrete(ConcreteReturn::F64(value)))
55        },
56    );
57
58    register_typed_fn_2::<_, f64, f64>(
59        &mut module,
60        "__intrinsic_random_int",
61        "Generate random integer in [lo, hi] (inclusive); returns as number",
62        [("lo", "number"), ("hi", "number")],
63        ConcreteType::Number,
64        |lo, hi, _ctx| {
65            let lo = lo as i64;
66            let hi = hi as i64;
67            if lo > hi {
68                return Err(format!(
69                    "__intrinsic_random_int: lo ({}) must be <= hi ({})",
70                    lo, hi
71                ));
72            }
73            let value = with_rng(|rng| rng.gen_range(lo..=hi));
74            Ok(TypedReturn::Concrete(ConcreteReturn::F64(value as f64)))
75        },
76    );
77
78    register_typed_fn_1::<_, f64>(
79        &mut module,
80        "__intrinsic_random_seed",
81        "Seed the thread-local RNG for reproducibility",
82        "seed",
83        "number",
84        ConcreteType::Unit,
85        |seed, _ctx| {
86            let seed = seed as u64;
87            with_rng(|rng| {
88                *rng = ChaCha8Rng::seed_from_u64(seed);
89            });
90            Ok(TypedReturn::Concrete(ConcreteReturn::Unit))
91        },
92    );
93
94    register_typed_fn_2::<_, f64, f64>(
95        &mut module,
96        "__intrinsic_random_normal",
97        "Generate random number from a normal distribution (Box-Muller)",
98        [("mean", "number"), ("std", "number")],
99        ConcreteType::Number,
100        |mean, std, _ctx| {
101            if std < 0.0 {
102                return Err("__intrinsic_random_normal: std must be non-negative".to_string());
103            }
104            let value = with_rng(|rng| {
105                let u1: f64 = rng.r#gen();
106                let u2: f64 = rng.r#gen();
107                let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
108                mean + std * z
109            });
110            Ok(TypedReturn::Concrete(ConcreteReturn::F64(value)))
111        },
112    );
113
114    register_typed_fn_1::<_, i64>(
115        &mut module,
116        "__intrinsic_random_array",
117        "Generate array of n random numbers in [0, 1)",
118        "n",
119        "int",
120        ConcreteType::ArrayNumber,
121        |n, _ctx| {
122            if n < 0 {
123                return Err("__intrinsic_random_array: n must be non-negative".to_string());
124            }
125            let n = n as usize;
126            let values: Vec<f64> = with_rng(|rng| (0..n).map(|_| rng.r#gen::<f64>()).collect());
127            Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(values)))
128        },
129    );
130
131    module
132}