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}