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}