Skip to main content

runmat_runtime/execution/value_codec/
mod.rs

1mod decode;
2mod encode;
3mod error;
4
5pub use decode::decode_inline_value;
6pub use encode::encode_inline_value;
7pub use error::ValueCodecError;
8
9#[cfg(test)]
10mod tests {
11    use crate::execution::RuntimeExecutionServices;
12    use runmat_value::{
13        CellArray, CharArray, ComplexTensor, ForeignAffinity, ForeignLifetime, ForeignOwnership,
14        ForeignRef, ForeignTypeIdentity, IntValue, IntegerComplexStorage, IntegerStorage,
15        MException, StringArray, StructValue, Tensor, Value,
16    };
17
18    use runmat_execution::value::{InlineValue, ValuePayload};
19
20    use super::{decode_inline_value, encode_inline_value, ValueCodecError};
21
22    #[test]
23    fn portable_value_roundtrip_is_centralized_and_bit_exact() {
24        let value = Value::Cell(
25            CellArray::new(
26                vec![
27                    Value::Num(f64::from_bits(0x7ff8_0000_0000_0042)),
28                    Value::Int(IntValue::U64(u64::MAX)),
29                ],
30                1,
31                2,
32            )
33            .unwrap(),
34        );
35        let payload = encode_inline_value(&value).unwrap();
36        let Value::Cell(decoded) = decode_inline_value(&payload).unwrap() else {
37            panic!("expected decoded cell");
38        };
39        let Value::Num(number) = decoded.data[0] else {
40            panic!("expected decoded number");
41        };
42        assert_eq!(number.to_bits(), 0x7ff8_0000_0000_0042);
43        assert_eq!(decoded.data[1], Value::Int(IntValue::U64(u64::MAX)));
44        assert_eq!(decoded.shape, vec![1, 2]);
45    }
46
47    #[test]
48    fn stable_immutable_runtime_forms_round_trip_exactly() {
49        let mut structure = StructValue::new();
50        structure.insert(
51            "tensor",
52            Value::Tensor(
53                Tensor::new(vec![f64::from_bits(0x8000_0000_0000_0000), 2.5], vec![2, 1]).unwrap(),
54            ),
55        );
56        structure.insert(
57            "strings",
58            Value::StringArray(StringArray::new(vec!["α".into(), "β".into()], vec![1, 2]).unwrap()),
59        );
60        structure.insert(
61            "chars",
62            Value::CharArray(CharArray::new(vec!['a', 'β'], 2, 1).unwrap()),
63        );
64        structure.insert(
65            "error",
66            Value::MException(MException {
67                identifier: "RunMat:test".into(),
68                message: "failure".into(),
69                stack: vec!["main:1".into()],
70            }),
71        );
72        let value = Value::Struct(structure);
73        let payload = encode_inline_value(&value).unwrap();
74        let decoded = decode_inline_value(&payload).unwrap();
75        assert_eq!(decoded, value);
76    }
77
78    #[test]
79    fn native_complex_classes_round_trip_without_widening() {
80        let single = Value::ComplexTensor(
81            ComplexTensor::from_f32(
82                vec![
83                    (f32::from_bits(0x8000_0000), f32::from_bits(0x7fc0_0042)),
84                    (f32::MAX, f32::MIN_POSITIVE),
85                ],
86                vec![1, 2],
87            )
88            .unwrap(),
89        );
90        let Value::ComplexTensor(decoded_single) =
91            decode_inline_value(&encode_inline_value(&single).unwrap()).unwrap()
92        else {
93            panic!("expected decoded single complex tensor");
94        };
95        let decoded_values = decoded_single.as_f32_slice().unwrap();
96        assert_eq!(decoded_values[0].0.to_bits(), 0x8000_0000);
97        assert_eq!(decoded_values[0].1.to_bits(), 0x7fc0_0042);
98        assert_eq!(decoded_values[1], (f32::MAX, f32::MIN_POSITIVE));
99        assert_eq!(decoded_single.shape, vec![1, 2]);
100
101        let integer_components = [
102            (
103                IntegerStorage::I8(vec![i8::MIN, i8::MAX]),
104                IntegerStorage::I8(vec![i8::MAX, i8::MIN]),
105            ),
106            (
107                IntegerStorage::I16(vec![i16::MIN, i16::MAX]),
108                IntegerStorage::I16(vec![i16::MAX, i16::MIN]),
109            ),
110            (
111                IntegerStorage::I32(vec![i32::MIN, i32::MAX]),
112                IntegerStorage::I32(vec![i32::MAX, i32::MIN]),
113            ),
114            (
115                IntegerStorage::I64(vec![i64::MIN, i64::MAX]),
116                IntegerStorage::I64(vec![i64::MAX, i64::MIN]),
117            ),
118            (
119                IntegerStorage::U8(vec![u8::MIN, u8::MAX]),
120                IntegerStorage::U8(vec![u8::MAX, u8::MIN]),
121            ),
122            (
123                IntegerStorage::U16(vec![u16::MIN, u16::MAX]),
124                IntegerStorage::U16(vec![u16::MAX, u16::MIN]),
125            ),
126            (
127                IntegerStorage::U32(vec![u32::MIN, u32::MAX]),
128                IntegerStorage::U32(vec![u32::MAX, u32::MIN]),
129            ),
130            (
131                IntegerStorage::U64(vec![u64::MIN, u64::MAX]),
132                IntegerStorage::U64(vec![u64::MAX, u64::MIN]),
133            ),
134        ];
135        for (real, imaginary) in integer_components {
136            let value = Value::ComplexTensor(
137                ComplexTensor::new_integer(
138                    IntegerComplexStorage::new(real, imaginary).unwrap(),
139                    vec![2, 1],
140                )
141                .unwrap(),
142            );
143            assert_eq!(
144                decode_inline_value(&encode_inline_value(&value).unwrap()).unwrap(),
145                value
146            );
147        }
148    }
149
150    #[test]
151    fn callable_captures_round_trip_and_failures_name_the_local_path() {
152        let closure = Value::Closure(runmat_value::Closure {
153            function_name: "worker".into(),
154            bound_function: Some(7),
155            captures: vec![Value::Num(2.0)],
156        });
157        assert_eq!(
158            decode_inline_value(&encode_inline_value(&closure).unwrap()).unwrap(),
159            closure
160        );
161        let mut tampered = encode_inline_value(&closure).unwrap();
162        let ValuePayload::Inline(ref mut inline) = tampered else {
163            unreachable!("runtime encoding is inline");
164        };
165        let InlineValue::Callable(callable) = inline.as_mut() else {
166            unreachable!("closure encoding is callable");
167        };
168        callable.qualified_name = "other_worker".into();
169        assert!(decode_inline_value(&tampered).is_err());
170
171        let mut structure = StructValue::new();
172        let service = super::super::RuntimeExecutionService::new();
173        structure.insert(
174            "bad",
175            Value::Future(runmat_execution::FutureHandle {
176                id: runmat_execution::FutureId::derive(&[b"future"]),
177                scope_id: service.scope_id(),
178                outputs: runmat_execution::OutputContract {
179                    requested_outputs: 1,
180                },
181            }),
182        );
183        let error = encode_inline_value(&Value::Cell(
184            CellArray::new(vec![Value::Struct(structure)], 1, 1).unwrap(),
185        ))
186        .unwrap_err();
187        assert!(error.to_string().contains("$[0].bad"));
188    }
189
190    #[test]
191    fn live_execution_handles_are_never_boundary_payloads() {
192        let service = super::super::RuntimeExecutionService::new();
193        let future = runmat_execution::FutureHandle {
194            id: runmat_execution::FutureId::derive(&[b"future"]),
195            scope_id: service.scope_id(),
196            outputs: runmat_execution::OutputContract {
197                requested_outputs: 1,
198            },
199        };
200        assert!(encode_inline_value(&Value::Future(future)).is_err());
201    }
202
203    #[test]
204    fn live_foreign_references_require_a_manifest_adapter() {
205        let reference = ForeignRef {
206            host_identity: "host-a".into(),
207            handle: 17,
208            generation: 2,
209            type_identity: ForeignTypeIdentity {
210                family: "java".into(),
211                name: "java.lang.Object".into(),
212                version: 1,
213            },
214            ownership: ForeignOwnership::Shared,
215            affinity: ForeignAffinity::OriginProcess,
216            lifetime: ForeignLifetime::Session,
217        };
218        let error = encode_inline_value(&Value::Foreign(reference)).unwrap_err();
219        assert!(matches!(
220            error,
221            ValueCodecError::Unsupported { ref path, rule }
222                if path == "$" && rule.contains("interop-manifest adapter")
223        ));
224    }
225}