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: &input.bindings,
47 roots,
48 })
49 .map_err(SemanticTransformError::from)
50 }
51
52 pub fn builder(&mut self) -> &mut SemanticProgramBuilder {
54 self.builder
55 }
56}
57
58pub trait SemanticTransform: Send + Sync {
90 fn identity(&self) -> TransformIdentity;
92
93 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(¤t, *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#[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}