tenferro_runtime/program/
transform.rs1use std::collections::HashMap;
2
3use super::{
4 FrozenProgram, ImportedProgramValues, ProgramImport, ProgramValue, SemanticFingerprint,
5 SemanticProgramBuilder, SemanticTransformError,
6};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub struct TransformIdentity([u8; 16]);
11
12impl TransformIdentity {
13 pub const fn from_bytes(bytes: [u8; 16]) -> Self {
15 Self(bytes)
16 }
17
18 pub const fn as_bytes(&self) -> &[u8; 16] {
20 &self.0
21 }
22}
23
24pub struct SemanticTransformContext<'a> {
26 builder: &'a mut SemanticProgramBuilder,
27}
28
29impl SemanticTransformContext<'_> {
30 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: Some(&input.bindings),
47 roots,
48 input_map: &[],
49 })
50 .map_err(SemanticTransformError::from)
51 }
52
53 pub fn builder(&mut self) -> &mut SemanticProgramBuilder {
55 self.builder
56 }
57}
58
59pub trait SemanticTransform: Send + Sync {
91 fn identity(&self) -> TransformIdentity;
93
94 fn apply(
105 &self,
106 context: &mut SemanticTransformContext<'_>,
107 input: &FrozenProgram,
108 ) -> Result<Box<[ProgramValue]>, SemanticTransformError>;
109}
110
111#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
112pub(crate) fn apply_semantic_transform(
113 input: &FrozenProgram,
114 transform: &dyn SemanticTransform,
115) -> Result<FrozenProgram, SemanticTransformError> {
116 let mut builder = SemanticProgramBuilder::new();
117 let roots = {
118 let mut context = SemanticTransformContext {
119 builder: &mut builder,
120 };
121 transform.apply(&mut context, input)?
122 };
123 if roots
124 .iter()
125 .any(|root| builder.validate_value(*root).is_err())
126 {
127 return Err(SemanticTransformError::ForeignReturnedValue);
128 }
129 let output = builder.finish(&roots)?;
130 if !output.bindings.preserves_all(&input.bindings) {
131 return Err(SemanticTransformError::DroppedBindings);
132 }
133 Ok(output)
134}
135
136#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
137pub(crate) fn apply_semantic_transforms(
138 input: &FrozenProgram,
139 transforms: &[&dyn SemanticTransform],
140) -> Result<FrozenProgram, SemanticTransformError> {
141 let mut current = input.clone();
142 for transform in transforms {
143 current = apply_semantic_transform(¤t, *transform)?;
144 }
145 Ok(current)
146}
147
148#[derive(Clone, PartialEq, Eq, Hash)]
149#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
150struct TransformCacheKey {
151 input: SemanticFingerprint,
152 ordered_transforms: Box<[TransformIdentity]>,
153}
154
155#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
156struct TransformCacheEntry {
157 input: FrozenProgram,
158 output: FrozenProgram,
159}
160
161#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
163pub(crate) struct SemanticTransformCache {
164 buckets: HashMap<TransformCacheKey, Vec<TransformCacheEntry>>,
165 len: usize,
166}
167
168#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
169impl SemanticTransformCache {
170 pub(crate) fn new() -> Self {
171 Self {
172 buckets: HashMap::new(),
173 len: 0,
174 }
175 }
176
177 pub(crate) fn len(&self) -> usize {
178 self.len
179 }
180
181 pub(crate) fn apply(
182 &mut self,
183 input: &FrozenProgram,
184 transforms: &[&dyn SemanticTransform],
185 ) -> Result<FrozenProgram, SemanticTransformError> {
186 let key = TransformCacheKey {
187 input: input.program.semantic_fingerprint(),
188 ordered_transforms: transforms
189 .iter()
190 .map(|transform| transform.identity())
191 .collect(),
192 };
193 if let Some(output) = self.buckets.get(&key).and_then(|bucket| {
194 bucket.iter().find_map(|entry| {
195 (entry.input.program.semantic_eq(input.program.as_ref())
196 && entry.input.bindings.cache_exact_eq(&input.bindings))
197 .then(|| entry.output.clone())
198 })
199 }) {
200 return Ok(output);
201 }
202
203 let output = apply_semantic_transforms(input, transforms)?;
204 self.buckets
205 .entry(key)
206 .or_default()
207 .push(TransformCacheEntry {
208 input: input.clone(),
209 output: output.clone(),
210 });
211 self.len += 1;
212 Ok(output)
213 }
214}