Skip to main content

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}