Skip to main content

shape_runtime/intrinsics/
mod.rs

1//! High-performance intrinsic functions for Shape
2//!
3//! Intrinsics are Rust-implemented functions that provide performance-critical
4//! operations while keeping domain logic in Shape stdlib.
5//!
6//! These functions are prefixed with `__intrinsic_` and should not be called
7//! directly by users - they are wrapped by Shape stdlib functions.
8
9use crate::context::ExecutionContext;
10use parking_lot::RwLock;
11use shape_ast::error::{Result, ShapeError};
12use shape_value::KindedSlot;
13use std::collections::HashMap;
14use std::sync::Arc;
15
16pub mod array_transforms;
17pub mod convolution;
18pub mod distributions;
19pub mod fft;
20pub mod math;
21pub mod matrix;
22pub mod matrix_kernels;
23pub mod random;
24pub mod recurrence;
25pub mod rolling;
26pub mod statistical;
27pub mod stochastic;
28pub mod vector;
29
30/// Function signature for intrinsics.
31///
32/// Per ADR-006 §2.7.1.4 (dispatch-slice), takes a slice of [`KindedSlot`]
33/// arguments and the execution context, returns a `KindedSlot`.
34pub type IntrinsicFn = fn(&[KindedSlot], &mut ExecutionContext) -> Result<KindedSlot>;
35
36/// Global intrinsics registry
37///
38/// This registry holds all registered intrinsic functions and provides
39/// fast dispatch. It's thread-safe and can be shared across contexts.
40#[derive(Clone)]
41pub struct IntrinsicsRegistry {
42    functions: Arc<RwLock<HashMap<String, IntrinsicFn>>>,
43}
44
45impl std::fmt::Debug for IntrinsicsRegistry {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        f.debug_struct("IntrinsicsRegistry")
48            .field("num_intrinsics", &self.functions.read().len())
49            .finish()
50    }
51}
52
53impl IntrinsicsRegistry {
54    /// Create new registry and register all intrinsics
55    pub fn new() -> Self {
56        let mut functions = HashMap::new();
57
58        // Register polymorphic-shape legacy intrinsic bodies still pending
59        // their architectural sub-decision sign-offs. Migrated intrinsics live
60        // in their respective `create_*_intrinsics_module` factories wired into
61        // `crates/shape-runtime/src/stdlib/mod.rs::all_stdlib_modules` per
62        // intrinsics-typed-CC cluster Q2-marshal-fold-light (M-A scope). See
63        // `docs/defections.md` 2026-05-07 intrinsics-typed-CC entry's sub-
64        // decision queue subsections for the per-fn deferral rationale.
65        Self::register_math_intrinsics(&mut functions);
66        Self::register_rolling_intrinsics(&mut functions);
67        Self::register_series_intrinsics(&mut functions);
68        Self::register_recurrence_intrinsics(&mut functions);
69
70        Self {
71            functions: Arc::new(RwLock::new(functions)),
72        }
73    }
74
75    /// Register a single intrinsic
76    pub fn register(&self, name: &str, func: IntrinsicFn) {
77        let full_name = if name.starts_with("__intrinsic_") {
78            name.to_string()
79        } else {
80            format!("__intrinsic_{}", name)
81        };
82
83        self.functions.write().insert(full_name, func);
84    }
85
86    /// Call an intrinsic function
87    pub fn call(
88        &self,
89        name: &str,
90        args: &[KindedSlot],
91        ctx: &mut ExecutionContext,
92    ) -> Result<KindedSlot> {
93        let functions = self.functions.read();
94
95        let func = functions
96            .get(name)
97            .ok_or_else(|| ShapeError::RuntimeError {
98                message: format!(
99                    "Unknown intrinsic: {}. Available intrinsics: {:?}",
100                    name,
101                    functions.keys().take(5).collect::<Vec<_>>()
102                ),
103                location: None,
104            })?;
105
106        func(args, ctx)
107    }
108
109    /// Check if a function name is an intrinsic
110    pub fn is_intrinsic(&self, name: &str) -> bool {
111        self.functions.read().contains_key(name)
112    }
113
114    /// Get list of all registered intrinsics
115    pub fn list_intrinsics(&self) -> Vec<String> {
116        self.functions.read().keys().cloned().collect()
117    }
118
119    /// Register the 5 math intrinsics whose migration is deferred pending
120    /// follow-on architectural sub-decisions (sum/min/max polymorphic
121    /// return; char_code multi-input-type dispatch; bspline2_3d_batch
122    /// Register polymorphic-shape legacy intrinsic bodies. Phase 1.B
123    /// (ADR-006 §2.7.1.4): the bodies route through [`KindedSlot`] and
124    /// return error stubs pending the M1-split sub-decision (polymorphic
125    /// returns / inputs that the typed marshal layer cannot yet
126    /// represent). Until M1-split lands, calls produce a runtime error
127    /// rather than emit a silent wrong-typed value.
128    fn register_math_intrinsics(functions: &mut HashMap<String, IntrinsicFn>) {
129        // W12-stdlib-intrinsic-collapse (Wave-2-Agent-G, 2026-05-14):
130        // `__intrinsic_sum` deleted — stdlib `sum()` now routes through
131        // PHF `.sum()` method dispatch (ADR-005 §1).
132        functions.insert("__intrinsic_min".to_string(), math::intrinsic_min);
133        functions.insert("__intrinsic_max".to_string(), math::intrinsic_max);
134        functions.insert(
135            "__intrinsic_char_code".to_string(),
136            math::intrinsic_char_code,
137        );
138        functions.insert(
139            "__intrinsic_bspline2_3d_batch".to_string(),
140            math::intrinsic_bspline2_3d_batch,
141        );
142    }
143
144    fn register_rolling_intrinsics(functions: &mut HashMap<String, IntrinsicFn>) {
145        functions.insert(
146            "__intrinsic_rolling_sum".to_string(),
147            rolling::intrinsic_rolling_sum,
148        );
149        functions.insert(
150            "__intrinsic_rolling_min".to_string(),
151            rolling::intrinsic_rolling_min,
152        );
153        functions.insert(
154            "__intrinsic_rolling_max".to_string(),
155            rolling::intrinsic_rolling_max,
156        );
157    }
158
159    fn register_series_intrinsics(functions: &mut HashMap<String, IntrinsicFn>) {
160        functions.insert(
161            "__intrinsic_diff".to_string(),
162            array_transforms::intrinsic_diff,
163        );
164        functions.insert(
165            "__intrinsic_cumsum".to_string(),
166            array_transforms::intrinsic_cumsum,
167        );
168    }
169
170    fn register_recurrence_intrinsics(functions: &mut HashMap<String, IntrinsicFn>) {
171        functions.insert(
172            "__intrinsic_linear_recurrence".to_string(),
173            recurrence::intrinsic_linear_recurrence,
174        );
175    }
176
177}
178
179impl Default for IntrinsicsRegistry {
180    fn default() -> Self {
181        Self::new()
182    }
183}
184
185// ============================================================================
186// Common arg extraction helpers (DRY across all intrinsic modules)
187//
188// Phase 1.B (ADR-006 §2.7.1.4 / §2.7.4 audit accuracy ruling): the
189// pre-bulldozer helpers decoded a `&ValueWord` via tag-bit dispatch
190// methods (`as_number_coerce`, `as_any_array`, `as_int_array`,
191// `as_native_scalar`) that no longer exist. Phase 2c rebuilds these on
192// top of the per-position `NativeKind` threading (the variadic-shape
193// helpers will receive their kind information through the registered
194// schema rather than tag bits). Until then, the helpers return
195// well-formed errors so callers see "deferred" rather than silent
196// wrong-typed reads.
197// ============================================================================
198
199fn deferred(label: &str) -> ShapeError {
200    ShapeError::RuntimeError {
201        message: format!(
202            "{}: pending Phase 2c intrinsic kind threading — see ADR-006 §2.7.4",
203            label
204        ),
205        location: None,
206    }
207}
208
209/// Extract a f64 from an intrinsic argument. Phase 1.B reads the slot's
210/// 8 bytes as `f64` directly — variadic intrinsic callers carry the
211/// kind contract per registration.
212pub fn extract_f64(slot: &KindedSlot, _label: &str) -> Result<f64> {
213    Ok(slot.slot().as_f64())
214}
215
216/// Extract a `usize` from an intrinsic argument (window size / period).
217pub fn extract_usize(slot: &KindedSlot, _label: &str) -> Result<usize> {
218    Ok(slot.slot().as_i64().max(0) as usize)
219}
220
221/// Extract a `Vec<f64>` from an intrinsic array argument.
222///
223/// Phase 1.B: the array-view decoders are deleted alongside `ValueWord`.
224/// Phase 2c rebuilds them per-`HeapKind::TypedArray` element type.
225/// Until then, returns a deferred error rather than silently
226/// fabricating a wrong-typed array.
227pub fn extract_f64_array(_slot: &KindedSlot, label: &str) -> Result<Vec<f64>> {
228    Err(deferred(&format!("{} (extract_f64_array)", label)))
229}
230
231/// Extract a string reference from an intrinsic argument. Phase 1.B
232/// reads the slot bits as `Arc<String>::into_raw`-shaped per registered
233/// `string` param; returns the borrowed string.
234pub fn extract_str<'a>(_slot: &'a KindedSlot, label: &str) -> Result<&'a str> {
235    Err(deferred(&format!("{} (extract_str)", label)))
236}
237
238/// Build a `KindedSlot` array from a `Vec<f64>`. Phase 2c lands the
239/// proper `HeapValue::TypedArray(TypedArrayData::F64)` constructor.
240pub fn f64_vec_to_nb_array(_data: Vec<f64>) -> KindedSlot {
241    KindedSlot::none()
242}
243
244/// Build a `KindedSlot` typed FloatArray from a `Vec<f64>`. See
245/// [`f64_vec_to_nb_array`] — Phase 2c rebuild deferral.
246pub fn f64_vec_to_float_array(_data: Vec<f64>) -> KindedSlot {
247    KindedSlot::none()
248}
249
250/// Build a `KindedSlot` typed IntArray from a `Vec<i64>`. See above.
251pub fn i64_vec_to_nb_int_array(_data: Vec<i64>) -> KindedSlot {
252    KindedSlot::none()
253}
254
255/// Try to read an i64 slice directly from a `KindedSlot` IntArray.
256/// Phase 1.B: deferred — returns `None`.
257pub fn try_extract_i64_slice(_slot: &KindedSlot) -> Option<&[i64]> {
258    None
259}
260
261/// Build a `KindedSlot` IntArray with validity bitmap from
262/// `Vec<Option<i64>>`. Phase 2c rebuild deferral.
263pub fn option_i64_vec_to_nb(_data: Vec<Option<i64>>) -> KindedSlot {
264    KindedSlot::none()
265}