Skip to main content

cel_runtime/
prepared_segment.rs

1//! Executes one construction-validated canonical borrowed frame.
2//!
3//! ```
4//! use cel_runtime::{DynSegment, Nil, Leaf, IntoCStackList};
5//! let mut body = DynSegment::new::<()>();
6//! body.just(7i32);
7//! let mut prepared = body.prepare::<Nil, Leaf<i32>>().unwrap();
8//! assert_eq!(prepared.call(&().into_c_stack_list()).unwrap(), 7);
9//! ```
10
11use std::marker::PhantomData;
12
13use crate::container_execution::{ArgumentPlan, ResultPlan};
14use crate::{DynSegment, ValueRepr};
15
16/// Owns the same frozen raw engine as [`PreparedSegment`] with runtime-bound witnesses.
17///
18/// Hosts validate their storage adapters once, then lend exact canonical pointers.
19pub struct PreparedRawSegment {
20    body: DynSegment,
21    result: ResultPlan,
22    arity: usize,
23}
24
25impl PreparedRawSegment {
26    /// Binds every argument read and the complete result before any operation executes.
27    ///
28    /// # Errors
29    /// Rejects dependent bodies, undeclared or mismatched reads, missing cloning,
30    /// and a mismatched result.
31    /// - Complexity: O(n) in descriptor nodes and emitted reads.
32    ///
33    /// # Examples
34    /// ```
35    /// use cel_runtime::{DynSegment, ValueType, prepared_segment::PreparedRawSegment};
36    /// let mut body = DynSegment::new::<()>();
37    /// body.just(7i32);
38    /// let mut body = PreparedRawSegment::new(body, &[], &ValueType::leaf::<i32>())?;
39    /// let result = unsafe { body.call(&[]) }?;
40    /// assert_eq!(result.try_into_typed::<cel_runtime::Leaf<i32>>()?, 7);
41    /// # Ok::<(), anyhow::Error>(())
42    /// ```
43    pub fn new(
44        body: DynSegment,
45        arguments: &[crate::ValueType],
46        result: &crate::ValueType,
47    ) -> anyhow::Result<Self> {
48        body.validate_independent_body()?;
49        for (index, read) in &body.argument_reads {
50            let expected = arguments
51                .get(*index)
52                .ok_or_else(|| anyhow::anyhow!("undeclared argument {index}"))?;
53            anyhow::ensure!(
54                expected.same_shape(read),
55                "argument {index} representation mismatch"
56            );
57            anyhow::ensure!(
58                read.can_clone(),
59                "argument {index} clone capability unavailable"
60            );
61        }
62        anyhow::ensure!(
63            body.result_type()?.same_shape(result),
64            "result representation mismatch"
65        );
66        let result = ResultPlan::new(0, body.result_type()?.clone())?;
67        Ok(Self {
68            body,
69            result,
70            arity: arguments.len(),
71        })
72    }
73
74    /// Executes the bound raw frame without conversions or descriptor comparisons.
75    ///
76    /// # Safety
77    /// `arguments` must have the prepared arity. Each pointer must borrow a live,
78    /// aligned value of its exact preparation descriptor throughout this call.
79    /// No pointer may alias mutable storage accessed by an operation.
80    /// # Errors
81    /// Propagates operation errors after frame cleanup.
82    /// - Complexity: Body execution and emitted clone costs.
83    ///
84    /// # Examples
85    /// See [`Self::new`].
86    pub unsafe fn call(&mut self, arguments: &[*const u8]) -> anyhow::Result<crate::OwnedValue> {
87        debug_assert_eq!(arguments.len(), self.arity);
88        let mut output = unsafe { self.body.execute_raw(arguments)? };
89        Ok(unsafe { self.result.take(&mut output) })
90    }
91}
92
93/// Owns a frozen raw body with exact canonical argument/result witnesses.
94///
95/// Calls borrow arguments, clone only emitted reads, and return independent owners.
96pub struct PreparedSegment<A: ValueRepr, R: ValueRepr> {
97    body: DynSegment,
98    arguments: ArgumentPlan,
99    pointers: Vec<*const u8>,
100    result: ResultPlan,
101    witness: PhantomData<fn(A, R)>,
102}
103
104impl<A: ValueRepr, R: ValueRepr> PreparedSegment<A, R> {
105    /// Validates exact read/result identities and borrowed clone capabilities once.
106    ///
107    /// # Errors
108    /// Rejects dependent bodies, non-list arguments, missing/mismatched positional
109    /// reads, missing clone capability, or a result differing at any recursive node.
110    /// - Complexity: O(n) complete descriptor comparisons and frame planning.
111    pub(crate) fn new(body: DynSegment) -> anyhow::Result<Self> {
112        body.validate_independent_body()?;
113        let args = A::value_type();
114        let fields = args
115            .tuple_elements()
116            .ok_or_else(|| anyhow::anyhow!("prepared arguments must be a canonical list"))?;
117        for (index, read) in &body.argument_reads {
118            let expected = fields
119                .get(*index)
120                .ok_or_else(|| anyhow::anyhow!("undeclared argument {index}"))?;
121            anyhow::ensure!(
122                expected.value_type.same_shape(read),
123                "argument {index} representation mismatch"
124            );
125            anyhow::ensure!(
126                read.can_clone(),
127                "argument {index} clone capability unavailable"
128            );
129        }
130        body.result_type()?.validate::<R>()?;
131        let result = ResultPlan::new(0, body.result_type()?.clone())?;
132        let pointers = vec![std::ptr::null(); fields.len()];
133        Ok(Self {
134            body,
135            arguments: ArgumentPlan::borrowed(args)?,
136            pointers,
137            result,
138            witness: PhantomData,
139        })
140    }
141
142    /// Executes without shape comparisons, checked conversions, or `Any` downcasts.
143    ///
144    /// - Postcondition: The input remains borrowed and every returned owner is independent.
145    /// # Errors
146    /// Propagates the original operation error and its context after frame cleanup.
147    /// - Complexity: O(a) pointer setup for a arguments plus body/clone costs.
148    ///
149    /// # Examples
150    /// See the module example.
151    pub fn call(&mut self, args: &A::Value) -> anyhow::Result<R::Value> {
152        let base = std::ptr::from_ref(args).cast::<u8>();
153        // SAFETY: The sealed witnesses, immutable body, and preparation bind all
154        // reads/results to this exact representation for every subsequent call.
155        unsafe {
156            self.arguments.bind_pointers(base, &mut self.pointers);
157            let mut output = self.body.execute_raw(&self.pointers)?;
158            Ok(self.result.take_typed::<R::Value>(&mut output))
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use crate::{Array, ArrayOf, Cons, IntoCStackList, Leaf, Nil, ValueType};
167
168    /// Prepared nested borrowed frames perform no recursive comparisons on repeated calls.
169    #[test]
170    fn trusted_calls_never_compare_descriptors() {
171        type Pair = Cons<Leaf<i32>, Cons<Leaf<f64>, Nil>>;
172        type Argument = ArrayOf<Pair>;
173        type Args = Cons<Argument, Nil>;
174        let mut body = DynSegment::new::<()>();
175        body.push_argument(0, Argument::value_type().with_clone::<Argument>().unwrap())
176            .unwrap();
177        let mut body = body.prepare::<Args, Argument>().unwrap();
178        let input = (Array::from_vec(vec![(1i32, 2f64).into_c_stack_list()]),).into_c_stack_list();
179        crate::value_type::SHAPE_COMPARISONS.with(|count| count.set(0));
180        let first = body.call(&input).unwrap();
181        let second = body.call(&input).unwrap();
182        crate::value_type::SHAPE_COMPARISONS.with(|count| assert_eq!(count.get(), 0));
183        assert_ne!(first.as_slice().as_ptr(), second.as_slice().as_ptr());
184        assert_ne!(input.1.as_slice().as_ptr(), first.as_slice().as_ptr());
185    }
186
187    /// Runtime-bound frames use the identical frozen execution and equality plans.
188    #[test]
189    fn erased_prepared_calls_never_compare_descriptors() {
190        type Pair = Cons<Leaf<i32>, Cons<ArrayOf<Leaf<f64>>, Nil>>;
191        type Argument = ArrayOf<Pair>;
192        let descriptor = Argument::value_type()
193            .with_clone::<Argument>()
194            .unwrap()
195            .with_equality::<Argument>()
196            .unwrap();
197        let mut body = DynSegment::new::<()>();
198        body.push_argument(0, descriptor.clone()).unwrap();
199        let mut body =
200            PreparedRawSegment::new(body, std::slice::from_ref(&descriptor), &descriptor).unwrap();
201        let input = Array::from_vec(vec![(1, Array::from_vec(vec![2.0])).into_c_stack_list()]);
202        let pointer = std::ptr::from_ref(&input).cast::<u8>();
203        crate::value_type::SHAPE_COMPARISONS.with(|count| count.set(0));
204        // SAFETY: Input uses exactly the witness bound at construction.
205        let first = unsafe { body.call(&[pointer]) }.unwrap();
206        let second = unsafe { body.call(&[pointer]) }.unwrap();
207        // SAFETY: Both values come from the same comparable bound result plan.
208        assert!(unsafe { first.eq_bound(&second) });
209        crate::value_type::SHAPE_COMPARISONS.with(|count| assert_eq!(count.get(), 0));
210        let first = first.try_into_typed::<Argument>().unwrap();
211        let second = second.try_into_typed::<Argument>().unwrap();
212        assert_ne!(input.as_slice().as_ptr(), first.as_slice().as_ptr());
213        assert_ne!(first.as_slice().as_ptr(), second.as_slice().as_ptr());
214        assert_ne!(
215            first.as_slice()[0].0.1.as_slice().as_ptr(),
216            second.as_slice()[0].0.1.as_slice().as_ptr()
217        );
218    }
219
220    /// Erased preparation rejects mismatched reads and results before executing effects.
221    #[test]
222    fn erased_preparation_checks_complete_signature_before_effects() {
223        let mut body = DynSegment::new::<()>();
224        body.push_arg::<i32>(0);
225        assert!(
226            PreparedRawSegment::new(body, &[ValueType::leaf::<u32>()], &ValueType::leaf::<i32>())
227                .is_err()
228        );
229        let mut body = DynSegment::new::<()>();
230        body.push_arg::<i32>(0);
231        assert!(PreparedRawSegment::new(body, &[], &ValueType::leaf::<i32>()).is_err());
232        let mut body = DynSegment::new::<()>();
233        body.just(1i32);
234        assert!(PreparedRawSegment::new(body, &[], &ValueType::leaf::<u32>()).is_err());
235        let body = DynSegment::new::<(i32,)>();
236        assert!(PreparedRawSegment::new(body, &[], &ValueType::leaf::<i32>()).is_err());
237    }
238
239    /// Exact argument/result identities, independent origins, and clone plans bind once.
240    #[test]
241    fn preparation_rejects_every_boundary_mismatch() {
242        let mut wrong_result = DynSegment::new::<()>();
243        wrong_result.just(1i32);
244        assert!(wrong_result.prepare::<Nil, Leaf<u32>>().is_err());
245
246        let mut wrong_arg = DynSegment::new::<()>();
247        wrong_arg.push_arg::<i32>(0);
248        assert!(
249            wrong_arg
250                .prepare::<Cons<Leaf<u32>, Nil>, Leaf<i32>>()
251                .is_err()
252        );
253
254        let mut undeclared = DynSegment::new::<()>();
255        undeclared.push_arg::<i32>(1);
256        assert!(
257            undeclared
258                .prepare::<Cons<Leaf<i32>, Nil>, Leaf<i32>>()
259                .is_err()
260        );
261
262        let mut nonlist = DynSegment::new::<()>();
263        nonlist.just(1i32);
264        assert!(nonlist.prepare::<Leaf<()>, Leaf<i32>>().is_err());
265
266        let preloaded = DynSegment::new::<(i32,)>();
267        assert!(
268            preloaded
269                .prepare::<Cons<Leaf<i32>, Nil>, Leaf<i32>>()
270                .is_err()
271        );
272
273        let mut nonclone = DynSegment::new::<()>();
274        assert!(
275            nonclone
276                .push_argument(0, ValueType::leaf::<String>())
277                .is_err()
278        );
279        assert!(nonclone.peek_stack_infos(0).is_empty());
280    }
281}