Skip to main content

cel_runtime/
callable.rs

1//! Calls native and prepared bodies through one statically fixed signature.
2//!
3//! ```
4//! use cel_runtime::{Callable, CNil, EmptyList};
5//! let f = Callable::from_fn(|_: &CNil<()>| Ok(7i32));
6//! assert_eq!(f.call(&CNil::<()>::root_empty()).unwrap(), 7);
7//! ```
8
9use std::marker::PhantomData;
10use std::mem::MaybeUninit;
11
12use crate::{ArgumentList, DynClosure, PreparedSegment, ValueRepr};
13
14/// Shares a callable whose borrowed arguments and owned result have fixed Rust types.
15///
16/// Every instantiation has exactly the canonical [`DynClosure`] representation.
17/// Clones share execution state, not arguments or results. Recursive entry into
18/// the same carrier returns an error; different carriers can call one another.
19///
20/// # Examples
21///
22/// ```
23/// use cel_runtime::Callable;
24/// let f = Callable::from_fn(|x: &i32| Ok(*x));
25/// assert_eq!(f.clone().call(&7).unwrap(), 7);
26/// ```
27///
28/// Wrong Rust arguments cannot enter the body:
29///
30/// ```compile_fail
31/// use cel_runtime::Callable;
32/// let f = Callable::from_fn(|x: &i32| Ok(*x));
33/// f.call(&"not an integer");
34/// ```
35#[repr(transparent)]
36pub struct Callable<Args, R> {
37    carrier: DynClosure,
38    signature: PhantomData<fn(&Args) -> R>,
39}
40
41impl<Args, R> Clone for Callable<Args, R> {
42    /// Shares the carrier without imposing clone bounds on the signature.
43    fn clone(&self) -> Self {
44        Self {
45            carrier: self.carrier.clone(),
46            signature: PhantomData,
47        }
48    }
49}
50
51impl<Args: 'static, R: 'static> Callable<Args, R> {
52    /// Shares a native function pointer with this exact borrowed/owned signature.
53    ///
54    /// # Examples
55    /// See the module example.
56    pub fn from_fn(f: fn(&Args) -> anyhow::Result<R>) -> Self {
57        /// Invokes the construction-bound native function with a matching raw frame.
58        ///
59        /// # Safety
60        /// `body` stores this exact function pointer, `args` borrows `Args`, and
61        /// `result` is aligned uninitialized storage for `R`.
62        unsafe fn dispatch<Args: 'static, R: 'static>(
63            body: *mut (),
64            args: *const u8,
65            result: *mut u8,
66        ) -> anyhow::Result<()> {
67            let f = unsafe { &*body.cast::<fn(&Args) -> anyhow::Result<R>>() };
68            let value = f(unsafe { &*args.cast::<Args>() })?;
69            unsafe { result.cast::<R>().write(value) };
70            Ok(())
71        }
72
73        Self {
74            // SAFETY: This payload and dispatcher share the exact fn signature.
75            carrier: unsafe { DynClosure::from_frame_body(Box::new(f), dispatch::<Args, R>, None) },
76            signature: PhantomData,
77        }
78    }
79
80    /// Shares an independently prepared body with matching canonical arguments.
81    ///
82    /// - Complexity: O(n) descriptor construction for n argument/result type nodes.
83    ///
84    /// # Examples
85    /// ```
86    /// use cel_runtime::{Callable, DynSegment, Nil, Leaf, CNil, EmptyList};
87    /// let mut body = DynSegment::new::<()>(); body.just(7i32);
88    /// let f = Callable::from_prepared(body.prepare::<Nil, Leaf<i32>>().unwrap());
89    /// assert_eq!(f.call(&CNil::<()>::root_empty()).unwrap(), 7);
90    /// ```
91    pub fn from_prepared<A, Result>(body: PreparedSegment<A, Result>) -> Self
92    where
93        A: ArgumentList<Value = Args> + 'static,
94        Result: ValueRepr<Value = R> + 'static,
95    {
96        /// Invokes an already prepared body and initializes the result only on success.
97        ///
98        /// # Safety
99        /// `body` stores exactly `PreparedSegment<A, R>` and both raw pointers
100        /// match its preparation-validated concrete representations.
101        unsafe fn dispatch<A: ValueRepr + 'static, R: ValueRepr + 'static>(
102            body: *mut (),
103            args: *const u8,
104            result: *mut u8,
105        ) -> anyhow::Result<()> {
106            let body = unsafe { &mut *body.cast::<PreparedSegment<A, R>>() };
107            let value = body.call(unsafe { &*args.cast::<A::Value>() })?;
108            unsafe { result.cast::<R::Value>().write(value) };
109            Ok(())
110        }
111        let args = A::value_type();
112        let parameters = args
113            .tuple_elements()
114            .expect("sealed canonical argument list")
115            .iter()
116            .map(|field| field.value_type.clone())
117            .collect();
118        let signature = crate::dyn_closure::ClosureSignature {
119            parameters,
120            result: Result::value_type(),
121        };
122        Self {
123            // SAFETY: Preparation validates the signature and concrete frames;
124            // this exact payload remains paired with its monomorphized dispatcher.
125            carrier: unsafe {
126                DynClosure::from_frame_body(Box::new(body), dispatch::<A, Result>, Some(signature))
127            },
128            signature: PhantomData,
129        }
130    }
131
132    /// Borrows arguments and returns an independent result without checking signatures.
133    ///
134    /// # Errors
135    /// Returns an error on reentrant entry into this shared carrier, or propagates
136    /// the original body error. Errors and unwinding release the execution guard
137    /// so later calls remain valid.
138    /// - Complexity: Body-dependent work; interpreted calls set up O(a) pointers
139    ///   for a arguments.
140    ///
141    /// # Examples
142    /// See the module example.
143    pub fn call(&self, args: &Args) -> anyhow::Result<R> {
144        let mut result = MaybeUninit::<R>::uninit();
145        // SAFETY: Only the exact native signature or matching prepared witnesses
146        // construct this facade. The carrier cannot escape through a mutable API.
147        unsafe {
148            self.carrier
149                .call_frame_trusted(std::ptr::from_ref(args).cast(), result.as_mut_ptr().cast())?;
150            Ok(result.assume_init())
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use crate::{Array, ArrayOf, Cons, DynSegment, FnOf, IntoCStackList, Leaf, Nil, ValueType};
159
160    /// Both typed body kinds and emitted invocation perform zero descriptor comparisons.
161    #[test]
162    fn trusted_callable_calls_never_compare_descriptors() {
163        type Args = Cons<ArrayOf<Leaf<i32>>, Nil>;
164        type Function = FnOf<Args, ArrayOf<Leaf<i32>>>;
165        /// Returns an independently owned copy of the borrowed array.
166        fn native(args: &<Args as ValueRepr>::Value) -> anyhow::Result<Array<i32>> {
167            Ok(args.1.clone())
168        }
169        let mut body = DynSegment::new::<()>();
170        body.push_argument(0, ValueType::array(ValueType::cloneable_leaf::<i32>()))
171            .unwrap();
172        let interpreted =
173            Callable::from_prepared(body.prepare::<Args, ArrayOf<Leaf<i32>>>().unwrap());
174        let args = (Array::from_vec(vec![7]),).into_c_stack_list();
175        for f in [Callable::from_fn(native), interpreted] {
176            let direct = f.clone();
177            let mut body = DynSegment::new::<()>();
178            body.op0_repr::<Function, _>(move || f.clone()).unwrap();
179            body.push_argument(0, ValueType::array(ValueType::cloneable_leaf::<i32>()))
180                .unwrap();
181            body.invoke_closure(1).unwrap();
182            let mut body = body.prepare::<Args, ArrayOf<Leaf<i32>>>().unwrap();
183            crate::value_type::SHAPE_COMPARISONS.with(|n| n.set(0));
184            for _ in 0..3 {
185                assert_eq!(direct.call(&args).unwrap().as_slice(), [7]);
186                assert_eq!(body.call(&args).unwrap().as_slice(), [7]);
187            }
188            crate::value_type::SHAPE_COMPARISONS.with(|n| assert_eq!(n.get(), 0));
189        }
190
191        let mut body = DynSegment::new::<()>();
192        body.just(7i32);
193        let closure = DynClosure::typed(vec![], body).unwrap();
194        let mut outer = DynSegment::new::<()>();
195        outer.push_closure(closure).unwrap();
196        let mut outer = outer.prepare::<Nil, FnOf<Nil, Leaf<i32>>>().unwrap();
197        let f = outer.call(&().into_c_stack_list()).unwrap();
198        drop(outer);
199        crate::value_type::SHAPE_COMPARISONS.with(|n| n.set(0));
200        assert_eq!(f.call(&().into_c_stack_list()).unwrap(), 7);
201        assert_eq!(f.call(&().into_c_stack_list()).unwrap(), 7);
202        crate::value_type::SHAPE_COMPARISONS.with(|n| assert_eq!(n.get(), 0));
203    }
204}