#![cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use crate::core::Value;
use crate::vm::{Program, VmFiber};
#[derive(Clone)]
pub(crate) struct ValidatedProgram {
program: Rc<Program>,
}
impl ValidatedProgram {
pub(crate) fn from_compiler(program: Rc<Program>) -> Self {
Self { program }
}
pub(crate) fn from_artifact(program: Rc<Program>) -> Self {
Self { program }
}
pub(crate) fn validate(program: Rc<Program>) -> Result<Self, String> {
crate::vm::validate::validate(&program)
.map_err(|error| format!("native backend received invalid bytecode: {error}"))?;
Ok(Self { program })
}
pub(crate) fn program(&self) -> Rc<Program> {
self.program.clone()
}
}
#[derive(Debug, Clone)]
pub struct NativeExecutionReport {
pub value: Value,
pub bytecode_functions: usize,
pub bytecode_instructions: usize,
pub native_target_calls: usize,
pub invocations: usize,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct NativeExecutionTelemetry {
pub bytecode_functions: usize,
pub bytecode_instructions: usize,
pub native_target_calls: usize,
pub invocations: usize,
}
impl NativeExecutionReport {
pub const BACKEND: &'static str = "bytecode-vm-native-substrate";
}
#[derive(Default)]
struct NativeEngineState {
bytecode_functions: Cell<usize>,
bytecode_instructions: Cell<usize>,
native_target_calls: Cell<usize>,
invocations: Cell<usize>,
}
#[derive(Clone)]
pub(crate) struct NativeExecutionScope {
state: Rc<NativeEngineState>,
}
thread_local! {
static ACTIVE_NATIVE_ENGINE: RefCell<Option<Rc<NativeEngineState>>> = const { RefCell::new(None) };
}
#[derive(Clone, Default)]
pub struct NativeEngine {
state: Rc<NativeEngineState>,
}
impl NativeEngine {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&self) {
self.state.bytecode_functions.set(0);
self.state.bytecode_instructions.set(0);
self.state.native_target_calls.set(0);
self.state.invocations.set(0);
}
pub fn telemetry(&self) -> NativeExecutionTelemetry {
let bytecode_functions = self.state.bytecode_functions.get();
let bytecode_instructions = self.state.bytecode_instructions.get();
NativeExecutionTelemetry {
bytecode_functions,
bytecode_instructions,
native_target_calls: self.state.native_target_calls.get(),
invocations: self.state.invocations.get(),
}
}
pub fn execute(&self, program: Rc<Program>) -> Result<NativeExecutionReport, String> {
self.execute_vm(ValidatedProgram::validate(program)?)
}
pub fn execute_blocking(&self, program: Rc<Program>) -> Result<NativeExecutionReport, String> {
self.execute_vm(ValidatedProgram::validate(program)?)
}
pub(crate) fn execute_blocking_validated_with_multimethods(
&self,
program: ValidatedProgram,
multimethods: crate::core::MultiMethodRegistry,
) -> Result<NativeExecutionReport, String> {
let context = crate::core::DirectNativeContext::capture_with_multimethods(multimethods);
context.with(|| self.execute_validated(program))
}
fn execute_validated(
&self,
validated: ValidatedProgram,
) -> Result<NativeExecutionReport, String> {
self.execute_vm(validated)
}
fn execute_vm(&self, validated: ValidatedProgram) -> Result<NativeExecutionReport, String> {
let program = validated.program();
let bytecode_functions = program.functions.len();
let bytecode_instructions = program
.functions
.iter()
.map(|function| function.code.len())
.sum();
self.record_bytecode_program(&program);
self.state
.invocations
.set(self.state.invocations.get().saturating_add(1));
let before_targets = self.state.native_target_calls.get();
let state = self.state.clone();
let run = || {
let mut fiber = VmFiber::start(program);
fiber.drive_sync().map_err(|error| error.to_string())
};
let result = with_active_engine(state, || crate::core::with_direct_native_execution(run));
let native_target_calls = self
.state
.native_target_calls
.get()
.saturating_sub(before_targets);
let value = result?;
Ok(NativeExecutionReport {
value,
bytecode_functions,
bytecode_instructions,
native_target_calls,
invocations: 1,
})
}
fn record_bytecode_program(&self, program: &Program) {
self.state.bytecode_functions.set(
self.state
.bytecode_functions
.get()
.saturating_add(program.functions.len()),
);
let instructions = program
.functions
.iter()
.map(|function| function.code.len())
.sum::<usize>();
self.state.bytecode_instructions.set(
self.state
.bytecode_instructions
.get()
.saturating_add(instructions),
);
}
}
fn with_active_engine<R>(state: Rc<NativeEngineState>, action: impl FnOnce() -> R) -> R {
ACTIVE_NATIVE_ENGINE.with(|active| {
let previous = active.borrow_mut().replace(state);
let result = action();
*active.borrow_mut() = previous;
result
})
}
pub(crate) fn capture_execution_scope() -> Option<NativeExecutionScope> {
ACTIVE_NATIVE_ENGINE.with(|active| {
active
.borrow()
.as_ref()
.cloned()
.map(|state| NativeExecutionScope { state })
})
}
impl NativeExecutionScope {
pub(crate) fn with<R>(&self, action: impl FnOnce() -> R) -> R {
with_active_engine(self.state.clone(), || {
crate::core::with_direct_native_execution(action)
})
}
}
pub(crate) fn with_captured_context<R>(
scope: Option<&NativeExecutionScope>,
context: Option<&crate::core::DirectNativeContext>,
action: impl FnOnce() -> R,
) -> R {
if let Some(scope) = scope {
scope.with(|| {
if let Some(context) = context {
context.with(action)
} else {
action()
}
})
} else if let Some(context) = context {
context.with(action)
} else {
action()
}
}
pub(crate) fn is_native_target_symbol(name: &str) -> bool {
crate::core::IntrinsicOp::from_symbol(name).is_some()
|| matches!(name, "disj" | "quot" | "rem" | "mod")
|| crate::core::canonical_intrinsic_callable_symbol(name).is_some()
}
pub(crate) fn record_native_target(name: &str) {
if !is_native_target_symbol(name) {
return;
}
ACTIVE_NATIVE_ENGINE.with(|active| {
if let Some(state) = active.borrow().as_ref() {
state
.native_target_calls
.set(state.native_target_calls.get().saturating_add(1));
}
});
}
#[cfg(test)]
mod tests {
use super::*;
fn program(source: &str) -> Rc<Program> {
Rc::new(crate::vm::compile_source(source).expect("test program compiles"))
}
#[test]
fn native_engine_executes_hara_in_the_vm_and_counts_targets() {
let engine = NativeEngine::new();
let report = engine
.execute(program("(let [value 20] (+ value 22))"))
.expect("native-substrate execution");
assert_eq!(report.value, Value::Number(42));
assert_eq!(report.bytecode_functions, 1);
assert!(report.bytecode_instructions > 0);
assert!(report.native_target_calls > 0);
assert_eq!(report.invocations, 1);
let telemetry = engine.telemetry();
assert_eq!(telemetry.bytecode_functions, 1);
assert_eq!(telemetry.invocations, 1);
assert!(telemetry.native_target_calls > 0);
}
#[test]
fn native_engine_reset_is_idempotent_and_does_not_discard_programs() {
let engine = NativeEngine::new();
let program = program("(+ 20 22)");
engine.execute(program.clone()).expect("first execution");
engine.reset();
engine.reset();
assert_eq!(engine.telemetry(), NativeExecutionTelemetry::default());
assert_eq!(engine.execute(program).unwrap().value, Value::Number(42));
}
#[test]
fn only_closed_native_target_names_are_classified() {
assert!(is_native_target_symbol("+"));
assert!(is_native_target_symbol("std.native.String/length"));
assert!(is_native_target_symbol(
"std.protocol.ilookup.ILookup/lookup"
));
assert!(is_native_target_symbol("quot"));
assert!(!is_native_target_symbol("std.foundation.core/map"));
assert!(!is_native_target_symbol("example.application/start"));
}
}