Skip to main content

harn_vm/vm/
callable_entry.rs

1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::chunk::ChunkRef;
5use crate::value::{VmError, VmValue};
6
7use super::{ScopeSpan, Vm};
8
9pub(super) enum TopLevelEntry {
10    Chunk(ChunkRef),
11    Callable {
12        bootstrap: ChunkRef,
13        has_fixture: bool,
14        args: Vec<VmValue>,
15    },
16}
17
18impl TopLevelEntry {
19    pub(super) async fn run(self, vm: &mut Vm) -> Result<VmValue, VmError> {
20        match self {
21            Self::Chunk(chunk) => vm.run_chunk(chunk).await,
22            Self::Callable {
23                bootstrap,
24                has_fixture,
25                mut args,
26            } => {
27                let value = vm.run_chunk(bootstrap).await?;
28                let target = if has_fixture {
29                    let VmValue::List(callables) = value else {
30                        return Err(VmError::Runtime(
31                            "callable entry bootstrap did not return [fixture, target]".to_string(),
32                        ));
33                    };
34                    let [VmValue::Closure(fixture), VmValue::Closure(target)] =
35                        callables.as_slice()
36                    else {
37                        return Err(VmError::Runtime(
38                            "callable entry bootstrap returned invalid fixture callables"
39                                .to_string(),
40                        ));
41                    };
42                    let fixture_value = vm.call_closure_pub(fixture, &[]).await?;
43                    args.insert(0, fixture_value);
44                    Arc::clone(target)
45                } else {
46                    let VmValue::Closure(target) = value else {
47                        return Err(VmError::Runtime(
48                            "callable entry bootstrap did not return a callable".to_string(),
49                        ));
50                    };
51                    target
52                };
53                vm.call_closure_pub(&target, &args).await
54            }
55        }
56    }
57}
58
59impl Vm {
60    /// Execute a compiled callable entry with explicit values under a host
61    /// wall-clock limit.
62    ///
63    /// The entry bootstrap initializes top-level state once. A bundled fixture
64    /// is then invoked with no arguments and its result is prepended to `args`;
65    /// the target callable is invoked through ordinary arity/type guards.
66    /// Pipeline-finish hooks wrap the complete operation exactly once.
67    pub async fn execute_callable_entry_with_timeout(
68        &mut self,
69        entry: &crate::CompiledCallableEntry,
70        args: &[VmValue],
71        timeout: Duration,
72    ) -> Result<VmValue, VmError> {
73        self.execute_top_level_with_timeout(
74            TopLevelEntry::Callable {
75                bootstrap: Arc::new(entry.bootstrap.clone()),
76                has_fixture: entry.has_fixture,
77                args: args.to_vec(),
78            },
79            timeout,
80        )
81        .await
82    }
83
84    pub(super) async fn execute_top_level(
85        &mut self,
86        entry: TopLevelEntry,
87    ) -> Result<VmValue, VmError> {
88        self.ensure_execution_available()?;
89        let registry = self.pool_registry.clone();
90        let owner = crate::observability::execution_scope::mint_execution_scope();
91        let ambient = crate::orchestration::AmbientExecutionScope::capture_for_top_level_execution(
92            owner,
93            self.llm_mock_context.clone(),
94        );
95        let execution = crate::stdlib::pool::with_pool_registry_scope(registry, async {
96            self.execute_entry_scoped(entry).await
97        });
98        crate::orchestration::scope_ambient(ambient, execution).await
99    }
100
101    async fn execute_entry_scoped(&mut self, entry: TopLevelEntry) -> Result<VmValue, VmError> {
102        let _execution_activity = self
103            .wait_for_graph
104            .register_task(self.runtime_context.task_id.clone());
105        let _span = ScopeSpan::new(crate::tracing::SpanKind::Pipeline, "main".into());
106        match entry.run(self).await {
107            Ok(value) => self.run_pipeline_finish_lifecycle(value).await,
108            Err(error) => {
109                crate::orchestration::clear_pipeline_on_finish();
110                Err(error)
111            }
112        }
113    }
114}