1use runmat_execution::value::{
2 CallableValue, DenseValue, ElementType, ExceptionValue, InlineValue, SparseValue, ValueLimits,
3 ValuePayload,
4};
5use runmat_value::{
6 CellArray, CharArray, Closure, ComplexTensor, IntValue, IntegerComplexStorage, IntegerStorage,
7 LogicalArray, MException, SparseTensor, StringArray, StructValue, Tensor, Value,
8};
9
10use super::ValueCodecError;
11
12pub fn decode_inline_value(payload: &ValuePayload) -> Result<Value, ValueCodecError> {
13 payload
14 .validate(ValueLimits::default())
15 .map_err(|error| ValueCodecError::invalid("$", error.to_string()))?;
16 decode(payload, "$")
17}
18
19fn decode(payload: &ValuePayload, path: &str) -> Result<Value, ValueCodecError> {
20 let ValuePayload::Inline(value) = payload else {
21 return Err(ValueCodecError::unsupported(
22 path,
23 "object references require an execution object-store decoder",
24 ));
25 };
26 match value.as_ref() {
27 InlineValue::Null => Err(ValueCodecError::unsupported(
28 path,
29 "MATLAB has no null runtime value",
30 )),
31 InlineValue::Logical(value) => Ok(Value::Bool(*value)),
32 InlineValue::F64Bits(value) => Ok(Value::Num(f64::from_bits(*value))),
33 InlineValue::I8(value) => Ok(Value::Int(IntValue::I8(*value))),
34 InlineValue::I16(value) => Ok(Value::Int(IntValue::I16(*value))),
35 InlineValue::I32(value) => Ok(Value::Int(IntValue::I32(*value))),
36 InlineValue::I64(value) => Ok(Value::Int(IntValue::I64(*value))),
37 InlineValue::U8(value) => Ok(Value::Int(IntValue::U8(*value))),
38 InlineValue::U16(value) => Ok(Value::Int(IntValue::U16(*value))),
39 InlineValue::U32(value) => Ok(Value::Int(IntValue::U32(*value))),
40 InlineValue::U64(value) => Ok(Value::Int(IntValue::U64(*value))),
41 InlineValue::ComplexF64Bits { real, imaginary } => Ok(Value::Complex(
42 f64::from_bits(*real),
43 f64::from_bits(*imaginary),
44 )),
45 InlineValue::String(value) => Ok(Value::String(value.clone())),
46 InlineValue::StringArray { shape, values } => Ok(Value::StringArray(
47 StringArray::new(values.clone(), usize_shape(shape, path)?)
48 .map_err(|error| ValueCodecError::invalid(path, error))?,
49 )),
50 InlineValue::Char { shape, code_points } => {
51 if shape.len() != 2 {
52 return Err(ValueCodecError::invalid(
53 path,
54 "char arrays require exactly two dimensions",
55 ));
56 }
57 let shape = usize_shape(shape, path)?;
58 let chars = code_points
59 .iter()
60 .map(|value| {
61 char::from_u32(*value)
62 .ok_or_else(|| ValueCodecError::invalid(path, "invalid Unicode scalar"))
63 })
64 .collect::<Result<_, _>>()?;
65 Ok(Value::CharArray(
66 CharArray::new(chars, shape[0], shape[1])
67 .map_err(|error| ValueCodecError::invalid(path, error))?,
68 ))
69 }
70 InlineValue::Dense(value) => decode_dense(value, path),
71 InlineValue::Sparse(value) => decode_sparse(value, path),
72 InlineValue::Symbolic(_) => Err(ValueCodecError::unsupported(
73 path,
74 "the registered symbolic codec is unavailable in this runtime",
75 )),
76 InlineValue::Cell { shape, values } => Ok(Value::Cell(
77 CellArray::new_with_shape(
78 values
79 .iter()
80 .enumerate()
81 .map(|(index, value)| decode(value, &format!("{path}[{index}]")))
82 .collect::<Result<_, _>>()?,
83 usize_shape(shape, path)?,
84 )
85 .map_err(|error| ValueCodecError::invalid(path, error))?,
86 )),
87 InlineValue::Struct(fields) => {
88 let mut value = StructValue::new();
89 for field in fields {
90 value.insert(
91 field.name.clone(),
92 decode(&field.value, &format!("{path}.{}", field.name))?,
93 );
94 }
95 Ok(Value::Struct(value))
96 }
97 InlineValue::OutputList(values) => Ok(Value::OutputList(
98 values
99 .iter()
100 .enumerate()
101 .map(|(index, value)| decode(value, &format!("{path}[{index}]")))
102 .collect::<Result<_, _>>()?,
103 )),
104 InlineValue::Exception(value) => decode_exception(value, path),
105 InlineValue::Callable(value) => decode_callable(value, path),
106 InlineValue::ImmutableValueClass(_) => Err(ValueCodecError::unsupported(
107 path,
108 "the registered immutable value-class codec is unavailable in this runtime",
109 )),
110 }
111}
112
113fn decode_dense(value: &DenseValue, path: &str) -> Result<Value, ValueCodecError> {
114 let shape = usize_shape(&value.shape, path)?;
115 match value.element_type {
116 ElementType::Logical => Ok(Value::LogicalArray(
117 LogicalArray::new(value.little_endian_data.clone(), shape)
118 .map_err(|error| ValueCodecError::invalid(path, error))?,
119 )),
120 ElementType::F32 => Ok(Value::Tensor(
121 Tensor::from_f32(decode_f32(&value.little_endian_data, path)?, shape)
122 .map_err(|error| ValueCodecError::invalid(path, error))?,
123 )),
124 ElementType::F64 => Ok(Value::Tensor(
125 Tensor::new(decode_f64(&value.little_endian_data, path)?, shape)
126 .map_err(|error| ValueCodecError::invalid(path, error))?,
127 )),
128 ElementType::ComplexF64 => {
129 let words = decode_words::<8>(&value.little_endian_data, path)?;
130 let data = words
131 .chunks_exact(2)
132 .map(|pair| (f64::from_bits(pair[0]), f64::from_bits(pair[1])))
133 .collect();
134 Ok(Value::ComplexTensor(
135 ComplexTensor::new(data, shape)
136 .map_err(|error| ValueCodecError::invalid(path, error))?,
137 ))
138 }
139 ElementType::ComplexF32 => {
140 let words = decode_arrays::<4>(&value.little_endian_data, path)?;
141 let data = words
142 .chunks_exact(2)
143 .map(|pair| {
144 (
145 f32::from_bits(u32::from_le_bytes(pair[0])),
146 f32::from_bits(u32::from_le_bytes(pair[1])),
147 )
148 })
149 .collect();
150 Ok(Value::ComplexTensor(
151 ComplexTensor::from_f32(data, shape)
152 .map_err(|error| ValueCodecError::invalid(path, error))?,
153 ))
154 }
155 element_type if complex_integer_element_type(element_type).is_some() => {
156 let (scalar_type, width) = complex_integer_element_type(element_type)
157 .expect("guarded complex integer element type");
158 let pair_width = width * 2;
159 if !value.little_endian_data.len().is_multiple_of(pair_width) {
160 return Err(ValueCodecError::invalid(
161 path,
162 "complex integer byte length is not element aligned",
163 ));
164 }
165 let mut real = Vec::with_capacity(value.little_endian_data.len() / 2);
166 let mut imag = Vec::with_capacity(value.little_endian_data.len() / 2);
167 for pair in value.little_endian_data.chunks_exact(pair_width) {
168 real.extend_from_slice(&pair[..width]);
169 imag.extend_from_slice(&pair[width..]);
170 }
171 let storage = IntegerComplexStorage::new(
172 decode_integer_storage(scalar_type, &real, path)?,
173 decode_integer_storage(scalar_type, &imag, path)?,
174 )
175 .map_err(|error| ValueCodecError::invalid(path, error))?;
176 Ok(Value::ComplexTensor(
177 ComplexTensor::new_integer(storage, shape)
178 .map_err(|error| ValueCodecError::invalid(path, error))?,
179 ))
180 }
181 element_type => Ok(Value::Tensor(
182 Tensor::new_integer(
183 decode_integer_storage(element_type, &value.little_endian_data, path)?,
184 shape,
185 )
186 .map_err(|error| ValueCodecError::invalid(path, error))?,
187 )),
188 }
189}
190
191fn decode_sparse(value: &SparseValue, path: &str) -> Result<Value, ValueCodecError> {
192 let rows = usize::try_from(value.rows)
193 .map_err(|_| ValueCodecError::invalid(path, "row count exceeds platform limits"))?;
194 let columns = usize::try_from(value.columns)
195 .map_err(|_| ValueCodecError::invalid(path, "column count exceeds platform limits"))?;
196 let column_offsets = usize_indices(&value.column_offsets, path)?;
197 let row_indices = usize_indices(&value.row_indices, path)?;
198 let sparse = match value.element_type {
199 ElementType::F64 => SparseTensor::new(
200 rows,
201 columns,
202 column_offsets,
203 row_indices,
204 decode_f64(&value.little_endian_data, path)?,
205 ),
206 ElementType::F32 => SparseTensor::new_f32(
207 rows,
208 columns,
209 column_offsets,
210 row_indices,
211 decode_f32(&value.little_endian_data, path)?,
212 ),
213 ElementType::Logical
214 | ElementType::ComplexF64
215 | ElementType::ComplexF32
216 | ElementType::ComplexI8
217 | ElementType::ComplexI16
218 | ElementType::ComplexI32
219 | ElementType::ComplexI64
220 | ElementType::ComplexU8
221 | ElementType::ComplexU16
222 | ElementType::ComplexU32
223 | ElementType::ComplexU64 => {
224 return Err(ValueCodecError::unsupported(
225 path,
226 "the runtime has no matching sparse value class",
227 ))
228 }
229 element_type => SparseTensor::new_integer(
230 rows,
231 columns,
232 column_offsets,
233 row_indices,
234 decode_integer_storage(element_type, &value.little_endian_data, path)?,
235 ),
236 }
237 .map_err(|error| ValueCodecError::invalid(path, error))?;
238 Ok(Value::SparseTensor(sparse))
239}
240
241fn complex_integer_element_type(element_type: ElementType) -> Option<(ElementType, usize)> {
242 Some(match element_type {
243 ElementType::ComplexI8 => (ElementType::I8, 1),
244 ElementType::ComplexI16 => (ElementType::I16, 2),
245 ElementType::ComplexI32 => (ElementType::I32, 4),
246 ElementType::ComplexI64 => (ElementType::I64, 8),
247 ElementType::ComplexU8 => (ElementType::U8, 1),
248 ElementType::ComplexU16 => (ElementType::U16, 2),
249 ElementType::ComplexU32 => (ElementType::U32, 4),
250 ElementType::ComplexU64 => (ElementType::U64, 8),
251 _ => return None,
252 })
253}
254
255fn decode_integer_storage(
256 element_type: ElementType,
257 bytes: &[u8],
258 path: &str,
259) -> Result<IntegerStorage, ValueCodecError> {
260 Ok(match element_type {
261 ElementType::I8 => IntegerStorage::I8(bytes.iter().map(|value| *value as i8).collect()),
262 ElementType::I16 => {
263 IntegerStorage::I16(decode_signed::<2, i16>(bytes, path, i16::from_le_bytes)?)
264 }
265 ElementType::I32 => {
266 IntegerStorage::I32(decode_signed::<4, i32>(bytes, path, i32::from_le_bytes)?)
267 }
268 ElementType::I64 => {
269 IntegerStorage::I64(decode_signed::<8, i64>(bytes, path, i64::from_le_bytes)?)
270 }
271 ElementType::U8 => IntegerStorage::U8(bytes.to_vec()),
272 ElementType::U16 => {
273 IntegerStorage::U16(decode_signed::<2, u16>(bytes, path, u16::from_le_bytes)?)
274 }
275 ElementType::U32 => {
276 IntegerStorage::U32(decode_signed::<4, u32>(bytes, path, u32::from_le_bytes)?)
277 }
278 ElementType::U64 => {
279 IntegerStorage::U64(decode_signed::<8, u64>(bytes, path, u64::from_le_bytes)?)
280 }
281 _ => {
282 return Err(ValueCodecError::invalid(
283 path,
284 "element type is not an integer",
285 ))
286 }
287 })
288}
289
290fn decode_signed<const N: usize, T>(
291 bytes: &[u8],
292 path: &str,
293 decode: fn([u8; N]) -> T,
294) -> Result<Vec<T>, ValueCodecError> {
295 decode_arrays(bytes, path)?
296 .into_iter()
297 .map(|word| Ok(decode(word)))
298 .collect()
299}
300
301fn decode_f32(bytes: &[u8], path: &str) -> Result<Vec<f32>, ValueCodecError> {
302 Ok(decode_arrays::<4>(bytes, path)?
303 .into_iter()
304 .map(|word| f32::from_bits(u32::from_le_bytes(word)))
305 .collect())
306}
307
308fn decode_f64(bytes: &[u8], path: &str) -> Result<Vec<f64>, ValueCodecError> {
309 Ok(decode_arrays::<8>(bytes, path)?
310 .into_iter()
311 .map(|word| f64::from_bits(u64::from_le_bytes(word)))
312 .collect())
313}
314
315fn decode_words<const N: usize>(bytes: &[u8], path: &str) -> Result<Vec<u64>, ValueCodecError> {
316 if N != 8 {
317 return Err(ValueCodecError::invalid(path, "invalid word width"));
318 }
319 Ok(decode_arrays::<N>(bytes, path)?
320 .into_iter()
321 .map(|word| {
322 let mut full = [0_u8; 8];
323 full.copy_from_slice(&word);
324 u64::from_le_bytes(full)
325 })
326 .collect())
327}
328
329fn decode_arrays<const N: usize>(
330 bytes: &[u8],
331 path: &str,
332) -> Result<Vec<[u8; N]>, ValueCodecError> {
333 if !bytes.len().is_multiple_of(N) {
334 return Err(ValueCodecError::invalid(
335 path,
336 "encoded data is not aligned to its element width",
337 ));
338 }
339 Ok(bytes
340 .chunks_exact(N)
341 .map(|chunk| chunk.try_into().expect("chunks have exact width"))
342 .collect())
343}
344
345fn decode_exception(value: &ExceptionValue, path: &str) -> Result<Value, ValueCodecError> {
346 if !value.causes.is_empty() {
347 return Err(ValueCodecError::unsupported(
348 path,
349 "nested exception causes are not represented by the current runtime MException",
350 ));
351 }
352 Ok(Value::MException(MException {
353 identifier: value.identifier.clone(),
354 message: value.message.clone(),
355 stack: value.stack.clone(),
356 }))
357}
358
359fn decode_callable(value: &CallableValue, path: &str) -> Result<Value, ValueCodecError> {
360 value
361 .validate_identity()
362 .map_err(|error| ValueCodecError::invalid(path, error.to_string()))?;
363 let captures = value
364 .captures
365 .iter()
366 .enumerate()
367 .map(|(index, value)| decode(value, &format!("{path}.capture[{index}]")))
368 .collect::<Result<Vec<_>, _>>()?;
369 let bound_function = value
370 .owner_identity
371 .strip_prefix("bound:")
372 .map(|value| {
373 value
374 .parse()
375 .map_err(|_| ValueCodecError::invalid(path, "invalid bound callable identity"))
376 })
377 .transpose()?;
378 if !captures.is_empty() {
379 return Ok(Value::Closure(Closure {
380 function_name: value.qualified_name.clone(),
381 bound_function,
382 captures,
383 }));
384 }
385 match value.owner_identity.as_str() {
386 "workspace" => Ok(Value::FunctionHandle(value.qualified_name.clone())),
387 "external" => Ok(Value::ExternalFunctionHandle(value.qualified_name.clone())),
388 "method" => Ok(Value::MethodFunctionHandle(value.qualified_name.clone())),
389 _ if bound_function.is_some() => Ok(Value::BoundFunctionHandle {
390 name: value.qualified_name.clone(),
391 function: bound_function.expect("checked above"),
392 }),
393 _ => Err(ValueCodecError::unsupported(
394 path,
395 "callable owner is not available in this runtime",
396 )),
397 }
398}
399
400fn usize_shape(shape: &[u64], path: &str) -> Result<Vec<usize>, ValueCodecError> {
401 usize_indices(shape, path)
402}
403
404fn usize_indices(values: &[u64], path: &str) -> Result<Vec<usize>, ValueCodecError> {
405 values
406 .iter()
407 .map(|value| {
408 usize::try_from(*value)
409 .map_err(|_| ValueCodecError::invalid(path, "value exceeds platform limits"))
410 })
411 .collect()
412}