Skip to main content

runmat_runtime/execution/value_codec/
encode.rs

1use super::ValueCodecError;
2use runmat_execution::value::{
3    CallableValue, DenseValue, ElementType, ExceptionValue, InlineValue, SparseValue, StructField,
4    ValuePayload,
5};
6use runmat_value::{ComplexStorage, IntValue, IntegerStorage, SparseTensor, Tensor, Value};
7
8pub fn encode_inline_value(value: &Value) -> Result<ValuePayload, ValueCodecError> {
9    encode(value, "$")
10}
11
12fn encode(value: &Value, path: &str) -> Result<ValuePayload, ValueCodecError> {
13    let inline = match value {
14        Value::Int(value) => encode_integer_scalar(value),
15        Value::Num(value) => InlineValue::F64Bits(value.to_bits()),
16        Value::Complex(real, imaginary) => InlineValue::ComplexF64Bits {
17            real: real.to_bits(),
18            imaginary: imaginary.to_bits(),
19        },
20        Value::Bool(value) => InlineValue::Logical(*value),
21        Value::LogicalArray(value) => InlineValue::Dense(DenseValue {
22            element_type: ElementType::Logical,
23            shape: shape(&value.shape, path)?,
24            little_endian_data: value.data.clone(),
25        }),
26        Value::String(value) => InlineValue::String(value.clone()),
27        Value::StringArray(value) => InlineValue::StringArray {
28            shape: shape(&value.shape, path)?,
29            values: value.data.clone(),
30        },
31        Value::CharArray(value) => InlineValue::Char {
32            shape: vec![value.rows as u64, value.cols as u64],
33            code_points: value.data.iter().map(|value| *value as u32).collect(),
34        },
35        Value::Tensor(value) => InlineValue::Dense(encode_tensor(value, path)?),
36        Value::SparseTensor(value) => InlineValue::Sparse(encode_sparse(value, path)?),
37        Value::ComplexTensor(value) => {
38            let (element_type, data) = encode_complex_storage(value.complex_storage());
39            InlineValue::Dense(DenseValue {
40                element_type,
41                shape: shape(&value.shape, path)?,
42                little_endian_data: data,
43            })
44        }
45        Value::Symbolic(_) | Value::SymbolicArray(_) => {
46            return Err(ValueCodecError::unsupported(
47                path,
48                "a registered symbolic codec is required",
49            ))
50        }
51        Value::Cell(value) => InlineValue::Cell {
52            shape: shape(&value.shape, path)?,
53            values: value
54                .data
55                .iter()
56                .enumerate()
57                .map(|(index, value)| encode(value, &index_path(path, index)))
58                .collect::<Result<_, _>>()?,
59        },
60        Value::Struct(value) => {
61            let mut fields = value
62                .fields
63                .iter()
64                .map(|(name, value)| {
65                    Ok(StructField {
66                        name: name.clone(),
67                        value: encode(value, &field_path(path, name))?,
68                    })
69                })
70                .collect::<Result<Vec<_>, ValueCodecError>>()?;
71            fields.sort_by(|left, right| left.name.cmp(&right.name));
72            InlineValue::Struct(fields)
73        }
74        Value::GpuTensor(_) => {
75            return Err(ValueCodecError::unsupported(
76                path,
77                "GPU buffers require an object reference fenced to a worker and device",
78            ))
79        }
80        Value::Object(_) | Value::ObjectArray(_) => {
81            return Err(ValueCodecError::unsupported(
82                path,
83                "value classes require an explicitly registered immutable class codec",
84            ))
85        }
86        Value::HandleObject(_) => {
87            return Err(ValueCodecError::unsupported(
88                path,
89                "mutable handle identity cannot cross an execution boundary",
90            ))
91        }
92        Value::Listener(_) => {
93            return Err(ValueCodecError::unsupported(
94                path,
95                "event listeners are session-bound and cannot be transferred",
96            ))
97        }
98        Value::OutputList(values) => InlineValue::OutputList(
99            values
100                .iter()
101                .enumerate()
102                .map(|(index, value)| encode(value, &index_path(path, index)))
103                .collect::<Result<_, _>>()?,
104        ),
105        Value::FunctionHandle(name) => callable("workspace", name, Vec::new()),
106        Value::ExternalFunctionHandle(name) => callable("external", name, Vec::new()),
107        Value::MethodFunctionHandle(name) => callable("method", name, Vec::new()),
108        Value::BoundFunctionHandle { name, function } => {
109            callable(&format!("bound:{function}"), name, Vec::new())
110        }
111        Value::Closure(closure) => {
112            let captures = closure
113                .captures
114                .iter()
115                .enumerate()
116                .map(|(index, value)| encode(value, &format!("{path}.capture[{index}]")))
117                .collect::<Result<_, _>>()?;
118            let owner = closure
119                .bound_function
120                .map(|function| format!("bound:{function}"))
121                .unwrap_or_else(|| "workspace".into());
122            callable(&owner, &closure.function_name, captures)
123        }
124        Value::ClassRef(_) => {
125            return Err(ValueCodecError::unsupported(
126                path,
127                "class metadata references are session-bound",
128            ))
129        }
130        Value::MException(value) => InlineValue::Exception(ExceptionValue {
131            identifier: value.identifier.clone(),
132            message: value.message.clone(),
133            stack: value.stack.clone(),
134            causes: Vec::new(),
135        }),
136        Value::Future(_) | Value::Task(_) | Value::Pool(_) | Value::Job(_) => {
137            return Err(ValueCodecError::unsupported(
138                path,
139                "execution handles are capabilities, not transferable values",
140            ))
141        }
142        Value::Foreign(_) => return Err(ValueCodecError::unsupported(
143            path,
144            "foreign references require an interop-manifest adapter and cannot be encoded inline",
145        )),
146    };
147    Ok(ValuePayload::Inline(Box::new(inline)))
148}
149
150fn encode_integer_scalar(value: &IntValue) -> InlineValue {
151    match value {
152        IntValue::I8(value) => InlineValue::I8(*value),
153        IntValue::I16(value) => InlineValue::I16(*value),
154        IntValue::I32(value) => InlineValue::I32(*value),
155        IntValue::I64(value) => InlineValue::I64(*value),
156        IntValue::U8(value) => InlineValue::U8(*value),
157        IntValue::U16(value) => InlineValue::U16(*value),
158        IntValue::U32(value) => InlineValue::U32(*value),
159        IntValue::U64(value) => InlineValue::U64(*value),
160    }
161}
162
163fn encode_tensor(value: &Tensor, path: &str) -> Result<DenseValue, ValueCodecError> {
164    let (element_type, little_endian_data) = if let Some(storage) = value.integer_storage() {
165        encode_integer_storage(storage)
166    } else if let Some(values) = value.as_f64_slice() {
167        (
168            ElementType::F64,
169            values
170                .iter()
171                .flat_map(|value| value.to_bits().to_le_bytes())
172                .collect(),
173        )
174    } else if let Some(values) = value.as_f32_slice() {
175        (
176            ElementType::F32,
177            values
178                .iter()
179                .flat_map(|value| value.to_bits().to_le_bytes())
180                .collect(),
181        )
182    } else {
183        return Err(ValueCodecError::invalid(path, "unknown tensor storage"));
184    };
185    Ok(DenseValue {
186        element_type,
187        shape: shape(&value.shape, path)?,
188        little_endian_data,
189    })
190}
191
192fn encode_sparse(value: &SparseTensor, path: &str) -> Result<SparseValue, ValueCodecError> {
193    let (element_type, little_endian_data) = if let Some(storage) = value.integer_storage() {
194        encode_integer_storage(storage)
195    } else if let Some(values) = value.as_f64_slice() {
196        (
197            ElementType::F64,
198            values
199                .iter()
200                .flat_map(|value| value.to_bits().to_le_bytes())
201                .collect(),
202        )
203    } else if let Some(values) = value.as_f32_slice() {
204        (
205            ElementType::F32,
206            values
207                .iter()
208                .flat_map(|value| value.to_bits().to_le_bytes())
209                .collect(),
210        )
211    } else {
212        return Err(ValueCodecError::unsupported(
213            path,
214            "logical sparse transport",
215        ));
216    };
217    Ok(SparseValue {
218        element_type,
219        rows: value.rows as u64,
220        columns: value.cols as u64,
221        column_offsets: indices(&value.col_ptrs, path)?,
222        row_indices: indices(&value.row_indices, path)?,
223        little_endian_data,
224    })
225}
226
227fn encode_complex_storage(storage: &ComplexStorage) -> (ElementType, Vec<u8>) {
228    match storage {
229        ComplexStorage::F64(values) => (
230            ElementType::ComplexF64,
231            values
232                .iter()
233                .flat_map(|(real, imag)| {
234                    real.to_bits()
235                        .to_le_bytes()
236                        .into_iter()
237                        .chain(imag.to_bits().to_le_bytes())
238                })
239                .collect(),
240        ),
241        ComplexStorage::F32(values) => (
242            ElementType::ComplexF32,
243            values
244                .iter()
245                .flat_map(|(real, imag)| {
246                    real.to_bits()
247                        .to_le_bytes()
248                        .into_iter()
249                        .chain(imag.to_bits().to_le_bytes())
250                })
251                .collect(),
252        ),
253        ComplexStorage::Integer(storage) => {
254            let element_type = match &storage.real {
255                IntegerStorage::I8(_) => ElementType::ComplexI8,
256                IntegerStorage::I16(_) => ElementType::ComplexI16,
257                IntegerStorage::I32(_) => ElementType::ComplexI32,
258                IntegerStorage::I64(_) => ElementType::ComplexI64,
259                IntegerStorage::U8(_) => ElementType::ComplexU8,
260                IntegerStorage::U16(_) => ElementType::ComplexU16,
261                IntegerStorage::U32(_) => ElementType::ComplexU32,
262                IntegerStorage::U64(_) => ElementType::ComplexU64,
263            };
264            let (_, real) = encode_integer_storage(&storage.real);
265            let (_, imag) = encode_integer_storage(&storage.imag);
266            let width = real.len().checked_div(storage.len().max(1)).unwrap_or(0);
267            let mut interleaved = Vec::with_capacity(real.len() + imag.len());
268            for index in 0..storage.len() {
269                interleaved.extend_from_slice(&real[index * width..(index + 1) * width]);
270                interleaved.extend_from_slice(&imag[index * width..(index + 1) * width]);
271            }
272            (element_type, interleaved)
273        }
274    }
275}
276
277fn encode_integer_storage(storage: &IntegerStorage) -> (ElementType, Vec<u8>) {
278    match storage {
279        IntegerStorage::I8(values) => (ElementType::I8, values.iter().map(|v| *v as u8).collect()),
280        IntegerStorage::I16(values) => (ElementType::I16, le_bytes(values, i16::to_le_bytes)),
281        IntegerStorage::I32(values) => (ElementType::I32, le_bytes(values, i32::to_le_bytes)),
282        IntegerStorage::I64(values) => (ElementType::I64, le_bytes(values, i64::to_le_bytes)),
283        IntegerStorage::U8(values) => (ElementType::U8, values.clone()),
284        IntegerStorage::U16(values) => (ElementType::U16, le_bytes(values, u16::to_le_bytes)),
285        IntegerStorage::U32(values) => (ElementType::U32, le_bytes(values, u32::to_le_bytes)),
286        IntegerStorage::U64(values) => (ElementType::U64, le_bytes(values, u64::to_le_bytes)),
287    }
288}
289
290fn le_bytes<T: Copy, const N: usize>(values: &[T], encode: fn(T) -> [u8; N]) -> Vec<u8> {
291    values.iter().flat_map(|value| encode(*value)).collect()
292}
293
294fn callable(
295    owner_identity: &str,
296    qualified_name: &str,
297    captures: Vec<ValuePayload>,
298) -> InlineValue {
299    InlineValue::Callable(CallableValue {
300        owner_identity: owner_identity.into(),
301        qualified_name: qualified_name.into(),
302        callable_digest: CallableValue::identity_digest(owner_identity, qualified_name),
303        captures,
304    })
305}
306
307fn shape(shape: &[usize], path: &str) -> Result<Vec<u64>, ValueCodecError> {
308    shape
309        .iter()
310        .map(|dimension| {
311            u64::try_from(*dimension)
312                .map_err(|_| ValueCodecError::invalid(path, "shape exceeds portable limits"))
313        })
314        .collect()
315}
316
317fn indices(values: &[usize], path: &str) -> Result<Vec<u64>, ValueCodecError> {
318    values
319        .iter()
320        .map(|value| {
321            u64::try_from(*value)
322                .map_err(|_| ValueCodecError::invalid(path, "index exceeds portable limits"))
323        })
324        .collect()
325}
326
327fn index_path(parent: &str, index: usize) -> String {
328    format!("{parent}[{index}]")
329}
330
331fn field_path(parent: &str, field: &str) -> String {
332    format!("{parent}.{field}")
333}