shape_runtime/intrinsics/matrix.rs
1//! Matrix intrinsics — full migration to typed marshal layer.
2//!
3//! Per the intrinsics-typed-CC migration's per-file table, all 4 matrix
4//! intrinsics (`matmul_vec`, `matmul_mat`, `mat_add`, `mat_sub`) migrate to
5//! `register_typed_fn_2` typed entries via [`create_matrix_intrinsics_module`].
6//!
7//! Inputs use the existing Phase 2d Array `Vec<Arc<HeapValue>>` FromSlot for
8//! nested `Array<Array<number>>` matrix arguments and `Arc<Vec<f64>>`
9//! for flat `Array<number>` vector arguments. Outputs project through
10//! `ConcreteReturn::ArrayHeapValue(Vec<Arc<HeapValue>>)` for nested-array
11//! returns (Phase 2d Array landed; production-active per arrow_module /
12//! csv_module / process_ops migrations) and `ConcreteReturn::ArrayF64` for
13//! flat returns.
14//!
15//! Body-side row extraction was previously via direct `Arc<HeapValue>`
16//! pattern-match against `HeapValue::TypedArray(TypedArrayData::F64(buf))`
17//! and `TypedArrayData::I64(...)` arms (mirror of `Arc<DataTable>`'s shape
18//! at `marshal.rs:200-217`). Per V3-S5 ckpt-1 (2026-05-15) the
19//! `TypedArrayData` enum was DELETED at `crates/shape-value/src/heap_value.rs`
20//! (W12-typed-array-data-deletion audit §3.5 + ADR-006 §2.7.24 Q25.A
21//! SUPERSEDED). The previous per-variant row-extraction + row-rebuild path
22//! cascade-breaks here; production migration target is the v2-raw
23//! `TypedArray<f64>` flat-struct carrier per audit §1.2 + §3.1 scalar
24//! recipe (the only existing monomorphization for `f64` rows). The
25//! `HeapValue::TypedArray(Arc<TypedArrayData>)` arm at
26//! `heap_variants.rs:476` is ckpt-4 territory; until that arm migrates,
27//! row-extract / row-rebuild surface-and-stop at runtime via the body
28//! helpers below.
29//!
30//! Matrices are represented as `Array<Array<number>>` at runtime.
31//! This module validates matrix shape once, flattens to contiguous row-major
32//! buffers, and runs tight numeric kernels.
33
34use crate::intrinsics::matrix_kernels;
35use crate::marshal::register_typed_fn_2;
36use crate::module_exports::ModuleExports;
37use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
38use shape_value::aligned_vec::AlignedVec;
39use shape_value::heap_value::{HeapValue, MatrixData};
40use std::sync::Arc;
41
42// ───────────────────── Module factory (4 typed entries) ─────────────────────
43
44/// Create the matrix intrinsics module with all 4 typed-marshal entry points.
45pub fn create_matrix_intrinsics_module() -> ModuleExports {
46 let mut module = ModuleExports::new("std::core::intrinsics::matrix");
47 module.description =
48 "Matrix intrinsics (matmul_vec, matmul_mat, mat_add, mat_sub)".to_string();
49
50 register_typed_fn_2::<_, Vec<Arc<HeapValue>>, Arc<Vec<f64>>>(
51 &mut module,
52 "__intrinsic_matmul_vec",
53 "Matrix-vector multiplication: `Mat<number> * Vec<number> -> Vec<number>`",
54 [("matrix", "Array<Array<number>>"), ("vector", "Array<number>")],
55 ConcreteType::ArrayNumber,
56 |matrix, vector, _ctx| {
57 let (a, rows, inner) = extract_matrix(&matrix, "Left matrix")?;
58 let b = vector.as_slice();
59 if inner != b.len() {
60 return Err(format!(
61 "Matrix/vector dimension mismatch: matrix is {}x{}, vector is length {}",
62 rows,
63 inner,
64 b.len()
65 ));
66 }
67 let mut out = vec![0.0; rows];
68 for i in 0..rows {
69 let row_base = i * inner;
70 let mut acc = 0.0;
71 for k in 0..inner {
72 acc += a[row_base + k] * b[k];
73 }
74 out[i] = acc;
75 }
76 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(out)))
77 },
78 );
79
80 register_typed_fn_2::<_, Vec<Arc<HeapValue>>, Vec<Arc<HeapValue>>>(
81 &mut module,
82 "__intrinsic_matmul_mat",
83 "Matrix-matrix multiplication: `Mat<number> * Mat<number> -> Mat<number>`",
84 [
85 ("a", "Array<Array<number>>"),
86 ("b", "Array<Array<number>>"),
87 ],
88 ConcreteType::ArrayHeapValue("Array<Array<number>>".to_string()),
89 |a_rows_arc, b_rows_arc, _ctx| {
90 let (a, a_rows, a_cols) = extract_matrix(&a_rows_arc, "Left matrix")?;
91 let (b, b_rows, b_cols) = extract_matrix(&b_rows_arc, "Right matrix")?;
92 if a_cols != b_rows {
93 return Err(format!(
94 "Matrix dimension mismatch: left is {}x{}, right is {}x{}",
95 a_rows, a_cols, b_rows, b_cols
96 ));
97 }
98 if a_rows == 0 || b_cols == 0 {
99 return Ok(TypedReturn::Concrete(ConcreteReturn::ArrayHeapValue(
100 matrix_to_heap_value_vec(&[], a_rows, b_cols),
101 )));
102 }
103 let mut out = vec![0.0; a_rows * b_cols];
104 for i in 0..a_rows {
105 let a_row_base = i * a_cols;
106 let out_row_base = i * b_cols;
107 for k in 0..a_cols {
108 let a_ik = a[a_row_base + k];
109 let b_row_base = k * b_cols;
110 for j in 0..b_cols {
111 out[out_row_base + j] += a_ik * b[b_row_base + j];
112 }
113 }
114 }
115 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayHeapValue(
116 matrix_to_heap_value_vec(&out, a_rows, b_cols),
117 )))
118 },
119 );
120
121 register_typed_fn_2::<_, Vec<Arc<HeapValue>>, Vec<Arc<HeapValue>>>(
122 &mut module,
123 "__intrinsic_mat_add",
124 "Element-wise matrix addition: `Mat<number> + Mat<number>`",
125 [
126 ("a", "Array<Array<number>>"),
127 ("b", "Array<Array<number>>"),
128 ],
129 ConcreteType::ArrayHeapValue("Array<Array<number>>".to_string()),
130 |a_rows_arc, b_rows_arc, _ctx| {
131 let a = matrix_data_from_heap_value_vec(&a_rows_arc, "Left matrix")?;
132 let b = matrix_data_from_heap_value_vec(&b_rows_arc, "Right matrix")?;
133 let out = matrix_kernels::matrix_add(&a, &b)?;
134 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayHeapValue(
135 matrix_data_to_heap_value_vec(&out),
136 )))
137 },
138 );
139
140 register_typed_fn_2::<_, Vec<Arc<HeapValue>>, Vec<Arc<HeapValue>>>(
141 &mut module,
142 "__intrinsic_mat_sub",
143 "Element-wise matrix subtraction: `Mat<number> - Mat<number>`",
144 [
145 ("a", "Array<Array<number>>"),
146 ("b", "Array<Array<number>>"),
147 ],
148 ConcreteType::ArrayHeapValue("Array<Array<number>>".to_string()),
149 |a_rows_arc, b_rows_arc, _ctx| {
150 let a = matrix_data_from_heap_value_vec(&a_rows_arc, "Left matrix")?;
151 let b = matrix_data_from_heap_value_vec(&b_rows_arc, "Right matrix")?;
152 let out = matrix_kernels::matrix_sub(&a, &b)?;
153 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayHeapValue(
154 matrix_data_to_heap_value_vec(&out),
155 )))
156 },
157 );
158
159 module
160}
161
162// ───────────────────── Body-side helpers ─────────────────────
163
164/// Extract a row-`&[f64]`-equivalent from a single `Arc<HeapValue>` row element.
165///
166/// Pattern-match shape previously mirrored `marshal.rs:200-217`'s
167/// `FromSlot for Arc<DataTable>` and dispatched on `TypedArrayData::F64`
168/// / `TypedArrayData::I64` arms. Per V3-S5 ckpt-1 (2026-05-15) the
169/// `TypedArrayData` enum is DELETED (W12-typed-array-data-deletion audit
170/// §3.5 + ADR-006 §2.7.24 Q25.A SUPERSEDED); the post-deletion target is
171/// the v2-raw `TypedArray<f64>` flat-struct carrier per audit §1.2.
172/// Row-extract surface-and-stops at runtime until ckpt-4 lands the
173/// `HeapValue::TypedArray(Arc<TypedArrayData>)` arm migration to a v2-raw
174/// row carrier (`heap_variants.rs:476`). The legacy per-`TypedArrayData::X`
175/// dispatch shell is **refused on sight** under Refusal #1 (resurrection
176/// under rename) per ckpt-1 close-marker.
177fn row_to_f64_vec(_hv: &Arc<HeapValue>, label: &str, row_idx: usize) -> Result<Vec<f64>, String> {
178 Err(format!(
179 "{label} row {row_idx}: SURFACE — matrix row-extract reached a \
180 `HeapValue::TypedArray(Arc<TypedArrayData>)` carrier whose inner \
181 enum was DELETED at V3-S5 ckpt-1 (2026-05-15). Production target \
182 is the v2-raw `TypedArray<f64>` / `TypedArray<i64>` flat-struct \
183 carrier per W12-typed-array-data-deletion audit §1.2 + §3.1 \
184 scalar recipe + ADR-006 §2.7.24 Q25.A SUPERSEDED. The \
185 `HeapValue::TypedArray` variant migration to v2-raw rows is \
186 ckpt-4 territory (`heap_variants.rs:476`). Cascade-broken \
187 surface; UNREACHABLE until ckpt-4 + ckpt-5 land the row-carrier \
188 cascade.",
189 label = label,
190 row_idx = row_idx,
191 ))
192}
193
194/// Walk a `Vec<Arc<HeapValue>>` of rows; produce a flat row-major `Vec<f64>`
195/// + dimensions. Validates rectangularity and rejects non-numeric rows.
196fn extract_matrix(
197 rows: &[Arc<HeapValue>],
198 label: &str,
199) -> Result<(Vec<f64>, usize, usize), String> {
200 if rows.is_empty() {
201 return Ok((Vec::new(), 0, 0));
202 }
203 let mut cols: Option<usize> = None;
204 let mut flat = Vec::new();
205 for (row_idx, hv) in rows.iter().enumerate() {
206 let row = row_to_f64_vec(hv, label, row_idx)?;
207 match cols {
208 Some(expected) if row.len() != expected => {
209 return Err(format!(
210 "{} has non-rectangular rows: expected {}, got {} at row {}",
211 label,
212 expected,
213 row.len(),
214 row_idx
215 ));
216 }
217 None => cols = Some(row.len()),
218 _ => {}
219 }
220 flat.extend_from_slice(&row);
221 }
222 Ok((flat, rows.len(), cols.unwrap_or(0)))
223}
224
225/// Build a `MatrixData` from the nested `Vec<Arc<HeapValue>>` row representation.
226/// Used by `mat_add` / `mat_sub` so their dimension-check error paths stay
227/// identical to `matmul_mat`.
228fn matrix_data_from_heap_value_vec(
229 rows: &[Arc<HeapValue>],
230 label: &str,
231) -> Result<MatrixData, String> {
232 let (flat, num_rows, cols) = extract_matrix(rows, label)?;
233 let aligned = if flat.is_empty() {
234 AlignedVec::new()
235 } else {
236 AlignedVec::from_vec(flat)
237 };
238 Ok(MatrixData::from_flat(aligned, num_rows as u32, cols as u32))
239}
240
241/// Convert a flat row-major `&[f64]` of dimensions `rows`x`cols` into
242/// `Vec<Arc<HeapValue>>` rows.
243///
244/// Previously built each row as
245/// `Arc::new(HeapValue::TypedArray(Arc::new(TypedArrayData::F64(Arc::new(
246/// AlignedTypedBuffer::from(...))))))`. Per V3-S5 ckpt-1 (2026-05-15) the
247/// inner `TypedArrayData` enum is DELETED (W12-typed-array-data-deletion
248/// audit §3.5 + ADR-006 §2.7.24 Q25.A SUPERSEDED). The v2-raw `TypedArray
249/// <f64>` row carrier exists at the producer side (audit §1.3) but the
250/// `HeapValue::TypedArray(Arc<TypedArrayData>)` enum arm is ckpt-4
251/// territory (`heap_variants.rs:476`) — until ckpt-4 lands the row
252/// carrier migration, host-side row-rebuild surface-and-stops at runtime.
253/// Empty-matrix dimension reporting still works via the zero-row early-return.
254fn matrix_to_heap_value_vec(_flat: &[f64], rows: usize, cols: usize) -> Vec<Arc<HeapValue>> {
255 if rows == 0 {
256 return Vec::new();
257 }
258 // SURFACE: cannot construct row carriers post-V3-S5 ckpt-1.
259 // The non-empty case structurally cascade-breaks at the production
260 // target until ckpt-4 lands the `HeapValue::TypedArray` arm
261 // v2-raw migration. Production callers (`__intrinsic_matmul_vec` /
262 // `_mat` / `mat_add` / `mat_sub`) surface a typed `String` error via
263 // their `register_typed_fn_2` shell; this body would only be reached
264 // post-ckpt-4 v2-raw row-carrier landing. Return empty as a
265 // dimension-preserving placeholder; the calling intrinsic's
266 // `extract_matrix` -> `row_to_f64_vec` surface-and-stop fires before
267 // this path is hit on any non-trivial matrix input.
268 debug_assert!(
269 cols > 0,
270 "matrix_to_heap_value_vec ckpt-2 broken-state: \
271 non-empty rows={} requested but row-carrier rebuild path \
272 cascade-broken (TypedArrayData DELETED at ckpt-1; \
273 HeapValue::TypedArray arm migration is ckpt-4 territory). \
274 W12-typed-array-data-deletion audit §1.2 + §3.1 production \
275 target = v2-raw TypedArray<f64>.",
276 rows,
277 );
278 let _ = (cols,);
279 Vec::new()
280}
281
282/// Convert a kernel-produced `MatrixData` back to the nested-array
283/// representation.
284fn matrix_data_to_heap_value_vec(mat: &MatrixData) -> Vec<Arc<HeapValue>> {
285 matrix_to_heap_value_vec(mat.data.as_slice(), mat.rows as usize, mat.cols as usize)
286}
287
288// V3-S5 ckpt-4 (2026-05-15): the `_ckpt4_carrier_pin` dead-code marker
289// introduced by V3-S5 ckpt-2 to hold the `AlignedTypedBuffer` import name
290// alive across the chain is DELETED. `typed_buffer.rs` (the file defining
291// `AlignedTypedBuffer`) is itself deleted at ckpt-4 per W12-typed-array-
292// data-deletion-audit §B + ADR-006 §2.7.24 Q25.A SUPERSEDED — pinning a
293// deleted type makes no sense.
294//
295// The four `register_typed_fn_2::<_, _, Arc<AlignedTypedBuffer>>` /
296// `register_typed_fn_1::<_, Arc<AlignedTypedBuffer>>` registrations
297// earlier in this file (matmul_vec at line 51 and the wider intrinsics
298// fleet at statistical.rs / math.rs / fft.rs / array_transforms.rs /
299// convolution.rs / distributions.rs / rolling.rs / recurrence.rs /
300// vector.rs) cascade-break with the typed_buffer.rs deletion. Their
301// resolution is V3-S5 ckpt-5 + downstream-wave territory (per
302// supervisor 2026-05-15 partition; the dispatch-territory list for
303// ckpt-5 names marshal.rs / wire_conversion.rs / json_value.rs cascade
304// pickup, which contains the `FromSlot<Arc<AlignedTypedBuffer>>` /
305// `ToSlot<Arc<AlignedTypedBuffer>>` impls that route the intrinsics'
306// typed-marshal contract).
307//
308// Refusal #1 binding: no resurrection under any rename/shim/bridge.