hara_native/
direct_native.rs1#![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#[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#[derive(Debug, Clone)]
55pub struct NativeExecutionReport {
56 pub value: Value,
58 pub bytecode_functions: usize,
60 pub bytecode_instructions: usize,
62 pub native_target_calls: usize,
65 pub invocations: usize,
67}
68
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
71pub struct NativeExecutionTelemetry {
72 pub bytecode_functions: usize,
74 pub bytecode_instructions: usize,
76 pub native_target_calls: usize,
78 pub invocations: usize,
80}
81
82impl NativeExecutionReport {
83 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#[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#[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 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 pub fn execute(&self, program: Rc<Program>) -> Result<NativeExecutionReport, String> {
146 self.execute_vm(ValidatedProgram::validate(program)?)
147 }
148
149 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
253pub(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
275pub(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
283pub(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}