Skip to main content

hara_native/
direct_native.rs

1//! Opt-in native-substrate execution for validated Hara bytecode.
2//!
3//! The name of this compatibility module is retained for the public
4//! `direct-native` feature, but it is deliberately not a second Hara compiler.
5//! Hara source is compiled to [`crate::vm::Program`] and ordinary Hara
6//! functions execute in the bytecode VM. This module only owns the native
7//! execution boundary and its telemetry: canonical `std.native.*`,
8//! `std.protocol.*`, and Rust-owned evaluator primitives are invoked by the VM
9//! as Rust callouts. The optional Cranelift tracing tier remains attached to
10//! the VM's real basic-loop path through `crate::jit`.
11
12#![cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
13
14use std::cell::{Cell, RefCell};
15use std::rc::Rc;
16
17use crate::core::Value;
18use crate::vm::{Program, VmFiber};
19
20/// A program which has crossed the native execution validation boundary.
21///
22/// Compiler output is already validated by the compiler's `finish` step and
23/// artifact output is already validated by `decode_program`. Keeping that
24/// fact in a separate type lets internal callers execute either form without
25/// paying the structural validation cost again. Public entry points still
26/// accept an ordinary `Rc<Program>` and validate it before constructing this
27/// wrapper.
28#[derive(Clone)]
29pub(crate) struct ValidatedProgram {
30    program: Rc<Program>,
31}
32
33impl ValidatedProgram {
34    pub(crate) fn from_compiler(program: Rc<Program>) -> Self {
35        Self { program }
36    }
37
38    pub(crate) fn from_artifact(program: Rc<Program>) -> Self {
39        Self { program }
40    }
41
42    pub(crate) fn validate(program: Rc<Program>) -> Result<Self, String> {
43        crate::vm::validate::validate(&program)
44            .map_err(|error| format!("native backend received invalid bytecode: {error}"))?;
45        Ok(Self { program })
46    }
47
48    pub(crate) fn program(&self) -> Rc<Program> {
49        self.program.clone()
50    }
51}
52
53/// The result of one native-substrate execution.
54#[derive(Debug, Clone)]
55pub struct NativeExecutionReport {
56    /// The value returned by the VM entry function.
57    pub value: Value,
58    /// Number of Hara function prototypes validated for this bytecode unit.
59    pub bytecode_functions: usize,
60    /// Number of Hara VM instructions validated for this bytecode unit.
61    pub bytecode_instructions: usize,
62    /// Number of approved native/protocol/evaluator targets reached by the VM
63    /// during this execution.
64    pub native_target_calls: usize,
65    /// Number of native-substrate VM entries represented by this report.
66    pub invocations: usize,
67}
68
69/// Cumulative native-substrate counters for a reusable runtime owner.
70#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
71pub struct NativeExecutionTelemetry {
72    /// Cumulative number of validated Hara function prototypes encountered.
73    pub bytecode_functions: usize,
74    /// Cumulative number of validated Hara VM instructions encountered.
75    pub bytecode_instructions: usize,
76    /// Cumulative number of approved native/protocol/evaluator targets called.
77    pub native_target_calls: usize,
78    /// Cumulative number of native-substrate VM entries.
79    pub invocations: usize,
80}
81
82impl NativeExecutionReport {
83    /// Identifies the corrected two-stage backend in diagnostics and embedding
84    /// telemetry. `Cranelift` is reserved for the VM's approved loop tier.
85    pub const BACKEND: &'static str = "bytecode-vm-native-substrate";
86}
87
88#[derive(Default)]
89struct NativeEngineState {
90    bytecode_functions: Cell<usize>,
91    bytecode_instructions: Cell<usize>,
92    native_target_calls: Cell<usize>,
93    invocations: Cell<usize>,
94}
95
96/// A captured native-substrate scope used by VM closures which outlive the
97/// top-level entry call. It keeps the evaluator guard and target telemetry
98/// active when a promise or callback resumes later on the same thread.
99#[derive(Clone)]
100pub(crate) struct NativeExecutionScope {
101    state: Rc<NativeEngineState>,
102}
103
104thread_local! {
105    static ACTIVE_NATIVE_ENGINE: RefCell<Option<Rc<NativeEngineState>>> = const { RefCell::new(None) };
106}
107
108/// A reusable native-substrate runtime boundary.
109///
110/// The engine owns the native execution boundary and cumulative telemetry.
111/// Persistent source/artifact caching is configured by the Runtime owner so
112/// namespace, provider, protocol, and mutable Var state remain isolated.
113#[derive(Clone, Default)]
114pub struct NativeEngine {
115    state: Rc<NativeEngineState>,
116}
117
118impl NativeEngine {
119    pub fn new() -> Self {
120        Self::default()
121    }
122
123    /// Resets all cumulative counters. The operation is idempotent and does
124    /// not affect any Runtime namespace or any already-created VM closure.
125    pub fn reset(&self) {
126        self.state.bytecode_functions.set(0);
127        self.state.bytecode_instructions.set(0);
128        self.state.native_target_calls.set(0);
129        self.state.invocations.set(0);
130    }
131
132    pub fn telemetry(&self) -> NativeExecutionTelemetry {
133        let bytecode_functions = self.state.bytecode_functions.get();
134        let bytecode_instructions = self.state.bytecode_instructions.get();
135        NativeExecutionTelemetry {
136            bytecode_functions,
137            bytecode_instructions,
138            native_target_calls: self.state.native_target_calls.get(),
139            invocations: self.state.invocations.get(),
140        }
141    }
142
143    /// Executes a validated Hara program through the VM's synchronous fiber
144    /// boundary. No tree-evaluator fallback is available from this path.
145    pub fn execute(&self, program: Rc<Program>) -> Result<NativeExecutionReport, String> {
146        self.execute_vm(ValidatedProgram::validate(program)?)
147    }
148
149    /// Executes a validated Hara program and drives settled VM promises to the
150    /// same blocking boundary used by `Runtime::eval_native`.
151    pub fn execute_blocking(&self, program: Rc<Program>) -> Result<NativeExecutionReport, String> {
152        self.execute_vm(ValidatedProgram::validate(program)?)
153    }
154
155    pub(crate) fn execute_blocking_validated_with_multimethods(
156        &self,
157        program: ValidatedProgram,
158        multimethods: crate::core::MultiMethodRegistry,
159    ) -> Result<NativeExecutionReport, String> {
160        let context = crate::core::DirectNativeContext::capture_with_multimethods(multimethods);
161        context.with(|| self.execute_validated(program))
162    }
163
164    fn execute_validated(
165        &self,
166        validated: ValidatedProgram,
167    ) -> Result<NativeExecutionReport, String> {
168        self.execute_vm(validated)
169    }
170
171    fn execute_vm(&self, validated: ValidatedProgram) -> Result<NativeExecutionReport, String> {
172        let program = validated.program();
173        let bytecode_functions = program.functions.len();
174        let bytecode_instructions = program
175            .functions
176            .iter()
177            .map(|function| function.code.len())
178            .sum();
179        self.record_bytecode_program(&program);
180        self.state
181            .invocations
182            .set(self.state.invocations.get().saturating_add(1));
183        let before_targets = self.state.native_target_calls.get();
184        let state = self.state.clone();
185        let run = || {
186            let mut fiber = VmFiber::start(program);
187            fiber.drive_sync().map_err(|error| error.to_string())
188        };
189        let result = with_active_engine(state, || crate::core::with_direct_native_execution(run));
190        let native_target_calls = self
191            .state
192            .native_target_calls
193            .get()
194            .saturating_sub(before_targets);
195        let value = result?;
196        Ok(NativeExecutionReport {
197            value,
198            bytecode_functions,
199            bytecode_instructions,
200            native_target_calls,
201            invocations: 1,
202        })
203    }
204
205    fn record_bytecode_program(&self, program: &Program) {
206        self.state.bytecode_functions.set(
207            self.state
208                .bytecode_functions
209                .get()
210                .saturating_add(program.functions.len()),
211        );
212        let instructions = program
213            .functions
214            .iter()
215            .map(|function| function.code.len())
216            .sum::<usize>();
217        self.state.bytecode_instructions.set(
218            self.state
219                .bytecode_instructions
220                .get()
221                .saturating_add(instructions),
222        );
223    }
224}
225
226fn with_active_engine<R>(state: Rc<NativeEngineState>, action: impl FnOnce() -> R) -> R {
227    ACTIVE_NATIVE_ENGINE.with(|active| {
228        let previous = active.borrow_mut().replace(state);
229        let result = action();
230        *active.borrow_mut() = previous;
231        result
232    })
233}
234
235pub(crate) fn capture_execution_scope() -> Option<NativeExecutionScope> {
236    ACTIVE_NATIVE_ENGINE.with(|active| {
237        active
238            .borrow()
239            .as_ref()
240            .cloned()
241            .map(|state| NativeExecutionScope { state })
242    })
243}
244
245impl NativeExecutionScope {
246    pub(crate) fn with<R>(&self, action: impl FnOnce() -> R) -> R {
247        with_active_engine(self.state.clone(), || {
248            crate::core::with_direct_native_execution(action)
249        })
250    }
251}
252
253/// Re-enters both the native target scope and the captured runtime context for
254/// a VM callback which may run after its creating VM entry has returned.
255pub(crate) fn with_captured_context<R>(
256    scope: Option<&NativeExecutionScope>,
257    context: Option<&crate::core::DirectNativeContext>,
258    action: impl FnOnce() -> R,
259) -> R {
260    if let Some(scope) = scope {
261        scope.with(|| {
262            if let Some(context) = context {
263                context.with(action)
264            } else {
265                action()
266            }
267        })
268    } else if let Some(context) = context {
269        context.with(action)
270    } else {
271        action()
272    }
273}
274
275/// Returns whether a symbol belongs to the closed native target inventory.
276/// Ordinary Hara namespace Vars intentionally do not match this predicate.
277pub(crate) fn is_native_target_symbol(name: &str) -> bool {
278    crate::core::IntrinsicOp::from_symbol(name).is_some()
279        || matches!(name, "disj" | "quot" | "rem" | "mod")
280        || crate::core::canonical_intrinsic_callable_symbol(name).is_some()
281}
282
283/// Records one approved target call for the active native-substrate entry.
284pub(crate) fn record_native_target(name: &str) {
285    if !is_native_target_symbol(name) {
286        return;
287    }
288    ACTIVE_NATIVE_ENGINE.with(|active| {
289        if let Some(state) = active.borrow().as_ref() {
290            state
291                .native_target_calls
292                .set(state.native_target_calls.get().saturating_add(1));
293        }
294    });
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    fn program(source: &str) -> Rc<Program> {
302        Rc::new(crate::vm::compile_source(source).expect("test program compiles"))
303    }
304
305    #[test]
306    fn native_engine_executes_hara_in_the_vm_and_counts_targets() {
307        let engine = NativeEngine::new();
308        let report = engine
309            .execute(program("(let [value 20] (+ value 22))"))
310            .expect("native-substrate execution");
311        assert_eq!(report.value, Value::Number(42));
312        assert_eq!(report.bytecode_functions, 1);
313        assert!(report.bytecode_instructions > 0);
314        assert!(report.native_target_calls > 0);
315        assert_eq!(report.invocations, 1);
316        let telemetry = engine.telemetry();
317        assert_eq!(telemetry.bytecode_functions, 1);
318        assert_eq!(telemetry.invocations, 1);
319        assert!(telemetry.native_target_calls > 0);
320    }
321
322    #[test]
323    fn native_engine_reset_is_idempotent_and_does_not_discard_programs() {
324        let engine = NativeEngine::new();
325        let program = program("(+ 20 22)");
326        engine.execute(program.clone()).expect("first execution");
327        engine.reset();
328        engine.reset();
329        assert_eq!(engine.telemetry(), NativeExecutionTelemetry::default());
330        assert_eq!(engine.execute(program).unwrap().value, Value::Number(42));
331    }
332
333    #[test]
334    fn only_closed_native_target_names_are_classified() {
335        assert!(is_native_target_symbol("+"));
336        assert!(is_native_target_symbol("std.native.String/length"));
337        assert!(is_native_target_symbol(
338            "std.protocol.ilookup.ILookup/lookup"
339        ));
340        assert!(is_native_target_symbol("quot"));
341        assert!(!is_native_target_symbol("std.foundation.core/map"));
342        assert!(!is_native_target_symbol("example.application/start"));
343    }
344}