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}