Implement IntoFfi for Strings

This commit is contained in:
lumi 2025-06-24 13:03:40 +10:00
parent 45db380466
commit f8e7b8ae62
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
2 changed files with 196 additions and 52 deletions

View File

@ -1,11 +1,14 @@
use crate::__internal::{disp, display, export, write_sep}; use crate::{
__internal::{disp, display, export, write_sep},
string::{DROP_BUFFER_FN, IS_UTF8_FN, lua_buffer},
};
pub use luaffi_impl::*; pub use luaffi_impl::*;
use std::{ use std::{
collections::HashSet, collections::HashSet,
ffi::{c_double, c_float, c_void}, ffi::{c_double, c_float, c_void},
fmt::{self, Display, Formatter, Write}, fmt::{self, Display, Formatter, Write},
marker::PhantomData, marker::PhantomData,
mem, slice, mem,
}; };
pub mod future; pub mod future;
@ -14,24 +17,18 @@ pub mod string;
#[doc(hidden)] #[doc(hidden)]
#[path = "./internal.rs"] #[path = "./internal.rs"]
pub mod __internal; pub mod __internal;
pub mod result;
const KEEP_FN: &str = "luaffi_keep";
const IS_UTF8_FN: &str = "luaffi_is_utf8";
// Dummy function to ensure that strings passed to Rust via wrapper objects will not be // Dummy function to ensure that strings passed to Rust via wrapper objects will not be
// garbage-collected until the end of the function. This shall exist until LuaJIT one day implements // garbage-collected until the end of the function (used in string.rs when string marshalling is
// something like `ffi.keep(obj)`. // going through the slow-path). This shall exist until LuaJIT one day implements something like
// `ffi.keep(obj)`.
// //
// https://github.com/LuaJIT/LuaJIT/issues/1167 // https://github.com/LuaJIT/LuaJIT/issues/1167
pub const KEEP_FN: &str = "luaffi_keep";
#[unsafe(export_name = "luaffi_keep")] #[unsafe(export_name = "luaffi_keep")]
extern "C" fn __keep(_ptr: *const c_void) {} extern "C" fn __keep(_ptr: *const c_void) {}
export![__keep];
#[unsafe(export_name = "luaffi_is_utf8")]
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)] = &[ const CACHE_LIBS: &[(&str, &str)] = &[
("table", "table"), ("table", "table"),
@ -86,15 +83,15 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
("__sgsub", "string.gsub"), ("__sgsub", "string.gsub"),
("__sgmatch", "string.gmatch"), ("__sgmatch", "string.gmatch"),
("__sdump", "string.dump"), ("__sdump", "string.dump"),
// math // math (used in luaify! macro)
("__fmod", "math.fmod"), ("__fmod", "math.fmod"),
// coroutine // coroutine (used in future.rs)
("__yield", "coroutine.yield"), ("__yield", "coroutine.yield"),
// package // package
("__preload", "package.preload"), ("__preload", "package.preload"),
// debug // debug
("__traceback", "debug.traceback"), ("__traceback", "debug.traceback"),
("__registry", "debug.getregistry()"), ("__registry", "debug.getregistry()"), // (used in lib.lua)
// ffi // ffi
("__C", "ffi.C"), ("__C", "ffi.C"),
("__ct", "{}"), ("__ct", "{}"),
@ -107,8 +104,8 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
("__gc", "ffi.gc"), ("__gc", "ffi.gc"),
("__sizeof", "ffi.sizeof"), ("__sizeof", "ffi.sizeof"),
("__alignof", "ffi.alignof"), ("__alignof", "ffi.alignof"),
("__intern", "ffi.string"), ("__intern", "ffi.string"), // (used in string.rs)
// bit // bit (used in luaify! macro)
("__bnot", "bit.bnot"), ("__bnot", "bit.bnot"),
("__band", "bit.band"), ("__band", "bit.band"),
("__bor", "bit.bor"), ("__bor", "bit.bor"),
@ -142,6 +139,7 @@ impl Registry {
let mut s = Self::default(); let mut s = Self::default();
s.declare::<UnsafeExternCFn<(*const c_void,), ()>>(KEEP_FN); s.declare::<UnsafeExternCFn<(*const c_void,), ()>>(KEEP_FN);
s.declare::<UnsafeExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN); s.declare::<UnsafeExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
s.declare::<UnsafeExternCFn<(*mut lua_buffer,), ()>>(DROP_BUFFER_FN);
s s
} }
@ -162,10 +160,10 @@ impl Registry {
pub fn preload<T: Type>(&mut self, name: impl Display) -> &mut Self { pub fn preload<T: Type>(&mut self, name: impl Display) -> &mut Self {
self.include::<T>(); self.include::<T>();
let ct = T::name();
writeln!( writeln!(
self.lua, self.lua,
r#"__preload["{name}"] = function(...) return __ct.{}(...); end;"#, r#"__preload["{name}"] = function(...) return __ct.{ct}(...); end;"#,
T::name()
) )
.unwrap(); .unwrap();
self self
@ -308,7 +306,7 @@ pub unsafe trait Metatype {
#[derive(Debug)] #[derive(Debug)]
pub struct MetatypeBuilder<'r> { pub struct MetatypeBuilder<'r> {
registry: &'r mut Registry, registry: &'r mut Registry,
name: String, ct: String,
cdef: String, cdef: String,
lua: String, lua: String,
} }
@ -317,7 +315,7 @@ impl<'r> MetatypeBuilder<'r> {
fn new<T: Metatype>(registry: &'r mut Registry) -> Self { fn new<T: Metatype>(registry: &'r mut Registry) -> Self {
Self { Self {
registry, registry,
name: T::Target::name().to_string(), ct: T::Target::name().to_string(),
cdef: String::new(), cdef: String::new(),
lua: r#"do local __mt, __idx = {}, {}; __mt.__index = __idx; "#.into(), lua: r#"do local __mt, __idx = {}, {}; __mt.__index = __idx; "#.into(),
} }
@ -365,7 +363,7 @@ impl<'r> Drop for MetatypeBuilder<'r> {
fn drop(&mut self) { fn drop(&mut self) {
let Self { let Self {
registry, registry,
name, ct,
cdef, cdef,
lua, lua,
.. ..
@ -373,7 +371,7 @@ impl<'r> Drop for MetatypeBuilder<'r> {
registry.cdef.push_str(cdef); registry.cdef.push_str(cdef);
registry.lua.push_str(lua); registry.lua.push_str(lua);
writeln!(registry.lua, r#"__metatype(__ct.{name}, __mt); end;"#).unwrap(); writeln!(registry.lua, r#"__metatype(__ct.{ct}, __mt); end;"#).unwrap();
} }
} }

View File

@ -1,10 +1,28 @@
use crate::{ use crate::{
__internal::{disp, display}, __internal::{disp, display, export},
FfiReturnConvention, FromFfi, IS_UTF8_FN, IntoFfi, Type, FfiReturnConvention, FromFfi, IntoFfi,
}; };
use bstr::BStr; use bstr::{BStr, BString};
use luaffi_impl::{cdef, metatype}; use luaffi_impl::{cdef, metatype};
use std::{fmt::Display, slice}; use std::{fmt::Display, mem::ManuallyDrop, ptr, slice};
pub const IS_UTF8_FN: &str = "luaffi_is_utf8";
pub const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
#[unsafe(export_name = "luaffi_is_utf8")]
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
debug_assert!(!ptr.is_null());
simdutf8::basic::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }).is_ok()
}
#[unsafe(export_name = "luaffi_drop_buffer")]
unsafe extern "C" fn __drop_buffer(buf: *mut lua_buffer) {
debug_assert!(!buf.is_null());
debug_assert!(!unsafe { (*buf).__ptr.is_null() });
drop(unsafe { Vec::from_raw_parts((*buf).__ptr, (*buf).__len, (*buf).__cap) })
}
export![__is_utf8, __drop_buffer];
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[cdef] #[cdef]
@ -14,20 +32,15 @@ pub struct lua_buf {
} }
#[metatype] #[metatype]
impl lua_buf {} impl lua_buf {
fn null() -> Self {
// not implemented yet Self {
#[derive(Debug, Clone, Copy)] __ptr: ptr::null(),
#[cdef] __len: 0,
pub struct lua_buffer { }
__ptr: *const u8, }
__len: usize,
__cap: usize,
} }
#[metatype]
impl lua_buffer {}
unsafe impl<'s> FromFfi for &'s [u8] { unsafe impl<'s> FromFfi for &'s [u8] {
type From = Option<&'s lua_buf>; type From = Option<&'s lua_buf>;
@ -38,12 +51,11 @@ unsafe impl<'s> FromFfi for &'s [u8] {
fn prelude(arg: &str) -> impl Display { fn prelude(arg: &str) -> impl Display {
// this converts string arguments to a `lua_buf` with a pointer to the string and its length // this converts string arguments to a `lua_buf` with a pointer to the string and its length
disp(move |f| { disp(move |f| {
let ct = Self::From::name();
write!( write!(
f, f,
r#"assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "# r#"assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "#
)?; )?;
write!(f, "{arg} = __new(__ct.{ct}, {arg}, #{arg}); end; ") write!(f, "{arg} = __new(__ct.lua_buf, {arg}, #{arg}); end; ")
}) })
} }
@ -60,15 +72,15 @@ unsafe impl<'s> FromFfi for &'s BStr {
type From = <&'s [u8] as FromFfi>::From; type From = <&'s [u8] as FromFfi>::From;
fn require_keepalive() -> bool { fn require_keepalive() -> bool {
<&'s [u8] as FromFfi>::require_keepalive() <&[u8] as FromFfi>::require_keepalive()
} }
fn prelude(arg: &str) -> impl Display { fn prelude(arg: &str) -> impl Display {
<&'s [u8] as FromFfi>::prelude(arg) <&[u8] as FromFfi>::prelude(arg)
} }
fn convert(from: Self::From) -> Self { fn convert(from: Self::From) -> Self {
<&'s [u8] as FromFfi>::convert(from).into() <&[u8] as FromFfi>::convert(from).into()
} }
} }
@ -83,7 +95,6 @@ unsafe impl<'s> FromFfi for &'s str {
// this converts string arguments to a `lua_buf` with a pointer to the string and its length // this converts string arguments to a `lua_buf` with a pointer to the string and its length
// and ensures that the string is valid utf8 // and ensures that the string is valid utf8
disp(move |f| { disp(move |f| {
let ct = Self::From::name();
write!( write!(
f, f,
r#"assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "# r#"assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "#
@ -92,7 +103,7 @@ unsafe impl<'s> FromFfi for &'s str {
f, f,
r#"assert(__C.{IS_UTF8_FN}({arg}, #{arg}), "argument '{arg}' must be a valid utf-8 string"); "# r#"assert(__C.{IS_UTF8_FN}({arg}, #{arg}), "argument '{arg}' must be a valid utf-8 string"); "#
)?; )?;
write!(f, "{arg} = __new(__ct.{ct}, {arg}, #{arg}); ") write!(f, "{arg} = __new(__ct.lua_buf, {arg}, #{arg}); ")
}) })
} }
@ -110,6 +121,34 @@ unsafe impl<'s> FromFfi for &'s str {
} }
} }
macro_rules! impl_optional_ref_from {
($ty:ty) => {
unsafe impl<'s> FromFfi for Option<$ty> {
type From = <$ty as FromFfi>::From;
fn require_keepalive() -> bool {
<$ty as FromFfi>::require_keepalive()
}
fn prelude(arg: &str) -> impl Display {
// avoid constructing a `lua_buf` at all for nil arguments
display!(
"if {arg} ~= nil then {}end; ",
<$ty as FromFfi>::prelude(arg)
)
}
fn convert(from: Self::From) -> Self {
from.map(|s| <$ty as FromFfi>::convert(Some(s)))
}
}
};
}
impl_optional_ref_from!(&'s [u8]);
impl_optional_ref_from!(&'s BStr);
impl_optional_ref_from!(&'s str);
unsafe impl IntoFfi for &'static [u8] { unsafe impl IntoFfi for &'static [u8] {
type To = lua_buf; type To = lua_buf;
@ -129,11 +168,11 @@ unsafe impl IntoFfi for &'static BStr {
type To = <&'static [u8] as IntoFfi>::To; type To = <&'static [u8] as IntoFfi>::To;
fn convert(self) -> Self::To { fn convert(self) -> Self::To {
<&'static [u8] as IntoFfi>::convert(self.as_ref()) <&[u8] as IntoFfi>::convert(self.as_ref())
} }
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display { fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
<&'static [u8] as IntoFfi>::postlude(ret, conv) <&[u8] as IntoFfi>::postlude(ret, conv)
} }
} }
@ -141,10 +180,117 @@ unsafe impl IntoFfi for &'static str {
type To = <&'static [u8] as IntoFfi>::To; type To = <&'static [u8] as IntoFfi>::To;
fn convert(self) -> Self::To { fn convert(self) -> Self::To {
<&'static [u8] as IntoFfi>::convert(self.as_bytes()) <&[u8] as IntoFfi>::convert(self.as_bytes())
} }
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display { fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
<&'static [u8] as IntoFfi>::postlude(ret, conv) <&[u8] as IntoFfi>::postlude(ret, conv)
} }
} }
macro_rules! impl_optional_ref_into {
($ty:ty) => {
unsafe impl IntoFfi for Option<$ty> {
type To = <$ty as IntoFfi>::To;
fn convert(self) -> Self::To {
self.map_or(lua_buf::null(), <$ty as IntoFfi>::convert)
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
display!(
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
<$ty as IntoFfi>::postlude(ret, conv)
)
}
}
};
}
impl_optional_ref_into!(&'static [u8]);
impl_optional_ref_into!(&'static BStr);
impl_optional_ref_into!(&'static str);
#[derive(Debug, Clone, Copy)]
#[cdef]
pub struct lua_buffer {
__ptr: *mut u8,
__len: usize,
__cap: usize,
}
#[metatype]
impl lua_buffer {
fn null() -> Self {
Self {
__ptr: ptr::null_mut(),
__len: 0,
__cap: 0,
}
}
}
unsafe impl IntoFfi for Vec<u8> {
type To = lua_buffer;
fn convert(self) -> Self::To {
lua_buffer {
__cap: self.capacity(),
__len: self.len(),
__ptr: ManuallyDrop::new(self).as_mut_ptr(),
}
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
display!(
"do local __{ret} = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}(__{ret}); end; "
)
}
}
unsafe impl IntoFfi for BString {
type To = <Vec<u8> as IntoFfi>::To;
fn convert(self) -> Self::To {
<Vec<u8> as IntoFfi>::convert(self.into())
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
<Vec<u8> as IntoFfi>::postlude(ret, conv)
}
}
unsafe impl IntoFfi for String {
type To = <Vec<u8> as IntoFfi>::To;
fn convert(self) -> Self::To {
<Vec<u8> as IntoFfi>::convert(self.into_bytes())
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
<Vec<u8> as IntoFfi>::postlude(ret, conv)
}
}
macro_rules! impl_optional_into {
($ty:ty) => {
unsafe impl IntoFfi for Option<$ty> {
type To = <$ty as IntoFfi>::To;
fn convert(self) -> Self::To {
self.map_or(lua_buffer::null(), <$ty as IntoFfi>::convert)
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
display!(
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
<$ty as IntoFfi>::postlude(ret, conv)
)
}
}
};
}
impl_optional_into!(Vec<u8>);
impl_optional_into!(BString);
impl_optional_into!(String);