From def57bb880a761f4e48df7543998d5856d4e03da Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:53:44 -0400 Subject: [PATCH 1/9] Initial deno interpreter --- turing/Cargo.toml | 7 +- turing/src/engine/deno_engine.rs | 336 +++++++++++++++++++++++++++++++ turing/src/engine/mod.rs | 7 + turing/src/interop/params.rs | 25 ++- 4 files changed, 368 insertions(+), 7 deletions(-) create mode 100644 turing/src/engine/deno_engine.rs diff --git a/turing/Cargo.toml b/turing/Cargo.toml index 9b464b2..cc29059 100644 --- a/turing/Cargo.toml +++ b/turing/Cargo.toml @@ -7,9 +7,10 @@ edition = "2024" crate-type = ["cdylib", "staticlib", "rlib"] [features] -default = ["wasm", "lua"] +default = ["wasm", "lua", "deno"] wasm = ["dep:wasmtime", "dep:wasmtime-wasi"] lua = ["dep:mlua"] +deno = ["dep:deno_core", "dep:deno_error"] # Enables registration of global-based FFI functions for all engines global_ffi = [] @@ -39,11 +40,13 @@ smallvec = "1" parking_lot = "0.12.5" rustc-hash = "2" convert_case = "0.10.0" +deno_core = {version = "0.375.0", optional = true} +deno_error = {version = "0.7", optional = true} [dev-dependencies] # for testing with wasmtime wat = { version = "1.239.0" } -wasmprinter = {version = "0.243.0" } +wasmprinter = { version = "0.243.0" } serial_test = "3.2.0" criterion = "0.8" diff --git a/turing/src/engine/deno_engine.rs b/turing/src/engine/deno_engine.rs new file mode 100644 index 0000000..6658d9a --- /dev/null +++ b/turing/src/engine/deno_engine.rs @@ -0,0 +1,336 @@ +use std::fs; +use std::marker::PhantomData; +use std::path::Path; +use std::sync::Arc; + +use anyhow::{Result, anyhow}; +use deno_core::{JsRuntime, OpState, RuntimeOptions}; +use deno_core::op2; +use deno_error::JsErrorBox; +use parking_lot::RwLock; +use rustc_hash::FxHashMap; + +use crate::engine::types::ScriptFnMetadata; +use crate::interop::params::Params; +use crate::{EngineDataState, ExternalFunctions}; + +pub struct DenoEngine +where + Ext: ExternalFunctions + Send + Sync + 'static, +{ + runtime: JsRuntime, + module_name: Option, + deno_fns: FxHashMap, + data: Arc>, + _ext: PhantomData, +} + +// Single dispatch op which receives a JSON array like `["fn.name", [arg0, arg1, ...]]` +#[op2] +#[serde] +fn turing_dispatch( + state: &mut OpState, + #[serde] payload: serde_json::Value, +) -> Result { + use crate::OpaquePointerKey; + use crate::interop::params::{DataType, Param, Params}; + use slotmap::KeyData; + + // payload expected to be [name, args] + let (name, args) = match payload { + serde_json::Value::Array(mut a) if !a.is_empty() => { + let name = a + .remove(0) + .as_str() + .ok_or_else(|| JsErrorBox::generic("invalid call name"))? + .to_string(); + let args = if !a.is_empty() { + a.remove(0) + } else { + serde_json::Value::Array(vec![]) + }; + (name, args) + } + _ => return Err(JsErrorBox::generic("invalid payload")), + }; + + let map = state.borrow::>(); + let data = state.borrow::>>(); + + let metadata = map + .get(&name) + .ok_or_else(|| JsErrorBox::generic("function not found"))?; + let p_types = metadata.param_types.clone(); + + let args_arr = match args { + serde_json::Value::Array(a) => a, + other => vec![other], + }; + + let mut params = Params::of_size(p_types.len() as u32); + for (t, v) in p_types.iter().zip(args_arr.into_iter()) { + let p = match t { + DataType::I8 => Param::I8( + v.as_i64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as i8, + ), + DataType::I16 => Param::I16( + v.as_i64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as i16, + ), + DataType::I32 => Param::I32( + v.as_i64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as i32, + ), + DataType::I64 => Param::I64( + v.as_i64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?, + ), + DataType::U8 => Param::U8( + v.as_u64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as u8, + ), + DataType::U16 => Param::U16( + v.as_u64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as u16, + ), + DataType::U32 => Param::U32( + v.as_u64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as u32, + ), + DataType::U64 => Param::U64( + v.as_u64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?, + ), + DataType::F32 => Param::F32( + v.as_f64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as f32, + ), + DataType::F64 => Param::F64( + v.as_f64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?, + ), + DataType::Bool => Param::Bool( + v.as_bool() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?, + ), + DataType::RustString | DataType::ExtString => Param::String( + v.as_str() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? + .to_string(), + ), + DataType::Object => { + let id = v + .as_u64() + .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?; + let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(id)); + let real = data + .read() + .opaque_pointers + .get(pointer_key) + .copied() + .unwrap_or_default(); + Param::Object(real.ptr) + } + _ => return Err(JsErrorBox::type_error("unsupported parameter type")), + }; + params.push(p); + } + + let ffi = params.to_ffi::(); + let ffi_arr = ffi.as_ffi_array(); + let ret = (metadata.callback)(ffi_arr); + + // convert return to JSON + let j = match ret + .into_param::() + .map_err(|e| JsErrorBox::generic("failed return value"))? + { + Param::I8(i) => serde_json::Value::from(i), + Param::I16(i) => serde_json::Value::from(i), + Param::I32(i) => serde_json::Value::from(i), + Param::I64(i) => serde_json::Value::from(i), + Param::U8(u) => serde_json::Value::from(u), + Param::U16(u) => serde_json::Value::from(u), + Param::U32(u) => serde_json::Value::from(u), + Param::U64(u) => serde_json::Value::from(u), + Param::F32(f) => serde_json::Value::from(f), + Param::F64(f) => serde_json::Value::from(f), + Param::Bool(b) => serde_json::Value::from(b), + Param::String(s) => serde_json::Value::from(s), + Param::Void => serde_json::Value::Null, + Param::Object(ptr) => { + let mut s = data.write(); + let key = s.get_opaque_pointer(ptr.into()); + serde_json::Value::from(key.0.as_ffi()) + } + Param::Error(e) => return Err(JsErrorBox::generic(e)), + }; + + Ok(j) +} + +#[doc = r""] +#[doc = r" An extension for use with the Deno JS runtime."] +#[doc = r" To use it, provide it as an argument when instantiating your runtime:"] +#[doc = r""] +#[doc = r" ```rust,ignore"] +#[doc = r" use deno_core::{ JsRuntime, RuntimeOptions };"] +#[doc = r""] +#[doc = concat!("let mut extensions = vec![",stringify!(turing),"::init()];")] +#[doc = r" let mut js_runtime = JsRuntime::new(RuntimeOptions {"] +#[doc = r" extensions,"] +#[doc = r" ..Default::default()"] +#[doc = r" });"] +#[doc = r" ```"] +#[doc = r""] +#[allow(non_camel_case_types)] +pub struct turing_op { + _phantom: ::std::marker::PhantomData, +} + +impl turing_op { + fn ext() -> deno_core::Extension { + #[allow(unused_imports)] + use deno_core::Op; + deno_core::Extension { + name: ::std::stringify!(turing), + deps: &[], + js_files: { + const JS: &[deno_core::ExtensionFileSource] = + &deno_core::include_js_files!(turing); + ::std::borrow::Cow::Borrowed(JS) + }, + esm_files: { + const JS: &[deno_core::ExtensionFileSource] = + &deno_core::include_js_files!(turing); + ::std::borrow::Cow::Borrowed(JS) + }, + lazy_loaded_esm_files: { + const JS: &[deno_core::ExtensionFileSource] = + &deno_core::include_lazy_loaded_js_files!(turing); + ::std::borrow::Cow::Borrowed(JS) + }, + esm_entry_point: { + const V: ::std::option::Option<&'static ::std::primitive::str> = + deno_core::or!(, ::std::option::Option::None); + V + }, + ops: ::std::borrow::Cow::Owned(vec![{ turing_dispatch::() }]), + objects: ::std::borrow::Cow::Borrowed(&[]), + external_references: ::std::borrow::Cow::Borrowed(&[]), + global_template_middleware: ::std::option::Option::None, + global_object_middleware: ::std::option::Option::None, + op_state_fn: ::std::option::Option::None, + needs_lazy_init: false, + middleware_fn: ::std::option::Option::None, + enabled: true, + } + } + #[inline(always)] + #[allow(unused_variables)] + fn with_ops_fn(ext: &mut deno_core::Extension) { + deno_core::extension!(!__ops__ ext __eot__); + } + #[inline(always)] + #[allow(unused_variables)] + fn with_middleware(ext: &mut deno_core::Extension) {} + + #[inline(always)] + #[allow(unused_variables)] + #[allow(clippy::redundant_closure_call)] + fn with_customizer(ext: &mut deno_core::Extension) {} + + #[doc = r" Initialize this extension for runtime or snapshot creation."] + #[doc = r""] + #[doc = r" # Returns"] + #[doc = r" an Extension object that can be used during instantiation of a JsRuntime"] + #[allow(dead_code)] + pub fn init() -> deno_core::Extension { + let mut ext = Self::ext(); + Self::with_ops_fn(&mut ext); + deno_core::extension!(!__config__ ext); + Self::with_middleware(&mut ext); + Self::with_customizer(&mut ext); + ext + } + #[doc = r" Initialize this extension for runtime or snapshot creation."] + #[doc = r""] + #[doc = r" If this method is used, you must later call `JsRuntime::lazy_init_extensions`"] + #[doc = r" with the result of this extension's `args` method."] + #[doc = r""] + #[doc = r" # Returns"] + #[doc = r" an Extension object that can be used during instantiation of a JsRuntime"] + #[allow(dead_code)] + pub fn lazy_init() -> deno_core::Extension { + let mut ext = Self::ext(); + Self::with_ops_fn(&mut ext); + ext.needs_lazy_init = true; + Self::with_middleware(&mut ext); + Self::with_customizer(&mut ext); + ext + } + #[doc = r" Create an `ExtensionArguments` value which must be passed to"] + #[doc = r" `JsRuntime::lazy_init_extensions`."] + #[allow(dead_code, unused_mut)] + pub fn args() -> deno_core::ExtensionArguments { + + deno_core::extension!(!__config__ args); + deno_core::ExtensionArguments { + name: ::std::stringify!(turing), + op_state_fn: ::std::option::Option::None, + } + } +} + +impl DenoEngine +where + Ext: ExternalFunctions + Send + Sync + 'static, +{ + pub fn new( + js_functions: &FxHashMap, + data: Arc>, + ) -> Result { + // Register a single dispatch op generated by the `#[op]` macro. + + let runtime = JsRuntime::new(RuntimeOptions { + extensions: vec![ + turing_op::::init(), + ], + module_loader: None, + ..Default::default() + }); + + Ok(Self { + runtime, + module_name: None, + deno_fns: js_functions.clone(), + data, + _ext: PhantomData, + }) + } + + pub fn load_script(&mut self, path: &Path) -> Result<()> { + let script = fs::read_to_string(path)?; + + let mname = path.to_string_lossy().to_string(); + self.runtime + .execute_script(mname.clone(), script) + .map_err(|e| anyhow!(e.to_string()))?; + self.module_name = Some(mname); + + Ok(()) + } + + pub fn call_fn( + &mut self, + _name: &str, + _params: Params, + _ret_type: crate::interop::params::DataType, + _data: Arc>, + ) -> crate::interop::params::Param { + crate::interop::params::Param::Error( + "Deno engine call from Rust -> JS is not implemented".to_string(), + ) + } +} diff --git a/turing/src/engine/mod.rs b/turing/src/engine/mod.rs index aa38fce..fc90443 100644 --- a/turing/src/engine/mod.rs +++ b/turing/src/engine/mod.rs @@ -13,6 +13,9 @@ pub mod lua_engine; #[cfg(feature = "wasm")] pub mod wasm_engine; +#[cfg(feature = "deno")] +pub mod deno_engine; + pub mod types; pub enum Engine @@ -23,6 +26,8 @@ where Wasm(wasm_engine::WasmInterpreter), #[cfg(feature = "lua")] Lua(lua_engine::LuaInterpreter), + #[cfg(feature = "deno")] + Deno(deno_engine::DenoEngine), } impl Engine @@ -41,6 +46,8 @@ where Engine::Wasm(engine) => engine.call_fn(name, params, ret_type, data), #[cfg(feature = "lua")] Engine::Lua(engine) => engine.call_fn(name, params, ret_type, data), + #[cfg(feature = "deno")] + Engine::Deno(engine) => engine.call_fn(name, params, ret_type, data), _ => Param::Error("No code engine is active".to_string()), } } diff --git a/turing/src/interop/params.rs b/turing/src/interop/params.rs index 27c4be2..4b40ae7 100644 --- a/turing/src/interop/params.rs +++ b/turing/src/interop/params.rs @@ -4,6 +4,7 @@ use std::ffi::{CStr, CString, c_char, c_void}; use std::fmt::Display; use std::marker::PhantomData; use std::mem; +use std::ops::{Deref, DerefMut}; use std::sync::{Arc}; use anyhow::{anyhow, Result}; use num_enum::TryFromPrimitive; @@ -241,7 +242,7 @@ impl Param { caller: &wasmtime::Store, ) -> Self { use crate::engine::wasm_engine::get_wasm_string; - + match typ { DataType::I8 => Param::I8(val.unwrap_i32() as i8), DataType::I16 => Param::I16(val.unwrap_i32() as i16), @@ -447,7 +448,9 @@ impl Params { use wasmtime::Val; let mut s = data.write(); - let vals = self.params.into_iter().map(|p| + + + self.params.into_iter().map(|p| match p { Param::I8(i) => Ok(Val::I32(i as i32)), Param::I16(i) => Ok(Val::I32(i as i32)), @@ -480,9 +483,7 @@ impl Params { } _ => unreachable!("Void shouldn't ever be added as an arg"), } - ).collect(); - - vals + ).collect() } #[cfg(feature = "lua")] @@ -527,6 +528,20 @@ impl Params { } } +impl Deref for Params { + type Target = SmallVec<[Param; 4]>; + + fn deref(&self) -> &Self::Target { + &self.params + } +} + +impl DerefMut for Params { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.params + } +} + /// C repr of ffi data #[repr(C)] pub union RawParam { From e230a5296e6e39e6acff8d411ecd298bb556905d Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:20:21 -0400 Subject: [PATCH 2/9] Implement Deno --- turing/src/engine/deno_engine.rs | 173 +++++++++++++++++++++++-------- 1 file changed, 132 insertions(+), 41 deletions(-) diff --git a/turing/src/engine/deno_engine.rs b/turing/src/engine/deno_engine.rs index 6658d9a..f5343c8 100644 --- a/turing/src/engine/deno_engine.rs +++ b/turing/src/engine/deno_engine.rs @@ -4,15 +4,17 @@ use std::path::Path; use std::sync::Arc; use anyhow::{Result, anyhow}; -use deno_core::{JsRuntime, OpState, RuntimeOptions}; use deno_core::op2; +use deno_core::{JsRuntime, OpState, RuntimeOptions, serde_v8, v8}; use deno_error::JsErrorBox; use parking_lot::RwLock; use rustc_hash::FxHashMap; +use crate::OpaquePointerKey; use crate::engine::types::ScriptFnMetadata; -use crate::interop::params::Params; +use crate::interop::params::{DataType, Param, Params}; use crate::{EngineDataState, ExternalFunctions}; +use slotmap::KeyData; pub struct DenoEngine where @@ -25,7 +27,55 @@ where _ext: PhantomData, } +fn param_to_js( + param: Param, + data: &Arc>, +) -> Result { + Ok(match param { + Param::I8(i) => serde_json::Value::from(i), + Param::I16(i) => serde_json::Value::from(i), + Param::I32(i) => serde_json::Value::from(i), + Param::I64(i) => serde_json::Value::from(i), + Param::U8(u) => serde_json::Value::from(u), + Param::U16(u) => serde_json::Value::from(u), + Param::U32(u) => serde_json::Value::from(u), + Param::U64(u) => serde_json::Value::from(u), + Param::F32(f) => serde_json::Value::from(f), + Param::F64(f) => serde_json::Value::from(f), + Param::Bool(b) => serde_json::Value::from(b), + Param::String(s) => serde_json::Value::from(s), + Param::Void => serde_json::Value::Null, + Param::Object(ptr) => { + let mut s = data.write(); + let key = s.get_opaque_pointer(ptr.into()); + serde_json::Value::from(key.0.as_ffi()) + } + Param::Error(e) => return Err(JsErrorBox::generic(e)), + }) +} + +fn js_value_to_param(value: serde_json::Value) -> Param { + match value { + serde_json::Value::Number(n) => { + if let Some(i) = n.as_i64() { + Param::I64(i) + } else if let Some(u) = n.as_u64() { + Param::U64(u) + } else if let Some(f) = n.as_f64() { + Param::F64(f) + } else { + Param::Error("Invalid number".to_string()) + } + } + serde_json::Value::String(s) => Param::String(s), + serde_json::Value::Bool(b) => Param::Bool(b), + serde_json::Value::Null => Param::Void, + _ => Param::Error("Unsupported return type".to_string()), + } +} + // Single dispatch op which receives a JSON array like `["fn.name", [arg0, arg1, ...]]` +/// Handles calling registered FFI functions from JS. #[op2] #[serde] fn turing_dispatch( @@ -142,30 +192,11 @@ fn turing_dispatch( let ret = (metadata.callback)(ffi_arr); // convert return to JSON - let j = match ret + let ret = ret .into_param::() - .map_err(|e| JsErrorBox::generic("failed return value"))? - { - Param::I8(i) => serde_json::Value::from(i), - Param::I16(i) => serde_json::Value::from(i), - Param::I32(i) => serde_json::Value::from(i), - Param::I64(i) => serde_json::Value::from(i), - Param::U8(u) => serde_json::Value::from(u), - Param::U16(u) => serde_json::Value::from(u), - Param::U32(u) => serde_json::Value::from(u), - Param::U64(u) => serde_json::Value::from(u), - Param::F32(f) => serde_json::Value::from(f), - Param::F64(f) => serde_json::Value::from(f), - Param::Bool(b) => serde_json::Value::from(b), - Param::String(s) => serde_json::Value::from(s), - Param::Void => serde_json::Value::Null, - Param::Object(ptr) => { - let mut s = data.write(); - let key = s.get_opaque_pointer(ptr.into()); - serde_json::Value::from(key.0.as_ffi()) - } - Param::Error(e) => return Err(JsErrorBox::generic(e)), - }; + .map_err(|e| JsErrorBox::generic("failed return value"))?; + + let j = param_to_js(ret, data)?; Ok(j) } @@ -197,13 +228,11 @@ impl turing_op { name: ::std::stringify!(turing), deps: &[], js_files: { - const JS: &[deno_core::ExtensionFileSource] = - &deno_core::include_js_files!(turing); + const JS: &[deno_core::ExtensionFileSource] = &deno_core::include_js_files!(turing); ::std::borrow::Cow::Borrowed(JS) }, esm_files: { - const JS: &[deno_core::ExtensionFileSource] = - &deno_core::include_js_files!(turing); + const JS: &[deno_core::ExtensionFileSource] = &deno_core::include_js_files!(turing); ::std::borrow::Cow::Borrowed(JS) }, lazy_loaded_esm_files: { @@ -274,7 +303,6 @@ impl turing_op { #[doc = r" `JsRuntime::lazy_init_extensions`."] #[allow(dead_code, unused_mut)] pub fn args() -> deno_core::ExtensionArguments { - deno_core::extension!(!__config__ args); deno_core::ExtensionArguments { name: ::std::stringify!(turing), @@ -293,14 +321,19 @@ where ) -> Result { // Register a single dispatch op generated by the `#[op]` macro. - let runtime = JsRuntime::new(RuntimeOptions { - extensions: vec![ - turing_op::::init(), - ], + let mut runtime = JsRuntime::new(RuntimeOptions { + extensions: vec![turing_op::::init()], module_loader: None, ..Default::default() }); + // Inject a small helper once to minimize per-call overhead. + // `__turing_call(name, argsArray)` will look up the function and call it. + let helper = r#"globalThis.__turing_call = function(name, args) { const fn = globalThis[name]; if (typeof fn !== 'function') throw new Error('function not found'); return fn.apply(null, args); };"#; + runtime + .execute_script("__turing_helper", helper) + .map_err(|e| anyhow!(e.to_string()))?; + Ok(Self { runtime, module_name: None, @@ -324,13 +357,71 @@ where pub fn call_fn( &mut self, - _name: &str, - _params: Params, - _ret_type: crate::interop::params::DataType, - _data: Arc>, + name: &str, + params: Params, + ret_type: crate::interop::params::DataType, + data: Arc>, ) -> crate::interop::params::Param { - crate::interop::params::Param::Error( - "Deno engine call from Rust -> JS is not implemented".to_string(), - ) + // Basic implementation: serialize params to JSON and invoke the global JS + // function by name. For now the return value is not converted in full + // generality — we return `Void` on success and `Error(...)` on failure. + use crate::interop::params::Param; + + // build JSON array of args + let args_vec = params + .into_iter() + .map(|p| param_to_js(p, &data)) + .collect::>(); + + let args_vec = match args_vec { + Ok(v) => v, + Err(e) => { + return Param::Error(format!("argument conversion error: {}", e)); + } + }; + + // Serialize the args as a JS array literal (no JSON.parse). + let args_literal = serde_json::to_string(&serde_json::Value::Array(args_vec)) + .unwrap_or_else(|_| "[]".to_string()); + + let call_code = format!( + "__turing_call({}, {});", + serde_json::to_string(&name).unwrap(), + args_literal + ); + + let script_name = format!("turing_call:{}", name); + let exec_res = self.runtime.execute_script(script_name, call_code); + + match exec_res { + Ok(global_val) => { + deno_core::scope!(scope, &mut self.runtime); + let local = v8::Local::new(scope, &global_val); + + let json_val: serde_json::Value = match serde_v8::from_v8(scope, local) { + Ok(v) => v, + Err(e) => return Param::Error(format!("return conversion error: {}", e)), + }; + + // If the caller expects an object (opaque pointer id), map it back. + if ret_type == DataType::Object { + if let Some(id) = json_val.as_u64() { + let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(id)); + let real = data + .read() + .opaque_pointers + .get(pointer_key) + .copied() + .unwrap_or_default(); + return Param::Object(real.ptr); + } else { + return Param::Error("expected object id (number) from JS".to_string()); + } + } + + js_value_to_param(json_val) + } + Err(e) => Param::Error(e.to_string()), + } } } From 6cd0ad6e4de9717266a3ddeb87c6039411a54e1c Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:55:44 -0400 Subject: [PATCH 3/9] V8 value encoding/decoding --- turing/src/engine/deno_engine.rs | 488 +++++++++++++------- turing/src/engine/deno_engine/conversion.rs | 55 +++ turing/src/interop/params.rs | 289 ++++++++---- 3 files changed, 563 insertions(+), 269 deletions(-) create mode 100644 turing/src/engine/deno_engine/conversion.rs diff --git a/turing/src/engine/deno_engine.rs b/turing/src/engine/deno_engine.rs index f5343c8..3a81390 100644 --- a/turing/src/engine/deno_engine.rs +++ b/turing/src/engine/deno_engine.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use anyhow::{Result, anyhow}; use deno_core::op2; +use deno_core::v8::{HandleScope, PinScope}; use deno_core::{JsRuntime, OpState, RuntimeOptions, serde_v8, v8}; use deno_error::JsErrorBox; use parking_lot::RwLock; @@ -13,9 +14,12 @@ use rustc_hash::FxHashMap; use crate::OpaquePointerKey; use crate::engine::types::ScriptFnMetadata; use crate::interop::params::{DataType, Param, Params}; +use crate::interop::types::ExtPointer; use crate::{EngineDataState, ExternalFunctions}; use slotmap::KeyData; +pub mod conversion; + pub struct DenoEngine where Ext: ExternalFunctions + Send + Sync + 'static, @@ -23,168 +27,210 @@ where runtime: JsRuntime, module_name: Option, deno_fns: FxHashMap, + deno_fn_handles: FxHashMap>, data: Arc>, _ext: PhantomData, } -fn param_to_js( +// Convert a host Param into a V8 `Value` within the provided scope. +fn param_to_v8<'s>( + scope: &mut deno_core::v8::PinScope<'s, '_>, param: Param, data: &Arc>, -) -> Result { - Ok(match param { - Param::I8(i) => serde_json::Value::from(i), - Param::I16(i) => serde_json::Value::from(i), - Param::I32(i) => serde_json::Value::from(i), - Param::I64(i) => serde_json::Value::from(i), - Param::U8(u) => serde_json::Value::from(u), - Param::U16(u) => serde_json::Value::from(u), - Param::U32(u) => serde_json::Value::from(u), - Param::U64(u) => serde_json::Value::from(u), - Param::F32(f) => serde_json::Value::from(f), - Param::F64(f) => serde_json::Value::from(f), - Param::Bool(b) => serde_json::Value::from(b), - Param::String(s) => serde_json::Value::from(s), - Param::Void => serde_json::Value::Null, +) -> Result, JsErrorBox> { + // Convert Param -> serde_json -> V8 using serde_v8 to avoid scope type mismatches + + match param { + Param::I8(i) => serde_v8::to_v8(scope, i).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::I16(i) => serde_v8::to_v8(scope, i).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::I32(i) => serde_v8::to_v8(scope, i).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::I64(i) => serde_v8::to_v8(scope, i).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::U8(u) => serde_v8::to_v8(scope, u).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::U16(u) => serde_v8::to_v8(scope, u).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::U32(u) => serde_v8::to_v8(scope, u).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::U64(u) => serde_v8::to_v8(scope, u).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::F32(f) => serde_v8::to_v8(scope, f).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::F64(f) => serde_v8::to_v8(scope, f).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::Bool(b) => serde_v8::to_v8(scope, b).map_err(|e| JsErrorBox::generic(e.to_string())), + Param::String(s) => { + serde_v8::to_v8(scope, s).map_err(|e| JsErrorBox::generic(e.to_string())) + } + Param::Void => serde_v8::to_v8(scope, ()).map_err(|e| JsErrorBox::generic(e.to_string())), Param::Object(ptr) => { - let mut s = data.write(); - let key = s.get_opaque_pointer(ptr.into()); - serde_json::Value::from(key.0.as_ffi()) + let mut write = data.write(); + let key = write.get_opaque_pointer(ExtPointer { ptr }); + let id = key.0.as_ffi(); + serde_v8::to_v8(scope, id).map_err(|e| JsErrorBox::generic(e.to_string())) } - Param::Error(e) => return Err(JsErrorBox::generic(e)), - }) + Param::Error(e) => Err(JsErrorBox::generic(e)), + } } -fn js_value_to_param(value: serde_json::Value) -> Param { - match value { - serde_json::Value::Number(n) => { - if let Some(i) = n.as_i64() { - Param::I64(i) - } else if let Some(u) = n.as_u64() { - Param::U64(u) - } else if let Some(f) = n.as_f64() { - Param::F64(f) - } else { - Param::Error("Invalid number".to_string()) +// Convert a V8 `Value` into a host `Param`. +fn v8_to_param<'s>( + scope: &mut v8::PinnedRef<'s, HandleScope<'s>>, + data: &Arc>, + value: v8::Local<'s, v8::Value>, + expect_type: Option, +) -> Param { + if let Some(expect_type) = expect_type { + return match expect_type { + DataType::Void => Param::Void, + DataType::Bool => { + if value.is_boolean() { + Param::Bool(value.boolean_value(scope)) + } else { + Param::Error("expected boolean".to_string()) + } } - } - serde_json::Value::String(s) => Param::String(s), - serde_json::Value::Bool(b) => Param::Bool(b), - serde_json::Value::Null => Param::Void, - _ => Param::Error("Unsupported return type".to_string()), + DataType::I32 => { + if value.is_int32() { + Param::I32(value.int32_value(scope).unwrap()) + } else { + Param::Error("expected int32".to_string()) + } + } + DataType::F64 => { + if value.is_number() { + Param::F64(value.number_value(scope).unwrap()) + } else { + Param::Error("expected number".to_string()) + } + } + DataType::RustString | DataType::ExtString => { + if value.is_string() { + let s = value.to_rust_string_lossy(scope); + Param::String(s) + } else { + Param::Error("expected string".to_string()) + } + } + DataType::Object => { + // expect a big integer id + if value.is_big_int() { + let id = value.to_big_int(scope).unwrap().i64_value().0 as u64; + let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(id)); + + let read = data.read(); + let Some(real) = read.opaque_pointers.get(pointer_key) else { + return Param::Error(format!("Invalid opaque pointer id: {}", id)); + }; + return Param::Object(real.ptr); + } else { + Param::Error("expected object id (bigint)".to_string()) + } + } + _ => unreachable!("unsupported expected type {}", expect_type), + }; + } + + if value.is_undefined() || value.is_null() { + return Param::Void; + } + if value.is_boolean() { + return Param::Bool(value.boolean_value(scope)); } + if value.is_int32() { + return Param::I32(value.int32_value(scope).unwrap()); + } + if value.is_uint32() { + return Param::U32(value.uint32_value(scope).unwrap()); + } + if value.is_big_int() { + return Param::I64(value.to_big_int(scope).unwrap().i64_value().0); + } + if value.is_number() { + return Param::F64(value.number_value(scope).unwrap()); + } + if value.is_string() { + let s = value.to_rust_string_lossy(scope); + return Param::String(s); + } + if value.is_object() { + // get the object's identity field + let obj = value.to_object(scope).unwrap(); + let id_key = v8::String::new(scope, "__turing_pointer_id").unwrap(); + let id_val = obj.get(scope, id_key.into()).unwrap(); + // assume it's a big integer + let id = id_val.to_big_int(scope).unwrap().i64_value().0 as u64; + let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(id)); + + let read = data.read(); + let Some(real) = read.opaque_pointers.get(pointer_key) else { + return Param::Error(format!("Invalid opaque pointer id: {}", id)); + }; + return Param::Object(real.ptr); + } + + if value.is_array() { + return Param::Error("Array return types are not supported".to_string()); + } + + if value.is_function() { + return Param::Error("Function return types are not supported".to_string()); + } + + unreachable!("Does not support {value:?}") } + + + // Single dispatch op which receives a JSON array like `["fn.name", [arg0, arg1, ...]]` /// Handles calling registered FFI functions from JS. #[op2] -#[serde] +#[global] fn turing_dispatch( state: &mut OpState, - #[serde] payload: serde_json::Value, -) -> Result { - use crate::OpaquePointerKey; - use crate::interop::params::{DataType, Param, Params}; - use slotmap::KeyData; - + payload: v8::Local, +) -> Result, JsErrorBox> { // payload expected to be [name, args] - let (name, args) = match payload { - serde_json::Value::Array(mut a) if !a.is_empty() => { - let name = a - .remove(0) - .as_str() - .ok_or_else(|| JsErrorBox::generic("invalid call name"))? - .to_string(); - let args = if !a.is_empty() { - a.remove(0) - } else { - serde_json::Value::Array(vec![]) - }; - (name, args) - } - _ => return Err(JsErrorBox::generic("invalid payload")), - }; - let map = state.borrow::>(); - let data = state.borrow::>>(); + if !payload.is_array() { + return Err(JsErrorBox::generic("invalid payload")); + } + + let data = state.borrow::>>().clone(); + let map = state + .borrow::>() + .clone(); + + // parse array + let array = v8::Local::::try_from(payload) + .map_err(|_| JsErrorBox::generic("invalid payload"))?; + + let runtime = state.borrow_mut::(); + deno_core::scope!(scope, runtime); + + let (name, args) = { + let length = array.length(); + let name = array + .get_index(scope, 0) + .ok_or_else(|| JsErrorBox::generic("invalid payload"))? + .to_string(scope) + .ok_or_else(|| JsErrorBox::generic("invalid function name"))? + .to_rust_string_lossy(scope); + let args = (0..length) + .filter_map(|i| array.get_index(scope, i)) + .collect::>(); + + (name, args) + }; let metadata = map .get(&name) - .ok_or_else(|| JsErrorBox::generic("function not found"))?; - let p_types = metadata.param_types.clone(); + .ok_or_else(|| JsErrorBox::generic("function not found"))? + .clone(); - let args_arr = match args { - serde_json::Value::Array(a) => a, - other => vec![other], - }; + let p_types = metadata.param_types.clone(); let mut params = Params::of_size(p_types.len() as u32); - for (t, v) in p_types.iter().zip(args_arr.into_iter()) { - let p = match t { - DataType::I8 => Param::I8( - v.as_i64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as i8, - ), - DataType::I16 => Param::I16( - v.as_i64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as i16, - ), - DataType::I32 => Param::I32( - v.as_i64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as i32, - ), - DataType::I64 => Param::I64( - v.as_i64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?, - ), - DataType::U8 => Param::U8( - v.as_u64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as u8, - ), - DataType::U16 => Param::U16( - v.as_u64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as u16, - ), - DataType::U32 => Param::U32( - v.as_u64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as u32, - ), - DataType::U64 => Param::U64( - v.as_u64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?, - ), - DataType::F32 => Param::F32( - v.as_f64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? as f32, - ), - DataType::F64 => Param::F64( - v.as_f64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?, - ), - DataType::Bool => Param::Bool( - v.as_bool() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?, - ), - DataType::RustString | DataType::ExtString => Param::String( - v.as_str() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))? - .to_string(), - ), - DataType::Object => { - let id = v - .as_u64() - .ok_or_else(|| JsErrorBox::type_error("type mismatch"))?; - let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(id)); - let real = data - .read() - .opaque_pointers - .get(pointer_key) - .copied() - .unwrap_or_default(); - Param::Object(real.ptr) - } - _ => return Err(JsErrorBox::type_error("unsupported parameter type")), - }; - params.push(p); + for (i, exp_type) in p_types.iter().enumerate() { + let arg = args + .get(i) + .ok_or_else(|| JsErrorBox::generic("missing argument"))?; + let param = v8_to_param(scope, &data, *arg, Some(*exp_type)); + params.push(param); } let ffi = params.to_ffi::(); @@ -196,9 +242,11 @@ fn turing_dispatch( .into_param::() .map_err(|e| JsErrorBox::generic("failed return value"))?; - let j = param_to_js(ret, data)?; + let j = param_to_v8(scope, ret, &data)?; - Ok(j) + let global = v8::Global::new(scope, j); + + Ok(global) } #[doc = r""] @@ -334,7 +382,33 @@ where .execute_script("__turing_helper", helper) .map_err(|e| anyhow!(e.to_string()))?; + // Pre-cache function handles for faster calls. + let mut fn_handles: FxHashMap> = FxHashMap::default(); + { + let context = runtime.main_context(); + deno_core::scope!(scope, &mut runtime); + + for name in js_functions.keys() { + let ctx = v8::Local::new(scope, &context); + let g = ctx.global(scope); + let Some(key) = v8::String::new(scope, name.as_str()) else { + continue; + }; + let Some(val) = g.get(scope, key.into()) else { + continue; + }; + if !val.is_function() { + continue; + } + let Ok(func) = v8::Local::::try_from(val) else { + continue; + }; + fn_handles.insert(name.clone(), v8::Global::new(scope, func)); + } + } + Ok(Self { + deno_fn_handles: fn_handles, runtime, module_name: None, deno_fns: js_functions.clone(), @@ -365,25 +439,23 @@ where // Basic implementation: serialize params to JSON and invoke the global JS // function by name. For now the return value is not converted in full // generality — we return `Void` on success and `Error(...)` on failure. - use crate::interop::params::Param; - // build JSON array of args - let args_vec = params + // If we have a cached function handle, call it directly using V8 locals + if let Some(func_global) = self.deno_fn_handles.get(name).cloned() { + return self.quick_call(ret_type, &data, params, &func_global); + } + // Fallback: stringify args and run the helper (older path) + let json_args = params .into_iter() - .map(|p| param_to_js(p, &data)) - .collect::>(); + .map(|p| p.to_serde(&data)) + .collect::, _>>(); - let args_vec = match args_vec { - Ok(v) => v, - Err(e) => { - return Param::Error(format!("argument conversion error: {}", e)); - } + let args_literal = match json_args { + Ok(vec) => serde_json::to_string(&serde_json::Value::Array(vec)) + .unwrap_or_else(|_| "[]".to_string()), + Err(e) => return Param::Error(format!("argument conversion error: {}", e)), }; - // Serialize the args as a JS array literal (no JSON.parse). - let args_literal = serde_json::to_string(&serde_json::Value::Array(args_vec)) - .unwrap_or_else(|_| "[]".to_string()); - let call_code = format!( "__turing_call({}, {});", serde_json::to_string(&name).unwrap(), @@ -391,37 +463,109 @@ where ); let script_name = format!("turing_call:{}", name); - let exec_res = self.runtime.execute_script(script_name, call_code); - - match exec_res { + match self.runtime.execute_script(script_name, call_code) { Ok(global_val) => { - deno_core::scope!(scope, &mut self.runtime); + // convert return value directly from V8 + let runtime_ref = &mut self.runtime; + deno_core::scope!(scope, runtime_ref); let local = v8::Local::new(scope, &global_val); - let json_val: serde_json::Value = match serde_v8::from_v8(scope, local) { - Ok(v) => v, - Err(e) => return Param::Error(format!("return conversion error: {}", e)), - }; + let param = v8_to_param(scope, &data, local, Some(ret_type)); - // If the caller expects an object (opaque pointer id), map it back. if ret_type == DataType::Object { - if let Some(id) = json_val.as_u64() { - let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(id)); - let real = data - .read() - .opaque_pointers - .get(pointer_key) - .copied() - .unwrap_or_default(); - return Param::Object(real.ptr); - } else { - return Param::Error("expected object id (number) from JS".to_string()); + match param { + Param::I64(i) => { + let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(i as u64)); + let real = data + .read() + .opaque_pointers + .get(pointer_key) + .copied() + .unwrap_or_default(); + return Param::Object(real.ptr); + } + Param::U64(u) => { + let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(u)); + let real = data + .read() + .opaque_pointers + .get(pointer_key) + .copied() + .unwrap_or_default(); + return Param::Object(real.ptr); + } + _ => { + return Param::Error("expected object id (number) from JS".to_string()); + } } } - js_value_to_param(json_val) + param } Err(e) => Param::Error(e.to_string()), } } + + fn quick_call( + &mut self, + ret_type: DataType, + data: &Arc>, + args_vec: Params, + func_global: &v8::Global, + ) -> Param { + deno_core::scope!(scope, self.runtime); + + // enter scope and convert Params -> V8 locals + let mut v8_args: Vec> = match args_vec + .iter() + .map(|p| match param_to_v8(scope, p.clone(), &data) { + Ok(l) => Ok(l), + Err(e) => return Err(format!("argument conversion error: {}", e)), + }) + .collect::>() + { + Ok(v) => v, + Err(e) => return Param::Error(e), + }; + + let local_func = v8::Local::new(scope, func_global); + let recv = v8::undefined(scope).into(); + let result = local_func.call(scope, recv, &v8_args); + let result = match result { + Some(r) => r, + None => return Param::Error("JS call threw".to_string()), + }; + + // convert V8 value -> Param directly + let param = v8_to_param(scope, data, result, None); + + // If the caller expects an object, interpret numeric return as opaque id + if ret_type == DataType::Object { + match param { + Param::I64(i) => { + let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(i as u64)); + let real = data + .read() + .opaque_pointers + .get(pointer_key) + .copied() + .unwrap_or_default(); + return Param::Object(real.ptr); + } + Param::U64(u) => { + let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(u)); + let real = data + .read() + .opaque_pointers + .get(pointer_key) + .copied() + .unwrap_or_default(); + return Param::Object(real.ptr); + } + _ => return Param::Error("expected object id (number) from JS".to_string()), + } + } + + param + } } diff --git a/turing/src/engine/deno_engine/conversion.rs b/turing/src/engine/deno_engine/conversion.rs new file mode 100644 index 0000000..1593790 --- /dev/null +++ b/turing/src/engine/deno_engine/conversion.rs @@ -0,0 +1,55 @@ +use deno_core::{FromV8, ToV8, op2, v8::BigInt}; + +use crate::interop::params::Param; + +pub struct TuringFunctionDispatch(String, Vec); + +impl<'a> ToV8<'a> for Param { + type Error = std::convert::Infallible; + + fn to_v8<'i>( + self, + scope: &mut deno_core::v8::PinScope<'a, 'i>, + ) -> Result, >::Error> { + match self { + Param::String(s) => s.to_v8(scope).map_err(|e| e.into()), + Param::I8(i) => i.to_v8(scope).map_err(|e| e.into()), + Param::I16(i) => i.to_v8(scope).map_err(|e| e.into()), + Param::I32(i) => i.to_v8(scope).map_err(|e| e.into()), + Param::I64(i) => Ok(BigInt::new_from_i64(scope, i).cast()), + Param::U8(u) => u.to_v8(scope).map_err(|e| e.into()), + Param::U16(u) => u.to_v8(scope).map_err(|e| e.into()), + Param::U32(u) => u.to_v8(scope).map_err(|e| e.into()), + Param::U64(u) => Ok(BigInt::new_from_u64(scope, u).cast()), + Param::F32(f) => f.to_v8(scope).map_err(|e| e.into()), + Param::F64(f) => Ok(deno_core::v8::Number::new(scope, f as f64).cast()), + Param::Bool(b) => b.to_v8(scope).map_err(|e| e.into()), + Param::Object(o) => { + todo!() + }, + Param::Error(_) => todo!(), + Param::Void => todo!(), + } + } +} +impl<'a> FromV8<'a> for Param { + type Error = std::convert::Infallible; + + fn from_v8<'i>( + scope: &mut deno_core::v8::PinScope<'a, 'i>, + value: deno_core::v8::Local<'a, deno_core::v8::Value>, + ) -> Result>::Error> { + if value.is_string() { + let s = String::from_v8(scope, value).unwrap(); + Ok(Param::String(s)) + } else if value.is_big_int() { + let bi = deno_core::v8::Local::::try_from(value).unwrap(); + let u = bi.u64_value().0; + Ok(Param::U64(u)) + } else { + unimplemented!() + } + } + + +} diff --git a/turing/src/interop/params.rs b/turing/src/interop/params.rs index 4b40ae7..ff6dcfb 100644 --- a/turing/src/interop/params.rs +++ b/turing/src/interop/params.rs @@ -1,17 +1,16 @@ +use crate::interop::types::ExtString; +use crate::{EngineDataState, ExternalFunctions, OpaquePointerKey}; +use anyhow::{Result, anyhow}; +use num_enum::TryFromPrimitive; use parking_lot::RwLock; +use slotmap::KeyData; use smallvec::SmallVec; use std::ffi::{CStr, CString, c_char, c_void}; use std::fmt::Display; use std::marker::PhantomData; use std::mem; use std::ops::{Deref, DerefMut}; -use std::sync::{Arc}; -use anyhow::{anyhow, Result}; -use num_enum::TryFromPrimitive; -use slotmap::KeyData; -use crate::{ExternalFunctions, OpaquePointerKey, EngineDataState}; -use crate::interop::types::ExtString; - +use std::sync::Arc; #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, TryFromPrimitive)] @@ -74,19 +73,19 @@ impl DataType { matches!( self, DataType::I8 - | DataType::I16 - | DataType::I32 - | DataType::I64 - | DataType::U8 - | DataType::U16 - | DataType::U32 - | DataType::U64 - | DataType::F32 - | DataType::F64 - | DataType::Bool - | DataType::RustString - | DataType::ExtString - | DataType::Object + | DataType::I16 + | DataType::I32 + | DataType::I64 + | DataType::U8 + | DataType::U16 + | DataType::U32 + | DataType::U64 + | DataType::F32 + | DataType::F64 + | DataType::Bool + | DataType::RustString + | DataType::ExtString + | DataType::Object ) } @@ -94,20 +93,20 @@ impl DataType { matches!( self, DataType::I8 - | DataType::I16 - | DataType::I32 - | DataType::I64 - | DataType::U8 - | DataType::U16 - | DataType::U32 - | DataType::U64 - | DataType::F32 - | DataType::F64 - | DataType::Bool - | DataType::RustString - | DataType::ExtString - | DataType::Object - | DataType::Void + | DataType::I16 + | DataType::I32 + | DataType::I64 + | DataType::U8 + | DataType::U16 + | DataType::U32 + | DataType::U64 + | DataType::F32 + | DataType::F64 + | DataType::Bool + | DataType::RustString + | DataType::ExtString + | DataType::Object + | DataType::Void ) } @@ -130,14 +129,19 @@ impl DataType { DataType::F32 => Ok(wasmtime::ValType::F32), DataType::F64 => Ok(wasmtime::ValType::F64), - _ => Err(anyhow!("Invalid wasm value type: {}", self)) + _ => Err(anyhow!("Invalid wasm value type: {}", self)), } } #[cfg(feature = "wasm")] - pub fn to_wasm_val_param(&self, val: &wasmtime::Val, caller: &mut wasmtime::Caller<'_, wasmtime_wasi::p1::WasiP1Ctx>, data: &Arc>) -> Result { - use wasmtime::Val; + pub fn to_wasm_val_param( + &self, + val: &wasmtime::Val, + caller: &mut wasmtime::Caller<'_, wasmtime_wasi::p1::WasiP1Ctx>, + data: &Arc>, + ) -> Result { use crate::engine::wasm_engine::get_wasm_string; + use wasmtime::Val; match (self, val) { (DataType::I8, Val::I32(i)) => Ok(Param::I8(*i as i8)), @@ -152,62 +156,74 @@ impl DataType { (DataType::F64, Val::F64(f)) => Ok(Param::F64(f64::from_bits(*f))), (DataType::Bool, Val::I32(b)) => Ok(Param::Bool(*b != 0)), (DataType::RustString | DataType::ExtString, Val::I32(ptr)) => { - let ptr = *ptr as u32; let Some(memory) = caller.get_export("memory").and_then(|e| e.into_memory()) else { - return Err(anyhow!("wasm does not export memory")) + return Err(anyhow!("wasm does not export memory")); }; let st = get_wasm_string(ptr, memory.data(&caller)); Ok(Param::String(st)) } (DataType::Object, Val::I64(pointer_id)) => { - let pointer_key = - OpaquePointerKey::from(KeyData::from_ffi(*pointer_id as u64)); + let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(*pointer_id as u64)); if let Some(true_pointer) = data.read().opaque_pointers.get(pointer_key) { Ok(Param::Object(**true_pointer)) } else { - Err(anyhow!("opaque pointer does not correspond to a real pointer")) + Err(anyhow!( + "opaque pointer does not correspond to a real pointer" + )) } - } - _ => Err(anyhow!("Mismatched parameter type")) + _ => Err(anyhow!("Mismatched parameter type")), } } #[cfg(feature = "lua")] - pub fn to_lua_val_param(&self, val: &mlua::Value, data: &Arc>) -> mlua::Result { + pub fn to_lua_val_param( + &self, + val: &mlua::Value, + data: &Arc>, + ) -> mlua::Result { match (self, val) { - (DataType::I8, mlua::Value::Integer(i)) => Ok(Param::I8(*i as i8)), + (DataType::I8, mlua::Value::Integer(i)) => Ok(Param::I8(*i as i8)), (DataType::I16, mlua::Value::Integer(i)) => Ok(Param::I16(*i as i16)), (DataType::I32, mlua::Value::Integer(i)) => Ok(Param::I32(*i as i32)), (DataType::I64, mlua::Value::Integer(i)) => Ok(Param::I64(*i)), - (DataType::U8, mlua::Value::Integer(u)) => Ok(Param::U8(*u as u8)), + (DataType::U8, mlua::Value::Integer(u)) => Ok(Param::U8(*u as u8)), (DataType::U16, mlua::Value::Integer(u)) => Ok(Param::U16(*u as u16)), (DataType::U32, mlua::Value::Integer(u)) => Ok(Param::U32(*u as u32)), (DataType::U64, mlua::Value::Integer(u)) => Ok(Param::U64(*u as u64)), (DataType::F32, mlua::Value::Number(f)) => Ok(Param::F32(*f as f32)), (DataType::F64, mlua::Value::Number(f)) => Ok(Param::F64(*f)), (DataType::Bool, mlua::Value::Boolean(b)) => Ok(Param::Bool(*b)), - (DataType::RustString | DataType::ExtString, mlua::Value::String(s)) => Ok(Param::String(s.to_string_lossy())), + (DataType::RustString | DataType::ExtString, mlua::Value::String(s)) => { + Ok(Param::String(s.to_string_lossy())) + } (DataType::Object, mlua::Value::Table(t)) => { let key = t.raw_get::("opaqu")?; let key = match key { mlua::Value::Integer(i) => i as u64, - _ => return Err(mlua::Error::RuntimeError("Incorrect type for opaque handle".to_string())) + _ => { + return Err(mlua::Error::RuntimeError( + "Incorrect type for opaque handle".to_string(), + )); + } }; let pointer_key = OpaquePointerKey::from(KeyData::from_ffi(key)); if let Some(true_pointer) = data.read().opaque_pointers.get(pointer_key) { Ok(Param::Object(**true_pointer)) } else { - Err(mlua::Error::RuntimeError("opaque pointer does not correspond to a real pointer".to_string())) + Err(mlua::Error::RuntimeError( + "opaque pointer does not correspond to a real pointer".to_string(), + )) } } - _ => Err(mlua::Error::RuntimeError(format!("Mismatched parameter type: {self} with {val:?}"))) + _ => Err(mlua::Error::RuntimeError(format!( + "Mismatched parameter type: {self} with {val:?}" + ))), } } - } #[derive(Debug, Clone, PartialEq)] @@ -229,9 +245,7 @@ pub enum Param { Void, } - impl Param { - /// Constructs a Param from a Wasmtime Val and type id. #[cfg(feature = "wasm")] pub fn from_wasm_type_val( @@ -257,7 +271,6 @@ impl Param { DataType::Bool => Param::Bool(val.unwrap_i32() != 0), // allocated externally, we copy the string DataType::ExtString => { - let ptr = val.unwrap_i32() as u32; let st = get_wasm_string(ptr, memory.data(caller)); Param::String(st) @@ -267,7 +280,8 @@ impl Param { let op = val.unwrap_i64() as u64; let key = OpaquePointerKey::from(KeyData::from_ffi(op)); - let real = data.read() + let real = data + .read() .opaque_pointers .get(key) .copied() @@ -289,7 +303,7 @@ impl Param { typ: DataType, val: mlua::Value, data: &Arc>, - lua: &mlua::Lua + lua: &mlua::Lua, ) -> Self { match typ { DataType::I8 => Param::I8(val.as_integer().unwrap() as i8), @@ -311,7 +325,8 @@ impl Param { let op = table.get("opaqu").unwrap(); let key = OpaquePointerKey::from(KeyData::from_ffi(op)); - let real = data.read() + let real = data + .read() .opaque_pointers .get(key) .copied() @@ -324,14 +339,61 @@ impl Param { } } - pub fn to_rs_param(self) -> FfiParam { self.to_param_inner(DataType::RustString, DataType::RustError) } pub fn to_ext_param(self) -> FfiParam { self.to_param_inner(DataType::ExtString, DataType::ExtError) } - + + pub fn to_serde( + self, + data: &Arc>, + ) -> Result { + Ok(match self { + Param::I8(i) => serde_json::Value::from(i), + Param::I16(i) => serde_json::Value::from(i), + Param::I32(i) => serde_json::Value::from(i), + Param::I64(i) => serde_json::Value::from(i), + Param::U8(u) => serde_json::Value::from(u), + Param::U16(u) => serde_json::Value::from(u), + Param::U32(u) => serde_json::Value::from(u), + Param::U64(u) => serde_json::Value::from(u), + Param::F32(f) => serde_json::Value::from(f), + Param::F64(f) => serde_json::Value::from(f), + Param::Bool(b) => serde_json::Value::from(b), + Param::String(s) => serde_json::Value::from(s), + Param::Void => serde_json::Value::Null, + Param::Object(ptr) => { + let mut s = data.write(); + let key = s.get_opaque_pointer(ptr.into()); + serde_json::Value::from(key.0.as_ffi()) + } + Param::Error(e) => return Err(anyhow!("{}", e)), + }) + } + + pub fn from_serde(value: serde_json::Value) -> Self { + match value { + serde_json::Value::Number(n) => { + if let Some(i) = n.as_i64() { + Param::I64(i) + } else if let Some(u) = n.as_u64() { + Param::U64(u) + } else if let Some(f) = n.as_f64() { + Param::F64(f) + } else { + Param::Error("Invalid number".to_string()) + } + } + serde_json::Value::String(s) => Param::String(s), + serde_json::Value::Bool(b) => Param::Bool(b), + serde_json::Value::Null => Param::Void, + _ => Param::Error("Unsupported return type".to_string()), + } + } + + #[rustfmt::skip] fn to_param_inner(self, str_type: DataType, err_type: DataType) -> FfiParam { match self { @@ -357,7 +419,6 @@ impl Param { pub fn to_result(self) -> Result { T::from_param(self) } - } pub trait FromParam: Sized { @@ -368,7 +429,7 @@ macro_rules! deref_param { match $param { Param::$case(v) => Ok(v), Param::Error(e) => Err(anyhow!("{}", e)), - _ => Err(anyhow!("Incorrect data type")) + _ => Err(anyhow!("Incorrect data type")), } }; ( $tp:ty => $case:tt ) => { @@ -377,7 +438,7 @@ macro_rules! deref_param { deref_param!(param, $case) } } - } + }; } deref_param! { i8 => I8 } deref_param! { i16 => I16 } @@ -396,7 +457,7 @@ impl FromParam for () { match param { Param::Void => Ok(()), Param::Error(e) => Err(anyhow!("{}", e)), - _ => Err(anyhow!("Incorrect data type")) + _ => Err(anyhow!("Incorrect data type")), } } } @@ -442,16 +503,19 @@ impl Params { /// Converts the Params into a vector of Wasmtime Val types for function calling. #[cfg(feature = "wasm")] - pub fn to_wasm_args(self, data: &Arc>) -> Result> { + pub fn to_wasm_args( + self, + data: &Arc>, + ) -> Result> { // Acquire a single write lock for the duration of conversion to avoid // repeated locking/unlocking when pushing strings or registering objects. use wasmtime::Val; let mut s = data.write(); - - self.params.into_iter().map(|p| - match p { + self.params + .into_iter() + .map(|p| match p { Param::I8(i) => Ok(Val::I32(i as i32)), Param::I16(i) => Ok(Val::I32(i as i32)), Param::I32(i) => Ok(Val::I32(i)), @@ -478,19 +542,23 @@ impl Params { Val::I64(op.0.as_ffi() as i64) }) } - Param::Error(st) => { - Err(anyhow!("{st}")) - } + Param::Error(st) => Err(anyhow!("{st}")), _ => unreachable!("Void shouldn't ever be added as an arg"), - } - ).collect() + }) + .collect() } #[cfg(feature = "lua")] - pub fn to_lua_args(self, lua: &mlua::Lua, data: &Arc>) -> Result { + pub fn to_lua_args( + self, + lua: &mlua::Lua, + data: &Arc>, + ) -> Result { let mut s = data.write(); - let vals = self.params.into_iter().map(|p| - match p { + let vals = self + .params + .into_iter() + .map(|p| match p { Param::I8(i) => Ok(mlua::Value::Integer(i as i64)), Param::I16(i) => Ok(mlua::Value::Integer(i as i64)), Param::I32(i) => Ok(mlua::Value::Integer(i as i64)), @@ -513,17 +581,18 @@ impl Params { mlua::Value::Integer(op.0.as_ffi() as i64) }) } - Param::Error(st) => { - Err(anyhow!("{st}")) - } - _ => unreachable!("Void shouldn't ever be added as an arg") - } - ).collect::>>()?; + Param::Error(st) => Err(anyhow!("{st}")), + _ => unreachable!("Void shouldn't ever be added as an arg"), + }) + .collect::>>()?; Ok(mlua::MultiValue::from_vec(vals)) } - pub fn to_ffi(self) -> FfiParams where Ext: ExternalFunctions { + pub fn to_ffi(self) -> FfiParams + where + Ext: ExternalFunctions, + { FfiParams::from_params(self.params) } } @@ -542,6 +611,15 @@ impl DerefMut for Params { } } +impl IntoIterator for Params { + type Item = Param; + type IntoIter = smallvec::IntoIter<[Param; 4]>; + + fn into_iter(self) -> Self::IntoIter { + self.params.into_iter() + } +} + /// C repr of ffi data #[repr(C)] pub union RawParam { @@ -578,8 +656,10 @@ pub struct FfiParams { marker: PhantomData, } - -impl Drop for FfiParams where Ext: ExternalFunctions { +impl Drop for FfiParams +where + Ext: ExternalFunctions, +{ fn drop(&mut self) { if self.params.is_empty() { return; @@ -598,21 +678,36 @@ impl Drop for FfiParams where Ext: ExternalFunctions { } } -impl Default for FfiParams where Ext: ExternalFunctions { +impl Default for FfiParams +where + Ext: ExternalFunctions, +{ fn default() -> Self { Self::empty() } } -impl FfiParams where Ext: ExternalFunctions { +impl FfiParams +where + Ext: ExternalFunctions, +{ pub fn empty() -> Self { - Self { params: SmallVec::new(), marker: PhantomData } + Self { + params: SmallVec::new(), + marker: PhantomData, + } } /// Creates FfiParams from a vector of Params. - pub fn from_params(params: T) -> Self where T: IntoIterator { + pub fn from_params(params: T) -> Self + where + T: IntoIterator, + { let ffi_params = params.into_iter().map(|p| p.to_rs_param()).collect(); - Self { params: ffi_params, marker: PhantomData } + Self { + params: ffi_params, + marker: PhantomData, + } } /// Creates FfiParams from an FfiParamArray with 'static lifetime. @@ -621,14 +716,15 @@ impl FfiParams where Ext: ExternalFunctions { return Ok(Self::default()); } unsafe { - let raw_vec = - std::ptr::slice_from_raw_parts_mut(array.ptr as *mut FfiParam, array.count as usize); + let raw_vec = std::ptr::slice_from_raw_parts_mut( + array.ptr as *mut FfiParam, + array.count as usize, + ); let raw_vec = Box::from_raw(raw_vec); // take ownership of the raw_vec let owned = raw_vec.into_vec(); - Ok(Self { params: SmallVec::from_vec(owned), marker: PhantomData, @@ -656,7 +752,7 @@ impl FfiParams where Ext: ExternalFunctions { } /// Leaks the FfiParams into an FfiParamArray with 'static lifetime. - /// Caller is responsible for freeing the memory. + /// Caller is responsible for freeing the memory. /// Freeing is possible by converting back via FfiParams::from_ffi_array and dropping the FfiParams. pub fn leak(mut self) -> FfiParamArray<'static> { let boxed_slice = mem::take(&mut self.params).into_boxed_slice(); @@ -704,7 +800,8 @@ impl<'a> FfiParamArray<'a> { std::ptr::slice_from_raw_parts(self.ptr as *mut FfiParam, self.count as usize); let slice = &*raw_slice; - let result = slice.iter() + let result = slice + .iter() .map(|p| p.as_param::()) .collect::>()?; Ok(Params { params: result }) @@ -784,7 +881,6 @@ impl FfiParam { DataType::Void => Param::Void, }) } - } impl From for FfiParam { @@ -792,4 +888,3 @@ impl From for FfiParam { value.to_rs_param() } } - From 8f7e8e4af2d971daf85538178020015703be3b08 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:10:55 -0400 Subject: [PATCH 4/9] Finish v8 values --- turing/src/engine/deno_engine.rs | 49 +++++++++++++++++++------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/turing/src/engine/deno_engine.rs b/turing/src/engine/deno_engine.rs index 3a81390..4cbf467 100644 --- a/turing/src/engine/deno_engine.rs +++ b/turing/src/engine/deno_engine.rs @@ -5,7 +5,6 @@ use std::sync::Arc; use anyhow::{Result, anyhow}; use deno_core::op2; -use deno_core::v8::{HandleScope, PinScope}; use deno_core::{JsRuntime, OpState, RuntimeOptions, serde_v8, v8}; use deno_error::JsErrorBox; use parking_lot::RwLock; @@ -18,7 +17,6 @@ use crate::interop::types::ExtPointer; use crate::{EngineDataState, ExternalFunctions}; use slotmap::KeyData; -pub mod conversion; pub struct DenoEngine where @@ -68,7 +66,7 @@ fn param_to_v8<'s>( // Convert a V8 `Value` into a host `Param`. fn v8_to_param<'s>( - scope: &mut v8::PinnedRef<'s, HandleScope<'s>>, + scope: &mut deno_core::v8::PinScope<'s, '_>, data: &Arc>, value: v8::Local<'s, v8::Value>, expect_type: Option, @@ -173,9 +171,6 @@ fn v8_to_param<'s>( unreachable!("Does not support {value:?}") } - - - // Single dispatch op which receives a JSON array like `["fn.name", [arg0, arg1, ...]]` /// Handles calling registered FFI functions from JS. #[op2] @@ -201,6 +196,7 @@ fn turing_dispatch( let runtime = state.borrow_mut::(); deno_core::scope!(scope, runtime); + // let scope = state.borrow_mut(); let (name, args) = { let length = array.length(); @@ -224,14 +220,19 @@ fn turing_dispatch( let p_types = metadata.param_types.clone(); - let mut params = Params::of_size(p_types.len() as u32); - for (i, exp_type) in p_types.iter().enumerate() { - let arg = args - .get(i) - .ok_or_else(|| JsErrorBox::generic("missing argument"))?; - let param = v8_to_param(scope, &data, *arg, Some(*exp_type)); - params.push(param); - } + let params = Params::from_iter( + p_types + .iter() + .enumerate() + .map(|(i, exp_type)| { + let arg = args + .get(i) + .ok_or_else(|| JsErrorBox::generic("missing argument"))?; + let param = v8_to_param(scope, &data, *arg, Some(*exp_type)); + Ok(param) + }) + .collect::, JsErrorBox>>()?, + ); let ffi = params.to_ffi::(); let ffi_arr = ffi.as_ffi_array(); @@ -245,6 +246,10 @@ fn turing_dispatch( let j = param_to_v8(scope, ret, &data)?; let global = v8::Global::new(scope, j); + drop(j); + drop(args); + drop(name); + drop(scope); Ok(global) } @@ -466,8 +471,7 @@ where match self.runtime.execute_script(script_name, call_code) { Ok(global_val) => { // convert return value directly from V8 - let runtime_ref = &mut self.runtime; - deno_core::scope!(scope, runtime_ref); + deno_core::scope!(scope, self.runtime); let local = v8::Local::new(scope, &global_val); let param = v8_to_param(scope, &data, local, Some(ret_type)); @@ -514,13 +518,18 @@ where func_global: &v8::Global, ) -> Param { deno_core::scope!(scope, self.runtime); + // let context = self.runtime.main_context(); + // let isolate = self.runtime.v8_isolate(); + // deno_core::v8::scope!(let scope, isolate); + // let context = v8::Local::new(scope, &context); + // let scope = &mut ContextScope::new(scope, context); // enter scope and convert Params -> V8 locals - let mut v8_args: Vec> = match args_vec - .iter() - .map(|p| match param_to_v8(scope, p.clone(), &data) { + let v8_args: Vec> = match args_vec + .into_iter() + .map(|p| match param_to_v8(scope, p, data) { Ok(l) => Ok(l), - Err(e) => return Err(format!("argument conversion error: {}", e)), + Err(e) => Err(format!("argument conversion error: {}", e)), }) .collect::>() { From d4fc187dcac885d56e067b152ace9ebd4d3722d3 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:13:27 -0400 Subject: [PATCH 5/9] Deno JS/TS recognize --- turing/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/turing/src/lib.rs b/turing/src/lib.rs index 220fae6..6e2933e 100644 --- a/turing/src/lib.rs +++ b/turing/src/lib.rs @@ -152,6 +152,15 @@ impl Turing { lua_interpreter.load_script(source)?; self.engine = Some(Engine::Lua(lua_interpreter)); } + #[cfg(feature = "deno")] + "js" | "ts" => { + let mut deno_engine = engine::deno_engine::DenoEngine::new( + &self.script_fns, + Arc::clone(&self.data), + )?; + deno_engine.load_script(source)?; + self.engine = Some(Engine::Deno(deno_engine)); + } _ => { return Err(anyhow!( "Unknown script extension: '{extension:?}' must be .wasm or .lua" From 24e7080ecfb88e208cb56c23b24d74231ea86ace Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:34:31 -0400 Subject: [PATCH 6/9] Deno tests --- turing/tests/deno_integration_test.rs | 64 +++++++++++++++++++++++++++ turing/tests/deno_test.js | 13 ++++++ 2 files changed, 77 insertions(+) create mode 100644 turing/tests/deno_integration_test.rs create mode 100644 turing/tests/deno_test.js diff --git a/turing/tests/deno_integration_test.rs b/turing/tests/deno_integration_test.rs new file mode 100644 index 0000000..77d8c19 --- /dev/null +++ b/turing/tests/deno_integration_test.rs @@ -0,0 +1,64 @@ +use std::sync::Arc; +use parking_lot::RwLock; +use rustc_hash::FxHashMap; +use turing::{Turing, TuringSetup}; +use turing::engine::types::ScriptFnMetadata; +use turing::interop::params::{DataType, Params, Param}; + +#[test] +fn deno_basic_call() { + // This test only checks that loading and calling a deno script compiles and runs + // in minimal fashion. It will skip if the deno feature is not enabled. + #[cfg(not(feature = "deno"))] + { + eprintln!("deno feature not enabled; skipping test"); + return; + } + + // build a Turing instance with a minimal dummy `ExternalFunctions` impl + struct TestExt; + impl turing::ExternalFunctions for TestExt { + fn abort(_error_type: String, _error: String) -> ! { panic!("abort") } + fn log_info(_msg: impl ToString) {} + fn log_warn(_msg: impl ToString) {} + fn log_debug(_msg: impl ToString) {} + fn log_critical(_msg: impl ToString) {} + fn free_string(_ptr: *const std::os::raw::c_char) {} + } + + let setup: TuringSetup = Turing::new(); + let mut t = setup.build().unwrap(); + + // load the test deno script + let script_path = std::path::Path::new("tests/deno_test.js"); + // create engine directly + let data = Arc::new(RwLock::new(turing::EngineDataState::default())); + let script_fns: FxHashMap = FxHashMap::default(); + + // load script via the public Turing API and call functions + #[cfg(feature = "deno")] + { + t.load_script(script_path.to_string_lossy(), &["deno"]).unwrap(); + + // call add(2,3) via Turing API + let mut params = Params::of_size(2); + params.push(Param::I32(2)); + params.push(Param::I32(3)); + let ret = t.call_fn("add", params, DataType::I32); + match ret { + Param::I32(v) => assert_eq!(v, 5), + Param::I64(v) => assert_eq!(v as i32, 5), + _ => panic!("unexpected return type: {:?}", ret), + } + + // call makeOpaque(42) — the JS helper returns a numeric id in this test + let mut p2 = Params::of_size(1); + p2.push(Param::I64(42)); + let ret2 = t.call_fn("makeOpaque", p2, DataType::I64); + match ret2 { + Param::I64(v) => assert_eq!(v, 42), + Param::U64(u) => assert_eq!(u as i64, 42), + _ => panic!("expected numeric id from makeOpaque, got {:?}", ret2), + } + } +} diff --git a/turing/tests/deno_test.js b/turing/tests/deno_test.js new file mode 100644 index 0000000..367ff88 --- /dev/null +++ b/turing/tests/deno_test.js @@ -0,0 +1,13 @@ +// Minimal Deno test script used by Rust integration test +// Expose a simple function that returns a number and an object id +function add(a, b) { + return a + b; +} + +function makeOpaque(id) { + // return numeric id representing opaque pointer + return id; +} + +globalThis.add = add; +globalThis.makeOpaque = makeOpaque; From 880f09fe00954ff176d1d1baee1711c1bb638eee Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:38:52 -0400 Subject: [PATCH 7/9] Cleanup --- turing/src/engine/deno_engine.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/turing/src/engine/deno_engine.rs b/turing/src/engine/deno_engine.rs index 4cbf467..6591b9e 100644 --- a/turing/src/engine/deno_engine.rs +++ b/turing/src/engine/deno_engine.rs @@ -246,10 +246,6 @@ fn turing_dispatch( let j = param_to_v8(scope, ret, &data)?; let global = v8::Global::new(scope, j); - drop(j); - drop(args); - drop(name); - drop(scope); Ok(global) } From 463c37d831043a6d70d5ac640d211b07ed24e3df Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:40:37 -0400 Subject: [PATCH 8/9] FromIterator --- turing/src/interop/params.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/turing/src/interop/params.rs b/turing/src/interop/params.rs index ff6dcfb..83cb7c8 100644 --- a/turing/src/interop/params.rs +++ b/turing/src/interop/params.rs @@ -620,6 +620,12 @@ impl IntoIterator for Params { } } +impl FromIterator for Params { + fn from_iter>(iter: T) -> Self { + Self { params: iter.into_iter().collect() } + } +} + /// C repr of ffi data #[repr(C)] pub union RawParam { From 1ce17a7e01ae3c07de76a4fe5945c28e9ff81262 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Thu, 18 Dec 2025 00:07:37 -0400 Subject: [PATCH 9/9] Add arguments for build --- xtask/src/main.rs | 71 ++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index d1fbe34..db08637 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,11 +1,11 @@ -use std::{env, fs}; +use serde::Deserialize; use std::path::Path; use std::process::Command; -use serde::Deserialize; +use std::{env, fs}; #[derive(Deserialize)] struct CargoToml { - package: Package + package: Package, } #[derive(Deserialize)] @@ -22,79 +22,76 @@ fn main() { }); match task.as_str() { - "win-build" | "w" => build_windows(), + "build" => { + build(None); + } + "win-build" | "w" => build(Some("x86_64-pc-windows-gnu")), "test-run" | "t" => test_run(), unknown => { eprintln!("Unknown task: {}", unknown); std::process::exit(1); } } - - } -fn compile_package(target: &str, crate_name: &str, mode: &str) { - +fn compile_package(target: Option<&str>, crate_name: &str, mode: &str) { let cargo_bin = env::var("CARGO").unwrap_or("cargo".to_string()); let mut status = Command::new(cargo_bin); if mode == "--debug" { - status.args(["build", "--target", target, "-p", crate_name]); + status.args(["build", "-p", crate_name]); } else { - status.args(["build", mode, "--target", target, "-p", crate_name]); + status.args(["build", mode, "-p", crate_name]); } - let status = status.status() - .expect("Failed to build Turing"); + if let Some(t) = target { + status.args(["--target", t]); + } + // Ensure V8 is built monolithically for shared-library compatibility + status.env("V8_FROM_SOURCE", "1"); + status.env("PRINT_GN_ARGS", "1"); + status.env( + "GN_ARGS", + "v8_monolithic=true v8_monolithic_for_shared_library=true", + ); + let status = status.status().expect("Failed to build Turing"); if !status.success() { eprintln!("Failed to compile {} crate", crate_name); std::process::exit(1); } - } -fn build_windows() { - let target = "x86_64-pc-windows-gnu"; +fn build(target: Option<&str>) { let crate_name = "turing"; - compile_package( - target, - crate_name, - "--release" - ); + compile_package(target, crate_name, "--release"); let raw_cargo = fs::read_to_string(format!("{}/Cargo.toml", crate_name)) .expect("Failed to read Cargo.toml"); - let cargo: CargoToml = toml::from_str(&raw_cargo) - .expect("Failed to parse Cargo.toml"); + let cargo: CargoToml = toml::from_str(&raw_cargo).expect("Failed to parse Cargo.toml"); let version = cargo.package.version; let lib_name = cargo.package.name; - let built = format!("target/{}/release/{}.dll", target, lib_name); + let built = format!("target/{}/release/{}.dll", target.unwrap_or(&env::var("TARGET").unwrap()), lib_name); let output = Path::new("dist").join(format!("{}-{}.dll", lib_name, version)); fs::create_dir_all("dist").expect("Failed to create dist directory"); - fs::copy(&built, &output) - .unwrap_or_else(|e| panic!("Failed to copy DLL: {}", e)); + fs::copy(&built, &output).unwrap_or_else(|e| panic!("Failed to copy DLL: {}", e)); println!("Windows dll generated in dist"); - } fn test_run() { - compile_package( - "wasm32-wasip1", - "wasm_tests", - "--debug" - ); + compile_package(Some("wasm32-wasip1"), "wasm_tests", "--debug"); let _ = fs::remove_file("tests/wasm/wasm_tests.wasm"); fs::copy( "target/wasm32-wasip1/debug/wasm_tests.wasm", - "tests/wasm/wasm_tests.wasm" - ).unwrap_or_else(|e| panic!("Failed to copy wasm file for testing: {}", e)); + "tests/wasm/wasm_tests.wasm", + ) + .unwrap_or_else(|e| panic!("Failed to copy wasm file for testing: {}", e)); println!("Copied wasm test script to tests/wasm, running tests..."); @@ -102,12 +99,16 @@ fn test_run() { let status = Command::new(cargo_bin) .args(["test", "-p", "turing", "--", "--nocapture"]) + .env("V8_FROM_SOURCE", "1") + .env("PRINT_GN_ARGS", "1") + .env( + "GN_ARGS", + "v8_monolithic=true v8_monolithic_for_shared_library=true", + ) .status() .expect("Failed to run tests"); if !status.success() { println!("Turing tests failed to run") } - } -