From 40478fb7de6e0ddb0b5814ef655cc41fef1c063a Mon Sep 17 00:00:00 2001 From: luaneko Date: Sun, 22 Jun 2025 18:41:19 +1000 Subject: [PATCH] Add ctype new support --- Cargo.lock | 7 -- crates/luaffi/src/internal.rs | 13 +++- crates/luaffi/src/lib.rs | 117 +++++++++++++++++------------ crates/luaffi/src/option.rs | 2 +- crates/luaffi/src/string.rs | 13 +--- crates/luaffi_impl/src/metatype.rs | 45 +++++++---- src/main.rs | 2 +- 7 files changed, 118 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6520d62..1de0b60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,7 +812,6 @@ dependencies = [ "luaify", "rustc-hash", "simdutf8", - "static_assertions", ] [[package]] @@ -1334,12 +1333,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" diff --git a/crates/luaffi/src/internal.rs b/crates/luaffi/src/internal.rs index b67242d..67a0823 100644 --- a/crates/luaffi/src/internal.rs +++ b/crates/luaffi/src/internal.rs @@ -12,11 +12,22 @@ pub fn type_id() -> u64 { hash.finish() } +macro_rules! export { + ($($fn:expr),+ $(,)?) => { + // this ensures ffi function symbol exports are actually present in the resulting binary, + // otherwise they may get dead code-eliminated before it reaches the linker + #[used] + static __FFI_EXPORTS: &[fn()] = unsafe { + &[$(::std::mem::transmute($fn as *const ())),*] + }; + }; +} + macro_rules! display { ($($fmt:expr),+) => {{ crate::__internal::disp(move |f| write!(f, $($fmt),+)) }}; } -pub(crate) use display; +pub(crate) use {display, export}; pub fn disp(f: impl Fn(&mut Formatter) -> fmt::Result) -> impl Display { struct Disp fmt::Result>(F); diff --git a/crates/luaffi/src/lib.rs b/crates/luaffi/src/lib.rs index 09d5ead..98ca943 100644 --- a/crates/luaffi/src/lib.rs +++ b/crates/luaffi/src/lib.rs @@ -1,4 +1,4 @@ -use crate::__internal::{disp, display, write_sep}; +use crate::__internal::{disp, display, export, write_sep}; pub use luaffi_impl::*; use std::{ collections::HashSet, @@ -31,6 +31,8 @@ unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool { simdutf8::basic::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }).is_ok() } +export![__keep, __is_utf8]; + const CACHE_LIBS: &[(&str, &str)] = &[ ("table", "table"), ("string", "string"), @@ -47,7 +49,7 @@ const CACHE_LIBS: &[(&str, &str)] = &[ ]; // https://www.lua.org/manual/5.1/manual.html#5.1 -const CACHE_GLOBALS: &[(&str, &str)] = &[ +const CACHE_LOCALS: &[(&str, &str)] = &[ // base ("assert", "assert"), ("error", "error"), @@ -86,14 +88,16 @@ const CACHE_GLOBALS: &[(&str, &str)] = &[ ("__fmod", "math.fmod"), // coroutine ("__yield", "coroutine.yield"), + // package + ("__preload", "package.preload"), // debug ("__traceback", "debug.traceback"), // ffi ("__C", "ffi.C"), + ("__ct", "{}"), ("__cdef", "ffi.cdef"), - ("__cnew", "ffi.new"), - ("__ctype", "ffi.typeof"), - ("__ctypes", "{}"), + ("__new", "ffi.new"), + ("__typeof", "ffi.typeof"), ("__istype", "ffi.istype"), ("__metatype", "ffi.metatype"), ("__cast", "ffi.cast"), @@ -117,7 +121,7 @@ const CACHE_GLOBALS: &[(&str, &str)] = &[ fn cache_local(f: &mut Formatter, list: &[(&str, &str)]) -> fmt::Result { write!(f, "local ")?; write_sep(f, ", ", list.iter().map(|(s, _)| s))?; - write!(f, "\n = ")?; + write!(f, " = ")?; write_sep(f, ", ", list.iter().map(|(_, s)| s))?; writeln!(f, ";") } @@ -141,7 +145,7 @@ impl Registry { pub fn include(&mut self) -> &mut Self { self.types .insert(T::name().to_string()) - .then(|| T::build(&mut TypeBuilder::new(self))); + .then(|| T::build(&mut TypeBuilder::new::(self))); self } @@ -149,7 +153,18 @@ impl Registry { self.include::() .funcs .insert(name.to_string()) - .then(|| writeln!(self.cdef, "{};", T::cdecl(name)).unwrap()); + .then(|| writeln!(self.cdef, "{};", T::extern_cdecl(name)).unwrap()); + self + } + + pub fn preload(&mut self, name: impl Display) -> &mut Self { + self.include::(); + writeln!( + self.lua, + r#"__preload["{name}"] = function(...) return __ct.{}(...); end;"#, + T::name() + ) + .unwrap(); self } @@ -164,8 +179,8 @@ impl Display for Registry { let version = env!("CARGO_PKG_VERSION"); writeln!(f, "--- automatically generated by {name} {version}")?; cache_local(f, CACHE_LIBS)?; - cache_local(f, CACHE_GLOBALS)?; - writeln!(f, "__cdef [[{}]];", self.cdef)?; + cache_local(f, CACHE_LOCALS)?; + writeln!(f, "__cdef [[\n{}\n]];", self.cdef.trim())?; write!(f, "{}", self.lua) } } @@ -173,6 +188,10 @@ impl Display for Registry { pub unsafe trait Type { fn name() -> impl Display; fn cdecl(name: impl Display) -> impl Display; + fn extern_cdecl(name: impl Display) -> impl Display { + Self::cdecl(name) + } + fn build(b: &mut TypeBuilder); } @@ -182,7 +201,10 @@ pub struct TypeBuilder<'r> { } impl<'r> TypeBuilder<'r> { - fn new(registry: &'r mut Registry) -> Self { + fn new(registry: &'r mut Registry) -> Self { + let ct = T::name(); + let cdecl = T::cdecl(""); + writeln!(registry.lua, r#"__ct.{ct} = __typeof("{cdecl}");"#).unwrap(); Self { registry } } @@ -283,7 +305,6 @@ pub unsafe trait Metatype { pub struct MetatypeBuilder<'r> { registry: &'r mut Registry, name: String, - cdecl: String, cdef: String, lua: String, } @@ -293,7 +314,6 @@ impl<'r> MetatypeBuilder<'r> { Self { registry, name: T::Target::name().to_string(), - cdecl: T::Target::cdecl("").to_string(), cdef: String::new(), lua: r#"do local __mt, __idx = {}, {}; __mt.__index = __idx; "#.into(), } @@ -325,7 +345,7 @@ impl<'r> MetatypeBuilder<'r> { name: impl Display, f: impl FnOnce(&mut MetatypeMethodBuilder), ) -> &mut Self { - write!(self.lua, "__mt.{name} = ").unwrap(); + write!(self.lua, "__mt.__{name} = ").unwrap(); f(&mut MetatypeMethodBuilder::new(self)); write!(self.lua, "; ").unwrap(); self @@ -342,7 +362,6 @@ impl<'r> Drop for MetatypeBuilder<'r> { let Self { registry, name, - cdecl, cdef, lua, .. @@ -350,12 +369,7 @@ impl<'r> Drop for MetatypeBuilder<'r> { registry.cdef.push_str(cdef); registry.lua.push_str(lua); - - writeln!( - registry.lua, - r#"__ctypes.{name} = __metatype("{cdecl}", __mt); end;"# - ) - .unwrap(); + writeln!(registry.lua, r#"__metatype(__ct.{name}, __mt); end;"#).unwrap(); } } @@ -414,8 +428,6 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> { } pub fn param(&mut self, name: impl Display) -> &mut Self { - self.metatype.registry.include::(); - (!self.params.is_empty()).then(|| self.params.push_str(", ")); (!self.args.is_empty()).then(|| self.args.push_str(", ")); write!(self.params, "{name}").unwrap(); @@ -450,6 +462,12 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> { self } + pub fn param_ignored(&mut self) -> &mut Self { + (!self.params.is_empty()).then(|| self.params.push_str(", ")); + write!(self.params, "_").unwrap(); + self + } + pub fn call(&mut self, func: impl Display, ret: FfiReturnConvention) { let Self { metatype, @@ -459,8 +477,6 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> { postlude, } = self; - metatype.registry.include::(); - let lua = &mut metatype.lua; write!(lua, "function({params}) {prelude}").unwrap(); @@ -469,21 +485,21 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> { write!(lua, "__C.{func}({args}); {postlude}end").unwrap(); } FfiReturnConvention::ByValue => { - let check = T::postlude("res", ret); + let check = T::postlude("__res", ret); write!( lua, - "local res = __C.{func}({args}); {check}{postlude}return res; end" + "local __res = __C.{func}({args}); {check}{postlude}return __res; end" ) .unwrap(); } FfiReturnConvention::ByOutParam => { let ct = T::To::name(); - let check = T::postlude("res", ret); - write!(lua, "local res = __cnew(__ctypes.{ct}); __C.{func}(res").unwrap(); + let check = T::postlude("__res", ret); + write!(lua, "local __res = __new(__ct.{ct}); __C.{func}(__res").unwrap(); if !args.is_empty() { write!(lua, ", {args}").unwrap(); } - write!(lua, "); {check}{postlude}return res; end").unwrap() + write!(lua, "); {check}{postlude}return __res; end").unwrap() } } } @@ -560,7 +576,7 @@ macro_rules! impl_copy_primitive { #[allow(unused)] fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display { - disp(move |f| { + disp(move |f| Ok({ match conv { FfiReturnConvention::Void => unreachable!(), FfiReturnConvention::ByValue => {}, @@ -568,9 +584,7 @@ macro_rules! impl_copy_primitive { // the cdata containing the value and convert it to the equivalent lua value FfiReturnConvention::ByOutParam => { $(write!(f, "{ret} = {}; ", $unwrap(ret))?;)? }, } - - Ok(()) - }) + })) } } }; @@ -592,7 +606,7 @@ impl_copy_primitive!(c_double, "double", "number", |n| display!("tonumber({n})") unsafe impl Type for *const T { fn name() -> impl Display { - display!("ptr_const_{}", T::name()) + display!("const_{}_ptr", T::name()) } fn cdecl(name: impl Display) -> impl Display { @@ -606,7 +620,7 @@ unsafe impl Type for *const T { unsafe impl Type for *mut T { fn name() -> impl Display { - display!("ptr_mut_{}", T::name()) + display!("{}_ptr", T::name()) } fn cdecl(name: impl Display) -> impl Display { @@ -684,7 +698,7 @@ unsafe impl ToFfi for *mut T { // unsafe impl Type for &T { fn name() -> impl Display { - display!("ref_const_{}", T::name()) + display!("const_{}_ptr", T::name()) } fn cdecl(name: impl Display) -> impl Display { @@ -698,7 +712,7 @@ unsafe impl Type for &T { unsafe impl Type for &mut T { fn name() -> impl Display { - display!("ref_mut_{}", T::name()) + display!("{}_ptr", T::name()) } fn cdecl(name: impl Display) -> impl Display { @@ -782,7 +796,7 @@ unsafe impl FromFfi for &mut T { // unsafe impl Type for [T] { fn name() -> impl Display { - display!("arr_{}", T::name()) + display!("{}_arr", T::name()) } fn cdecl(name: impl Display) -> impl Display { @@ -796,7 +810,7 @@ unsafe impl Type for [T] { unsafe impl Type for [T; N] { fn name() -> impl Display { - display!("arr{N}_{}", T::name()) + display!("{}_arr{N}", T::name()) } fn cdecl(name: impl Display) -> impl Display { @@ -824,20 +838,29 @@ macro_rules! impl_function { // unsafe impl<$($arg: Type,)* $ret: Type> Type for $($type)+ { fn name() -> impl Display { - disp(|f| { - write!(f, "fn")?; + disp(|f| Ok({ + write!(f, "fn_{}", $ret::name())?; $(write!(f, "_{}", $arg::name())?;)* - write!(f, "_{}", $ret::name()) - }) + })) } fn cdecl(name: impl Display) -> impl Display { - $ret::cdecl(disp(move |f| { + $ret::cdecl(disp(move |f| Ok({ let mut _n = 0; write!(f, "(*{name})(")?; $(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)* - write!(f, ")") - })) + write!(f, ")")?; + }))) + } + + fn extern_cdecl(name: impl Display) -> impl Display { + $ret::cdecl(disp(move |f| Ok({ + // for top-level function declarations in cdef + let mut _n = 0; + write!(f, "{name}(")?; + $(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)* + write!(f, ")")?; + }))) } fn build(b: &mut TypeBuilder) { diff --git a/crates/luaffi/src/option.rs b/crates/luaffi/src/option.rs index 6802e2f..dcaba52 100644 --- a/crates/luaffi/src/option.rs +++ b/crates/luaffi/src/option.rs @@ -39,7 +39,7 @@ unsafe impl FromFfi for Option { fn prelude(arg: &str) -> impl Display { let ct = Self::From::name(); display!( - "if {arg} == nil then {arg} = __cnew(__ctypes.{ct}); else {}{arg} = __cnew(__ctypes.{ct}, 1, {arg}); end; ", + "if {arg} == nil then {arg} = __new(__ct.{ct}); else {}{arg} = __new(__ct.{ct}, 1, {arg}); end; ", T::prelude(arg) ) } diff --git a/crates/luaffi/src/string.rs b/crates/luaffi/src/string.rs index a8de81a..ef0299d 100644 --- a/crates/luaffi/src/string.rs +++ b/crates/luaffi/src/string.rs @@ -5,17 +5,12 @@ use std::{fmt, ptr, slice}; #[derive(Debug, Clone, Copy)] #[cdef] pub struct lua_buf { - __ptr: *mut u8, + __ptr: *const u8, __len: usize, } #[metatype] -impl lua_buf { - #[new] - extern "Lua-C" fn new() -> u32 { - todo!() - } -} +impl lua_buf {} unsafe impl FromFfi for *const [u8] { type From = lua_buf; @@ -33,7 +28,7 @@ unsafe impl FromFfi for *const [u8] { f, r#"if {arg} ~= nil then assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "# )?; - write!(f, "{arg} = __cnew(__ctypes.{ct}, {arg}, #{arg}); end; ") + write!(f, "{arg} = __new(__ct.{ct}, {arg}, #{arg}); end; ") }) } @@ -69,7 +64,7 @@ unsafe impl FromFfi for &str { f, r#"assert(__C.{IS_UTF8_FN}({arg}, #{arg}), "argument '{arg}' must be a valid utf-8 string"); "# )?; - write!(f, "{arg} = __cnew(__ctypes.{ct}, {arg}, #{arg}); ") + write!(f, "{arg} = __new(__ct.{ct}, {arg}, #{arg}); ") }) } diff --git a/crates/luaffi_impl/src/metatype.rs b/crates/luaffi_impl/src/metatype.rs index bfbfb8d..95d4e48 100644 --- a/crates/luaffi_impl/src/metatype.rs +++ b/crates/luaffi_impl/src/metatype.rs @@ -44,6 +44,17 @@ fn generate_impls(imp: &mut ItemImpl) -> Result { .map(generate_ffi_register) .collect::>()?; + let ffi_new_fallback = match ffi_funcs + .iter() + .find(|f| f.attrs.metatable.as_deref() == Some("new")) + { + Some(_) => None, + None => Some({ + let err = format!(r#"function() error("type '{ty_name}' has no constructor"); end"#); + quote! { b.metatable_raw("new", #err); } + }), + }; + let ffi_drop_rname = format_ident!("__ffi_drop"); let ffi_drop_cname = format!("{ty_name}_drop"); @@ -78,6 +89,8 @@ fn generate_impls(imp: &mut ItemImpl) -> Result { #(#ffi_register)* #(#lua_register)* + #ffi_new_fallback + b.declare::(#ffi_drop_cname); b.metatable_raw("gc", ::std::format_args!("__C.{}", #ffi_drop_cname)); } @@ -100,7 +113,7 @@ struct FfiFunction { #[derive(Default)] struct FfiFunctionAttrs { - metatable: Option, + metatable: Option, } fn get_ffi_functions(imp: &mut ItemImpl) -> Result> { @@ -161,11 +174,11 @@ fn parse_ffi_function_attrs(attrs: &mut Vec) -> Result()?.value()); attrs.remove(i); continue; } else if name == "new" { - parsed.metatable = parse_quote!("new"); + parsed.metatable = Some("new".into()); attrs.remove(i); continue; } @@ -194,7 +207,7 @@ fn generate_ffi_wrapper(func: &FfiFunction) -> Result { let mut args = vec![]; for (i, param) in func.params.iter().enumerate() { - let name = format_ident!("__arg{i}"); + let name = format_ident!("arg{i}"); let ty = ¶m.ty; match get_ffi_arg_type(ty) { @@ -208,10 +221,10 @@ fn generate_ffi_wrapper(func: &FfiFunction) -> Result { let (ret, call) = if func.ret_by_out { // make return by out-param the first parameter let ret = &func.ret; - params.insert(0, quote! { __out: *mut #ret }); + params.insert(0, quote! { out: *mut #ret }); ( quote!(()), - quote! { ::std::ptr::write(__out, Self::#name(#(#args),*)) }, + quote! { ::std::ptr::write(out, Self::#name(#(#args),*)) }, ) } else { let ret = &func.ret; @@ -220,9 +233,7 @@ fn generate_ffi_wrapper(func: &FfiFunction) -> Result { Ok(quote! { #[unsafe(export_name = #c_name)] - unsafe extern "C" fn #rust_name(#(#params),*) -> #ret { - unsafe { #call } - } + unsafe extern "C" fn #rust_name(#(#params),*) -> #ret { unsafe { #call } } }) } @@ -234,6 +245,11 @@ fn generate_ffi_register(func: &FfiFunction) -> Result { let mut params = vec![]; let mut register = vec![]; + // for __new metamethods, ignore the first argument (ctype of self) + if func.attrs.metatable.as_deref() == Some("new") { + register.push(quote! { b.param_ignored(); }); + } + for param in func.params.iter() { let name = format!("{}", pat_ident(¶m.pat)?); let ty = ¶m.ty; @@ -255,8 +271,10 @@ fn generate_ffi_register(func: &FfiFunction) -> Result { quote! { #ffi::FfiReturnConvention::ByValue } }; - let declare = quote! { - b.declare:: #ret>(#c_name); + let declare = if func.ret_by_out { + quote! { b.declare::(#c_name); } + } else { + quote! { b.declare:: #ret>(#c_name); } }; let register = match func.attrs.metatable { @@ -274,10 +292,7 @@ fn generate_ffi_register(func: &FfiFunction) -> Result { }, }; - Ok(quote! { - #declare - #register - }) + Ok(quote! { #declare #register }) } fn generate_ffi_exports<'a>( diff --git a/src/main.rs b/src/main.rs index 267f729..0318ad1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -136,7 +136,7 @@ fn init_vm(_args: &Args) -> luajit::State { luajit::State::new().unwrap_or_else(|err| panic!("failed to initialise runtime: {err}")); let mut registry = luaffi::Registry::new(); - registry.include::(); + registry.preload::("lb:core"); println!("{registry}");