Add ctype new support

This commit is contained in:
lumi 2025-06-22 18:41:19 +10:00
parent 0fd59f6874
commit 40478fb7de
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
7 changed files with 118 additions and 81 deletions

7
Cargo.lock generated
View File

@ -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"

View File

@ -12,11 +12,22 @@ pub fn type_id<T: 'static>() -> 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<F: Fn(&mut Formatter) -> fmt::Result>(F);

View File

@ -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<T: Type>(&mut self) -> &mut Self {
self.types
.insert(T::name().to_string())
.then(|| T::build(&mut TypeBuilder::new(self)));
.then(|| T::build(&mut TypeBuilder::new::<T>(self)));
self
}
@ -149,7 +153,18 @@ impl Registry {
self.include::<T>()
.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<T: Type>(&mut self, name: impl Display) -> &mut Self {
self.include::<T>();
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<T: Type>(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<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
self.metatype.registry.include::<T::From>();
(!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<T: ToFfi>(&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::<T::To>();
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<T: Type> 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<T: Type> Type for *const T {
unsafe impl<T: Type> 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<T: Type> ToFfi for *mut T {
//
unsafe impl<T: Type> 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<T: Type> Type for &T {
unsafe impl<T: Type> 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<T: Type> FromFfi for &mut T {
//
unsafe impl<T: Type> 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<T: Type> Type for [T] {
unsafe impl<T: Type, const N: usize> 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) {

View File

@ -39,7 +39,7 @@ unsafe impl<T: FromFfi> FromFfi for Option<T> {
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)
)
}

View File

@ -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}); ")
})
}

View File

@ -44,6 +44,17 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
.map(generate_ffi_register)
.collect::<Result<_>>()?;
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<TokenStream> {
#(#ffi_register)*
#(#lua_register)*
#ffi_new_fallback
b.declare::<unsafe extern "C" fn(*mut Self)>(#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<LitStr>,
metatable: Option<String>,
}
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
@ -161,11 +174,11 @@ fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAtt
while let Some(attr) = attrs.get(i) {
if let Some(name) = attr.path().get_ident() {
if name == "metatable" {
parsed.metatable = attr.parse_args()?;
parsed.metatable = Some(attr.parse_args::<LitStr>()?.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<TokenStream> {
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 = &param.ty;
match get_ffi_arg_type(ty) {
@ -208,10 +221,10 @@ fn generate_ffi_wrapper(func: &FfiFunction) -> Result<TokenStream> {
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<TokenStream> {
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<TokenStream> {
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(&param.pat)?);
let ty = &param.ty;
@ -255,8 +271,10 @@ fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
quote! { #ffi::FfiReturnConvention::ByValue }
};
let declare = quote! {
b.declare::<unsafe extern "C" fn(#(#params),*) -> #ret>(#c_name);
let declare = if func.ret_by_out {
quote! { b.declare::<unsafe extern "C" fn(*mut #ret, #(#params),*)>(#c_name); }
} else {
quote! { b.declare::<unsafe extern "C" fn(#(#params),*) -> #ret>(#c_name); }
};
let register = match func.attrs.metatable {
@ -274,10 +292,7 @@ fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
},
};
Ok(quote! {
#declare
#register
})
Ok(quote! { #declare #register })
}
fn generate_ffi_exports<'a>(

View File

@ -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::<lb_core::lb_core>();
registry.preload::<lb_core::lb_core>("lb:core");
println!("{registry}");