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::*;
use std::{
collections::HashSet,
ffi::{c_double, c_float, c_void},
fmt::{self, Display, Formatter, Write},
marker::PhantomData,
mem, slice,
mem,
};
pub mod future;
@ -14,24 +17,18 @@ pub mod string;
#[doc(hidden)]
#[path = "./internal.rs"]
pub mod __internal;
const KEEP_FN: &str = "luaffi_keep";
const IS_UTF8_FN: &str = "luaffi_is_utf8";
pub mod result;
// 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
// something like `ffi.keep(obj)`.
// garbage-collected until the end of the function (used in string.rs when string marshalling is
// 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
pub const KEEP_FN: &str = "luaffi_keep";
#[unsafe(export_name = "luaffi_keep")]
extern "C" fn __keep(_ptr: *const c_void) {}
#[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];
export![__keep];
const CACHE_LIBS: &[(&str, &str)] = &[
("table", "table"),
@ -86,15 +83,15 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
("__sgsub", "string.gsub"),
("__sgmatch", "string.gmatch"),
("__sdump", "string.dump"),
// math
// math (used in luaify! macro)
("__fmod", "math.fmod"),
// coroutine
// coroutine (used in future.rs)
("__yield", "coroutine.yield"),
// package
("__preload", "package.preload"),
// debug
("__traceback", "debug.traceback"),
("__registry", "debug.getregistry()"),
("__registry", "debug.getregistry()"), // (used in lib.lua)
// ffi
("__C", "ffi.C"),
("__ct", "{}"),
@ -107,8 +104,8 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
("__gc", "ffi.gc"),
("__sizeof", "ffi.sizeof"),
("__alignof", "ffi.alignof"),
("__intern", "ffi.string"),
// bit
("__intern", "ffi.string"), // (used in string.rs)
// bit (used in luaify! macro)
("__bnot", "bit.bnot"),
("__band", "bit.band"),
("__bor", "bit.bor"),
@ -142,6 +139,7 @@ impl Registry {
let mut s = Self::default();
s.declare::<UnsafeExternCFn<(*const c_void,), ()>>(KEEP_FN);
s.declare::<UnsafeExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
s.declare::<UnsafeExternCFn<(*mut lua_buffer,), ()>>(DROP_BUFFER_FN);
s
}
@ -162,10 +160,10 @@ impl Registry {
pub fn preload<T: Type>(&mut self, name: impl Display) -> &mut Self {
self.include::<T>();
let ct = T::name();
writeln!(
self.lua,
r#"__preload["{name}"] = function(...) return __ct.{}(...); end;"#,
T::name()
r#"__preload["{name}"] = function(...) return __ct.{ct}(...); end;"#,
)
.unwrap();
self
@ -308,7 +306,7 @@ pub unsafe trait Metatype {
#[derive(Debug)]
pub struct MetatypeBuilder<'r> {
registry: &'r mut Registry,
name: String,
ct: String,
cdef: String,
lua: String,
}
@ -317,7 +315,7 @@ impl<'r> MetatypeBuilder<'r> {
fn new<T: Metatype>(registry: &'r mut Registry) -> Self {
Self {
registry,
name: T::Target::name().to_string(),
ct: T::Target::name().to_string(),
cdef: String::new(),
lua: r#"do local __mt, __idx = {}, {}; __mt.__index = __idx; "#.into(),
}
@ -365,7 +363,7 @@ impl<'r> Drop for MetatypeBuilder<'r> {
fn drop(&mut self) {
let Self {
registry,
name,
ct,
cdef,
lua,
..
@ -373,7 +371,7 @@ impl<'r> Drop for MetatypeBuilder<'r> {
registry.cdef.push_str(cdef);
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::{
__internal::{disp, display},
FfiReturnConvention, FromFfi, IS_UTF8_FN, IntoFfi, Type,
__internal::{disp, display, export},
FfiReturnConvention, FromFfi, IntoFfi,
};
use bstr::BStr;
use bstr::{BStr, BString};
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)]
#[cdef]
@ -14,20 +32,15 @@ pub struct lua_buf {
}
#[metatype]
impl lua_buf {}
// not implemented yet
#[derive(Debug, Clone, Copy)]
#[cdef]
pub struct lua_buffer {
__ptr: *const u8,
__len: usize,
__cap: usize,
impl lua_buf {
fn null() -> Self {
Self {
__ptr: ptr::null(),
__len: 0,
}
}
}
#[metatype]
impl lua_buffer {}
unsafe impl<'s> FromFfi for &'s [u8] {
type From = Option<&'s lua_buf>;
@ -38,12 +51,11 @@ unsafe impl<'s> FromFfi for &'s [u8] {
fn prelude(arg: &str) -> impl Display {
// this converts string arguments to a `lua_buf` with a pointer to the string and its length
disp(move |f| {
let ct = Self::From::name();
write!(
f,
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;
fn require_keepalive() -> bool {
<&'s [u8] as FromFfi>::require_keepalive()
<&[u8] as FromFfi>::require_keepalive()
}
fn prelude(arg: &str) -> impl Display {
<&'s [u8] as FromFfi>::prelude(arg)
<&[u8] as FromFfi>::prelude(arg)
}
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
// and ensures that the string is valid utf8
disp(move |f| {
let ct = Self::From::name();
write!(
f,
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,
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] {
type To = lua_buf;
@ -129,11 +168,11 @@ unsafe impl IntoFfi for &'static BStr {
type To = <&'static [u8] as IntoFfi>::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 {
<&'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;
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 {
<&'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);