Skip to main content

tenferro_runtime/
trace.rs

1use std::fmt;
2use std::sync::Arc;
3
4use tenferro_ops::ext_op::ExtensionOp;
5use tenferro_tensor::Tensor;
6
7use crate::extension_cache::ExtensionCacheStore;
8use crate::program::{
9    BindingKey, CoreSemanticOp, FrozenProgram, ProgramBindings, ProgramBuildError,
10    ProgramFinishError, ProgramInputSpec, ProgramValue, ProgramValueMetadata, SemanticProgram,
11    SemanticProgramBuilder,
12};
13
14/// Mutable owner of one backend-neutral semantic trace.
15///
16/// Values issued by one context cannot be used by another. Consuming
17/// [`finish`](Self::finish) freezes the trace into an immutable
18/// [`TracedGraph`].
19pub struct TraceContext {
20    builder: SemanticProgramBuilder,
21    extension_caches: ExtensionCacheStore,
22}
23
24impl TraceContext {
25    /// Construct an empty trace context.
26    pub fn new() -> Self {
27        Self {
28            builder: SemanticProgramBuilder::new(),
29            extension_caches: ExtensionCacheStore::new(),
30        }
31    }
32
33    /// Add one ordered external input.
34    ///
35    /// # Errors
36    ///
37    /// Returns [`ProgramBuildError::TooManyValues`] if another value cannot be
38    /// represented.
39    pub fn input(&mut self, spec: ProgramInputSpec) -> Result<TraceValue, ProgramBuildError> {
40        let value = self.builder.input(spec)?;
41        Ok(TraceValue { value })
42    }
43
44    /// Add one ordered input and attach its default tensor.
45    ///
46    /// # Errors
47    ///
48    /// Returns [`ProgramBuildError::TooManyValues`] when another input cannot be
49    /// represented, [`ProgramBuildError::BindingTargetNotInput`] if the new
50    /// value is not accepted as an input, or
51    /// [`ProgramBuildError::DuplicateBinding`] if it is already bound.
52    pub fn input_with_default(
53        &mut self,
54        spec: ProgramInputSpec,
55        tensor: Arc<Tensor>,
56    ) -> Result<TraceValue, ProgramBuildError> {
57        let input = self.input(spec)?;
58        self.bind_input(input, tensor)?;
59        Ok(input)
60    }
61
62    /// Attach a tensor default to an existing trace input.
63    ///
64    /// # Errors
65    ///
66    /// Returns [`ProgramBuildError::ForeignValue`] for another context's
67    /// value, [`ProgramBuildError::BindingTargetNotInput`] for a computed
68    /// value, or [`ProgramBuildError::DuplicateBinding`] for a repeated
69    /// binding.
70    pub fn bind_input(
71        &mut self,
72        input: TraceValue,
73        tensor: Arc<Tensor>,
74    ) -> Result<BindingKey, ProgramBuildError> {
75        self.builder.bind_input(input.value, tensor)
76    }
77
78    /// Add one canonical core semantic operation.
79    ///
80    /// # Errors
81    ///
82    /// Returns [`ProgramBuildError::ForeignValue`] for an input from another
83    /// context, [`ProgramBuildError::Arity`] for the wrong input count, or
84    /// [`ProgramBuildError::Metadata`] /
85    /// [`ProgramBuildError::OutputMetadataCount`] when inference returns
86    /// invalid metadata.
87    pub fn add_op(
88        &mut self,
89        op: CoreSemanticOp,
90        inputs: &[TraceValue],
91    ) -> Result<Box<[TraceValue]>, ProgramBuildError> {
92        let inputs = program_values(inputs);
93        self.builder.add_op(op, &inputs).map(trace_values)
94    }
95
96    /// Add one extension semantic operation.
97    ///
98    /// # Errors
99    ///
100    /// Returns [`ProgramBuildError::ForeignValue`] or
101    /// [`ProgramBuildError::Arity`] for invalid inputs,
102    /// [`ProgramBuildError::UndeclaredExtensionEffects`] /
103    /// [`ProgramBuildError::UndeclaredExtensionAliases`] for an incomplete
104    /// extension contract, or [`ProgramBuildError::Metadata`] when inference
105    /// fails.
106    pub fn add_extension(
107        &mut self,
108        op: Arc<dyn ExtensionOp>,
109        inputs: &[TraceValue],
110    ) -> Result<Box<[TraceValue]>, ProgramBuildError> {
111        let inputs = program_values(inputs);
112        self.builder.add_extension(op, &inputs).map(trace_values)
113    }
114
115    /// Borrow metadata for a context-owned value.
116    ///
117    /// # Errors
118    ///
119    /// Returns [`ProgramBuildError::ForeignValue`] for a value from another
120    /// context.
121    pub fn value_metadata(
122        &self,
123        value: TraceValue,
124    ) -> Result<&ProgramValueMetadata, ProgramBuildError> {
125        self.builder.value_metadata(value.value)
126    }
127
128    /// Borrow the generic trace-time extension cache store.
129    pub fn extension_caches_mut(&mut self) -> &mut ExtensionCacheStore {
130        &mut self.extension_caches
131    }
132
133    /// Consume and atomically freeze this context.
134    ///
135    /// Ordered and duplicate outputs are preserved.
136    ///
137    /// # Errors
138    ///
139    /// Returns [`ProgramFinishError::ForeignOutput`] if an output belongs to
140    /// another context, or a typed structural/binding finalization failure.
141    pub fn finish(self, outputs: &[TraceValue]) -> Result<TracedGraph, ProgramFinishError> {
142        let outputs = program_values(outputs);
143        let frozen = self.builder.finish(&outputs)?;
144        Ok(TracedGraph { frozen })
145    }
146}
147
148impl Default for TraceContext {
149    fn default() -> Self {
150        Self::new()
151    }
152}
153
154/// Opaque value handle owned by one [`TraceContext`].
155#[derive(Clone, Copy)]
156pub struct TraceValue {
157    value: ProgramValue,
158}
159
160impl fmt::Debug for TraceValue {
161    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162        formatter.write_str("TraceValue(<opaque>)")
163    }
164}
165
166/// Immutable backend-neutral output of one completed trace.
167#[derive(Clone)]
168pub struct TracedGraph {
169    frozen: FrozenProgram,
170}
171
172impl TracedGraph {
173    /// Borrow the immutable semantic program.
174    pub fn program(&self) -> &SemanticProgram {
175        &self.frozen.program
176    }
177
178    /// Borrow process-local tensor defaults and large constants.
179    pub fn bindings(&self) -> &ProgramBindings {
180        &self.frozen.bindings
181    }
182
183    pub(crate) fn frozen(&self) -> &FrozenProgram {
184        &self.frozen
185    }
186}
187
188impl fmt::Debug for TracedGraph {
189    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
190        formatter
191            .debug_struct("TracedGraph")
192            .field("inputs", &self.program().inputs().len())
193            .field("outputs", &self.program().outputs().len())
194            .field("bindings", &self.bindings().len())
195            .field(
196                "semantic_fingerprint",
197                &self.program().semantic_fingerprint(),
198            )
199            .finish()
200    }
201}
202
203fn program_values(values: &[TraceValue]) -> Vec<ProgramValue> {
204    values.iter().map(|value| value.value).collect()
205}
206
207fn trace_values(values: Box<[ProgramValue]>) -> Box<[TraceValue]> {
208    values
209        .into_vec()
210        .into_iter()
211        .map(|value| TraceValue { value })
212        .collect()
213}
214
215#[cfg(test)]
216mod tests {
217    use tenferro_ops::ShapeExtent;
218    use tenferro_tensor::DType;
219
220    use super::*;
221
222    #[test]
223    fn unknown_input_extents_keep_ordered_semantic_inputs() {
224        let spec = || {
225            ProgramInputSpec::from_metadata(ProgramValueMetadata::from_extents(
226                DType::F64,
227                [ShapeExtent::Unknown],
228            ))
229        };
230        let mut context = TraceContext::new();
231        let first = context.input(spec()).unwrap();
232        let second = context.input(spec()).unwrap();
233        let graph = context.finish(&[first, second]).unwrap();
234
235        assert_eq!(graph.program().inputs().len(), 2);
236        for input in graph.program().inputs() {
237            assert_eq!(
238                graph.program().value_metadata(*input).unwrap().shape(),
239                [ShapeExtent::Unknown]
240            );
241        }
242    }
243}