Skip to main content

sim_lib_function/
instance.rs

1// conformance: managed function instances preserve captures and callable identity.
2
3use std::{any::Any, error::Error, fmt};
4
5use sim_kernel::{
6    Args, Callable, ClassRef, Cx, Object, ObjectCompat, Result as KernelResult, ShapeRef, Value,
7};
8use sim_lib_binding::BindingCell;
9use sim_lib_mutation::{
10    EdgeId, EdgeVisitor, ManagedHandle, ManagedId, ManagedNode, ManagedObject,
11    StrongEdgeMutationError,
12};
13
14use crate::{BoundCall, CallInput, FunctionPlan, bind};
15
16/// Guest-owned execution policy for one concrete function body type.
17///
18/// The policy is statically selected by [`FunctionInstance`]. It receives the
19/// neutral call record and shared capture cells, leaving defaults, keyword
20/// rules, receiver behavior, evaluation, and diagnostics to the guest.
21pub trait FunctionBodyPolicy: Send + Sync + 'static {
22    /// Executes this body using the immutable declaration and live captures.
23    fn invoke(
24        &self,
25        cx: &mut Cx,
26        plan: &FunctionPlan,
27        captures: &[CapturedBinding],
28        call: BoundCall,
29    ) -> KernelResult<Value>;
30}
31
32/// One shared binding cell paired with its identity in the managed graph.
33#[derive(Clone, Debug)]
34pub struct CapturedBinding {
35    cell: BindingCell,
36    managed: ManagedHandle,
37}
38
39impl CapturedBinding {
40    /// Associates an existing binding cell with its managed allocation.
41    pub const fn new(cell: BindingCell, managed: ManagedHandle) -> Self {
42        Self { cell, managed }
43    }
44
45    /// Borrows the shared lexical cell.
46    pub const fn cell(&self) -> &BindingCell {
47        &self.cell
48    }
49
50    /// Returns the managed identity traced for this capture.
51    pub const fn managed(&self) -> ManagedHandle {
52        self.managed
53    }
54}
55
56/// Failure to construct a managed function instance.
57#[derive(Debug)]
58pub enum InstanceError {
59    /// Capture cells must exactly follow the plan's declared slots.
60    CaptureMismatch {
61        /// Number of capture descriptors in the plan.
62        expected: usize,
63        /// Number of supplied managed binding cells.
64        actual: usize,
65    },
66    /// A supplied cell did not match the corresponding frozen capture slot.
67    CaptureNameMismatch {
68        /// Zero-based position in the frozen capture sequence.
69        index: usize,
70        /// Name declared by the immutable function plan.
71        expected: String,
72        /// Name carried by the supplied shared binding cell.
73        actual: String,
74    },
75    /// The shared managed node refused a capture edge.
76    ManagedEdge(StrongEdgeMutationError),
77}
78
79impl fmt::Display for InstanceError {
80    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self {
82            Self::CaptureMismatch { expected, actual } => write!(
83                formatter,
84                "function plan declares {expected} captures but {actual} were supplied"
85            ),
86            Self::CaptureNameMismatch {
87                index,
88                expected,
89                actual,
90            } => write!(
91                formatter,
92                "function capture {index} is named {actual}, expected {expected}"
93            ),
94            Self::ManagedEdge(error) => write!(formatter, "cannot trace function capture: {error}"),
95        }
96    }
97}
98
99impl Error for InstanceError {}
100
101/// The typed payload retained by a managed function node.
102#[derive(Clone)]
103struct FunctionRole<B> {
104    plan: FunctionPlan,
105    body: B,
106    captures: Vec<CapturedBinding>,
107    class: ClassRef,
108    args_shape: Option<ShapeRef>,
109    result_shape: Option<ShapeRef>,
110}
111
112/// A language-neutral, managed function object with a concrete guest body.
113///
114/// Each capture is represented twice for distinct purposes: its existing
115/// [`BindingCell`] supplies shared lexical mutation, while its `ManagedHandle`
116/// becomes a strong edge in the common managed graph. No private environment
117/// graph or body registry is involved.
118#[derive(Clone)]
119pub struct FunctionInstance<B: FunctionBodyPolicy> {
120    node: ManagedNode<FunctionRole<B>>,
121}
122
123impl<B: FunctionBodyPolicy> FunctionInstance<B> {
124    /// Builds an instance from a plan, typed body, managed captures, and runtime metadata.
125    pub fn new(
126        plan: FunctionPlan,
127        body: B,
128        captures: Vec<CapturedBinding>,
129        class: ClassRef,
130        args_shape: Option<ShapeRef>,
131        result_shape: Option<ShapeRef>,
132    ) -> Result<Self, InstanceError> {
133        validate_capture_bindings(&plan, &captures)?;
134        let targets = captures
135            .iter()
136            .map(|capture| capture.managed().id())
137            .collect::<Vec<_>>();
138        let mut node = ManagedNode::new(FunctionRole {
139            plan,
140            body,
141            captures,
142            class,
143            args_shape,
144            result_shape,
145        });
146        for target in targets {
147            node.insert_strong(target)
148                .map_err(InstanceError::ManagedEdge)?;
149        }
150        Ok(Self { node })
151    }
152
153    /// Borrows the immutable declaration plan.
154    pub const fn plan(&self) -> &FunctionPlan {
155        &self.node.role().plan
156    }
157
158    /// Borrows the concrete guest body policy without erasure or downcasting.
159    pub const fn body(&self) -> &B {
160        &self.node.role().body
161    }
162
163    /// Borrows the capture cells in plan declaration order.
164    pub fn captures(&self) -> &[CapturedBinding] {
165        &self.node.role().captures
166    }
167
168    /// Borrows the caller-supplied runtime class.
169    pub const fn supplied_class(&self) -> &ClassRef {
170        &self.node.role().class
171    }
172
173    /// Borrows the caller-supplied argument Shape, when present.
174    pub const fn args_shape(&self) -> Option<&ShapeRef> {
175        self.node.role().args_shape.as_ref()
176    }
177
178    /// Borrows the caller-supplied result Shape, when present.
179    pub const fn result_shape(&self) -> Option<&ShapeRef> {
180        self.node.role().result_shape.as_ref()
181    }
182
183    /// Invokes the guest policy through the neutral evaluated-value boundary.
184    ///
185    /// Kernel calls and optional dispatch-method adaptation both use this path,
186    /// so neither surface can change the policy-visible [`BoundCall`].
187    pub fn invoke_bound(&self, cx: &mut Cx, call: BoundCall) -> KernelResult<Value> {
188        self.body().invoke(cx, self.plan(), self.captures(), call)
189    }
190
191    pub(crate) fn invoke_values(&self, cx: &mut Cx, values: Vec<Value>) -> KernelResult<Value> {
192        self.invoke_bound(cx, bind(CallInput::from(Args::new(values))))
193    }
194}
195
196/// Validates that concrete capture cells exactly match every frozen plan slot.
197pub fn validate_capture_bindings(
198    plan: &FunctionPlan,
199    captures: &[CapturedBinding],
200) -> Result<(), InstanceError> {
201    if plan.captures().len() != captures.len() {
202        return Err(InstanceError::CaptureMismatch {
203            expected: plan.captures().len(),
204            actual: captures.len(),
205        });
206    }
207    for (index, (descriptor, capture)) in plan.captures().iter().zip(captures).enumerate() {
208        if descriptor.name() != capture.cell().name() {
209            return Err(InstanceError::CaptureNameMismatch {
210                index,
211                expected: descriptor.name().to_string(),
212                actual: capture.cell().name().to_string(),
213            });
214        }
215    }
216    Ok(())
217}
218
219impl<B: FunctionBodyPolicy> ManagedObject for FunctionInstance<B> {
220    fn trace_edges(&self, visitor: &mut dyn EdgeVisitor) {
221        self.node.trace_edges(visitor);
222    }
223
224    fn clear_weak_edge(&mut self, edge: EdgeId, expected: ManagedId) -> bool {
225        self.node.clear_weak_edge(edge, expected)
226    }
227
228    fn clear_ephemeron_edge(
229        &mut self,
230        edge: EdgeId,
231        expected_key: ManagedId,
232        expected_value: ManagedId,
233    ) -> bool {
234        self.node
235            .clear_ephemeron_edge(edge, expected_key, expected_value)
236    }
237}
238
239impl<B: FunctionBodyPolicy> Object for FunctionInstance<B> {
240    fn display(&self, _cx: &mut Cx) -> KernelResult<String> {
241        Ok(format!("#<function {}>", self.plan().display_identity()))
242    }
243
244    fn as_any(&self) -> &dyn Any {
245        self
246    }
247}
248
249impl<B: FunctionBodyPolicy> ObjectCompat for FunctionInstance<B> {
250    fn class(&self, _cx: &mut Cx) -> KernelResult<ClassRef> {
251        Ok(self.supplied_class().clone())
252    }
253
254    fn as_callable(&self) -> Option<&dyn Callable> {
255        Some(self)
256    }
257}
258
259impl<B: FunctionBodyPolicy> Callable for FunctionInstance<B> {
260    fn call(&self, cx: &mut Cx, args: Args) -> KernelResult<Value> {
261        self.invoke_values(cx, args.into_vec())
262    }
263
264    fn browse_args_shape(&self, _cx: &mut Cx) -> KernelResult<Option<ShapeRef>> {
265        Ok(self.args_shape().cloned())
266    }
267
268    fn browse_result_shape(&self, _cx: &mut Cx) -> KernelResult<Option<ShapeRef>> {
269        Ok(self.result_shape().cloned())
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use sim_kernel::{ShapeId, Symbol, testing::bare_cx};
276    use sim_lib_gc_tracing::{CollectionLimits, ManagedHeap};
277    use sim_lib_mutation::{EdgeSnapshot, ManagedNode};
278
279    use super::*;
280    use crate::CaptureDescriptor;
281
282    #[derive(Clone)]
283    struct EchoBody;
284
285    impl FunctionBodyPolicy for EchoBody {
286        fn invoke(
287            &self,
288            _cx: &mut Cx,
289            _plan: &FunctionPlan,
290            _captures: &[CapturedBinding],
291            call: BoundCall,
292        ) -> KernelResult<Value> {
293            match call.arguments()[0].input() {
294                crate::ArgumentInput::Positional(value) => Ok(value.clone()),
295                _ => unreachable!("kernel arguments are positional"),
296            }
297        }
298    }
299
300    fn plan(captures: usize) -> FunctionPlan {
301        FunctionPlan::new(
302            Symbol::new("guest:echo"),
303            Vec::new(),
304            (0..captures)
305                .map(|index| CaptureDescriptor::new(Symbol::new(format!("slot-{index}")), None))
306                .collect(),
307            Some(ShapeId(9)),
308        )
309        .unwrap()
310    }
311
312    fn metadata(cx: &mut Cx) -> (ClassRef, ShapeRef, ShapeRef) {
313        (
314            cx.factory().symbol(Symbol::new("guest-class")).unwrap(),
315            cx.factory().symbol(Symbol::new("args-shape")).unwrap(),
316            cx.factory().symbol(Symbol::new("result-shape")).unwrap(),
317        )
318    }
319
320    fn collection_limits() -> CollectionLimits {
321        CollectionLimits {
322            objects: 4,
323            edges: 4,
324            stack: 4,
325            work: 32,
326            clears: 4,
327            finalizers: 4,
328        }
329    }
330
331    #[test]
332    fn invocation_and_runtime_metadata_are_delegated_without_body_erasure() {
333        let mut cx = bare_cx();
334        let (class, args_shape, result_shape) = metadata(&mut cx);
335        let instance = FunctionInstance::new(
336            plan(0),
337            EchoBody,
338            Vec::new(),
339            class.clone(),
340            Some(args_shape.clone()),
341            Some(result_shape.clone()),
342        )
343        .unwrap();
344        let argument = cx.factory().symbol(Symbol::new("answer")).unwrap();
345
346        assert!(std::ptr::eq(instance.body(), &instance.node.role().body));
347        assert_eq!(instance.class(&mut cx).unwrap(), class);
348        assert_eq!(
349            instance.browse_args_shape(&mut cx).unwrap(),
350            Some(args_shape)
351        );
352        assert_eq!(
353            instance.browse_result_shape(&mut cx).unwrap(),
354            Some(result_shape)
355        );
356        assert_eq!(
357            instance
358                .call(&mut cx, Args::new(vec![argument.clone()]))
359                .unwrap(),
360            argument
361        );
362    }
363
364    #[test]
365    fn same_plan_instances_receive_distinct_managed_identities() {
366        let mut cx = bare_cx();
367        let (class, _, _) = metadata(&mut cx);
368        let mut heap = ManagedHeap::tracing(4, collection_limits()).unwrap();
369        let first = heap
370            .allocate(
371                FunctionInstance::new(plan(0), EchoBody, vec![], class.clone(), None, None)
372                    .unwrap(),
373            )
374            .unwrap();
375        let second = heap
376            .allocate(FunctionInstance::new(plan(0), EchoBody, vec![], class, None, None).unwrap())
377            .unwrap();
378
379        assert_ne!(first.id(), second.id());
380    }
381
382    #[derive(Clone)]
383    enum CycleObject {
384        Function(FunctionInstance<EchoBody>),
385        Environment(ManagedNode<()>),
386    }
387
388    impl ManagedObject for CycleObject {
389        fn trace_edges(&self, visitor: &mut dyn EdgeVisitor) {
390            match self {
391                Self::Function(function) => function.trace_edges(visitor),
392                Self::Environment(environment) => environment.trace_edges(visitor),
393            }
394        }
395
396        fn clear_weak_edge(&mut self, edge: EdgeId, expected: ManagedId) -> bool {
397            match self {
398                Self::Function(function) => function.clear_weak_edge(edge, expected),
399                Self::Environment(environment) => environment.clear_weak_edge(edge, expected),
400            }
401        }
402
403        fn clear_ephemeron_edge(
404            &mut self,
405            edge: EdgeId,
406            expected_key: ManagedId,
407            expected_value: ManagedId,
408        ) -> bool {
409            match self {
410                Self::Function(function) => {
411                    function.clear_ephemeron_edge(edge, expected_key, expected_value)
412                }
413                Self::Environment(environment) => {
414                    environment.clear_ephemeron_edge(edge, expected_key, expected_value)
415                }
416            }
417        }
418    }
419
420    #[test]
421    fn closure_environment_cycle_is_collected_through_capture_edge() {
422        let mut cx = bare_cx();
423        let (class, _, _) = metadata(&mut cx);
424        let mut heap = ManagedHeap::tracing(4, collection_limits()).unwrap();
425        let environment = heap
426            .allocate(CycleObject::Environment(ManagedNode::new(())))
427            .unwrap();
428        let cell = BindingCell::uninitialized(Symbol::new("slot-0"));
429        let function = FunctionInstance::new(
430            plan(1),
431            EchoBody,
432            vec![CapturedBinding::new(cell, environment)],
433            class,
434            None,
435            None,
436        )
437        .unwrap();
438        assert_eq!(
439            function.node.edge_snapshot(),
440            vec![EdgeSnapshot::Strong {
441                edge: EdgeId(0),
442                target: environment.id(),
443            }]
444        );
445        let function = heap.allocate(CycleObject::Function(function)).unwrap();
446        match heap.get_mut(environment).unwrap() {
447            CycleObject::Environment(node) => {
448                node.insert_strong(function.id()).unwrap();
449            }
450            CycleObject::Function(_) => unreachable!(),
451        }
452
453        let receipt = heap.collect().unwrap().unwrap();
454        assert_eq!(receipt.swept, vec![environment.id(), function.id()]);
455        assert_eq!(heap.live_len(), 0);
456    }
457}