1use std::collections::BTreeMap;
2
3use runmat_types::{
4 AliasFact, CallableFact, CallableIdentity, CellFact, CertaintyFact, ContiguityFact,
5 DimensionFact, DynamicReason, ExceptionFact, ExecutionFact, ForeignAffinity,
6 ForeignAffinityFact, ForeignFact, ForeignLifetime, ForeignLifetimeFact, ForeignOwnership,
7 ForeignOwnershipFact, InvalidationVector, LayoutFact, MutationFact, NumericClass,
8 NumericDomain, NumericFact, ObjectFact, OutputListFact, QualifiedName, ResidencyFact,
9 ShapeFact, StorageFact, StructFact, SymbolName, ValueFact, ValueKindFact, ViewFact,
10};
11use runmat_value::{IntValue, NumericDType, Value};
12
13pub fn value_fact(value: &Value) -> ValueFact {
19 match value {
20 Value::Int(value) => numeric_scalar(int_class(value), NumericDomain::Real),
21 Value::Num(_) => numeric_scalar(NumericClass::Double, NumericDomain::Real),
22 Value::Complex(_, _) => numeric_scalar(NumericClass::Double, NumericDomain::Complex),
23 Value::Bool(_) => scalar(ValueKindFact::Logical),
24 Value::LogicalArray(value) => dense(ValueKindFact::Logical, &value.shape),
25 Value::String(_) => scalar(ValueKindFact::String),
26 Value::StringArray(value) => dense(ValueKindFact::String, &value.shape),
27 Value::CharArray(value) => dense(ValueKindFact::Character, &value.shape),
28 Value::Tensor(value) => dense(
29 numeric_kind(dtype_class(value.numeric_dtype()), NumericDomain::Real),
30 &value.shape,
31 ),
32 Value::SparseTensor(value) => {
33 let kind = value
34 .numeric_dtype()
35 .map_or(ValueKindFact::Logical, |dtype| {
36 numeric_kind(dtype_class(dtype), NumericDomain::Real)
37 });
38 sparse(kind, &value.shape())
39 }
40 Value::ComplexTensor(value) => dense(
41 numeric_kind(dtype_class(value.numeric_dtype()), NumericDomain::Complex),
42 &value.shape,
43 ),
44 Value::Symbolic(_) => scalar(ValueKindFact::Symbolic),
45 Value::SymbolicArray(value) => dense(ValueKindFact::Symbolic, &value.shape),
46 Value::Cell(value) => {
47 let elements = value.data.iter().map(value_fact).collect::<Vec<_>>();
48 let element = elements
49 .iter()
50 .cloned()
51 .reduce(|left, right| runmat_types::FactJoin::join(&left, &right))
52 .unwrap_or_else(|| ValueFact::unknown(DynamicReason::RuntimeValue));
53 dense(
54 ValueKindFact::Cell(CellFact {
55 element: Box::new(element),
56 elements,
57 elements_complete: true,
58 }),
59 &value.shape,
60 )
61 }
62 Value::Struct(value) => scalar(ValueKindFact::Struct(StructFact {
63 fields: value
64 .fields
65 .iter()
66 .map(|(name, value)| (name.clone(), value_fact(value)))
67 .collect(),
68 fields_complete: true,
69 })),
70 Value::GpuTensor(value) => {
71 let kind = gpu_kind(value);
72 ValueFact {
73 kind: kind.clone().unwrap_or(ValueKindFact::Unknown),
74 shape: shape(&value.shape),
75 storage: StorageFact::Opaque,
76 layout: LayoutFact::Unknown,
77 contiguity: ContiguityFact::Unknown,
78 view: ViewFact::Unknown,
79 residency: ResidencyFact::Device {
80 provider: Some(format!("device:{}", value.device_id)),
81 },
82 alias: AliasFact::Identity,
83 mutation: MutationFact::HandleSemantics,
84 certainty: if kind.is_some() {
85 CertaintyFact::Proven
86 } else {
87 CertaintyFact::Dynamic(DynamicReason::UnsupportedRepresentation)
88 },
89 invalidation: InvalidationVector::default(),
90 }
91 }
92 Value::Object(value) => object(
93 &value.class_name,
94 value
95 .properties
96 .iter()
97 .map(|(name, value)| (name.clone(), value_fact(value)))
98 .collect(),
99 true,
100 false,
101 ShapeFact::Scalar,
102 ),
103 Value::ObjectArray(value) => object(
104 value.class_name(),
105 BTreeMap::new(),
106 false,
107 value
108 .data()
109 .iter()
110 .any(|element| matches!(element, Value::HandleObject(_))),
111 shape(value.shape()),
112 ),
113 Value::HandleObject(value) => object(
114 &value.class_name,
115 BTreeMap::new(),
116 false,
117 true,
118 ShapeFact::Scalar,
119 ),
120 Value::Listener(value) => object(
121 &value.target_class_name,
122 BTreeMap::new(),
123 false,
124 true,
125 ShapeFact::Scalar,
126 ),
127 Value::OutputList(values) => scalar(ValueKindFact::OutputList(OutputListFact {
128 outputs: values.iter().map(value_fact).collect(),
129 variadic: false,
130 })),
131 Value::FunctionHandle(name) => {
132 callable(CallableIdentity::DynamicName(SymbolName(name.clone())))
133 }
134 Value::ExternalFunctionHandle(name) => {
135 callable(CallableIdentity::ExternalName(qualified_name(name)))
136 }
137 Value::MethodFunctionHandle(name) => callable(CallableIdentity::Method(
138 runmat_types::MethodId(name.clone()),
139 )),
140 Value::BoundFunctionHandle { function, .. } => callable(CallableIdentity::BoundFunction(
141 runmat_types::FunctionId(*function),
142 )),
143 Value::Closure(value) => {
144 let mut fact = callable_kind(CallableIdentity::DynamicName(SymbolName(
145 value.function_name.clone(),
146 )));
147 fact.captures = value.captures.iter().map(value_fact).collect();
148 scalar(ValueKindFact::Callable(fact))
149 }
150 Value::ClassRef(name) => scalar(ValueKindFact::ClassReference(
151 runmat_types::ClassReferenceFact {
152 class: None,
153 runtime_class: Some(qualified_name(name)),
154 },
155 )),
156 Value::MException(value) => scalar(ValueKindFact::Exception(ExceptionFact {
157 identifier: Some(value.identifier.clone()),
158 })),
159 Value::Future(_) => execution(ExecutionFact::Future {
160 output: Box::new(ValueFact::unknown(DynamicReason::RuntimeValue)),
161 state: runmat_types::FutureStateFact::Unknown,
162 }),
163 Value::Task(_) => execution(ExecutionFact::Task {
164 output: Box::new(ValueFact::unknown(DynamicReason::RuntimeValue)),
165 spawn_safety: runmat_types::SpawnSafetyFact::RequiresIsolation,
166 }),
167 Value::Pool(_) => execution(ExecutionFact::Pool),
168 Value::Job(_) => execution(ExecutionFact::Job {
169 output: Box::new(ValueFact::unknown(DynamicReason::RuntimeValue)),
170 }),
171 Value::Foreign(reference) => {
172 let mut fact = fact(
173 ValueKindFact::Foreign(ForeignFact {
174 family: reference.type_identity.family.clone(),
175 type_name: Some(reference.type_identity.name.clone()),
176 type_version: Some(reference.type_identity.version),
177 ownership: match reference.ownership {
178 ForeignOwnership::Borrowed => ForeignOwnershipFact::Borrowed,
179 ForeignOwnership::Owned => ForeignOwnershipFact::Owned,
180 ForeignOwnership::Shared => ForeignOwnershipFact::Shared,
181 },
182 affinity: match reference.affinity {
183 ForeignAffinity::AnyThread => ForeignAffinityFact::AnyThread,
184 ForeignAffinity::OriginThread => ForeignAffinityFact::OriginThread,
185 ForeignAffinity::OriginProcess => ForeignAffinityFact::OriginProcess,
186 ForeignAffinity::RemoteHost => ForeignAffinityFact::RemoteHost,
187 },
188 lifetime: match reference.lifetime {
189 ForeignLifetime::Call => ForeignLifetimeFact::Call,
190 ForeignLifetime::Session => ForeignLifetimeFact::Session,
191 ForeignLifetime::Persistent => ForeignLifetimeFact::Persistent,
192 ForeignLifetime::External => ForeignLifetimeFact::External,
193 },
194 }),
195 ShapeFact::Scalar,
196 StorageFact::Opaque,
197 );
198 fact.alias = AliasFact::Identity;
199 fact.mutation = MutationFact::HandleSemantics;
200 fact
201 }
202 }
203}
204
205fn numeric_scalar(class: NumericClass, domain: NumericDomain) -> ValueFact {
206 scalar(numeric_kind(class, domain))
207}
208
209fn numeric_kind(class: NumericClass, domain: NumericDomain) -> ValueKindFact {
210 ValueKindFact::Numeric(NumericFact { class, domain })
211}
212
213fn scalar(kind: ValueKindFact) -> ValueFact {
214 fact(kind, ShapeFact::Scalar, StorageFact::Scalar)
215}
216
217fn dense(kind: ValueKindFact, dimensions: &[usize]) -> ValueFact {
218 fact(kind, shape(dimensions), StorageFact::Dense)
219}
220
221fn sparse(kind: ValueKindFact, dimensions: &[usize]) -> ValueFact {
222 fact(kind, shape(dimensions), StorageFact::Sparse)
223}
224
225fn fact(kind: ValueKindFact, shape: ShapeFact, storage: StorageFact) -> ValueFact {
226 ValueFact {
227 kind,
228 shape,
229 storage,
230 layout: LayoutFact::ColumnMajor,
231 contiguity: ContiguityFact::Contiguous,
232 view: ViewFact::Materialized,
233 residency: ResidencyFact::Host,
234 alias: AliasFact::Unique,
235 mutation: MutationFact::ValueSemantics,
236 certainty: CertaintyFact::Proven,
237 invalidation: InvalidationVector::default(),
238 }
239}
240
241fn object(
242 class_name: &str,
243 properties: BTreeMap<String, ValueFact>,
244 properties_complete: bool,
245 handle_semantics: bool,
246 shape: ShapeFact,
247) -> ValueFact {
248 let mut fact = fact(
249 ValueKindFact::Object(ObjectFact {
250 class: None,
251 runtime_class: Some(qualified_name(class_name)),
252 properties,
253 properties_complete,
254 handle_semantics: Some(handle_semantics),
255 }),
256 shape,
257 StorageFact::Opaque,
258 );
259 if handle_semantics {
260 fact.alias = AliasFact::Identity;
261 fact.mutation = MutationFact::HandleSemantics;
262 }
263 fact
264}
265
266fn callable(identity: CallableIdentity) -> ValueFact {
267 scalar(ValueKindFact::Callable(callable_kind(identity)))
268}
269
270fn callable_kind(identity: CallableIdentity) -> CallableFact {
271 CallableFact {
272 identity: Some(identity),
273 parameters: Vec::new(),
274 parameters_complete: false,
275 outputs: Vec::new(),
276 outputs_complete: false,
277 variadic_inputs: true,
278 variadic_outputs: true,
279 captures: Vec::new(),
280 captures_complete: true,
281 }
282}
283
284fn execution(execution: ExecutionFact) -> ValueFact {
285 let mut fact = scalar(ValueKindFact::Execution(execution));
286 fact.alias = AliasFact::Identity;
287 fact.mutation = MutationFact::HandleSemantics;
288 fact
289}
290
291fn shape(dimensions: &[usize]) -> ShapeFact {
292 ShapeFact::Shaped {
293 dims: dimensions
294 .iter()
295 .copied()
296 .map(DimensionFact::Known)
297 .collect(),
298 }
299}
300
301fn qualified_name(name: &str) -> QualifiedName {
302 QualifiedName(
303 name.split('.')
304 .map(|segment| SymbolName(segment.to_owned()))
305 .collect(),
306 )
307}
308
309fn int_class(value: &IntValue) -> NumericClass {
310 match value {
311 IntValue::I8(_) => NumericClass::Int8,
312 IntValue::I16(_) => NumericClass::Int16,
313 IntValue::I32(_) => NumericClass::Int32,
314 IntValue::I64(_) => NumericClass::Int64,
315 IntValue::U8(_) => NumericClass::UInt8,
316 IntValue::U16(_) => NumericClass::UInt16,
317 IntValue::U32(_) => NumericClass::UInt32,
318 IntValue::U64(_) => NumericClass::UInt64,
319 }
320}
321
322fn dtype_class(value: NumericDType) -> NumericClass {
323 match value {
324 NumericDType::F64 => NumericClass::Double,
325 NumericDType::F32 => NumericClass::Single,
326 NumericDType::I8 => NumericClass::Int8,
327 NumericDType::I16 => NumericClass::Int16,
328 NumericDType::I32 => NumericClass::Int32,
329 NumericDType::I64 => NumericClass::Int64,
330 NumericDType::U8 => NumericClass::UInt8,
331 NumericDType::U16 => NumericClass::UInt16,
332 NumericDType::U32 => NumericClass::UInt32,
333 NumericDType::U64 => NumericClass::UInt64,
334 }
335}
336
337fn gpu_kind(handle: &runmat_accelerate_api::GpuTensorHandle) -> Option<ValueKindFact> {
338 use runmat_accelerate_api::{GpuTensorStorage, IntegerElementType, ProviderPrecision};
339
340 if runmat_accelerate_api::handle_is_logical(handle) {
341 return Some(ValueKindFact::Logical);
342 }
343 let class = runmat_accelerate_api::handle_integer_type(handle)
344 .map(|integer| match integer {
345 IntegerElementType::I8 => NumericClass::Int8,
346 IntegerElementType::I16 => NumericClass::Int16,
347 IntegerElementType::I32 => NumericClass::Int32,
348 IntegerElementType::I64 => NumericClass::Int64,
349 IntegerElementType::U8 => NumericClass::UInt8,
350 IntegerElementType::U16 => NumericClass::UInt16,
351 IntegerElementType::U32 => NumericClass::UInt32,
352 IntegerElementType::U64 => NumericClass::UInt64,
353 })
354 .or_else(|| match runmat_accelerate_api::handle_precision(handle) {
355 Some(ProviderPrecision::F32) => Some(NumericClass::Single),
356 Some(ProviderPrecision::F64) => Some(NumericClass::Double),
357 None => match runmat_accelerate_api::handle_class_name(handle).as_deref() {
358 Some("single") => Some(NumericClass::Single),
359 Some("double") => Some(NumericClass::Double),
360 _ => None,
361 },
362 })?;
363 let domain = match runmat_accelerate_api::handle_storage(handle) {
364 GpuTensorStorage::Real => NumericDomain::Real,
365 GpuTensorStorage::ComplexInterleaved => NumericDomain::Complex,
366 };
367 Some(numeric_kind(class, domain))
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373 use runmat_accelerate_api::{
374 clear_handle_metadata, GpuTensorHandle, GpuTensorStorage, NumericElementType,
375 };
376 use runmat_value::{CellArray, ForeignRef, Tensor};
377
378 #[test]
379 fn preserves_recursive_numeric_class_and_shape() {
380 let value = Value::Cell(
381 CellArray::new_with_shape(
382 vec![
383 Value::Tensor(Tensor::from_f32(vec![1.0, 2.0], vec![1, 2]).unwrap()),
384 Value::Tensor(Tensor::from_f32(vec![3.0, 4.0], vec![1, 2]).unwrap()),
385 ],
386 vec![1, 2],
387 )
388 .unwrap(),
389 );
390 let fact = value_fact(&value);
391 let ValueKindFact::Cell(cell) = fact.kind else {
392 panic!("expected cell fact");
393 };
394 assert!(matches!(
395 cell.element.kind,
396 ValueKindFact::Numeric(NumericFact {
397 class: NumericClass::Single,
398 domain: NumericDomain::Real
399 })
400 ));
401 assert_eq!(fact.shape, shape(&[1, 2]));
402 assert_eq!(cell.elements.len(), 2);
403 assert!(cell.elements_complete);
404 }
405
406 #[test]
407 fn preserves_device_integer_class_domain_shape_and_residency() {
408 let handle = GpuTensorHandle::new(vec![2, 3], 71, 9).with_numeric_descriptor(
409 NumericElementType::U64,
410 GpuTensorStorage::ComplexInterleaved,
411 );
412 let fact = value_fact(&Value::GpuTensor(handle.clone()));
413 assert_eq!(
414 fact.kind,
415 ValueKindFact::Numeric(NumericFact {
416 class: NumericClass::UInt64,
417 domain: NumericDomain::Complex,
418 })
419 );
420 assert_eq!(fact.shape, shape(&[2, 3]));
421 assert_eq!(
422 fact.residency,
423 ResidencyFact::Device {
424 provider: Some("device:71".into())
425 }
426 );
427 assert_eq!(fact.certainty, CertaintyFact::Proven);
428 clear_handle_metadata(&handle);
429 }
430
431 #[test]
432 fn unknown_device_element_type_is_an_explicit_dynamic_boundary() {
433 let handle = GpuTensorHandle::new(vec![1, 4], 73, 11);
434 clear_handle_metadata(&handle);
435 let fact = value_fact(&Value::GpuTensor(handle));
436 assert_eq!(fact.kind, ValueKindFact::Unknown);
437 assert_eq!(
438 fact.certainty,
439 CertaintyFact::Dynamic(DynamicReason::UnsupportedRepresentation)
440 );
441 }
442
443 #[test]
444 fn foreign_reference_preserves_static_type_and_lifecycle_without_live_identity() {
445 let value = Value::Foreign(ForeignRef {
446 host_identity: "worker-7".into(),
447 handle: 91,
448 generation: 4,
449 type_identity: runmat_types::ForeignTypeIdentity {
450 family: "java".into(),
451 name: "java.lang.StringBuilder".into(),
452 version: 2,
453 },
454 ownership: ForeignOwnership::Shared,
455 affinity: ForeignAffinity::OriginProcess,
456 lifetime: ForeignLifetime::Session,
457 });
458
459 let fact = value_fact(&value);
460 assert_eq!(
461 fact.kind,
462 ValueKindFact::Foreign(ForeignFact {
463 family: "java".into(),
464 type_name: Some("java.lang.StringBuilder".into()),
465 type_version: Some(2),
466 ownership: ForeignOwnershipFact::Shared,
467 affinity: ForeignAffinityFact::OriginProcess,
468 lifetime: ForeignLifetimeFact::Session,
469 })
470 );
471 assert_eq!(fact.shape, ShapeFact::Scalar);
472 assert_eq!(fact.storage, StorageFact::Opaque);
473 assert_eq!(fact.alias, AliasFact::Identity);
474 assert_eq!(fact.mutation, MutationFact::HandleSemantics);
475 }
476}