runmat_runtime/execution/value_codec/
mod.rs1mod 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}