Skip to main content

tenferro_runtime/program/
transform.rs

1use std::collections::HashMap;
2
3use super::{
4    FrozenProgram, ImportedProgramValues, ProgramImport, ProgramValue, SemanticFingerprint,
5    SemanticProgramBuilder, SemanticTransformError,
6};
7
8/// Opaque fixed-size identity of one semantic transform implementation/configuration.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub struct TransformIdentity([u8; 16]);
11
12impl TransformIdentity {
13    /// Construct a caller-stable transform identity.
14    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
15        Self(bytes)
16    }
17
18    /// Borrow the fixed-size identity bytes.
19    pub const fn as_bytes(&self) -> &[u8; 16] {
20        &self.0
21    }
22}
23
24/// Validation-preserving destination transaction exposed to transforms.
25pub struct SemanticTransformContext<'a> {
26    builder: &'a mut SemanticProgramBuilder,
27}
28
29impl SemanticTransformContext<'_> {
30    /// Import source structure, bindings, and ordered roots atomically.
31    ///
32    /// # Errors
33    ///
34    /// Returns [`SemanticTransformError::Build`] when the underlying
35    /// [`SemanticProgramBuilder`](super::SemanticProgramBuilder) rejects
36    /// foreign roots/bindings, invalid structure, or an unrepresentable value
37    /// count.
38    pub fn import_program(
39        &mut self,
40        input: &FrozenProgram,
41        roots: &[ProgramValue],
42    ) -> Result<ImportedProgramValues, SemanticTransformError> {
43        self.builder
44            .import(ProgramImport {
45                program: input.program.as_ref(),
46                bindings: &input.bindings,
47                roots,
48            })
49            .map_err(SemanticTransformError::from)
50    }
51
52    /// Borrow the destination builder for validated semantic additions.
53    pub fn builder(&mut self) -> &mut SemanticProgramBuilder {
54        self.builder
55    }
56}
57
58/// Object-safe transformation over immutable semantic programs and bindings.
59///
60/// # Examples
61///
62/// ```
63/// use tenferro_runtime::program::{
64///     FrozenProgram, ProgramValue, SemanticTransform, SemanticTransformContext,
65///     SemanticTransformError, TransformIdentity,
66/// };
67///
68/// struct Identity;
69/// impl SemanticTransform for Identity {
70///     fn identity(&self) -> TransformIdentity {
71///         TransformIdentity::from_bytes([7; 16])
72///     }
73///
74///     fn apply(
75///         &self,
76///         context: &mut SemanticTransformContext<'_>,
77///         input: &FrozenProgram,
78///     ) -> Result<Box<[ProgramValue]>, SemanticTransformError> {
79///         Ok(context
80///             .import_program(input, input.program.outputs())?
81///             .roots()
82///             .into())
83///     }
84/// }
85///
86/// fn accepts_object_safe(_: &dyn SemanticTransform) {}
87/// accepts_object_safe(&Identity);
88/// ```
89pub trait SemanticTransform: Send + Sync {
90    /// Return caller-stable identity including transform configuration.
91    fn identity(&self) -> TransformIdentity;
92
93    /// Import/build into the supplied fresh destination and return local roots.
94    ///
95    /// # Errors
96    ///
97    /// Implementations return [`SemanticTransformError::Build`] for validated
98    /// builder/import failures or [`SemanticTransformError::Rejected`] when
99    /// their semantic policy does not support the input. The compiler driver
100    /// additionally reports [`SemanticTransformError::ForeignReturnedValue`],
101    /// [`SemanticTransformError::DroppedBindings`], or
102    /// [`SemanticTransformError::Finish`] after this callback returns.
103    fn apply(
104        &self,
105        context: &mut SemanticTransformContext<'_>,
106        input: &FrozenProgram,
107    ) -> Result<Box<[ProgramValue]>, SemanticTransformError>;
108}
109
110#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
111pub(crate) fn apply_semantic_transform(
112    input: &FrozenProgram,
113    transform: &dyn SemanticTransform,
114) -> Result<FrozenProgram, SemanticTransformError> {
115    let mut builder = SemanticProgramBuilder::new();
116    let roots = {
117        let mut context = SemanticTransformContext {
118            builder: &mut builder,
119        };
120        transform.apply(&mut context, input)?
121    };
122    if roots
123        .iter()
124        .any(|root| builder.validate_value(*root).is_err())
125    {
126        return Err(SemanticTransformError::ForeignReturnedValue);
127    }
128    let output = builder.finish(&roots)?;
129    if !output.bindings.preserves_all(&input.bindings) {
130        return Err(SemanticTransformError::DroppedBindings);
131    }
132    Ok(output)
133}
134
135#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
136pub(crate) fn apply_semantic_transforms(
137    input: &FrozenProgram,
138    transforms: &[&dyn SemanticTransform],
139) -> Result<FrozenProgram, SemanticTransformError> {
140    let mut current = input.clone();
141    for transform in transforms {
142        current = apply_semantic_transform(&current, *transform)?;
143    }
144    Ok(current)
145}
146
147#[derive(Clone, PartialEq, Eq, Hash)]
148#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
149struct TransformCacheKey {
150    input: SemanticFingerprint,
151    ordered_transforms: Box<[TransformIdentity]>,
152}
153
154#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
155struct TransformCacheEntry {
156    input: FrozenProgram,
157    output: FrozenProgram,
158}
159
160/// Collision-safe cache used by the later graph compiler transform pipeline.
161#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
162pub(crate) struct SemanticTransformCache {
163    buckets: HashMap<TransformCacheKey, Vec<TransformCacheEntry>>,
164    len: usize,
165}
166
167#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
168impl SemanticTransformCache {
169    pub(crate) fn new() -> Self {
170        Self {
171            buckets: HashMap::new(),
172            len: 0,
173        }
174    }
175
176    pub(crate) fn len(&self) -> usize {
177        self.len
178    }
179
180    pub(crate) fn apply(
181        &mut self,
182        input: &FrozenProgram,
183        transforms: &[&dyn SemanticTransform],
184    ) -> Result<FrozenProgram, SemanticTransformError> {
185        let key = TransformCacheKey {
186            input: input.program.semantic_fingerprint(),
187            ordered_transforms: transforms
188                .iter()
189                .map(|transform| transform.identity())
190                .collect(),
191        };
192        if let Some(output) = self.buckets.get(&key).and_then(|bucket| {
193            bucket.iter().find_map(|entry| {
194                (entry.input.program.semantic_eq(input.program.as_ref())
195                    && entry.input.bindings.cache_exact_eq(&input.bindings))
196                .then(|| entry.output.clone())
197            })
198        }) {
199            return Ok(output);
200        }
201
202        let output = apply_semantic_transforms(input, transforms)?;
203        self.buckets
204            .entry(key)
205            .or_default()
206            .push(TransformCacheEntry {
207                input: input.clone(),
208                output: output.clone(),
209            });
210        self.len += 1;
211        Ok(output)
212    }
213}