shape_runtime/intrinsics/recurrence.rs
1//! Recurrence intrinsics — partial migration to typed marshal layer
2//! (cross-crate-dual-consumer pattern; see `docs/defections.md` 2026-05-07
3//! intrinsics-typed-CC entry's N1 sub-decision).
4//!
5//! `__intrinsic_linear_recurrence` has two distinct consumers:
6//!
7//! 1. **Compiler-typed-call-sites consumer** — typed entry via
8//! [`create_recurrence_intrinsics_module`] (`register_typed_fn_3_full`
9//! using the N1 `FromSlot<Option<f64>>` impl from `marshal.rs`).
10//!
11//! 2. **Shape-vm `BuiltinFunction` dispatcher consumer** — references the
12//! legacy [`intrinsic_linear_recurrence`] body directly from
13//! `crates/shape-vm/src/executor/builtins/runtime_delegated.rs:138`.
14//! No `vm_intrinsic_linear_recurrence` shape-vm-side parallel copy exists
15//! (unlike rolling/random/distributions which have `vm_intrinsic_*`
16//! copies in `crates/shape-vm/src/executor/builtins/intrinsics/`).
17//!
18//! Both consumers are real and structurally distinct. This is two-consumer-
19//! two-paths, NOT dual-registration soft-fail. Consolidation (drop legacy
20//! body + edit shape-vm dispatcher) is deferred to the shape-vm cleanup
21//! workstream per M-A scope binding.
22//!
23//! Computes `y[t] = y[t-1] * decay + input[t]`. Used for recursive indicators
24//! (EMA, etc.). Optional initial value: when omitted/null, `y[0] = input[0]`.
25
26use crate::context::ExecutionContext;
27use crate::marshal::register_typed_fn_3_full;
28use crate::module_exports::{ModuleExports, ModuleParam};
29use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
30use shape_ast::error::{Result, ShapeError};
31use shape_value::KindedSlot;
32use std::sync::Arc;
33
34// ───────────────────── Module factory (1 typed entry) ─────────────────────
35
36/// Create the recurrence intrinsics module with the typed-marshal entry
37/// for `__intrinsic_linear_recurrence`.
38///
39/// The legacy body [`intrinsic_linear_recurrence`] below is retained for
40/// the shape-vm `BuiltinFunction::IntrinsicLinearRecurrence` dispatcher
41/// arm at `runtime_delegated.rs:138` (cross-crate-dual-consumer pattern).
42pub fn create_recurrence_intrinsics_module() -> ModuleExports {
43 let mut module = ModuleExports::new("std::core::intrinsics::recurrence");
44 module.description =
45 "Recurrence intrinsics (typed entry; legacy body retained for shape-vm dispatcher consumer per cross-crate-dual-consumer pattern)"
46 .to_string();
47
48 register_typed_fn_3_full::<_, Arc<Vec<f64>>, f64, Option<f64>>(
49 &mut module,
50 "__intrinsic_linear_recurrence",
51 "Linear recurrence: y[t] = y[t-1] * decay + input[t]",
52 [
53 ModuleParam {
54 name: "input".to_string(),
55 type_name: "Array<number>".to_string(),
56 required: true,
57 description: "Input series".to_string(),
58 ..Default::default()
59 },
60 ModuleParam {
61 name: "decay".to_string(),
62 type_name: "number".to_string(),
63 required: true,
64 description: "Decay factor applied to previous output".to_string(),
65 ..Default::default()
66 },
67 ModuleParam {
68 name: "initial_value".to_string(),
69 type_name: "number?".to_string(),
70 required: false,
71 description: "Optional seed for y[0]; when omitted, y[0] = input[0]"
72 .to_string(),
73 default_snippet: Some("null".to_string()),
74 ..Default::default()
75 },
76 ],
77 ConcreteType::ArrayNumber,
78 |input, decay, initial_value, _ctx| {
79 let data = input.as_slice();
80 if data.is_empty() {
81 return Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(vec![])));
82 }
83
84 let mut result = Vec::with_capacity(data.len());
85 if let Some(init) = initial_value {
86 let mut prev = init;
87 for &val in data {
88 let curr = prev * decay + val;
89 result.push(curr);
90 prev = curr;
91 }
92 } else {
93 let first = data[0];
94 result.push(first);
95 let mut prev = first;
96 for &val in &data[1..] {
97 let curr = prev * decay + val;
98 result.push(curr);
99 prev = curr;
100 }
101 }
102 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
103 },
104 );
105
106 module
107}
108
109// ───────────────────── Legacy body (shape-vm dispatcher consumer) ──────────
110
111/// Intrinsic: Linear Recurrence
112///
113/// Computes y[t] = y[t-1] * decay + input[t]
114///
115/// **Retained alongside the typed factory entry** for the shape-vm
116/// `BuiltinFunction::IntrinsicLinearRecurrence` dispatcher arm at
117/// `runtime_delegated.rs:138`. Removal blocked on shape-vm cleanup
118/// workstream (M-A scope binding) — see module docstring.
119pub fn intrinsic_linear_recurrence(
120 _args: &[KindedSlot],
121 _ctx: &mut ExecutionContext,
122) -> Result<KindedSlot> {
123 Err(ShapeError::RuntimeError {
124 message: "intrinsic_linear_recurrence: pending Phase 2c intrinsic kind threading — see ADR-006 §2.7.4".to_string(),
125 location: None,
126 })
127}