diff --git a/crates/luaffi/src/lib.rs b/crates/luaffi/src/lib.rs index 9ab7360..1468c02 100644 --- a/crates/luaffi/src/lib.rs +++ b/crates/luaffi/src/lib.rs @@ -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::>(KEEP_FN); s.declare::>(IS_UTF8_FN); + s.declare::>(DROP_BUFFER_FN); s } @@ -162,10 +160,10 @@ impl Registry { pub fn preload(&mut self, name: impl Display) -> &mut Self { self.include::(); + 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(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(); } } diff --git a/crates/luaffi/src/string.rs b/crates/luaffi/src/string.rs index c07670e..f1ac7dc 100644 --- a/crates/luaffi/src/string.rs +++ b/crates/luaffi/src/string.rs @@ -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 { + 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 = as IntoFfi>::To; + + fn convert(self) -> Self::To { + as IntoFfi>::convert(self.into()) + } + + fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display { + as IntoFfi>::postlude(ret, conv) + } +} + +unsafe impl IntoFfi for String { + type To = as IntoFfi>::To; + + fn convert(self) -> Self::To { + as IntoFfi>::convert(self.into_bytes()) + } + + fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display { + 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); +impl_optional_into!(BString); +impl_optional_into!(String);