From ba8d29dc55f320aca975fb52ad879cbe85659423 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Sat, 24 Jan 2026 16:24:36 -0400 Subject: [PATCH 1/6] Refactor f32 queue to be contiguous --- turing/src/engine/wasm_engine.rs | 60 +++++++++++++++++--------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/turing/src/engine/wasm_engine.rs b/turing/src/engine/wasm_engine.rs index b22f2f9..9a5fbfc 100644 --- a/turing/src/engine/wasm_engine.rs +++ b/turing/src/engine/wasm_engine.rs @@ -98,13 +98,15 @@ impl Param { memory: &Memory, caller: &Store, ) -> Self { - macro_rules! dequeue { - ($typ:tt :: $init:tt; $x:tt ) => { - { let mut s = data.write(); - let arr = s.f32_queue.drain(..$x).collect::>(); - Param::$typ(glam::$typ::$init(arr.as_slice().try_into().unwrap())) } - }; + ($typ:tt :: $init:tt; $x:tt ) => {{ + let mut s = data.write(); + // ensure contiguous slice for easier conversion + s.f32_queue.make_contiguous(); + Param::$typ(glam::$typ::$init( + s.f32_queue.as_slices().0.try_into().unwrap(), + )) + }}; } match typ { @@ -142,26 +144,28 @@ impl Param { Param::Error(st) } DataType::Void => Param::Void, - DataType::Vec2 => dequeue!(Vec2::from_array; 2), - DataType::Vec3 => dequeue!(Vec3::from_array; 3), - DataType::RustVec4 | DataType::ExtVec4 => dequeue!(Vec4::from_array; 4), - DataType::RustQuat | DataType::ExtQuat => dequeue!(Quat::from_array; 4), - DataType::RustMat4 | DataType::ExtMat4 => dequeue!(Mat4::from_cols_array; 16), - _ => unreachable!("Cannot convert to {} from wasm value", typ) + + + DataType::Vec2 => dequeue!(Vec2::from_slice; 2), + DataType::Vec3 => dequeue!(Vec3::from_slice; 3), + DataType::RustVec4 | DataType::ExtVec4 => dequeue!(Vec4::from_slice; 4), + DataType::RustQuat | DataType::ExtQuat => dequeue!(Quat::from_slice; 4), + DataType::RustMat4 | DataType::ExtMat4 => dequeue!(Mat4::from_cols_slice; 16), + _ => unreachable!("Cannot convert to {} from wasm value", typ), } } pub fn into_wasm_val(self, data: &Arc>) -> Result> { let mut s = data.write(); macro_rules! enqueue { - ( $v:tt ; $sz:tt ) => { - { s.f32_queue.append(&mut $v.to_array().into()); - Val::I32($sz) } - }; - ($m:tt # $sz:tt) => { - { s.f32_queue.append(&mut $m.to_cols_array().into()); - Val::I32($sz) } - } + ( $v:tt ; $sz:tt ) => {{ + s.f32_queue.append(&mut $v.to_array().into()); + Val::I32($sz) + }}; + ($m:tt # $sz:tt) => {{ + s.f32_queue.append(&mut $m.to_cols_array().into()); + Val::I32($sz) + }}; } Ok(Some(match self { Param::I8(i) => Val::I32(i as i32), @@ -210,14 +214,14 @@ impl Params { let mut s = data.write(); macro_rules! enqueue { - ( $v:tt ; $sz:tt ) => { - { s.f32_queue.append(&mut $v.to_array().into()); - Ok(Val::I32($sz)) } - }; - ($m:tt # $sz:tt) => { - { s.f32_queue.append(&mut $m.to_cols_array().into()); - Ok(Val::I32($sz)) } - } + ( $v:tt ; $sz:tt ) => {{ + s.f32_queue.append(&mut $v.to_array().into()); + Ok(Val::I32($sz)) + }}; + ($m:tt # $sz:tt) => {{ + s.f32_queue.append(&mut $m.to_cols_array().into()); + Ok(Val::I32($sz)) + }}; } self.params.into_iter().map(|p| From 07021fde144317b32414b9e395b50801f1e92643 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:00:20 -0400 Subject: [PATCH 2/6] Refactor string handling to use CString to reduce interop --- turing/benches/lua_api_bench.rs | 4 ++-- turing/benches/wasm_api_bench.rs | 2 +- turing/src/engine/lua_engine.rs | 9 +++++---- turing/src/engine/wasm_engine.rs | 26 +++++++++++--------------- turing/src/interop/params.rs | 22 ++++++++++++++-------- turing/src/interop/types.rs | 5 ++++- turing/src/lib.rs | 4 ++-- turing/src/tests.rs | 8 ++++---- 8 files changed, 43 insertions(+), 37 deletions(-) diff --git a/turing/benches/lua_api_bench.rs b/turing/benches/lua_api_bench.rs index 07dc06a..e63efc5 100644 --- a/turing/benches/lua_api_bench.rs +++ b/turing/benches/lua_api_bench.rs @@ -36,7 +36,7 @@ extern "C" fn log_info_wasm( extern "C" fn fetch_string( _params: turing_rs::interop::params::FfiParamArray, ) -> turing_rs::interop::params::FfiParam { - Param::String("this is a host provided string!".to_string()).to_ext_param() + Param::String(CString::new("this is a host provided string!").unwrap()).to_ext_param() } fn setup_turing_for_lua() -> Turing { @@ -82,7 +82,7 @@ fn bench_turing_lua_string_roundtrip(c: &mut Criterion) { c.bench_function("turing_lua_string_roundtrip", |b| { b.iter(|| { let mut params = Params::of_size(1); - params.push(Param::String("Message from host".to_string())); + params.push(Param::String(CString::new("Message from host").unwrap())); let res = turing.call_fn(string_test, params, DataType::ExtString); let _ = black_box(res.to_result::().unwrap()); diff --git a/turing/benches/wasm_api_bench.rs b/turing/benches/wasm_api_bench.rs index e85bee9..1db178a 100644 --- a/turing/benches/wasm_api_bench.rs +++ b/turing/benches/wasm_api_bench.rs @@ -38,7 +38,7 @@ extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { // called from wasm extern "C" fn fetch_string(_params: FfiParamArray) -> FfiParam { - Param::String("this is a host provided string!".to_string()).to_ext_param() + Param::String(CString::new("this is a host provided string!").unwrap()).to_ext_param() } fn setup_turing_with_callbacks() -> Turing { diff --git a/turing/src/engine/lua_engine.rs b/turing/src/engine/lua_engine.rs index c93e36f..c290e74 100644 --- a/turing/src/engine/lua_engine.rs +++ b/turing/src/engine/lua_engine.rs @@ -1,3 +1,4 @@ +use std::ffi::CString; use std::fs; use std::marker::PhantomData; use std::path::Path; @@ -31,7 +32,7 @@ impl DataType { (DataType::F32, Value::Number(f)) => Ok(Param::F32(*f as f32)), (DataType::F64, Value::Number(f)) => Ok(Param::F64(*f)), (DataType::Bool, Value::Boolean(b)) => Ok(Param::Bool(*b)), - (DataType::RustString | DataType::ExtString, Value::String(s)) => Ok(Param::String(s.to_string_lossy())), + (DataType::RustString | DataType::ExtString, Value::String(s)) => Ok(Param::String(CString::new(s.to_string_lossy().to_string()).unwrap())), (DataType::Object, Value::Table(t)) => { let key = t.raw_get::("opaqu")?; let key = match key { @@ -71,7 +72,7 @@ impl Param { DataType::F64 => Param::F64(val.as_number().unwrap()), DataType::Bool => Param::Bool(val.as_boolean().unwrap()), // allocated externally, we copy the string - DataType::RustString | DataType::ExtString => Param::String(val.as_string().unwrap().to_string_lossy()), + DataType::RustString | DataType::ExtString => Param::String(CString::new(val.as_string().unwrap().to_string_lossy()).unwrap()), DataType::Object => { let table = val.as_table().unwrap(); let op = table.get("opaqu").unwrap(); @@ -113,7 +114,7 @@ impl Param { Param::F32(f) => Value::Number(f as f64), Param::F64(f) => Value::Number(f), Param::Bool(b) => Value::Boolean(b), - Param::String(s) => Value::String(lua.create_string(&s)?), + Param::String(s) => Value::String(lua.create_string(&s.as_bytes())?), Param::Object(pointer) => { let pointer = ExtPointer::from(pointer); let opaque = s.get_opaque_pointer(pointer); @@ -152,7 +153,7 @@ impl Params { Param::F32(f) => Ok(Value::Number(f as f64)), Param::F64(f) => Ok(Value::Number(f)), Param::Bool(b) => Ok(Value::Boolean(b)), - Param::String(s) => Ok(Value::String(lua.create_string(&s).unwrap())), + Param::String(s) => Ok(Value::String(lua.create_string(&s.as_bytes()).unwrap())), Param::Object(rp) => { let pointer = rp.into(); Ok(if let Some(op) = s.pointer_backlink.get(&pointer) { diff --git a/turing/src/engine/wasm_engine.rs b/turing/src/engine/wasm_engine.rs index 9a5fbfc..d3629d5 100644 --- a/turing/src/engine/wasm_engine.rs +++ b/turing/src/engine/wasm_engine.rs @@ -47,7 +47,7 @@ impl DataType { return Err(anyhow!("wasm does not export memory")) }; let st = get_wasm_string(ptr, memory.data(&caller)); - Ok(Param::String(st)) + Ok(Param::String(st.to_owned())) } (DataType::Object, Val::I64(pointer_id)) => { let pointer_key = @@ -125,7 +125,7 @@ impl Param { let ptr = val.unwrap_i32() as u32; let st = get_wasm_string(ptr, memory.data(caller)); - Param::String(st) + Param::String(st.to_owned()) } DataType::Object => { let op = val.unwrap_i64() as u64; @@ -141,7 +141,7 @@ impl Param { DataType::RustError | DataType::ExtError => { let ptr = val.unwrap_i32() as u32; let st = get_wasm_string(ptr, memory.data(caller)); - Param::Error(st) + Param::Error(st.to_string_lossy().to_string()) } DataType::Void => Param::Void, @@ -180,7 +180,7 @@ impl Param { Param::F64(f) => Val::F64(f.to_bits()), Param::Bool(b) => Val::I32(if b { 1 } else { 0 }), Param::String(st) => { - let l = st.len() + 1; + let l = st.as_bytes().len() + 1; s.str_cache.push_back(st); Val::I32(l as i32) } @@ -239,7 +239,7 @@ impl Params { Param::F64(f) => Ok(Val::F64(f.to_bits())), Param::Bool(b) => Ok(Val::I32(if b { 1 } else { 0 })), Param::String(st) => { - let l = st.len() + 1; + let l = st.as_bytes().len() + 1; s.str_cache.push_back(st); Ok(Val::I32(l as i32)) } @@ -481,23 +481,19 @@ impl StdoutStream for WriterInit } /// gets a string out of wasm memory into rust memory. -pub fn get_wasm_string(message: u32, data: &[u8]) -> String { - let c = CStr::from_bytes_until_nul(&data[message as usize..]).expect("Not a valid CStr"); - match c.to_str() { - Ok(s) => s.to_owned(), - Err(_) => c.to_string_lossy().into_owned(), - } +pub fn get_wasm_string(message: u32, data: &[u8]) -> &CStr { + CStr::from_bytes_until_nul(&data[message as usize..]).expect("Not a valid CStr") + } /// writes a string from rust memory to wasm memory. pub fn write_wasm_string( pointer: u32, - string: &str, + string: &CStr, memory: &Memory, caller: Caller<'_, WasiP1Ctx>, ) -> Result<(), MemoryAccessError> { - let c = CString::new(string).unwrap(); - let bytes = c.into_bytes_with_nul(); + let bytes = string.to_bytes_with_nul(); memory.write(caller, pointer as usize, &bytes) } @@ -811,7 +807,7 @@ pub fn wasm_host_strcpy( let size = ps[1].i32().unwrap(); if let Some(next_str) = data.write().str_cache.pop_front() - && next_str.len() + 1 == size as usize + && next_str.as_bytes().len() + 1 == size as usize { if let Some(memory) = caller.get_export("memory").and_then(|m| m.into_memory()) { write_wasm_string(ptr as u32, &next_str, &memory, caller)?; diff --git a/turing/src/interop/params.rs b/turing/src/interop/params.rs index 7aecb2f..30a9a6c 100644 --- a/turing/src/interop/params.rs +++ b/turing/src/interop/params.rs @@ -162,7 +162,7 @@ pub enum Param { F32(f32), F64(f64), Bool(bool), - String(String), + String(CString), Object(*const c_void), Error(String), Void, @@ -246,7 +246,7 @@ deref_param! { u64 => U64 } deref_param! { f32 => F32 } deref_param! { f64 => F64 } deref_param! { bool => Bool } -deref_param! { String => String } +deref_param! { CString => String } deref_param! { Vec2 => Vec2 } deref_param! { Vec3 => Vec3 } deref_param! { Vec4 => Vec4 } @@ -261,6 +261,15 @@ impl FromParam for () { } } } +impl FromParam for String { + fn from_param(param: Param) -> Result { + match param { + Param::String(s) => Ok(s.to_string_lossy().into_owned()), + Param::Error(e) => Err(anyhow!("{}", e)), + _ => Err(anyhow!("Incorrect data type")) + } + } +} #[derive(Debug, Default, Clone)] pub struct Params { @@ -537,11 +546,9 @@ impl FfiParam { DataType::Bool => Param::Bool(unsafe { self.value.bool }), DataType::RustString => Param::String(unsafe { CString::from_raw(self.value.string as *mut c_char) - .to_string_lossy() - .into_owned() }), DataType::ExtString => { - Param::String(unsafe { ExtString::::from(self.value.string).to_string() }) + Param::String(unsafe { ExtString::::from(self.value.string).to_owned() }) } DataType::Object => Param::Object(unsafe { self.value.object }), DataType::RustError => Param::Error(unsafe { @@ -585,11 +592,10 @@ impl FfiParam { DataType::Bool => Param::Bool(unsafe { self.value.bool }), DataType::RustString => Param::String(unsafe { CStr::from_ptr(self.value.string) - .to_string_lossy() - .into_owned() + .to_owned() }), DataType::ExtString => { - Param::String(unsafe { ExtString::::from(self.value.string).to_string() }) + Param::String(unsafe { ExtString::::from(self.value.string).to_owned() }) } DataType::Object => Param::Object(unsafe { self.value.object }), DataType::RustError => Param::Error(unsafe { diff --git a/turing/src/interop/types.rs b/turing/src/interop/types.rs index 290c60a..b703c76 100644 --- a/turing/src/interop/types.rs +++ b/turing/src/interop/types.rs @@ -1,5 +1,6 @@ +use std::borrow::Borrow; use std::cmp::Ordering; -use std::ffi::{c_char, c_void, CStr}; +use std::ffi::{CStr, CString, c_char, c_void}; use std::fmt::{Display, Formatter}; use std::hash::Hash; use std::marker::PhantomData; @@ -63,6 +64,8 @@ impl Display for Semver { /// A string allocated externally, to be managed by the external environment. +/// +/// This takes ownership of the string pointer and will free it when dropped. pub struct ExtString { pub ptr: *const c_char, _ext: PhantomData diff --git a/turing/src/lib.rs b/turing/src/lib.rs index bae83c9..e16bfac 100644 --- a/turing/src/lib.rs +++ b/turing/src/lib.rs @@ -1,7 +1,7 @@ extern crate core; use std::collections::{HashMap, VecDeque}; -use std::ffi::{c_char, c_void}; +use std::ffi::{CString, c_char, c_void}; use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -69,7 +69,7 @@ pub struct EngineDataState { /// maps real pointers back to their opaque pointer ids pub pointer_backlink: FxHashMap, /// queue of strings for wasm to fetch (needed due to reentrancy limitations) - pub str_cache: VecDeque, + pub str_cache: VecDeque, /// which mods are currently active pub active_capabilities: FxHashSet, /// queue for algebraic type's data diff --git a/turing/src/tests.rs b/turing/src/tests.rs index a4a67ef..24365fd 100644 --- a/turing/src/tests.rs +++ b/turing/src/tests.rs @@ -2,7 +2,7 @@ use crate::engine::types::ScriptFnMetadata; use crate::interop::params::{DataType, FfiParam, FfiParamArray, FreeableDataType, Param, Params}; use crate::{ExternalFunctions, Turing}; use anyhow::Result; -use std::ffi::{CString, c_char, c_void}; +use std::ffi::{CStr, CString, c_char, c_void}; struct DirectExt {} impl ExternalFunctions for DirectExt { @@ -46,7 +46,7 @@ extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { match msg { Param::String(s) => { - println!("[wasm/info]: {}", s); + println!("[wasm/info]: {}", s.to_string_lossy()); Param::Void.to_ext_param() } _ => Param::Error(format!( @@ -58,7 +58,7 @@ extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { } extern "C" fn fetch_string(_params: FfiParamArray) -> FfiParam { - Param::String("this is a host provided string!".to_string()).to_ext_param() + Param::String(CString::new("this is a host provided string!").unwrap()).to_ext_param() } fn common_setup_direct(source: &str) -> Result> { @@ -161,7 +161,7 @@ pub fn test_lua_string_fetch() -> Result<()> { let mut turing = common_setup_direct(LUA_SCRIPT)?; let mut s = Params::of_size(1); - s.push(Param::String("Message from host".to_string())); + s.push(Param::String(CString::new("Message from host").unwrap())); let res = turing .call_fn_by_name("string_test", s, DataType::ExtString) From 6b520d19b52d88aaa94f1ad5e0c4090fba2696d8 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:11:48 -0400 Subject: [PATCH 3/6] Improving string management barely --- turing/src/engine/lua_engine.rs | 12 +- turing/src/engine/mod.rs | 2 +- turing/src/engine/runtime_modules/lua_glam.rs | 20 ++-- turing/src/engine/wasm_engine.rs | 12 +- turing/src/global_ffi/ffi.rs | 6 +- turing/src/interop/mod.rs | 3 +- turing/src/interop/params.rs | 15 ++- turing/src/interop/string.rs | 111 ++++++++++++++++++ turing/src/interop/types.rs | 8 ++ turing/src/lib.rs | 6 +- turing/src/tests.rs | 6 +- 11 files changed, 160 insertions(+), 41 deletions(-) create mode 100644 turing/src/interop/string.rs diff --git a/turing/src/engine/lua_engine.rs b/turing/src/engine/lua_engine.rs index c290e74..2924db4 100644 --- a/turing/src/engine/lua_engine.rs +++ b/turing/src/engine/lua_engine.rs @@ -85,7 +85,7 @@ impl Param { .unwrap_or_default(); Param::Object(real.ptr) } - DataType::RustError | DataType::ExtError => Param::Error(val.as_error().unwrap().to_string()), + DataType::RustError | DataType::ExtError => Param::Error(val.as_error().unwrap().to_string().into()), DataType::Void => Param::Void, DataType::Vec2 => lua_glam::unpack_vec2(val), DataType::Vec3 => lua_glam::unpack_vec3(val), @@ -378,7 +378,7 @@ impl LuaInterpreter { data: &Arc> ) -> Param { let Some((lua, module, api)) = &mut self.engine else { - return Param::Error("No script is loaded".to_string()) + return Param::Error("No script is loaded".into()) }; // we assume the function exists because we cached it earlier @@ -387,12 +387,12 @@ impl LuaInterpreter { let func = module.get::(name); if let Err(e) = func { - return Param::Error(format!("Failed to find function '{name}': {e}")); + return Param::Error(format!("Failed to find function '{name}': {e}").into()); } let func = func.unwrap(); let args = params.to_lua_args(lua, data, api); if let Err(e) = args { - return Param::Error(format!("{e}")) + return Param::Error(format!("{e}").into()) } let args = args.unwrap(); @@ -400,11 +400,11 @@ impl LuaInterpreter { Value::Function(f) => { f.call::(args) } - _ => return Param::Error(format!("'{name}' is not a function")) + _ => return Param::Error(format!("'{name}' is not a function").into()) }; if let Err(e) = res { - return Param::Error(e.to_string()); + return Param::Error(e.to_string().into()); } let res = res.unwrap(); if res.is_null() || res.is_nil() { diff --git a/turing/src/engine/mod.rs b/turing/src/engine/mod.rs index d98141a..4b90bae 100644 --- a/turing/src/engine/mod.rs +++ b/turing/src/engine/mod.rs @@ -57,7 +57,7 @@ where Engine::Wasm(engine) => engine.call_fn(cache_key, params, ret_type, data), #[cfg(feature = "lua")] Engine::Lua(engine) => engine.call_fn(cache_key, params, ret_type, data), - _ => Param::Error("No code engine is active".to_string()), + _ => Param::Error("No code engine is active".into()), } } diff --git a/turing/src/engine/runtime_modules/lua_glam.rs b/turing/src/engine/runtime_modules/lua_glam.rs index ef75354..545a9eb 100644 --- a/turing/src/engine/runtime_modules/lua_glam.rs +++ b/turing/src/engine/runtime_modules/lua_glam.rs @@ -698,10 +698,10 @@ pub fn unpack_vec2(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Vec2(v.0), - Err(e) => Param::Error(format!("Expected LuaVec2 userdata, got different UserData: {e}")), + Err(e) => Param::Error(format!("Expected LuaVec2 userdata, got different UserData: {e}").into()), } } - other => Param::Error(format!("Expected Vec2 userdata, got {}", other.type_name())), + other => Param::Error(format!("Expected Vec2 userdata, got {}", other.type_name()).into()), } } @@ -710,10 +710,10 @@ pub fn unpack_vec3(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Vec3(v.0), - Err(e) => Param::Error(format!("Expected LuaVec3 userdata, got different UserData: {e}")), + Err(e) => Param::Error(format!("Expected LuaVec3 userdata, got different UserData: {e}").into()), } } - other => Param::Error(format!("Expected Vec3 userdata, got {}", other.type_name())), + other => Param::Error(format!("Expected Vec3 userdata, got {}", other.type_name()).into()), } } @@ -722,10 +722,10 @@ pub fn unpack_vec4(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Vec4(v.0), - Err(e) => Param::Error(format!("Expected LuaVec4 userdata, got different UserData: {e}")), + Err(e) => Param::Error(format!("Expected LuaVec4 userdata, got different UserData: {e}").into()), } } - other => Param::Error(format!("Expected Vec4 userdata, got {}", other.type_name())), + other => Param::Error(format!("Expected Vec4 userdata, got {}", other.type_name()).into()), } } @@ -734,10 +734,10 @@ pub fn unpack_quat(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Quat(v.0), - Err(e) => Param::Error(format!("Expected LuaQuat userdata, got different UserData: {e}")), + Err(e) => Param::Error(format!("Expected LuaQuat userdata, got different UserData: {e}").into()), } } - other => Param::Error(format!("Expected Quat userdata, got {}", other.type_name())), + other => Param::Error(format!("Expected Quat userdata, got {}", other.type_name()).into()), } } @@ -746,9 +746,9 @@ pub fn unpack_mat4(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Mat4(v.0), - Err(e) => Param::Error(format!("Expected LuaMat4 userdata, got different UserData: {e}")), + Err(e) => Param::Error(format!("Expected LuaMat4 userdata, got different UserData: {e}").into()), } } - other => Param::Error(format!("Expected Mat4 userdata, got {}", other.type_name())), + other => Param::Error(format!("Expected Mat4 userdata, got {}", other.type_name()).into()), } } diff --git a/turing/src/engine/wasm_engine.rs b/turing/src/engine/wasm_engine.rs index d3629d5..671e50e 100644 --- a/turing/src/engine/wasm_engine.rs +++ b/turing/src/engine/wasm_engine.rs @@ -141,7 +141,7 @@ impl Param { DataType::RustError | DataType::ExtError => { let ptr = val.unwrap_i32() as u32; let st = get_wasm_string(ptr, memory.data(caller)); - Param::Error(st.to_string_lossy().to_string()) + Param::Error(st.into()) } DataType::Void => Param::Void, @@ -686,12 +686,12 @@ impl WasmInterpreter { data: &Arc>, ) -> Param { let Some(instance) = &mut self.script_instance else { - return Param::Error("No script is loaded or reentry was attempted".to_string()); + return Param::Error("No script is loaded or reentry was attempted".into()); }; // Fast-path: typed cache (common signatures). Falls back to dynamic call below. if let Some(entry) = self.typed_cache.get(&cache_key) { - return entry.invoke(&mut self.store, params).unwrap_or_else(Param::Error) + return entry.invoke(&mut self.store, params).unwrap_or_else(|s| Param::Error(s.into())) } // Try cache first to avoid repeated name lookup and Val boxing/unboxing. @@ -701,7 +701,7 @@ impl WasmInterpreter { let args = params.to_wasm_args(data); if let Err(e) = args { - return Param::Error(format!("{e}")) + return Param::Error(format!("{e}").into()) } let args = args.unwrap(); @@ -716,7 +716,7 @@ impl WasmInterpreter { }; if let Err(e) = f.call(&mut self.store, &args, &mut res) { - return Param::Error(e.to_string()); + return Param::Error(e.to_string().into()); } // Return void quickly if res.is_empty() { @@ -726,7 +726,7 @@ impl WasmInterpreter { let memory = match &self.memory { Some(m) => m, - None => return Param::Error("WASM memory not initialized".to_string()), + None => return Param::Error("WASM memory not initialized".into()), }; // convert Val to Param diff --git a/turing/src/global_ffi/ffi.rs b/turing/src/global_ffi/ffi.rs index 4c390d2..92ba51c 100644 --- a/turing/src/global_ffi/ffi.rs +++ b/turing/src/global_ffi/ffi.rs @@ -200,11 +200,11 @@ unsafe extern "C" fn turing_script_load(turing: *mut TuringInstance, source: *co let capabilities = match res { Ok(ls) => ls, - Err(e) => return Param::Error(format!("{}", e)).to_rs_param() + Err(e) => return Param::Error(format!("{}", e).into()).to_rs_param() }; if let Err(e) = turing.load_script(source, &capabilities) { - Param::Error(format!("{}\n{}", e, e.backtrace())) + Param::Error(format!("{}\n{}", e, e.backtrace()).into()) } else { Param::Void }.to_rs_param() @@ -377,7 +377,7 @@ unsafe extern "C" fn turing_params_get_param(params: *mut Params, index: u32) -> if let Some(p) = params.get(index as usize) { p.clone().to_rs_param() } else { - Param::Error("index out of bounds".to_string()).to_rs_param() + Param::Error("index out of bounds".into()).to_rs_param() } } diff --git a/turing/src/interop/mod.rs b/turing/src/interop/mod.rs index e831309..5bbadd5 100644 --- a/turing/src/interop/mod.rs +++ b/turing/src/interop/mod.rs @@ -1,2 +1,3 @@ pub mod params; -pub mod types; \ No newline at end of file +pub mod types; +pub mod string; \ No newline at end of file diff --git a/turing/src/interop/params.rs b/turing/src/interop/params.rs index 30a9a6c..2f6f130 100644 --- a/turing/src/interop/params.rs +++ b/turing/src/interop/params.rs @@ -9,6 +9,7 @@ use glam::{Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec4}; use num_enum::TryFromPrimitive; use serde::{Deserialize, Serialize}; use crate::ExternalFunctions; +use crate::interop::string::RustString; use crate::interop::types::ExtString; @@ -164,7 +165,7 @@ pub enum Param { Bool(bool), String(CString), Object(*const c_void), - Error(String), + Error(RustString), Void, Vec2(Vec2), Vec3(Vec3), @@ -200,7 +201,7 @@ impl Param { // allocated via CString, must be freed via CString::from_raw Param::String(x) => FfiParam { type_id: T::STRING, value: RawParam { string: CString::new(x).unwrap().into_raw() } }, Param::Object(x) => FfiParam { type_id: DataType::Object, value: RawParam { object: x } }, - Param::Error(x) => FfiParam { type_id: T::ERROR, value: RawParam { error: CString::new(x).unwrap().into_raw() } }, + Param::Error(x) => FfiParam { type_id: T::ERROR, value: RawParam { error: x.into_cstring().into_raw() } }, Param::Void => FfiParam { type_id: DataType::Void, value: RawParam { void: () } }, Param::Vec2(v) => FfiParam { type_id: DataType::Vec2, value: RawParam { vec2: v } }, Param::Vec3(v) => FfiParam { type_id: DataType::Vec3, value: RawParam { vec3: v } }, @@ -553,11 +554,10 @@ impl FfiParam { DataType::Object => Param::Object(unsafe { self.value.object }), DataType::RustError => Param::Error(unsafe { CString::from_raw(self.value.error as *mut c_char) - .to_string_lossy() - .into_owned() + .into() }), DataType::ExtError => { - Param::Error(unsafe { ExtString::::from(self.value.error).to_string() }) + Param::Error(unsafe { ExtString::::from(self.value.error).into_string().into() }) } DataType::Void => Param::Void, DataType::Vec2 => Param::Vec2(unsafe { self.value.vec2 }), @@ -600,11 +600,10 @@ impl FfiParam { DataType::Object => Param::Object(unsafe { self.value.object }), DataType::RustError => Param::Error(unsafe { CStr::from_ptr(self.value.error) - .to_string_lossy() - .into_owned() + .into() }), DataType::ExtError => { - Param::Error(unsafe { ExtString::::from(self.value.error).to_string() }) + Param::Error(unsafe { ExtString::::from(self.value.error).into_cstring().into() }) } DataType::Void => Param::Void, DataType::Vec2 => Param::Vec2(unsafe { self.value.vec2 }), diff --git a/turing/src/interop/string.rs b/turing/src/interop/string.rs new file mode 100644 index 0000000..8caaa04 --- /dev/null +++ b/turing/src/interop/string.rs @@ -0,0 +1,111 @@ +use std::{ffi::{CStr, CString}, fmt::{Display, Formatter}, ops::Deref}; + +/// A Rust string representation for interop purposes. +/// It can either be a CString or a standard String. +/// This is used to reduce unnecessary allocations and conversions. +#[derive(Clone, Debug, PartialEq, Hash , Eq)] +pub enum RustString { + CString(CString), + String(String), +} + +impl From for RustString { + fn from(value: CString) -> Self { + RustString::CString(value) + } +} + +impl From for RustString { + fn from(value: String) -> Self { + RustString::String(value) + } +} + +impl From<&str> for RustString { + fn from(value: &str) -> Self { + RustString::String(value.to_owned()) + } +} + +impl From<&CStr> for RustString { + fn from(value: &CStr) -> Self { + RustString::CString(value.to_owned()) + } +} + +impl RustString { + /// Converts the RustString into a CString. + /// If it's already a CString, it returns it directly. + /// If it's a String, it converts it to CString. + pub fn to_cstring(&self) -> CString { + match self { + RustString::CString(cstr) => cstr.clone(), + RustString::String(s) => CString::new(s.as_str()).unwrap(), + } + } + + pub fn as_bytes(&self) -> &[u8] { + match self { + RustString::CString(cstr) => cstr.as_bytes(), + RustString::String(s) => s.as_bytes(), + } + } + + pub fn as_str(&self) -> &str { + match self { + RustString::CString(cstr) => cstr.to_str().unwrap(), + RustString::String(s) => s.as_str(), + } + } + + pub fn len(&self) -> usize { + match self { + RustString::CString(cstr) => cstr.to_bytes().len(), + RustString::String(s) => s.len(), + } + } + + pub fn is_empty(&self) -> bool { + match self { + RustString::CString(cstr) => cstr.to_bytes().is_empty(), + RustString::String(s) => s.is_empty(), + } + } + + pub fn into_string(self) -> String { + match self { + RustString::CString(cstr) => cstr.into_string().unwrap(), + RustString::String(s) => s, + } + } + + pub fn into_cstring(self) -> CString { + match self { + RustString::CString(cstr) => cstr, + RustString::String(s) => CString::new(s).unwrap(), + } + } +} + +/// Converts the RustString into a standard String. +/// If it's already a String, it returns it directly. +/// If it's a CString, it converts it to String. +impl Display for RustString { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + RustString::CString(cstr) => write!(f, "{}", cstr.to_string_lossy()), + RustString::String(s) => write!(f, "{}", s), + } + } +} + +impl Deref for RustString { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + match self { + RustString::CString(cstr) => cstr.as_bytes(), + RustString::String(s) => s.as_bytes(), + } + } +} \ No newline at end of file diff --git a/turing/src/interop/types.rs b/turing/src/interop/types.rs index b703c76..b5b5253 100644 --- a/turing/src/interop/types.rs +++ b/turing/src/interop/types.rs @@ -75,6 +75,14 @@ impl ExtString { pub fn new(ptr: *const c_char) -> Self { ExtString { ptr, _ext: PhantomData } } + + pub fn into_string(self) -> String { + unsafe { CStr::from_ptr(self.ptr).to_string_lossy().into_owned() } + } + + pub fn into_cstring(self) -> CString { + unsafe { CStr::from_ptr(self.ptr).to_owned() } + } } impl Drop for ExtString { diff --git a/turing/src/lib.rs b/turing/src/lib.rs index e16bfac..7e04bcd 100644 --- a/turing/src/lib.rs +++ b/turing/src/lib.rs @@ -216,12 +216,12 @@ impl Turing { pub fn call_fn_by_name(&mut self, name: impl ToString, params: Params, expected_return_type: DataType) -> Param { let Some(engine) = &mut self.engine else { - return Param::Error("No code engine is active".to_string()) + return Param::Error("No code engine is active".into()) }; let key = engine.get_fn_key(&name.to_string()); let Some(key) = key else { - return Param::Error(format!("Function '{}' not found", name.to_string())); + return Param::Error(format!("Function '{}' not found", name.to_string()).into()); }; self.call_fn(key, params, expected_return_type) } @@ -229,7 +229,7 @@ impl Turing { pub fn call_fn(&mut self, cache_key: ScriptFnKey, params: Params, expected_return_type: DataType) -> Param { // let name = name.to_string(); let Some(engine) = &mut self.engine else { - return Param::Error("No code engine is active".to_string()) + return Param::Error("No code engine is active".into()) }; engine.call_fn( diff --git a/turing/src/tests.rs b/turing/src/tests.rs index 24365fd..f74bc38 100644 --- a/turing/src/tests.rs +++ b/turing/src/tests.rs @@ -37,11 +37,11 @@ impl ExternalFunctions for DirectExt { extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { let Ok(local) = params.as_params::() else { - return Param::Error("Failed to unpack params".to_string()).to_ext_param(); + return Param::Error("Failed to unpack params".into()).to_ext_param(); }; let Some(msg) = local.get(0) else { - return Param::Error("Missing argument: msg".to_string()).to_ext_param(); + return Param::Error("Missing argument: msg".into()).to_ext_param(); }; match msg { @@ -52,7 +52,7 @@ extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { _ => Param::Error(format!( "Invalid argument type, expected String, got {:?}", msg - )) + ).into()) .to_ext_param(), } } From 768c950bf4537efed07dafc89057015c9ed04384 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:12:26 -0400 Subject: [PATCH 4/6] Revert "Improving string management barely" This reverts commit 6b520d19b52d88aaa94f1ad5e0c4090fba2696d8. --- turing/src/engine/lua_engine.rs | 12 +- turing/src/engine/mod.rs | 2 +- turing/src/engine/runtime_modules/lua_glam.rs | 20 ++-- turing/src/engine/wasm_engine.rs | 12 +- turing/src/global_ffi/ffi.rs | 6 +- turing/src/interop/mod.rs | 3 +- turing/src/interop/params.rs | 15 +-- turing/src/interop/string.rs | 111 ------------------ turing/src/interop/types.rs | 8 -- turing/src/lib.rs | 6 +- turing/src/tests.rs | 6 +- 11 files changed, 41 insertions(+), 160 deletions(-) delete mode 100644 turing/src/interop/string.rs diff --git a/turing/src/engine/lua_engine.rs b/turing/src/engine/lua_engine.rs index 2924db4..c290e74 100644 --- a/turing/src/engine/lua_engine.rs +++ b/turing/src/engine/lua_engine.rs @@ -85,7 +85,7 @@ impl Param { .unwrap_or_default(); Param::Object(real.ptr) } - DataType::RustError | DataType::ExtError => Param::Error(val.as_error().unwrap().to_string().into()), + DataType::RustError | DataType::ExtError => Param::Error(val.as_error().unwrap().to_string()), DataType::Void => Param::Void, DataType::Vec2 => lua_glam::unpack_vec2(val), DataType::Vec3 => lua_glam::unpack_vec3(val), @@ -378,7 +378,7 @@ impl LuaInterpreter { data: &Arc> ) -> Param { let Some((lua, module, api)) = &mut self.engine else { - return Param::Error("No script is loaded".into()) + return Param::Error("No script is loaded".to_string()) }; // we assume the function exists because we cached it earlier @@ -387,12 +387,12 @@ impl LuaInterpreter { let func = module.get::(name); if let Err(e) = func { - return Param::Error(format!("Failed to find function '{name}': {e}").into()); + return Param::Error(format!("Failed to find function '{name}': {e}")); } let func = func.unwrap(); let args = params.to_lua_args(lua, data, api); if let Err(e) = args { - return Param::Error(format!("{e}").into()) + return Param::Error(format!("{e}")) } let args = args.unwrap(); @@ -400,11 +400,11 @@ impl LuaInterpreter { Value::Function(f) => { f.call::(args) } - _ => return Param::Error(format!("'{name}' is not a function").into()) + _ => return Param::Error(format!("'{name}' is not a function")) }; if let Err(e) = res { - return Param::Error(e.to_string().into()); + return Param::Error(e.to_string()); } let res = res.unwrap(); if res.is_null() || res.is_nil() { diff --git a/turing/src/engine/mod.rs b/turing/src/engine/mod.rs index 4b90bae..d98141a 100644 --- a/turing/src/engine/mod.rs +++ b/turing/src/engine/mod.rs @@ -57,7 +57,7 @@ where Engine::Wasm(engine) => engine.call_fn(cache_key, params, ret_type, data), #[cfg(feature = "lua")] Engine::Lua(engine) => engine.call_fn(cache_key, params, ret_type, data), - _ => Param::Error("No code engine is active".into()), + _ => Param::Error("No code engine is active".to_string()), } } diff --git a/turing/src/engine/runtime_modules/lua_glam.rs b/turing/src/engine/runtime_modules/lua_glam.rs index 545a9eb..ef75354 100644 --- a/turing/src/engine/runtime_modules/lua_glam.rs +++ b/turing/src/engine/runtime_modules/lua_glam.rs @@ -698,10 +698,10 @@ pub fn unpack_vec2(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Vec2(v.0), - Err(e) => Param::Error(format!("Expected LuaVec2 userdata, got different UserData: {e}").into()), + Err(e) => Param::Error(format!("Expected LuaVec2 userdata, got different UserData: {e}")), } } - other => Param::Error(format!("Expected Vec2 userdata, got {}", other.type_name()).into()), + other => Param::Error(format!("Expected Vec2 userdata, got {}", other.type_name())), } } @@ -710,10 +710,10 @@ pub fn unpack_vec3(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Vec3(v.0), - Err(e) => Param::Error(format!("Expected LuaVec3 userdata, got different UserData: {e}").into()), + Err(e) => Param::Error(format!("Expected LuaVec3 userdata, got different UserData: {e}")), } } - other => Param::Error(format!("Expected Vec3 userdata, got {}", other.type_name()).into()), + other => Param::Error(format!("Expected Vec3 userdata, got {}", other.type_name())), } } @@ -722,10 +722,10 @@ pub fn unpack_vec4(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Vec4(v.0), - Err(e) => Param::Error(format!("Expected LuaVec4 userdata, got different UserData: {e}").into()), + Err(e) => Param::Error(format!("Expected LuaVec4 userdata, got different UserData: {e}")), } } - other => Param::Error(format!("Expected Vec4 userdata, got {}", other.type_name()).into()), + other => Param::Error(format!("Expected Vec4 userdata, got {}", other.type_name())), } } @@ -734,10 +734,10 @@ pub fn unpack_quat(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Quat(v.0), - Err(e) => Param::Error(format!("Expected LuaQuat userdata, got different UserData: {e}").into()), + Err(e) => Param::Error(format!("Expected LuaQuat userdata, got different UserData: {e}")), } } - other => Param::Error(format!("Expected Quat userdata, got {}", other.type_name()).into()), + other => Param::Error(format!("Expected Quat userdata, got {}", other.type_name())), } } @@ -746,9 +746,9 @@ pub fn unpack_mat4(v: Value) -> Param { Value::UserData(d) => { match d.borrow::() { Ok(v) => Param::Mat4(v.0), - Err(e) => Param::Error(format!("Expected LuaMat4 userdata, got different UserData: {e}").into()), + Err(e) => Param::Error(format!("Expected LuaMat4 userdata, got different UserData: {e}")), } } - other => Param::Error(format!("Expected Mat4 userdata, got {}", other.type_name()).into()), + other => Param::Error(format!("Expected Mat4 userdata, got {}", other.type_name())), } } diff --git a/turing/src/engine/wasm_engine.rs b/turing/src/engine/wasm_engine.rs index 671e50e..d3629d5 100644 --- a/turing/src/engine/wasm_engine.rs +++ b/turing/src/engine/wasm_engine.rs @@ -141,7 +141,7 @@ impl Param { DataType::RustError | DataType::ExtError => { let ptr = val.unwrap_i32() as u32; let st = get_wasm_string(ptr, memory.data(caller)); - Param::Error(st.into()) + Param::Error(st.to_string_lossy().to_string()) } DataType::Void => Param::Void, @@ -686,12 +686,12 @@ impl WasmInterpreter { data: &Arc>, ) -> Param { let Some(instance) = &mut self.script_instance else { - return Param::Error("No script is loaded or reentry was attempted".into()); + return Param::Error("No script is loaded or reentry was attempted".to_string()); }; // Fast-path: typed cache (common signatures). Falls back to dynamic call below. if let Some(entry) = self.typed_cache.get(&cache_key) { - return entry.invoke(&mut self.store, params).unwrap_or_else(|s| Param::Error(s.into())) + return entry.invoke(&mut self.store, params).unwrap_or_else(Param::Error) } // Try cache first to avoid repeated name lookup and Val boxing/unboxing. @@ -701,7 +701,7 @@ impl WasmInterpreter { let args = params.to_wasm_args(data); if let Err(e) = args { - return Param::Error(format!("{e}").into()) + return Param::Error(format!("{e}")) } let args = args.unwrap(); @@ -716,7 +716,7 @@ impl WasmInterpreter { }; if let Err(e) = f.call(&mut self.store, &args, &mut res) { - return Param::Error(e.to_string().into()); + return Param::Error(e.to_string()); } // Return void quickly if res.is_empty() { @@ -726,7 +726,7 @@ impl WasmInterpreter { let memory = match &self.memory { Some(m) => m, - None => return Param::Error("WASM memory not initialized".into()), + None => return Param::Error("WASM memory not initialized".to_string()), }; // convert Val to Param diff --git a/turing/src/global_ffi/ffi.rs b/turing/src/global_ffi/ffi.rs index 92ba51c..4c390d2 100644 --- a/turing/src/global_ffi/ffi.rs +++ b/turing/src/global_ffi/ffi.rs @@ -200,11 +200,11 @@ unsafe extern "C" fn turing_script_load(turing: *mut TuringInstance, source: *co let capabilities = match res { Ok(ls) => ls, - Err(e) => return Param::Error(format!("{}", e).into()).to_rs_param() + Err(e) => return Param::Error(format!("{}", e)).to_rs_param() }; if let Err(e) = turing.load_script(source, &capabilities) { - Param::Error(format!("{}\n{}", e, e.backtrace()).into()) + Param::Error(format!("{}\n{}", e, e.backtrace())) } else { Param::Void }.to_rs_param() @@ -377,7 +377,7 @@ unsafe extern "C" fn turing_params_get_param(params: *mut Params, index: u32) -> if let Some(p) = params.get(index as usize) { p.clone().to_rs_param() } else { - Param::Error("index out of bounds".into()).to_rs_param() + Param::Error("index out of bounds".to_string()).to_rs_param() } } diff --git a/turing/src/interop/mod.rs b/turing/src/interop/mod.rs index 5bbadd5..e831309 100644 --- a/turing/src/interop/mod.rs +++ b/turing/src/interop/mod.rs @@ -1,3 +1,2 @@ pub mod params; -pub mod types; -pub mod string; \ No newline at end of file +pub mod types; \ No newline at end of file diff --git a/turing/src/interop/params.rs b/turing/src/interop/params.rs index 2f6f130..30a9a6c 100644 --- a/turing/src/interop/params.rs +++ b/turing/src/interop/params.rs @@ -9,7 +9,6 @@ use glam::{Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec4}; use num_enum::TryFromPrimitive; use serde::{Deserialize, Serialize}; use crate::ExternalFunctions; -use crate::interop::string::RustString; use crate::interop::types::ExtString; @@ -165,7 +164,7 @@ pub enum Param { Bool(bool), String(CString), Object(*const c_void), - Error(RustString), + Error(String), Void, Vec2(Vec2), Vec3(Vec3), @@ -201,7 +200,7 @@ impl Param { // allocated via CString, must be freed via CString::from_raw Param::String(x) => FfiParam { type_id: T::STRING, value: RawParam { string: CString::new(x).unwrap().into_raw() } }, Param::Object(x) => FfiParam { type_id: DataType::Object, value: RawParam { object: x } }, - Param::Error(x) => FfiParam { type_id: T::ERROR, value: RawParam { error: x.into_cstring().into_raw() } }, + Param::Error(x) => FfiParam { type_id: T::ERROR, value: RawParam { error: CString::new(x).unwrap().into_raw() } }, Param::Void => FfiParam { type_id: DataType::Void, value: RawParam { void: () } }, Param::Vec2(v) => FfiParam { type_id: DataType::Vec2, value: RawParam { vec2: v } }, Param::Vec3(v) => FfiParam { type_id: DataType::Vec3, value: RawParam { vec3: v } }, @@ -554,10 +553,11 @@ impl FfiParam { DataType::Object => Param::Object(unsafe { self.value.object }), DataType::RustError => Param::Error(unsafe { CString::from_raw(self.value.error as *mut c_char) - .into() + .to_string_lossy() + .into_owned() }), DataType::ExtError => { - Param::Error(unsafe { ExtString::::from(self.value.error).into_string().into() }) + Param::Error(unsafe { ExtString::::from(self.value.error).to_string() }) } DataType::Void => Param::Void, DataType::Vec2 => Param::Vec2(unsafe { self.value.vec2 }), @@ -600,10 +600,11 @@ impl FfiParam { DataType::Object => Param::Object(unsafe { self.value.object }), DataType::RustError => Param::Error(unsafe { CStr::from_ptr(self.value.error) - .into() + .to_string_lossy() + .into_owned() }), DataType::ExtError => { - Param::Error(unsafe { ExtString::::from(self.value.error).into_cstring().into() }) + Param::Error(unsafe { ExtString::::from(self.value.error).to_string() }) } DataType::Void => Param::Void, DataType::Vec2 => Param::Vec2(unsafe { self.value.vec2 }), diff --git a/turing/src/interop/string.rs b/turing/src/interop/string.rs deleted file mode 100644 index 8caaa04..0000000 --- a/turing/src/interop/string.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::{ffi::{CStr, CString}, fmt::{Display, Formatter}, ops::Deref}; - -/// A Rust string representation for interop purposes. -/// It can either be a CString or a standard String. -/// This is used to reduce unnecessary allocations and conversions. -#[derive(Clone, Debug, PartialEq, Hash , Eq)] -pub enum RustString { - CString(CString), - String(String), -} - -impl From for RustString { - fn from(value: CString) -> Self { - RustString::CString(value) - } -} - -impl From for RustString { - fn from(value: String) -> Self { - RustString::String(value) - } -} - -impl From<&str> for RustString { - fn from(value: &str) -> Self { - RustString::String(value.to_owned()) - } -} - -impl From<&CStr> for RustString { - fn from(value: &CStr) -> Self { - RustString::CString(value.to_owned()) - } -} - -impl RustString { - /// Converts the RustString into a CString. - /// If it's already a CString, it returns it directly. - /// If it's a String, it converts it to CString. - pub fn to_cstring(&self) -> CString { - match self { - RustString::CString(cstr) => cstr.clone(), - RustString::String(s) => CString::new(s.as_str()).unwrap(), - } - } - - pub fn as_bytes(&self) -> &[u8] { - match self { - RustString::CString(cstr) => cstr.as_bytes(), - RustString::String(s) => s.as_bytes(), - } - } - - pub fn as_str(&self) -> &str { - match self { - RustString::CString(cstr) => cstr.to_str().unwrap(), - RustString::String(s) => s.as_str(), - } - } - - pub fn len(&self) -> usize { - match self { - RustString::CString(cstr) => cstr.to_bytes().len(), - RustString::String(s) => s.len(), - } - } - - pub fn is_empty(&self) -> bool { - match self { - RustString::CString(cstr) => cstr.to_bytes().is_empty(), - RustString::String(s) => s.is_empty(), - } - } - - pub fn into_string(self) -> String { - match self { - RustString::CString(cstr) => cstr.into_string().unwrap(), - RustString::String(s) => s, - } - } - - pub fn into_cstring(self) -> CString { - match self { - RustString::CString(cstr) => cstr, - RustString::String(s) => CString::new(s).unwrap(), - } - } -} - -/// Converts the RustString into a standard String. -/// If it's already a String, it returns it directly. -/// If it's a CString, it converts it to String. -impl Display for RustString { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - RustString::CString(cstr) => write!(f, "{}", cstr.to_string_lossy()), - RustString::String(s) => write!(f, "{}", s), - } - } -} - -impl Deref for RustString { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - match self { - RustString::CString(cstr) => cstr.as_bytes(), - RustString::String(s) => s.as_bytes(), - } - } -} \ No newline at end of file diff --git a/turing/src/interop/types.rs b/turing/src/interop/types.rs index b5b5253..b703c76 100644 --- a/turing/src/interop/types.rs +++ b/turing/src/interop/types.rs @@ -75,14 +75,6 @@ impl ExtString { pub fn new(ptr: *const c_char) -> Self { ExtString { ptr, _ext: PhantomData } } - - pub fn into_string(self) -> String { - unsafe { CStr::from_ptr(self.ptr).to_string_lossy().into_owned() } - } - - pub fn into_cstring(self) -> CString { - unsafe { CStr::from_ptr(self.ptr).to_owned() } - } } impl Drop for ExtString { diff --git a/turing/src/lib.rs b/turing/src/lib.rs index 7e04bcd..e16bfac 100644 --- a/turing/src/lib.rs +++ b/turing/src/lib.rs @@ -216,12 +216,12 @@ impl Turing { pub fn call_fn_by_name(&mut self, name: impl ToString, params: Params, expected_return_type: DataType) -> Param { let Some(engine) = &mut self.engine else { - return Param::Error("No code engine is active".into()) + return Param::Error("No code engine is active".to_string()) }; let key = engine.get_fn_key(&name.to_string()); let Some(key) = key else { - return Param::Error(format!("Function '{}' not found", name.to_string()).into()); + return Param::Error(format!("Function '{}' not found", name.to_string())); }; self.call_fn(key, params, expected_return_type) } @@ -229,7 +229,7 @@ impl Turing { pub fn call_fn(&mut self, cache_key: ScriptFnKey, params: Params, expected_return_type: DataType) -> Param { // let name = name.to_string(); let Some(engine) = &mut self.engine else { - return Param::Error("No code engine is active".into()) + return Param::Error("No code engine is active".to_string()) }; engine.call_fn( diff --git a/turing/src/tests.rs b/turing/src/tests.rs index f74bc38..24365fd 100644 --- a/turing/src/tests.rs +++ b/turing/src/tests.rs @@ -37,11 +37,11 @@ impl ExternalFunctions for DirectExt { extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { let Ok(local) = params.as_params::() else { - return Param::Error("Failed to unpack params".into()).to_ext_param(); + return Param::Error("Failed to unpack params".to_string()).to_ext_param(); }; let Some(msg) = local.get(0) else { - return Param::Error("Missing argument: msg".into()).to_ext_param(); + return Param::Error("Missing argument: msg".to_string()).to_ext_param(); }; match msg { @@ -52,7 +52,7 @@ extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { _ => Param::Error(format!( "Invalid argument type, expected String, got {:?}", msg - ).into()) + )) .to_ext_param(), } } From ecfe391aebe4c598c375f97f08cee8ed91f986d2 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:12:33 -0400 Subject: [PATCH 5/6] Revert "Refactor string handling to use CString to reduce interop" This reverts commit 07021fde144317b32414b9e395b50801f1e92643. --- turing/benches/lua_api_bench.rs | 4 ++-- turing/benches/wasm_api_bench.rs | 2 +- turing/src/engine/lua_engine.rs | 9 ++++----- turing/src/engine/wasm_engine.rs | 26 +++++++++++++++----------- turing/src/interop/params.rs | 22 ++++++++-------------- turing/src/interop/types.rs | 5 +---- turing/src/lib.rs | 4 ++-- turing/src/tests.rs | 8 ++++---- 8 files changed, 37 insertions(+), 43 deletions(-) diff --git a/turing/benches/lua_api_bench.rs b/turing/benches/lua_api_bench.rs index e63efc5..07dc06a 100644 --- a/turing/benches/lua_api_bench.rs +++ b/turing/benches/lua_api_bench.rs @@ -36,7 +36,7 @@ extern "C" fn log_info_wasm( extern "C" fn fetch_string( _params: turing_rs::interop::params::FfiParamArray, ) -> turing_rs::interop::params::FfiParam { - Param::String(CString::new("this is a host provided string!").unwrap()).to_ext_param() + Param::String("this is a host provided string!".to_string()).to_ext_param() } fn setup_turing_for_lua() -> Turing { @@ -82,7 +82,7 @@ fn bench_turing_lua_string_roundtrip(c: &mut Criterion) { c.bench_function("turing_lua_string_roundtrip", |b| { b.iter(|| { let mut params = Params::of_size(1); - params.push(Param::String(CString::new("Message from host").unwrap())); + params.push(Param::String("Message from host".to_string())); let res = turing.call_fn(string_test, params, DataType::ExtString); let _ = black_box(res.to_result::().unwrap()); diff --git a/turing/benches/wasm_api_bench.rs b/turing/benches/wasm_api_bench.rs index 1db178a..e85bee9 100644 --- a/turing/benches/wasm_api_bench.rs +++ b/turing/benches/wasm_api_bench.rs @@ -38,7 +38,7 @@ extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { // called from wasm extern "C" fn fetch_string(_params: FfiParamArray) -> FfiParam { - Param::String(CString::new("this is a host provided string!").unwrap()).to_ext_param() + Param::String("this is a host provided string!".to_string()).to_ext_param() } fn setup_turing_with_callbacks() -> Turing { diff --git a/turing/src/engine/lua_engine.rs b/turing/src/engine/lua_engine.rs index c290e74..c93e36f 100644 --- a/turing/src/engine/lua_engine.rs +++ b/turing/src/engine/lua_engine.rs @@ -1,4 +1,3 @@ -use std::ffi::CString; use std::fs; use std::marker::PhantomData; use std::path::Path; @@ -32,7 +31,7 @@ impl DataType { (DataType::F32, Value::Number(f)) => Ok(Param::F32(*f as f32)), (DataType::F64, Value::Number(f)) => Ok(Param::F64(*f)), (DataType::Bool, Value::Boolean(b)) => Ok(Param::Bool(*b)), - (DataType::RustString | DataType::ExtString, Value::String(s)) => Ok(Param::String(CString::new(s.to_string_lossy().to_string()).unwrap())), + (DataType::RustString | DataType::ExtString, Value::String(s)) => Ok(Param::String(s.to_string_lossy())), (DataType::Object, Value::Table(t)) => { let key = t.raw_get::("opaqu")?; let key = match key { @@ -72,7 +71,7 @@ impl Param { DataType::F64 => Param::F64(val.as_number().unwrap()), DataType::Bool => Param::Bool(val.as_boolean().unwrap()), // allocated externally, we copy the string - DataType::RustString | DataType::ExtString => Param::String(CString::new(val.as_string().unwrap().to_string_lossy()).unwrap()), + DataType::RustString | DataType::ExtString => Param::String(val.as_string().unwrap().to_string_lossy()), DataType::Object => { let table = val.as_table().unwrap(); let op = table.get("opaqu").unwrap(); @@ -114,7 +113,7 @@ impl Param { Param::F32(f) => Value::Number(f as f64), Param::F64(f) => Value::Number(f), Param::Bool(b) => Value::Boolean(b), - Param::String(s) => Value::String(lua.create_string(&s.as_bytes())?), + Param::String(s) => Value::String(lua.create_string(&s)?), Param::Object(pointer) => { let pointer = ExtPointer::from(pointer); let opaque = s.get_opaque_pointer(pointer); @@ -153,7 +152,7 @@ impl Params { Param::F32(f) => Ok(Value::Number(f as f64)), Param::F64(f) => Ok(Value::Number(f)), Param::Bool(b) => Ok(Value::Boolean(b)), - Param::String(s) => Ok(Value::String(lua.create_string(&s.as_bytes()).unwrap())), + Param::String(s) => Ok(Value::String(lua.create_string(&s).unwrap())), Param::Object(rp) => { let pointer = rp.into(); Ok(if let Some(op) = s.pointer_backlink.get(&pointer) { diff --git a/turing/src/engine/wasm_engine.rs b/turing/src/engine/wasm_engine.rs index d3629d5..9a5fbfc 100644 --- a/turing/src/engine/wasm_engine.rs +++ b/turing/src/engine/wasm_engine.rs @@ -47,7 +47,7 @@ impl DataType { return Err(anyhow!("wasm does not export memory")) }; let st = get_wasm_string(ptr, memory.data(&caller)); - Ok(Param::String(st.to_owned())) + Ok(Param::String(st)) } (DataType::Object, Val::I64(pointer_id)) => { let pointer_key = @@ -125,7 +125,7 @@ impl Param { let ptr = val.unwrap_i32() as u32; let st = get_wasm_string(ptr, memory.data(caller)); - Param::String(st.to_owned()) + Param::String(st) } DataType::Object => { let op = val.unwrap_i64() as u64; @@ -141,7 +141,7 @@ impl Param { DataType::RustError | DataType::ExtError => { let ptr = val.unwrap_i32() as u32; let st = get_wasm_string(ptr, memory.data(caller)); - Param::Error(st.to_string_lossy().to_string()) + Param::Error(st) } DataType::Void => Param::Void, @@ -180,7 +180,7 @@ impl Param { Param::F64(f) => Val::F64(f.to_bits()), Param::Bool(b) => Val::I32(if b { 1 } else { 0 }), Param::String(st) => { - let l = st.as_bytes().len() + 1; + let l = st.len() + 1; s.str_cache.push_back(st); Val::I32(l as i32) } @@ -239,7 +239,7 @@ impl Params { Param::F64(f) => Ok(Val::F64(f.to_bits())), Param::Bool(b) => Ok(Val::I32(if b { 1 } else { 0 })), Param::String(st) => { - let l = st.as_bytes().len() + 1; + let l = st.len() + 1; s.str_cache.push_back(st); Ok(Val::I32(l as i32)) } @@ -481,19 +481,23 @@ impl StdoutStream for WriterInit } /// gets a string out of wasm memory into rust memory. -pub fn get_wasm_string(message: u32, data: &[u8]) -> &CStr { - CStr::from_bytes_until_nul(&data[message as usize..]).expect("Not a valid CStr") - +pub fn get_wasm_string(message: u32, data: &[u8]) -> String { + let c = CStr::from_bytes_until_nul(&data[message as usize..]).expect("Not a valid CStr"); + match c.to_str() { + Ok(s) => s.to_owned(), + Err(_) => c.to_string_lossy().into_owned(), + } } /// writes a string from rust memory to wasm memory. pub fn write_wasm_string( pointer: u32, - string: &CStr, + string: &str, memory: &Memory, caller: Caller<'_, WasiP1Ctx>, ) -> Result<(), MemoryAccessError> { - let bytes = string.to_bytes_with_nul(); + let c = CString::new(string).unwrap(); + let bytes = c.into_bytes_with_nul(); memory.write(caller, pointer as usize, &bytes) } @@ -807,7 +811,7 @@ pub fn wasm_host_strcpy( let size = ps[1].i32().unwrap(); if let Some(next_str) = data.write().str_cache.pop_front() - && next_str.as_bytes().len() + 1 == size as usize + && next_str.len() + 1 == size as usize { if let Some(memory) = caller.get_export("memory").and_then(|m| m.into_memory()) { write_wasm_string(ptr as u32, &next_str, &memory, caller)?; diff --git a/turing/src/interop/params.rs b/turing/src/interop/params.rs index 30a9a6c..7aecb2f 100644 --- a/turing/src/interop/params.rs +++ b/turing/src/interop/params.rs @@ -162,7 +162,7 @@ pub enum Param { F32(f32), F64(f64), Bool(bool), - String(CString), + String(String), Object(*const c_void), Error(String), Void, @@ -246,7 +246,7 @@ deref_param! { u64 => U64 } deref_param! { f32 => F32 } deref_param! { f64 => F64 } deref_param! { bool => Bool } -deref_param! { CString => String } +deref_param! { String => String } deref_param! { Vec2 => Vec2 } deref_param! { Vec3 => Vec3 } deref_param! { Vec4 => Vec4 } @@ -261,15 +261,6 @@ impl FromParam for () { } } } -impl FromParam for String { - fn from_param(param: Param) -> Result { - match param { - Param::String(s) => Ok(s.to_string_lossy().into_owned()), - Param::Error(e) => Err(anyhow!("{}", e)), - _ => Err(anyhow!("Incorrect data type")) - } - } -} #[derive(Debug, Default, Clone)] pub struct Params { @@ -546,9 +537,11 @@ impl FfiParam { DataType::Bool => Param::Bool(unsafe { self.value.bool }), DataType::RustString => Param::String(unsafe { CString::from_raw(self.value.string as *mut c_char) + .to_string_lossy() + .into_owned() }), DataType::ExtString => { - Param::String(unsafe { ExtString::::from(self.value.string).to_owned() }) + Param::String(unsafe { ExtString::::from(self.value.string).to_string() }) } DataType::Object => Param::Object(unsafe { self.value.object }), DataType::RustError => Param::Error(unsafe { @@ -592,10 +585,11 @@ impl FfiParam { DataType::Bool => Param::Bool(unsafe { self.value.bool }), DataType::RustString => Param::String(unsafe { CStr::from_ptr(self.value.string) - .to_owned() + .to_string_lossy() + .into_owned() }), DataType::ExtString => { - Param::String(unsafe { ExtString::::from(self.value.string).to_owned() }) + Param::String(unsafe { ExtString::::from(self.value.string).to_string() }) } DataType::Object => Param::Object(unsafe { self.value.object }), DataType::RustError => Param::Error(unsafe { diff --git a/turing/src/interop/types.rs b/turing/src/interop/types.rs index b703c76..290c60a 100644 --- a/turing/src/interop/types.rs +++ b/turing/src/interop/types.rs @@ -1,6 +1,5 @@ -use std::borrow::Borrow; use std::cmp::Ordering; -use std::ffi::{CStr, CString, c_char, c_void}; +use std::ffi::{c_char, c_void, CStr}; use std::fmt::{Display, Formatter}; use std::hash::Hash; use std::marker::PhantomData; @@ -64,8 +63,6 @@ impl Display for Semver { /// A string allocated externally, to be managed by the external environment. -/// -/// This takes ownership of the string pointer and will free it when dropped. pub struct ExtString { pub ptr: *const c_char, _ext: PhantomData diff --git a/turing/src/lib.rs b/turing/src/lib.rs index e16bfac..bae83c9 100644 --- a/turing/src/lib.rs +++ b/turing/src/lib.rs @@ -1,7 +1,7 @@ extern crate core; use std::collections::{HashMap, VecDeque}; -use std::ffi::{CString, c_char, c_void}; +use std::ffi::{c_char, c_void}; use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -69,7 +69,7 @@ pub struct EngineDataState { /// maps real pointers back to their opaque pointer ids pub pointer_backlink: FxHashMap, /// queue of strings for wasm to fetch (needed due to reentrancy limitations) - pub str_cache: VecDeque, + pub str_cache: VecDeque, /// which mods are currently active pub active_capabilities: FxHashSet, /// queue for algebraic type's data diff --git a/turing/src/tests.rs b/turing/src/tests.rs index 24365fd..a4a67ef 100644 --- a/turing/src/tests.rs +++ b/turing/src/tests.rs @@ -2,7 +2,7 @@ use crate::engine::types::ScriptFnMetadata; use crate::interop::params::{DataType, FfiParam, FfiParamArray, FreeableDataType, Param, Params}; use crate::{ExternalFunctions, Turing}; use anyhow::Result; -use std::ffi::{CStr, CString, c_char, c_void}; +use std::ffi::{CString, c_char, c_void}; struct DirectExt {} impl ExternalFunctions for DirectExt { @@ -46,7 +46,7 @@ extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { match msg { Param::String(s) => { - println!("[wasm/info]: {}", s.to_string_lossy()); + println!("[wasm/info]: {}", s); Param::Void.to_ext_param() } _ => Param::Error(format!( @@ -58,7 +58,7 @@ extern "C" fn log_info_wasm(params: FfiParamArray) -> FfiParam { } extern "C" fn fetch_string(_params: FfiParamArray) -> FfiParam { - Param::String(CString::new("this is a host provided string!").unwrap()).to_ext_param() + Param::String("this is a host provided string!".to_string()).to_ext_param() } fn common_setup_direct(source: &str) -> Result> { @@ -161,7 +161,7 @@ pub fn test_lua_string_fetch() -> Result<()> { let mut turing = common_setup_direct(LUA_SCRIPT)?; let mut s = Params::of_size(1); - s.push(Param::String(CString::new("Message from host").unwrap())); + s.push(Param::String("Message from host".to_string())); let res = turing .call_fn_by_name("string_test", s, DataType::ExtString) From 258fe2225d7b7487680e53fcadcfd9378715aade Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Sat, 24 Jan 2026 19:10:57 -0400 Subject: [PATCH 6/6] Optimize slightly more --- turing/src/engine/wasm_engine.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/turing/src/engine/wasm_engine.rs b/turing/src/engine/wasm_engine.rs index 9a5fbfc..c1c0826 100644 --- a/turing/src/engine/wasm_engine.rs +++ b/turing/src/engine/wasm_engine.rs @@ -101,10 +101,8 @@ impl Param { macro_rules! dequeue { ($typ:tt :: $init:tt; $x:tt ) => {{ let mut s = data.write(); - // ensure contiguous slice for easier conversion - s.f32_queue.make_contiguous(); Param::$typ(glam::$typ::$init( - s.f32_queue.as_slices().0.try_into().unwrap(), + s.f32_queue.make_contiguous(), )) }}; }