1137 lines
31 KiB
Rust

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,
};
#[doc(hidden)]
#[path = "./internal.rs"]
pub mod __internal;
pub mod future;
pub mod marker;
pub mod option;
pub mod result;
pub mod string;
// Dummy function to ensure that strings passed to Rust via wrapper objects will not be
// 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(crate) const KEEP_FN: &str = "luaffi_keep";
#[unsafe(export_name = "luaffi_keep")]
extern "C" fn __keep(_ptr: *const c_void) {}
export![__keep];
const CACHE_LIBS: &[(&str, &str)] = &[
("table", "table"),
("string", "string"),
("math", "math"),
("coroutine", "coroutine"),
("package", "package"),
("debug", "debug"),
("jit", "jit"),
// requires
("bit", r#"require("bit")"#),
("ffi", r#"require("ffi")"#),
("__tnew", r#"require("table.new")"#),
("__tclear", r#"require("table.clear")"#),
];
// https://www.lua.org/manual/5.1/manual.html#5.1
const CACHE_LOCALS: &[(&str, &str)] = &[
// baselib
("assert", "assert"),
("error", "error"),
("type", "type"),
("print", "print"),
("pcall", "pcall"),
("xpcall", "xpcall"),
("getfenv", "getfenv"),
("setfenv", "setfenv"),
("getmetatable", "getmetatable"),
("setmetatable", "setmetatable"),
("pairs", "pairs"),
("ipairs", "ipairs"),
("next", "next"),
("rawget", "rawget"),
("rawset", "rawset"),
("rawequal", "rawequal"),
("select", "select"),
("tonumber", "tonumber"),
("tostring", "tostring"),
("require", "require"),
("__yield", "coroutine.yield"), // (used in future.rs)
// tablib
("__tconcat", "table.concat"),
("__tinsert", "table.insert"),
("__tmaxn", "table.maxn"),
("__tremove", "table.remove"),
("__tsort", "table.sort"),
("__tpack", "table.pack"),
("__tunpack", "table.unpack"),
// strlib
("__slen", "string.len"),
("__sprintf", "string.format"),
("__ssub", "string.sub"),
("__sgsub", "string.gsub"),
("__sgmatch", "string.gmatch"),
("__sdump", "string.dump"),
// mathlib (used in luaify! macro)
("__fmod", "math.fmod"),
// loadlib
("__preload", "package.preload"),
// dblib
("__traceback", "debug.traceback"),
("__registry", "debug.getregistry()"), // (used in lib.lua)
// ffilib
("__C", "ffi.C"),
("__ct", "{}"),
("__cdef", "ffi.cdef"),
("__new", "ffi.new"),
("__typeof", "ffi.typeof"),
("__istype", "ffi.istype"),
("__metatype", "ffi.metatype"),
("__cast", "ffi.cast"),
("__gc", "ffi.gc"),
("__sizeof", "ffi.sizeof"),
("__alignof", "ffi.alignof"),
("__intern", "ffi.string"), // (used in string.rs)
// bitlib (used in luaify! macro)
("__bnot", "bit.bnot"),
("__band", "bit.band"),
("__bor", "bit.bor"),
("__bxor", "bit.bxor"),
("__blshift", "bit.lshift"),
("__brshift", "bit.rshift"),
("__barshift", "bit.arshift"),
("__brol", "bit.rol"),
("__bror", "bit.ror"),
("__bswap", "bit.bswap"),
];
fn cache_local(f: &mut Formatter, list: &[(&str, &str)]) -> fmt::Result {
write!(f, "local ")?;
write_sep(f, ", ", list.iter().map(|(s, _)| s))?;
write!(f, " = ")?;
write_sep(f, ", ", list.iter().map(|(_, s)| s))?;
writeln!(f, ";")
}
#[derive(Debug, Clone, Default)]
pub struct Registry {
types: HashSet<String>,
decls: HashSet<String>,
cdef: String,
lua: String,
}
impl Registry {
pub fn new() -> Self {
let mut s = Self::default();
s.declare::<ExternCFn<(*const c_void,), ()>>(KEEP_FN);
s.declare::<ExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
s.declare::<ExternCFn<(*mut lua_buffer,), ()>>(DROP_BUFFER_FN);
s
}
pub fn include<T: Type>(&mut self) -> &mut Self {
self.types
.insert(T::name().to_string())
.then(|| T::build(&mut TypeBuilder::new(self)));
self
}
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
assert!(T::ty() != TypeType::Void, "cannot declare void type");
self.include::<T>()
.decls
.insert(T::extern_cdecl(&name).to_string())
.then(|| writeln!(self.cdef, "{};", T::extern_cdecl(&name)).unwrap());
self
}
pub fn preload<T: Module>(&mut self) -> &mut Self {
assert!(T::ty() != TypeType::Void, "cannot declare void type");
let name = <T as Module>::name();
let ct = <T as Type>::name();
writeln!(
self.lua,
r#"__preload["{name}"] = function(...) return __ct.{ct}(...); end;"#,
)
.unwrap();
self.include::<T>()
}
pub fn build(&self) -> String {
self.to_string()
}
}
impl Display for Registry {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
writeln!(f, "--- automatically generated by {name} {version}")?;
cache_local(f, CACHE_LIBS)?;
cache_local(f, CACHE_LOCALS)?;
writeln!(f, "{}", include_str!("./lib.lua"))?;
writeln!(f, "__cdef [[\n{}\n]];", self.cdef.trim_end())?;
write!(f, "{}", self.lua)
}
}
pub unsafe trait Type {
fn name() -> impl Display;
fn ty() -> TypeType;
fn cdecl(name: impl Display) -> impl Display;
fn extern_cdecl(name: impl Display) -> impl Display {
Self::cdecl(name)
}
fn build(b: &mut TypeBuilder);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TypeType {
Void,
Primitive,
Aggregate,
}
#[derive(Debug)]
pub struct TypeBuilder<'r> {
reg: &'r mut Registry,
}
impl<'r> TypeBuilder<'r> {
fn new(reg: &'r mut Registry) -> Self {
Self { reg }
}
pub fn cdef<T: Cdef>(&mut self) -> &mut Self {
let mut b = CdefBuilder::new::<T>(self.reg);
<T as Cdef>::build(&mut b);
drop(b);
self
}
pub fn metatype<T: Metatype>(&mut self) -> &mut Self {
let mut b = MetatypeBuilder::new::<T>(self.reg);
<T as Metatype>::build(&mut b);
drop(b);
self
}
}
pub trait Module: Type {
fn name() -> impl Display;
}
pub unsafe trait Cdef: Type {
fn build(b: &mut CdefBuilder);
}
#[derive(Debug)]
pub struct CdefBuilder<'r> {
reg: &'r mut Registry,
cdef: String,
align: usize,
opaque: usize,
}
impl<'r> CdefBuilder<'r> {
fn new<T: Cdef>(reg: &'r mut Registry) -> Self {
Self {
reg,
cdef: format!("{} {{ ", T::cdecl("")),
align: mem::align_of::<T>(),
opaque: 0,
}
}
pub fn field<T: Type>(&mut self, name: impl Display) -> &mut Self {
assert!(T::ty() != TypeType::Void, "cannot declare void field");
self.reg.include::<T>();
self.field_raw(T::cdecl(name))
}
pub fn field_opaque(&mut self, size: usize) -> &mut Self {
let i = self.opaque;
self.field_raw(format_args!("uint8_t __opaque{i}[{size}]"));
self.opaque += 1;
self
}
pub fn field_raw(&mut self, cdecl: impl Display) -> &mut Self {
write!(self.cdef, "{cdecl}; ").unwrap();
self
}
pub fn inner_struct(&mut self, f: impl FnOnce(&mut CdefBuilder)) -> &mut Self {
self.cdef.push_str("struct { ");
f(self);
self.cdef.push_str("}; ");
self
}
pub fn inner_union(&mut self, f: impl FnOnce(&mut CdefBuilder)) -> &mut Self {
self.cdef.push_str("union { ");
f(self);
self.cdef.push_str("}; ");
self
}
}
impl<'r> Drop for CdefBuilder<'r> {
fn drop(&mut self) {
let Self {
reg, cdef, align, ..
} = self;
reg.cdef.push_str(cdef);
writeln!(reg.cdef, "}} __attribute__((aligned({align})));").unwrap();
}
}
pub unsafe trait Metatype {
type Target: Cdef;
fn build(b: &mut MetatypeBuilder);
}
#[derive(Debug)]
pub struct MetatypeBuilder<'r> {
reg: &'r mut Registry,
ct: String,
cdef: String,
lua: String,
lua_includes: Vec<&'static str>,
has_index: bool,
}
impl<'r> MetatypeBuilder<'r> {
fn new<T: Metatype>(reg: &'r mut Registry) -> Self {
// NOTE: this needs to be written first, because recursively included dependency types might
// need it
let ct = T::Target::name();
let cdecl = T::Target::cdecl("");
writeln!(reg.lua, r#"__ct.{ct} = __typeof("{cdecl}");"#).unwrap();
Self {
reg,
ct: T::Target::name().to_string(),
cdef: String::new(),
lua: String::new(),
lua_includes: vec![],
has_index: false,
}
}
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
self.reg.declare::<T>(name);
self
}
pub fn include_lua(&mut self, lua: &'static str) -> &mut Self {
self.lua_includes.push(lua);
self
}
pub fn index(
&mut self,
name: impl Display,
f: impl FnOnce(&mut MetatypeFunctionBuilder),
) -> &mut Self {
write!(self.lua, "__idx.{name} = ").unwrap();
f(&mut MetatypeFunctionBuilder::new(self));
writeln!(self.lua, ";").unwrap();
self.has_index = true;
self
}
pub fn index_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
writeln!(self.lua, "__idx.{name} = {value};").unwrap();
self.has_index = true;
self
}
pub fn metatable(
&mut self,
name: impl Display,
f: impl FnOnce(&mut MetatypeFunctionBuilder),
) -> &mut Self {
write!(self.lua, "__mt.__{name} = ").unwrap();
f(&mut MetatypeFunctionBuilder::new(self));
writeln!(self.lua, ";").unwrap();
self
}
pub fn metatable_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
writeln!(self.lua, "__mt.__{name} = {value};").unwrap();
self
}
}
impl<'r> Drop for MetatypeBuilder<'r> {
fn drop(&mut self) {
let Self {
reg,
ct,
cdef,
lua,
lua_includes,
has_index,
} = self;
write!(reg.lua, r#"do local __mt = {{}}; "#).unwrap();
if *has_index {
write!(reg.lua, r#"local __idx = {{}}; __mt.__index = __idx; "#).unwrap();
}
reg.cdef.push_str(cdef);
reg.lua.push_str(lua.trim_end());
writeln!(reg.lua, r#"__metatype(__ct.{ct}, __mt); end;"#).unwrap();
for lua in lua_includes {
writeln!(reg.lua, "do {}\nend;", lua.trim_end()).unwrap();
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FfiReturnConvention {
ByValue,
ByOutParam,
}
pub unsafe trait FromFfi: Sized {
type From: Type + Sized;
fn require_keepalive() -> bool {
false
}
fn prelude(_arg: &str) -> impl Display {
""
}
fn convert(from: Self::From) -> Self;
}
pub unsafe trait IntoFfi: Sized {
type Into: Type + Sized;
fn convention() -> FfiReturnConvention {
match Self::Into::ty() {
TypeType::Void | TypeType::Primitive => FfiReturnConvention::ByValue,
TypeType::Aggregate => FfiReturnConvention::ByOutParam,
}
}
fn require_owned() -> bool {
true
}
fn postlude(_ret: &str) -> impl Display {
""
}
fn convert(self) -> Self::Into;
}
#[derive(Debug)]
pub struct MetatypeFunctionBuilder<'r, 'm> {
metatype: &'m mut MetatypeBuilder<'r>,
lparams: String, // lua function parameters
cparams: String, // C function parameters
cargs: String, // C call arguments
prelude: String, // lua function body prelude
postlude: String, // lua function body postlude
}
impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
pub fn new(metatype: &'m mut MetatypeBuilder<'r>) -> Self {
Self {
metatype,
lparams: String::new(),
cparams: String::new(),
cargs: String::new(),
prelude: String::new(),
postlude: String::new(),
}
}
pub fn param<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
assert!(
T::From::ty() != TypeType::Void,
"cannot declare void parameter"
);
let Self {
metatype: MetatypeBuilder { reg, .. },
lparams,
cparams,
cargs,
prelude,
postlude,
..
} = self;
reg.include::<T::From>();
(!lparams.is_empty()).then(|| lparams.push_str(", "));
(!cparams.is_empty()).then(|| cparams.push_str(", "));
(!cargs.is_empty()).then(|| cargs.push_str(", "));
write!(lparams, "{name}").unwrap();
write!(cparams, "{}", T::From::cdecl(&name)).unwrap();
write!(cargs, "{name}").unwrap();
if T::require_keepalive() {
write!(prelude, "local __keep_{name} = {name}; ").unwrap();
write!(postlude, "__C.{KEEP_FN}(__keep_{name}); ").unwrap();
}
write!(prelude, "{}", T::prelude(&name.to_string())).unwrap();
self
}
pub fn param_str(
&mut self,
name: impl Display,
allow_nil: bool,
check_utf8: bool,
) -> &mut Self {
// fast-path for &[u8] and &str-like parameters
//
// this passes one lua `string` argument as two C `const uint8_t *ptr` and `uintptr_t len`
// arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a
// temporary cdata to pass the string and its length in one argument.
let Self {
lparams,
cparams,
cargs,
prelude,
..
} = self;
let param_ptr = <*const u8>::cdecl(&name);
let param_len = usize::cdecl(format!("{name}_len"));
(!lparams.is_empty()).then(|| lparams.push_str(", "));
(!cparams.is_empty()).then(|| cparams.push_str(", "));
(!cargs.is_empty()).then(|| cargs.push_str(", "));
write!(lparams, "{name}").unwrap();
write!(cparams, "{param_ptr}, {param_len}").unwrap();
write!(cargs, "{name}, __{name}_len").unwrap();
write!(prelude, "local __{name}_len = 0; if {name} ~= nil then ").unwrap();
write!(prelude, r#"assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); "#).unwrap();
write!(prelude, r#"__{name}_len = #{name}; "#).unwrap();
if check_utf8 {
write!(prelude, r#"assert(__C.{IS_UTF8_FN}({name}, __{name}_len), "argument '{name}' must be a valid utf-8 string"); "#).unwrap();
}
if !allow_nil {
write!(prelude, r#"else return error("string expected in argument '{name}', got " .. type({name})); "#).unwrap();
}
write!(prelude, r#"end; "#).unwrap();
self
}
pub fn param_ignored(&mut self) -> &mut Self {
(!self.lparams.is_empty()).then(|| self.lparams.push_str(", "));
write!(self.lparams, "_").unwrap();
self
}
pub fn call<T: IntoFfi>(&mut self, func: impl Display) {
let Self {
metatype: MetatypeBuilder { reg, cdef, lua, .. },
lparams,
cparams,
cargs,
prelude,
postlude,
..
} = self;
reg.include::<T::Into>();
write!(lua, "function({lparams}) {prelude}").unwrap();
match T::convention() {
FfiReturnConvention::ByValue => {
if T::Into::ty() == TypeType::Void {
write!(lua, "__C.{func}({cargs}); {postlude}end").unwrap();
} else {
let check = T::postlude("__ret");
write!(lua, "local __ret = __C.{func}({cargs}); ").unwrap();
write!(lua, "{check}{postlude}return __ret; end").unwrap();
}
writeln!(cdef, "{};", T::Into::cdecl(display!("{func}({cparams})"))).unwrap();
}
FfiReturnConvention::ByOutParam => {
let ct = T::Into::name();
let check = T::postlude("__out");
write!(lua, "local __out = __new(__ct.{ct}); __C.{func}(__out").unwrap();
if !cargs.is_empty() {
write!(lua, ", {cargs}").unwrap();
}
write!(lua, "); {check}{postlude}return __out; end").unwrap();
write!(cdef, "void {func}({}", <*mut T::Into>::cdecl("out")).unwrap();
if !cparams.is_empty() {
write!(cdef, ", {cparams}").unwrap();
}
writeln!(cdef, ");").unwrap();
}
}
}
pub fn call_inferred<T: IntoFfi>(&mut self, func: impl Display, _infer: impl FnOnce() -> T) {
self.call::<T>(func)
}
}
pub trait Annotation {
fn annotation() -> impl Display;
}
//
// SAFETY: Unit type return maps to a C void return, which is a nil return in lua. There is no
// equivalent to passing a unit type as an argument in C.
//
macro_rules! impl_void {
($rty:ty) => {
unsafe impl Type for $rty {
fn name() -> impl Display {
"void"
}
fn ty() -> TypeType {
TypeType::Void
}
fn cdecl(name: impl Display) -> impl Display {
display!("void {name}")
}
fn build(_b: &mut TypeBuilder) {}
}
unsafe impl IntoFfi for $rty {
type Into = ();
fn convert(self) -> Self::Into {}
}
};
}
impl_void!(());
impl_void!(c_void);
macro_rules! impl_primitive {
($rty:ty, $cty:expr, $lty:expr) => {
unsafe impl Type for $rty {
fn name() -> impl Display {
$cty
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
display!("{} {name}", $cty)
}
fn build(_b: &mut TypeBuilder) {}
}
impl Annotation for $rty {
fn annotation() -> impl Display {
$lty
}
}
};
}
impl_primitive!(bool, "bool", "boolean");
impl_primitive!(u8, "uint8_t", "number");
impl_primitive!(u16, "uint16_t", "number");
impl_primitive!(u32, "uint32_t", "number");
impl_primitive!(u64, "uint64_t", "number");
impl_primitive!(usize, "uintptr_t", "number");
impl_primitive!(i8, "int8_t", "number");
impl_primitive!(i16, "int16_t", "number");
impl_primitive!(i32, "int32_t", "number");
impl_primitive!(i64, "int64_t", "number");
impl_primitive!(isize, "intptr_t", "number");
impl_primitive!(c_float, "float", "number");
impl_primitive!(c_double, "double", "number");
unsafe impl FromFfi for bool {
type From = bool;
fn prelude(arg: &str) -> impl Display {
display!(
r#"assert(type({arg}) == "boolean", "boolean expected in argument '{arg}', got " .. type({arg})); "#
)
}
fn convert(from: Self::From) -> Self {
from
}
}
unsafe impl IntoFfi for bool {
type Into = bool;
fn convert(self) -> Self::Into {
self
}
}
macro_rules! impl_number_fromabi {
($rty:ty) => {
unsafe impl FromFfi for $rty {
type From = Self;
fn prelude(arg: &str) -> impl Display {
display!(r#"do local __{arg} = {arg}; {arg} = tonumber({arg}); assert(type({arg}) == "number", "number expected in argument '{arg}', got " .. type(__{arg})); end; "#)
}
fn convert(from: Self::From) -> Self {
from
}
}
};
}
impl_number_fromabi!(u8);
impl_number_fromabi!(u16);
impl_number_fromabi!(u32);
impl_number_fromabi!(u64);
impl_number_fromabi!(usize);
impl_number_fromabi!(i8);
impl_number_fromabi!(i16);
impl_number_fromabi!(i32);
impl_number_fromabi!(i64);
impl_number_fromabi!(isize);
impl_number_fromabi!(f32);
impl_number_fromabi!(f64);
macro_rules! impl_number_intoabi {
($rty:ty) => {
unsafe impl IntoFfi for $rty {
type Into = Self;
fn convert(self) -> Self::Into {
self
}
}
};
}
impl_number_intoabi!(u8);
impl_number_intoabi!(u16);
impl_number_intoabi!(u32);
impl_number_intoabi!(i8);
impl_number_intoabi!(i16);
impl_number_intoabi!(i32);
#[cfg(target_pointer_width = "32")]
impl_number_intoabi!(usize);
#[cfg(target_pointer_width = "32")]
impl_number_intoabi!(isize);
impl_number_intoabi!(c_float);
impl_number_intoabi!(c_double);
macro_rules! impl_bigint_intoabi {
($rty:ty) => {
unsafe impl IntoFfi for $rty {
type Into = Self;
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str) -> impl Display {
// this isn't "correct" per se, but it's much more ergonomic to work with numbers in
// lua than with long longs wrapped in cdata. we gracefully accept the loss of
// precision here and that 53 bits of precision for big integers are enough. (the
// vain of Lua 5.3 integer subtype ;D )
display!("{ret} = tonumber({ret}); ")
}
}
};
}
impl_bigint_intoabi!(u64);
impl_bigint_intoabi!(i64);
#[cfg(target_pointer_width = "64")]
impl_bigint_intoabi!(usize);
#[cfg(target_pointer_width = "64")]
impl_bigint_intoabi!(isize);
macro_rules! impl_ptr {
($ty:ty, $mutable:expr) => {
unsafe impl<T> Type for $ty
where
T: Type,
{
fn name() -> impl Display {
disp(|f| {
if !$mutable {
write!(f, "const_")?;
}
write!(f, "{}_ptr", T::name())
})
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(disp(move |f| {
if !$mutable {
write!(f, "const ")?;
}
write!(f, "*{name}")
}))
}
fn build(b: &mut TypeBuilder) {
b.reg.include::<T>();
}
}
};
}
impl_ptr!(&T, false);
impl_ptr!(&mut T, true);
impl_ptr!(*const T, false);
impl_ptr!(*mut T, true);
impl_ptr!(Option<&T>, false);
impl_ptr!(Option<&mut T>, true);
macro_rules! impl_ptr_annotation {
($ty:ty) => {
impl<T> Annotation for $ty
where
T: Annotation,
{
fn annotation() -> impl Display {
"lightuserdata"
}
}
};
}
impl_ptr_annotation!(*const T);
impl_ptr_annotation!(*mut T);
macro_rules! impl_ref_annotation {
($ty:ty) => {
impl<T> Annotation for $ty
where
T: Annotation,
{
fn annotation() -> impl Display {
display!("{}", T::annotation())
}
}
};
}
impl_ref_annotation!(&T);
impl_ref_annotation!(&mut T);
//
// SAFETY: Pass by value for pointers, which maps to a `cdata` argument in lua containing either:
//
// * a reference which gets converted to a pointer (`T &` cdata), or
// * the pointer value itself (`T *` cdata), or
// * the pointer to the base of the cdata payload (`T` cdata).
//
// LuaJIT will check for pointer compatibility automatically.
//
macro_rules! impl_ptr_fromabi {
($ty:ty) => {
unsafe impl<T> FromFfi for $ty
where
T: Type,
{
type From = Self;
fn convert(from: Self::From) -> Self {
from
}
}
};
}
impl_ptr_fromabi!(*const T);
impl_ptr_fromabi!(*mut T);
impl_ptr_fromabi!(Option<&T>);
impl_ptr_fromabi!(Option<&mut T>);
//
// SAFETY: Return by value for pointers, which maps to a `cdata` return in lua containing the
// pointer (`T *`). We also map null pointers to `nil` for convenience (otherwise it's still a cdata
// value containing a null pointer)
//
macro_rules! impl_ptr_intoabi {
($ty:ty) => {
unsafe impl<T> IntoFfi for $ty
where
T: Type,
{
type Into = Self;
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str) -> impl Display {
display!("if {ret} == nil then {ret} = nil; end; ")
}
}
};
}
impl_ptr_intoabi!(*const T);
impl_ptr_intoabi!(*mut T);
#[cfg(feature = "option_ref_abi")] // disabled because it conflicts with the generic Option<T> impl
impl_ptr_intoabi!(Option<&'static T>);
#[cfg(feature = "option_ref_abi")]
impl_ptr_intoabi!(Option<&'static mut T>);
//
// SAFETY: `FromFfi` for *mutable* references is safe because it is guaranteed that no two Rust code
// called via FFI can be running at the same time on the same OS thread (no Lua reentrancy).
//
// i.e. The call stack will always look something like this:
//
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI): This is SAFE and the only use case we
// support. All references (mutable or not) to Rust user objects will be dropped before
// returning to Lua.
//
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI) -> Lua (via callback): This is UNSAFE
// because we cannot prevent the Lua callback from calling back into Rust code via FFI which
// could violate exclusive borrow semantics. This is prevented by not implementing `FromFfi` for
// function pointers (see below).
//
// The runtime does not keep any references to Rust user objects boxed in cdata (futures are the
// only exception; their ownership is transferred to the runtime via yield).
//
macro_rules! impl_ref_fromabi {
($ty:ty) => {
unsafe impl<'s, T> FromFfi for $ty
where
T: Type,
{
type From = Option<$ty>;
fn prelude(arg: &str) -> impl Display {
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
}
fn convert(from: Self::From) -> Self {
// SAFETY: we already checked that the reference is nonnull from the lua side
debug_assert!(
from.is_some(),
"<{}>::convert() called on a null reference when it was checked to be nonnull",
stringify!($ty),
);
unsafe { from.unwrap_unchecked() }
}
}
};
}
impl_ref_fromabi!(&'s T);
impl_ref_fromabi!(&'s mut T);
//
// SAFETY: `IntoFfi` only for 'static references because we cannot guarantee that the pointer will
// not outlive the pointee otherwise.
//
macro_rules! impl_ref_intoabi {
($ty:ty) => {
unsafe impl<T> IntoFfi for $ty
where
T: Type,
{
type Into = Self;
fn convert(self) -> Self::Into {
self
}
}
};
}
impl_ref_intoabi!(&'static T);
impl_ref_intoabi!(&'static mut T);
//
// SAFETY: No `FromFfi` and `IntoFfi` for arrays because passing or returning them by value is not a
// thing in C (they are just pointers).
//
// TODO: we could automatically convert them to tables and vice-versa
//
unsafe impl<T> Type for [T]
where
T: Type,
{
fn name() -> impl Display {
display!("{}_arr", T::name())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("{name}[]")
}
fn build(b: &mut TypeBuilder) {
b.reg.include::<T>();
}
}
impl<T> Annotation for [T]
where
T: Annotation,
{
fn annotation() -> impl Display {
display!("{}[]", T::annotation())
}
}
unsafe impl<T, const N: usize> Type for [T; N]
where
T: Type,
{
fn name() -> impl Display {
display!("{}_arr{N}", T::name())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("{name}[{N}]")
}
fn build(b: &mut TypeBuilder) {
b.reg.include::<T>();
}
}
impl<T, const N: usize> Annotation for [T; N]
where
T: Annotation,
{
fn annotation() -> impl Display {
display!("{}[]", T::annotation())
}
}
pub struct ExternCFn<I, O>(PhantomData<extern "C" fn(I) -> O>);
macro_rules! impl_externcfn {
(fn($($arg:ident),*) -> $ret:ident) => {
impl_externcfn!(ExternCFn, fn($($arg),*) -> $ret);
};
($ty:ident, fn($($arg:ident),*) -> $ret:ident) => {
//
// SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above
// in `&mut T`).
//
// We also can't implement `IntoFfi` because we can't call `FromFfi` and `IntoFfi` for the
// function's respective argument and return values.
//
unsafe impl<$($arg,)* $ret> Type for $ty<($($arg,)*), $ret>
where
$($arg: Type,)*
$ret: Type,
{
fn name() -> impl Display {
disp(|f| Ok({
write!(f, "fn_{}", $ret::name())?;
$(write!(f, "__{}", $arg::name())?;)*
}))
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
$ret::cdecl(disp(move |f| Ok({
write!(f, "(*{name})(")?;
let mut _n = 0;
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)*
write!(f, ")")?;
})))
}
fn extern_cdecl(name: impl Display) -> impl Display {
$ret::cdecl(disp(move |f| Ok({
// for top-level function declarations in cdef
write!(f, "{name}(")?;
let mut _n = 0;
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)*
write!(f, ")")?;
})))
}
fn build(b: &mut TypeBuilder) {
$(b.reg.include::<$arg>();)*
b.reg.include::<$ret>();
}
}
};
}
impl_externcfn!(fn() -> A);
impl_externcfn!(fn(A) -> B);
impl_externcfn!(fn(A, B) -> C);
impl_externcfn!(fn(A, B, C) -> D);
impl_externcfn!(fn(A, B, C, D) -> E);
impl_externcfn!(fn(A, B, C, D, E) -> F);
impl_externcfn!(fn(A, B, C, D, E, F) -> G);
impl_externcfn!(fn(A, B, C, D, E, F, G) -> H);
impl_externcfn!(fn(A, B, C, D, E, F, G, H) -> I);
impl_externcfn!(fn(A, B, C, D, E, F, G, H, I) -> J);
impl<'s> Annotation for &'s str {
fn annotation() -> impl Display {
"string"
}
}
impl Annotation for String {
fn annotation() -> impl Display {
"string"
}
}