Add ffi crate
This commit is contained in:
parent
86380a0957
commit
16ab2be6f4
5
.luarc.json
Normal file
5
.luarc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||
"runtime.version": "LuaJIT",
|
||||
"diagnostics.disable": ["redefined-local"]
|
||||
}
|
71
Cargo.lock
generated
71
Cargo.lock
generated
@ -2,9 +2,36 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luaffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"luaffi_impl",
|
||||
"luaify",
|
||||
"rustc-hash",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luaffi_impl"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luaify"
|
||||
@ -19,6 +46,12 @@ dependencies = [
|
||||
name = "luby"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@ -37,6 +70,44 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.103"
|
||||
|
@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["crates/luaffi", "crates/luaify"]
|
||||
members = ["crates/luaffi", "crates/luaffi_impl", "crates/luaify"]
|
||||
|
||||
[package]
|
||||
name = "luby"
|
||||
|
@ -4,3 +4,8 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bstr = "1.12.0"
|
||||
luaffi_impl = { version = "0.1.0", path = "../luaffi_impl" }
|
||||
luaify = { version = "0.1.0", path = "../luaify" }
|
||||
rustc-hash = "2.1.1"
|
||||
simdutf8 = "0.1.5"
|
||||
|
172
crates/luaffi/src/future.rs
Normal file
172
crates/luaffi/src/future.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use crate::{
|
||||
__internal::{display, type_id},
|
||||
CDef, CDefBuilder, Metatype, MetatypeBuilder, ToFfi, Type, TypeBuilder,
|
||||
};
|
||||
use luaify::luaify;
|
||||
use std::{
|
||||
fmt::Display,
|
||||
mem,
|
||||
pin::Pin,
|
||||
ptr,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct lua_future<F: Future<Output: ToFfi>> {
|
||||
//
|
||||
// SAFETY: .poll MUST be the first field. It is only to be called by the Rust async runtime to
|
||||
// advance the future without knowing its type (see `lua_pollable` below).
|
||||
//
|
||||
// This assumes that the crate containing the async runtime and the crate containing the future
|
||||
// type are ABI-compatible (compiled by the same compiler with the same target into the same binary).
|
||||
// This is always the case for luby because all modules are statically linked into one binary.
|
||||
//
|
||||
// .poll and .state are opaque to Lua itself.
|
||||
//
|
||||
poll: fn(&mut Self, cx: &mut Context) -> Poll<()>,
|
||||
state: State<F>,
|
||||
take: unsafe extern "C" fn(&mut Self) -> <F::Output as ToFfi>::To,
|
||||
drop: unsafe extern "C" fn(&mut Self),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct lua_pollable {
|
||||
//
|
||||
// SAFETY: The only way to obtain a reference to a `lua_pollable` is by returning a `lua_future<T>`
|
||||
// from Rust to Lua, which LuaJIT boxes into cdata and `coroutine.yield`'s back to Rust, then
|
||||
// casting the yielded pointer value to `*mut lua_pollable`.
|
||||
//
|
||||
// This is the type-erased "header" part of a `lua_future<T>` which allows the async runtime to
|
||||
// poll the future without knowing its concrete type (essentially dynamic dispatch). It has the
|
||||
// same layout as `lua_future<T>` without the state part.
|
||||
//
|
||||
poll: fn(&mut Self, cx: &mut Context) -> Poll<()>,
|
||||
}
|
||||
|
||||
enum State<F: Future> {
|
||||
Pending(F),
|
||||
Fulfilled(F::Output),
|
||||
Complete,
|
||||
}
|
||||
|
||||
impl<F: Future<Output: ToFfi>> lua_future<F> {
|
||||
pub fn new(fut: F) -> Self {
|
||||
Self {
|
||||
poll: Self::poll,
|
||||
state: State::Pending(fut),
|
||||
take: Self::take,
|
||||
drop: Self::drop,
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self, cx: &mut Context) -> Poll<()> {
|
||||
//
|
||||
// SAFETY: LuaJIT guarantees that cdata payloads, which are GC-managed, are never
|
||||
// relocated (i.e. pinned). We can safely assume that we are pinned and poll the future.
|
||||
//
|
||||
// We use this to our advantage by storing the future value directly inside the cdata
|
||||
// payload instead of boxing the future and introducing indirection.
|
||||
//
|
||||
// https://github.com/LuaJIT/LuaJIT/issues/1167#issuecomment-1968047229
|
||||
//
|
||||
match self.state {
|
||||
State::Pending(ref mut fut) => match unsafe { Pin::new_unchecked(fut) }.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(value) => Poll::Ready(self.state = State::Fulfilled(value)),
|
||||
},
|
||||
State::Fulfilled(_) => Poll::Ready(()),
|
||||
State::Complete => unreachable!("lua_future::poll() called on completed future"),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn take(&mut self) -> <F::Output as ToFfi>::To {
|
||||
// `fut:__take()` returns the fulfilled value by-value because it is the lowest common
|
||||
// denominator for supported return conventions (all `ToFfi` impls support return by-value;
|
||||
// primitives e.g. don't support return by out-param because they get boxed in cdata).
|
||||
//
|
||||
// Plus, if we preallocate a cdata for out-param and the thread for some reason gets dropped
|
||||
// and never resumed, GC could call the destructor on an uninitialised cdata.
|
||||
match self.state {
|
||||
State::Fulfilled(_) => match mem::replace(&mut self.state, State::Complete) {
|
||||
State::Fulfilled(value) => value.convert(),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
State::Pending(_) => panic!("lua_future::take() called on pending future"),
|
||||
State::Complete => panic!("lua_future::take() called twice"),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn drop(&mut self) {
|
||||
unsafe { ptr::drop_in_place(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for lua_pollable {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
// SAFETY: see comment above in `lua_future::poll()`
|
||||
(self.poll)(Pin::into_inner(self), cx)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: ToFfi> + 'static> Type for lua_future<F> {
|
||||
fn name() -> impl Display {
|
||||
display!("future__{:x}", type_id::<F>())
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("struct future__{:x} {name}", type_id::<F>())
|
||||
}
|
||||
|
||||
fn build(s: &mut TypeBuilder) {
|
||||
s.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: ToFfi> + 'static> CDef for lua_future<F> {
|
||||
fn build(s: &mut CDefBuilder) {
|
||||
s.field_opaque(mem::offset_of!(Self, take))
|
||||
.field::<unsafe extern "C" fn(*mut Self) -> <F::Output as ToFfi>::To>("__take")
|
||||
.field::<unsafe extern "C" fn(*mut Self)>("__drop");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: ToFfi> + 'static> Metatype for lua_future<F> {
|
||||
type Target = Self;
|
||||
|
||||
fn build(s: &mut MetatypeBuilder) {
|
||||
s.metatable_raw("gc", luaify!(|self| self.__drop()));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: ToFfi> + 'static> ToFfi for lua_future<F> {
|
||||
type To = lua_future<F>;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
self
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
// When returning a future from Rust to Lua, yield it immediately to the runtime which will
|
||||
// poll it to completion in the background, then take the fulfilled value once the thread
|
||||
// gets resumed. Lua user code should never to worry about awaiting futures.
|
||||
//
|
||||
// Once the current thread gets resumed and we take the future's fulfilled value, we clear
|
||||
// the finaliser on the future and forget it (there is nothing to call drop on).
|
||||
//
|
||||
// `coroutine.yield` is cached as `yield` and `ffi.gc` as `gc` in locals (see lib.rs)
|
||||
display!(
|
||||
"yield({ret}); {ret} = gc({ret}, nil):__take(); {}",
|
||||
<F::Output as ToFfi>::postlude(ret)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: IntoFuture<Output: ToFfi>> From<F> for lua_future<F::IntoFuture> {
|
||||
fn from(value: F) -> Self {
|
||||
Self::new(value.into_future())
|
||||
}
|
||||
}
|
43
crates/luaffi/src/internal.rs
Normal file
43
crates/luaffi/src/internal.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use rustc_hash::FxHasher;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
fmt::{self, Display, Formatter},
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
pub fn type_id<T: 'static>() -> u64 {
|
||||
let mut hash = FxHasher::default();
|
||||
TypeId::of::<T>().hash(&mut hash);
|
||||
hash.finish()
|
||||
}
|
||||
|
||||
macro_rules! display {
|
||||
($($fmt:expr),+) => {{ crate::__internal::disp(move |f| write!(f, $($fmt),+)) }};
|
||||
}
|
||||
|
||||
pub(crate) use display;
|
||||
|
||||
pub fn disp(f: impl Fn(&mut Formatter) -> fmt::Result) -> impl Display {
|
||||
struct Disp<F: Fn(&mut Formatter) -> fmt::Result>(F);
|
||||
impl<F: Fn(&mut Formatter) -> fmt::Result> Display for Disp<F> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
(self.0)(f)
|
||||
}
|
||||
}
|
||||
|
||||
Disp(f)
|
||||
}
|
||||
|
||||
pub fn write_sep<S: Display>(
|
||||
f: &mut Formatter,
|
||||
sep: impl Display,
|
||||
iter: impl IntoIterator<Item = S>,
|
||||
) -> fmt::Result {
|
||||
for (i, s) in iter.into_iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, "{sep}")?;
|
||||
}
|
||||
write!(f, "{s}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1,14 +1,822 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
use crate::__internal::{disp, display, write_sep};
|
||||
pub use luaffi_impl::*;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
ffi::{c_double, c_float, c_void},
|
||||
fmt::{self, Display, Formatter, Write},
|
||||
mem, slice,
|
||||
};
|
||||
|
||||
pub mod future;
|
||||
pub mod option;
|
||||
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";
|
||||
|
||||
// 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)`.
|
||||
//
|
||||
// https://github.com/LuaJIT/LuaJIT/issues/1167
|
||||
#[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()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const CACHE_LIBS: &[(&str, &str)] = &[
|
||||
// preloaded
|
||||
("table", "table"),
|
||||
("string", "string"),
|
||||
("math", "math"),
|
||||
("coroutine", "coroutine"),
|
||||
("package", "package"),
|
||||
("debug", "debug"),
|
||||
("jit", "jit"),
|
||||
// require
|
||||
("bit", r#"require("bit")"#),
|
||||
("ffi", r#"require("ffi")"#),
|
||||
("new", r#"require("table.new")"#),
|
||||
("clear", r#"require("table.clear")"#),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
// https://www.lua.org/manual/5.1/manual.html#5.1
|
||||
const CACHE_GLOBALS: &[(&str, &str)] = &[
|
||||
// base
|
||||
("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"),
|
||||
// table
|
||||
("concat", "table.concat"),
|
||||
("insert", "table.insert"),
|
||||
("maxn", "table.maxn"),
|
||||
("remove", "table.remove"),
|
||||
("sort", "table.sort"),
|
||||
// string
|
||||
("strlen", "string.len"),
|
||||
("format", "string.format"),
|
||||
("strsub", "string.sub"),
|
||||
("gsub", "string.gsub"),
|
||||
("gmatch", "string.gmatch"),
|
||||
("dump", "string.dump"),
|
||||
// math
|
||||
("random", "math.random"),
|
||||
// coroutine
|
||||
("yield", "coroutine.yield"),
|
||||
// debug
|
||||
("traceback", "debug.traceback"),
|
||||
// ffi
|
||||
("C", "ffi.C"),
|
||||
("cdef", "ffi.cdef"),
|
||||
("typeof", "ffi.typeof"),
|
||||
("metatype", "ffi.metatype"),
|
||||
("cast", "ffi.cast"),
|
||||
("gc", "ffi.gc"),
|
||||
// bit
|
||||
("tobit", "bit.tobit"),
|
||||
("tohex", "bit.tohex"),
|
||||
("bnot", "bit.bnot"),
|
||||
("band", "bit.band"),
|
||||
("bor", "bit.bor"),
|
||||
("bxor", "bit.bxor"),
|
||||
("lshift", "bit.lshift"),
|
||||
("rshift", "bit.rshift"),
|
||||
("arshift", "bit.arshift"),
|
||||
("rol", "bit.rol"),
|
||||
("ror", "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, "\n = ")?;
|
||||
write_sep(f, ", ", list.iter().map(|(_, s)| s))?;
|
||||
writeln!(f, ";")
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Registry {
|
||||
types: HashSet<String>,
|
||||
funcs: HashSet<String>,
|
||||
cdef: String,
|
||||
lua: String,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
pub fn new() -> Self {
|
||||
let mut s = Self::default();
|
||||
s.declare::<extern "C" fn(ptr: *const c_void)>(KEEP_FN);
|
||||
s.declare::<unsafe extern "C" fn(ptr: *const u8, len: usize) -> bool>(IS_UTF8_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 {
|
||||
self.include::<T>();
|
||||
self.funcs
|
||||
.insert(name.to_string())
|
||||
.then(|| writeln!(self.cdef, "{};", T::cdecl(name)).unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn done(&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_GLOBALS)?;
|
||||
writeln!(f, "cdef [[{}]];", self.cdef)?;
|
||||
write!(f, "{}", self.lua)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe trait Type {
|
||||
fn name() -> impl Display;
|
||||
fn cdecl(name: impl Display) -> impl Display;
|
||||
fn build(b: &mut TypeBuilder);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypeBuilder<'r> {
|
||||
registry: &'r mut Registry,
|
||||
}
|
||||
|
||||
impl<'r> TypeBuilder<'r> {
|
||||
fn new(registry: &'r mut Registry) -> Self {
|
||||
Self { registry }
|
||||
}
|
||||
|
||||
pub fn include<T: Type>(&mut self) -> &mut Self {
|
||||
self.registry.include::<T>();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cdef<T: CDef>(&mut self) -> &mut Self {
|
||||
let mut b = CDefBuilder::new::<T>(self.registry);
|
||||
<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.registry);
|
||||
<T as Metatype>::build(&mut b);
|
||||
drop(b);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe trait CDef: Type {
|
||||
fn build(b: &mut CDefBuilder);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CDefBuilder<'r> {
|
||||
registry: &'r mut Registry,
|
||||
cdef: String,
|
||||
align: usize,
|
||||
opaque: usize,
|
||||
}
|
||||
|
||||
impl<'r> CDefBuilder<'r> {
|
||||
fn new<T: CDef>(registry: &'r mut Registry) -> Self {
|
||||
writeln!(
|
||||
registry.lua,
|
||||
r#"local {} = typeof("{}");"#,
|
||||
T::name(),
|
||||
T::cdecl("")
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
registry,
|
||||
cdef: format!("{} {{ ", T::cdecl("")),
|
||||
align: mem::align_of::<T>(),
|
||||
opaque: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn field<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
self.registry.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 {
|
||||
registry,
|
||||
cdef,
|
||||
align,
|
||||
..
|
||||
} = self;
|
||||
|
||||
registry.cdef.push_str(cdef);
|
||||
writeln!(registry.cdef, "}} __attribute__((aligned({align})));").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe trait Metatype {
|
||||
type Target: CDef;
|
||||
fn build(b: &mut MetatypeBuilder);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MetatypeBuilder<'r> {
|
||||
registry: &'r mut Registry,
|
||||
name: String,
|
||||
cdef: String,
|
||||
lua: String,
|
||||
}
|
||||
|
||||
impl<'r> MetatypeBuilder<'r> {
|
||||
fn new<T: Metatype>(registry: &'r mut Registry) -> Self {
|
||||
Self {
|
||||
registry,
|
||||
name: T::Target::name().to_string(),
|
||||
cdef: String::new(),
|
||||
lua: format!(r#"do local __mt, __idx = {{}}, {{}}; __mt.__index = __idx; "#),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
self.registry.declare::<T>(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn index(
|
||||
&mut self,
|
||||
name: impl Display,
|
||||
f: impl FnOnce(&mut MetatypeMethodBuilder),
|
||||
) -> &mut Self {
|
||||
write!(self.lua, "__idx.{name} = ").unwrap();
|
||||
f(&mut MetatypeMethodBuilder::new(self));
|
||||
write!(self.lua, "; ").unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn index_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
|
||||
write!(self.lua, "__idx.{name} = {value}; ").unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metatable(
|
||||
&mut self,
|
||||
name: impl Display,
|
||||
f: impl FnOnce(&mut MetatypeMethodBuilder),
|
||||
) -> &mut Self {
|
||||
write!(self.lua, "__idx.{name} = ").unwrap();
|
||||
f(&mut MetatypeMethodBuilder::new(self));
|
||||
write!(self.lua, "; ").unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metatable_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
|
||||
write!(self.lua, "__mt.__{name} = {value}; ").unwrap();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Drop for MetatypeBuilder<'r> {
|
||||
fn drop(&mut self) {
|
||||
let Self {
|
||||
registry,
|
||||
name,
|
||||
cdef,
|
||||
lua,
|
||||
..
|
||||
} = self;
|
||||
|
||||
registry.cdef.push_str(cdef);
|
||||
registry.lua.push_str(lua);
|
||||
writeln!(registry.lua, "metatype({name}, __mt); end;").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe trait FromFfi: Sized {
|
||||
type From: Type + Sized;
|
||||
type FromValue: Type + Sized;
|
||||
|
||||
const ARG_KEEPALIVE: bool = false;
|
||||
|
||||
fn prelude(_arg: &str) -> impl Display {
|
||||
""
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self;
|
||||
fn convert_value(from: Self::FromValue) -> Self;
|
||||
}
|
||||
|
||||
pub unsafe trait ToFfi: Sized {
|
||||
type To: Type + Sized;
|
||||
|
||||
fn postlude(_ret: &str) -> impl Display {
|
||||
""
|
||||
}
|
||||
|
||||
fn convert(self) -> Self::To;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum FfiReturnConvention {
|
||||
Void,
|
||||
ByValue,
|
||||
ByOutParam,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MetatypeMethodBuilder<'r, 'm> {
|
||||
metatype: &'m mut MetatypeBuilder<'r>,
|
||||
params: String, // parameters to the lua function
|
||||
args: String, // arguments to the C call
|
||||
prelude: String, // function body prelude
|
||||
postlude: String, // function body postlude
|
||||
}
|
||||
|
||||
impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
||||
pub fn new(metatype: &'m mut MetatypeBuilder<'r>) -> Self {
|
||||
Self {
|
||||
metatype,
|
||||
params: String::new(),
|
||||
args: String::new(),
|
||||
prelude: String::new(),
|
||||
postlude: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
write!(self.args, "{name}").unwrap();
|
||||
|
||||
if T::ARG_KEEPALIVE {
|
||||
write!(self.prelude, "local __keep_{name} = {name}; ").unwrap();
|
||||
write!(self.postlude, "C.{KEEP_FN}(__keep_{name}); ").unwrap();
|
||||
}
|
||||
|
||||
let name = name.to_string();
|
||||
write!(self.prelude, "{}", T::prelude(&name)).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn param_str(&mut self, name: impl Display) -> &mut Self {
|
||||
// fast-path for &str and &[u8]-like parameters
|
||||
(!self.params.is_empty()).then(|| self.params.push_str(", "));
|
||||
(!self.args.is_empty()).then(|| self.args.push_str(", "));
|
||||
write!(self.params, "{name}").unwrap();
|
||||
write!(self.args, "{name}, __{name}_len").unwrap();
|
||||
write!(self.prelude, "local __{name}_len = 0; ").unwrap();
|
||||
write!(
|
||||
self.prelude,
|
||||
r#"if {name} ~= nil then assert(type({name}) == "string", "expected string in argument '{name}', got " .. type({name})); __{name}_len = #{name}; end; "#
|
||||
)
|
||||
.unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn call<T: ToFfi>(&mut self, func: impl Display, conv: FfiReturnConvention) {
|
||||
let Self {
|
||||
metatype,
|
||||
params,
|
||||
args,
|
||||
prelude,
|
||||
postlude,
|
||||
} = self;
|
||||
|
||||
metatype.registry.include::<T::To>();
|
||||
|
||||
let lua = &mut metatype.lua;
|
||||
write!(lua, "function({params}) {prelude}").unwrap();
|
||||
|
||||
match conv {
|
||||
FfiReturnConvention::Void => {
|
||||
write!(lua, "C.{func}({args}); {postlude}return nil; end").unwrap();
|
||||
}
|
||||
FfiReturnConvention::ByValue => {
|
||||
let check = T::postlude("res");
|
||||
write!(
|
||||
lua,
|
||||
"local res = C.{func}({args}); {check}{postlude}return res; end"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
FfiReturnConvention::ByOutParam => {
|
||||
let ct = T::To::name();
|
||||
let check = T::postlude("res");
|
||||
write!(lua, "local res = {ct}(); C.{func}(res").unwrap();
|
||||
if !args.is_empty() {
|
||||
write!(lua, ", {args}").unwrap();
|
||||
}
|
||||
write!(lua, "); {check}{postlude}return res; end").unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_primitive {
|
||||
($rtype:ty, $ctype:expr) => {
|
||||
unsafe impl Type for $rtype {
|
||||
fn name() -> impl Display {
|
||||
$ctype
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("{} {name}", $ctype)
|
||||
}
|
||||
|
||||
fn build(_b: &mut TypeBuilder) {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_primitive!((), "void");
|
||||
impl_primitive!(c_void, "void");
|
||||
|
||||
unsafe impl ToFfi for () {
|
||||
//
|
||||
// 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.
|
||||
// `c_void` cannot be returned from rust so it should return the unit type instead.
|
||||
//
|
||||
type To = ();
|
||||
fn convert(self) -> Self::To {}
|
||||
}
|
||||
|
||||
macro_rules! impl_copy_primitive {
|
||||
($rtype:ty, $ctype:expr, $ltype:expr) => {
|
||||
impl_primitive!($rtype, $ctype);
|
||||
|
||||
//
|
||||
// SAFETY: Primitive types are always copyable so we can pass and return them by value.
|
||||
//
|
||||
unsafe impl FromFfi for $rtype {
|
||||
type From = $rtype;
|
||||
type FromValue = $rtype;
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
display!(r#"assert(type({arg}) == "{0}", "expected {0} in argument '{arg}', got " .. type({arg})); "#, $ltype)
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
from
|
||||
}
|
||||
|
||||
fn convert_value(from: Self::FromValue) -> Self {
|
||||
from
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ToFfi for $rtype {
|
||||
type To = $rtype;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_copy_primitive!(bool, "bool", "boolean");
|
||||
impl_copy_primitive!(u8, "uint8_t", "number");
|
||||
impl_copy_primitive!(u16, "uint16_t", "number");
|
||||
impl_copy_primitive!(u32, "uint32_t", "number");
|
||||
impl_copy_primitive!(u64, "uint64_t", "number");
|
||||
impl_copy_primitive!(usize, "uintptr_t", "number");
|
||||
impl_copy_primitive!(i8, "int8_t", "number");
|
||||
impl_copy_primitive!(i16, "int16_t", "number");
|
||||
impl_copy_primitive!(i32, "int32_t", "number");
|
||||
impl_copy_primitive!(i64, "int64_t", "number");
|
||||
impl_copy_primitive!(isize, "intptr_t", "number");
|
||||
impl_copy_primitive!(c_float, "float", "number");
|
||||
impl_copy_primitive!(c_double, "double", "number");
|
||||
|
||||
unsafe impl<T: Type> Type for *const T {
|
||||
fn name() -> impl Display {
|
||||
display!("ptr_const_{}", T::name())
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
T::cdecl(display!("const *{name}"))
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> Type for *mut T {
|
||||
fn name() -> impl Display {
|
||||
display!("ptr_mut_{}", T::name())
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
T::cdecl(display!("*{name}"))
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SAFETY: Pass by value for pointers, which maps to a `cdata` argument in lua containing either:
|
||||
// * the pointer value itself (`T *`), or
|
||||
// * the pointer to the base of the cdata payload (`T`).
|
||||
//
|
||||
unsafe impl<T: Type> FromFfi for *const T {
|
||||
type From = *const T;
|
||||
type FromValue = *const T;
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
from
|
||||
}
|
||||
|
||||
fn convert_value(from: Self::FromValue) -> Self {
|
||||
from
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> FromFfi for *mut T {
|
||||
type From = *mut T;
|
||||
type FromValue = *mut T;
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
from
|
||||
}
|
||||
|
||||
fn convert_value(from: Self::FromValue) -> Self {
|
||||
from
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
unsafe impl<T: Type> ToFfi for *const T {
|
||||
type To = *const T;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
self
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!("if {ret} == nil then {ret} = nil; end; ")
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> ToFfi for *mut T {
|
||||
type To = *mut T;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
self
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!("if {ret} == nil then {ret} = nil; end; ")
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SAFETY: No `ToFfi` for references because we can't guarantee that the returned reference converted
|
||||
// to a pointer will not outlive the pointee.
|
||||
//
|
||||
unsafe impl<T: Type> Type for &T {
|
||||
fn name() -> impl Display {
|
||||
display!("ref_const_{}", T::name())
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
T::cdecl(display!("const *{name}"))
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> Type for &mut T {
|
||||
fn name() -> impl Display {
|
||||
display!("ref_mut_{}", T::name())
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
T::cdecl(display!("*{name}"))
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SAFETY: Pass by value for references, which have the same semantics as pointers (see above).
|
||||
// Must ensure that the pointer is not nil before being converted to a reference.
|
||||
//
|
||||
unsafe impl<T: Type> FromFfi for &T {
|
||||
type From = *const T;
|
||||
type FromValue = *const T;
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
Self::convert_value(from)
|
||||
}
|
||||
|
||||
fn convert_value(from: Self::FromValue) -> Self {
|
||||
debug_assert!(
|
||||
!from.is_null(),
|
||||
"<&T>::convert() called on a null pointer when it was checked to be non-null"
|
||||
);
|
||||
|
||||
unsafe { &*from }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> FromFfi for &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).
|
||||
//
|
||||
type From = *mut T;
|
||||
type FromValue = *mut T;
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
Self::convert_value(from)
|
||||
}
|
||||
|
||||
fn convert_value(from: Self::FromValue) -> Self {
|
||||
debug_assert!(
|
||||
!from.is_null(),
|
||||
"<&mut T>::convert() called on a null pointer when it was checked to be non-null"
|
||||
);
|
||||
|
||||
unsafe { &mut *from }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SAFETY: No `FromFfi` and `ToFfi` 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> Type for [T] {
|
||||
fn name() -> impl Display {
|
||||
display!("arr_{}", T::name())
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("{name}[]")
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type, const N: usize> Type for [T; N] {
|
||||
fn name() -> impl Display {
|
||||
display!("arr{N}_{}", T::name())
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("{name}[{N}]")
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_function {
|
||||
(fn($($arg:tt),*) -> $ret:tt) => {
|
||||
impl_function!((extern "C" fn($($arg),*) -> $ret), fn($($arg),*) -> $ret);
|
||||
impl_function!((unsafe extern "C" fn($($arg),*) -> $ret), fn($($arg),*) -> $ret);
|
||||
};
|
||||
|
||||
(($($type:tt)+), fn($($arg:tt),*) -> $ret:tt) => {
|
||||
//
|
||||
// SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above in `&mut T`).
|
||||
//
|
||||
// We also can't implement `ToFfi` because we can't call `FromFfi` and `ToFfi` for the
|
||||
// function's respective argument and return values.
|
||||
//
|
||||
unsafe impl<$($arg: Type,)* $ret: Type> Type for $($type)+ {
|
||||
fn name() -> impl Display {
|
||||
disp(|f| {
|
||||
write!(f, "fn")?;
|
||||
$(write!(f, "_{}", $arg::name())?;)*
|
||||
write!(f, "_{}", $ret::name())
|
||||
})
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
$ret::cdecl(disp(move |f| {
|
||||
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) {
|
||||
$(b.include::<$arg>();)*
|
||||
b.include::<$ret>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_function!(fn() -> Z);
|
||||
impl_function!(fn(A) -> Z);
|
||||
impl_function!(fn(A, B) -> Z);
|
||||
impl_function!(fn(A, B, C) -> Z);
|
||||
impl_function!(fn(A, B, C, D) -> Z);
|
||||
impl_function!(fn(A, B, C, D, E) -> Z);
|
||||
impl_function!(fn(A, B, C, D, E, F) -> Z);
|
||||
impl_function!(fn(A, B, C, D, E, F, G) -> Z);
|
||||
|
80
crates/luaffi/src/option.rs
Normal file
80
crates/luaffi/src/option.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use crate::{CDef, CDefBuilder, FromFfi, ToFfi, Type, TypeBuilder, display};
|
||||
use std::{ffi::c_int, fmt::Display, ptr};
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum lua_option<T> {
|
||||
None, // __tag = 0
|
||||
Some(T), // __tag = 1
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> Type for lua_option<T> {
|
||||
fn name() -> impl Display {
|
||||
display!("option__{}", T::name())
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("struct option__{} {name}", T::name())
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>().cdef::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> CDef for lua_option<T> {
|
||||
fn build(b: &mut CDefBuilder) {
|
||||
b.field::<c_int>("__tag").field::<T>("__value");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: FromFfi> FromFfi for Option<T> {
|
||||
type From = *mut Self::FromValue; // pass by-ref
|
||||
type FromValue = lua_option<T::FromValue>;
|
||||
|
||||
const ARG_KEEPALIVE: bool = T::ARG_KEEPALIVE;
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
let ct = Self::FromValue::name();
|
||||
display!(
|
||||
"if {arg} == nil then {arg} = {ct}(); else {}{arg} = {ct}(1, {arg}); end; ",
|
||||
T::prelude(arg)
|
||||
)
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
debug_assert!(
|
||||
!from.is_null(),
|
||||
"Option<T>::convert() called on a null lua_option<T>"
|
||||
);
|
||||
|
||||
Self::convert_value(unsafe { ptr::replace(from, lua_option::None) })
|
||||
}
|
||||
|
||||
fn convert_value(from: Self::FromValue) -> Self {
|
||||
match from {
|
||||
lua_option::Some(value) => Some(T::convert_value(value)),
|
||||
lua_option::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: ToFfi> ToFfi for Option<T> {
|
||||
type To = lua_option<T::To>;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
match self {
|
||||
Some(value) => lua_option::Some(value.convert()),
|
||||
None => lua_option::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
// if we don't have a value, return nil. otherwise copy out the inner value immediately,
|
||||
// forget the option cdata, then call postlude on the inner value.
|
||||
display!(
|
||||
"if {ret}.__tag == 0 then {ret} = nil; else {ret} = {ret}.__value; {}end; ",
|
||||
T::postlude(ret)
|
||||
)
|
||||
}
|
||||
}
|
89
crates/luaffi/src/string.rs
Normal file
89
crates/luaffi/src/string.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use crate::{__internal::disp, FromFfi, IS_UTF8_FN, Type};
|
||||
use luaffi_impl::{cdef, metatype};
|
||||
use std::{fmt, ptr, slice};
|
||||
|
||||
#[cdef]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct lua_buf {
|
||||
__ptr: *mut u8,
|
||||
__len: usize,
|
||||
}
|
||||
|
||||
#[metatype]
|
||||
impl lua_buf {}
|
||||
|
||||
unsafe impl FromFfi for *const [u8] {
|
||||
type From = *const Self::FromValue; // pass by-ref
|
||||
type FromValue = lua_buf;
|
||||
|
||||
const ARG_KEEPALIVE: bool = true;
|
||||
|
||||
fn prelude(arg: &str) -> impl fmt::Display {
|
||||
// this converts string arguments to a `lua_buf` with a pointer to the string and its length
|
||||
disp(move |f| {
|
||||
let ct = lua_buf::name();
|
||||
write!(
|
||||
f,
|
||||
r#"if {arg} ~= nil then assert(type({arg}) == "string", "expected string in argument '{arg}', got " .. type({arg})); "#
|
||||
)?;
|
||||
write!(f, "{arg} = {ct}({arg}, #{arg}); end; ")
|
||||
})
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
if from.is_null() {
|
||||
ptr::slice_from_raw_parts(ptr::null(), 0)
|
||||
} else {
|
||||
// SAFETY: this is safe because lua_buf is copyable
|
||||
unsafe { Self::convert_value(*from) }
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_value(from: Self::FromValue) -> Self {
|
||||
ptr::slice_from_raw_parts(from.__ptr, from.__len)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl FromFfi for &str {
|
||||
type From = *const Self::FromValue; // pass by-ref
|
||||
type FromValue = lua_buf;
|
||||
|
||||
const ARG_KEEPALIVE: bool = true;
|
||||
|
||||
fn prelude(arg: &str) -> impl fmt::Display {
|
||||
disp(move |f| {
|
||||
let ct = lua_buf::name();
|
||||
write!(
|
||||
f,
|
||||
r#"assert(type({arg}) == "string", "expected string in argument '{arg}', got " .. type({arg})); "#
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
r#"assert(C.{IS_UTF8_FN}({arg}, #{arg}), "argument '{arg}' must be a valid utf8 string"); "#
|
||||
)?;
|
||||
write!(f, "{arg} = {ct}({arg}, #{arg}); ")
|
||||
})
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
debug_assert!(
|
||||
!from.is_null(),
|
||||
"<&str>::convert() called on a null lua_buf"
|
||||
);
|
||||
|
||||
// SAFETY: this is safe because lua_buf is copyable
|
||||
unsafe { Self::convert_value(*from) }
|
||||
}
|
||||
|
||||
fn convert_value(from: Self::FromValue) -> Self {
|
||||
let s = unsafe { slice::from_raw_parts(from.__ptr, from.__len) };
|
||||
|
||||
debug_assert!(
|
||||
std::str::from_utf8(s).is_ok(),
|
||||
"<&str>::convert() called on an invalid utf8 string when it was checked to be valid"
|
||||
);
|
||||
|
||||
// SAFETY: we already checked that the string is valid utf8 from the lua side
|
||||
unsafe { std::str::from_utf8_unchecked(s) }
|
||||
}
|
||||
}
|
114
crates/luaffi/test.lua
Normal file
114
crates/luaffi/test.lua
Normal file
@ -0,0 +1,114 @@
|
||||
-- automatically generated by luaffi 0.1.0
|
||||
local table, string, math, coroutine, package, debug, jit, bit, ffi, new, clear =
|
||||
table,
|
||||
string,
|
||||
math,
|
||||
coroutine,
|
||||
package,
|
||||
debug,
|
||||
jit,
|
||||
require("bit"),
|
||||
require("ffi"),
|
||||
require("table.new"),
|
||||
require("table.clear")
|
||||
local assert, error, type, print, pcall, xpcall, getfenv, setfenv, getmetatable, setmetatable, pairs, ipairs, next, rawget, rawset, rawequal, select, tonumber, tostring, require, concat, insert, maxn, remove, sort, strlen, format, strsub, gsub, gmatch, dump, random, yield, traceback, C, cdef, typeof, metatype, cast, gc, tobit, tohex, bnot, band, bor, bxor, lshift, rshift, arshift, rol, ror, bswap =
|
||||
assert,
|
||||
error,
|
||||
type,
|
||||
print,
|
||||
pcall,
|
||||
xpcall,
|
||||
getfenv,
|
||||
setfenv,
|
||||
getmetatable,
|
||||
setmetatable,
|
||||
pairs,
|
||||
ipairs,
|
||||
next,
|
||||
rawget,
|
||||
rawset,
|
||||
rawequal,
|
||||
select,
|
||||
tonumber,
|
||||
tostring,
|
||||
require,
|
||||
table.concat,
|
||||
table.insert,
|
||||
table.maxn,
|
||||
table.remove,
|
||||
table.sort,
|
||||
string.len,
|
||||
string.format,
|
||||
string.sub,
|
||||
string.gsub,
|
||||
string.gmatch,
|
||||
string.dump,
|
||||
math.random,
|
||||
coroutine.yield,
|
||||
debug.traceback,
|
||||
ffi.C,
|
||||
ffi.cdef,
|
||||
ffi.typeof,
|
||||
ffi.metatype,
|
||||
ffi.cast,
|
||||
ffi.gc,
|
||||
bit.tobit,
|
||||
bit.tohex,
|
||||
bit.bnot,
|
||||
bit.band,
|
||||
bit.bor,
|
||||
bit.bxor,
|
||||
bit.lshift,
|
||||
bit.rshift,
|
||||
bit.arshift,
|
||||
bit.rol,
|
||||
bit.ror,
|
||||
bit.bswap
|
||||
cdef([[void (*luaffi_keep)(void const *);
|
||||
bool (*luaffi_is_utf8)(uint8_t const *, uintptr_t );
|
||||
struct Test { } __attribute__((aligned(1)));
|
||||
struct lua_buf { uint8_t *__ptr; uintptr_t __len; } __attribute__((aligned(8)));
|
||||
struct option__lua_buf { int32_t __tag; struct lua_buf __value; } __attribute__((aligned(8)));
|
||||
struct option__uint32_t { int32_t __tag; uint32_t __value; } __attribute__((aligned(4)));
|
||||
struct option__void { int32_t __tag; void __value; } __attribute__((aligned(4)));
|
||||
]])
|
||||
local Test = typeof("struct Test ")
|
||||
local lua_buf = typeof("struct lua_buf ")
|
||||
do
|
||||
local __mt, __idx = {}, {}
|
||||
__mt.__index = __idx
|
||||
metatype(lua_buf, __mt)
|
||||
end
|
||||
local option__lua_buf = typeof("struct option__lua_buf ")
|
||||
local option__uint32_t = typeof("struct option__uint32_t ")
|
||||
local option__void = typeof("struct option__void ")
|
||||
do
|
||||
local __mt, __idx = {}, {}
|
||||
__mt.__index = __idx
|
||||
__idx.method = function(my_arg, other)
|
||||
local __keep_my_arg = my_arg
|
||||
if my_arg == nil then
|
||||
my_arg = option__lua_buf()
|
||||
else
|
||||
assert(type(my_arg) == "string", "expected string in argument 'my_arg', got " .. type(my_arg))
|
||||
assert(C.luaffi_is_utf8(my_arg, #my_arg), "argument 'my_arg' must be a valid utf8 string")
|
||||
my_arg = lua_buf(my_arg, #my_arg)
|
||||
my_arg = option__lua_buf(1, my_arg)
|
||||
end
|
||||
if other == nil then
|
||||
other = option__uint32_t()
|
||||
else
|
||||
assert(type(other) == "number", "expected number in argument 'other', got " .. type(other))
|
||||
other = option__uint32_t(1, other)
|
||||
end
|
||||
local res = C.test_fn_impl(my_arg, other)
|
||||
if res.__tag == 0 then
|
||||
res = nil
|
||||
else
|
||||
res = res.__value
|
||||
end
|
||||
C.luaffi_keep(__keep_my_arg)
|
||||
return res
|
||||
end
|
||||
metatype(Test, __mt)
|
||||
end
|
12
crates/luaffi_impl/Cargo.toml
Normal file
12
crates/luaffi_impl/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "luaffi_impl"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.95"
|
||||
quote = "1.0.40"
|
||||
syn = { version = "2.0.103", features = ["full", "visit-mut"] }
|
204
crates/luaffi_impl/src/cdef.rs
Normal file
204
crates/luaffi_impl/src/cdef.rs
Normal file
@ -0,0 +1,204 @@
|
||||
use crate::utils::{ffi_crate, syn_assert, syn_error};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{spanned::*, *};
|
||||
|
||||
pub fn transform(mut item: Item) -> Result<TokenStream> {
|
||||
let (name, impl_type, impl_cdef) = match item {
|
||||
Item::Struct(ref mut str) => (
|
||||
str.ident.clone(),
|
||||
generate_type(&str.ident)?,
|
||||
generate_cdef_structure(str)?,
|
||||
),
|
||||
Item::Enum(ref mut enu) => (
|
||||
enu.ident.clone(),
|
||||
generate_type(&enu.ident)?,
|
||||
generate_cdef_enum(enu)?,
|
||||
),
|
||||
_ => syn_error!(item, "expected struct or enum"),
|
||||
};
|
||||
|
||||
let mod_name = format_ident!("__cdef__{name}");
|
||||
|
||||
Ok(quote! {
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#item
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(unused, non_snake_case)]
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
#impl_type
|
||||
#impl_cdef
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let fmt = quote!(::std::format!);
|
||||
let name_fmt = LitStr::new(&format!("{ty}"), ty.span());
|
||||
let cdecl_fmt = LitStr::new(&format!("struct {ty} {{name}}"), ty.span());
|
||||
|
||||
Ok(quote! {
|
||||
unsafe impl #ffi::Type for #ty {
|
||||
fn name() -> ::std::string::String {
|
||||
#fmt(#name_fmt)
|
||||
}
|
||||
|
||||
fn cdecl(name: impl ::std::fmt::Display) -> ::std::string::String {
|
||||
#fmt(#cdecl_fmt)
|
||||
}
|
||||
|
||||
fn build(s: &mut #ffi::TypeBuilder) {
|
||||
s.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||
syn_assert!(
|
||||
str.generics.params.is_empty(),
|
||||
str.generics,
|
||||
"cannot be generic (not yet implemented)"
|
||||
);
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &str.ident;
|
||||
let build = generate_build_cdef(&to_cfields(&mut str.fields)?)?;
|
||||
|
||||
Ok(quote! {
|
||||
unsafe impl #ffi::CDef for #ty {
|
||||
fn build(s: &mut #ffi::CDefBuilder) { #build }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
||||
syn_assert!(
|
||||
enu.generics.params.is_empty(),
|
||||
enu.generics,
|
||||
"cannot be generic (not yet implemented)"
|
||||
);
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &enu.ident;
|
||||
let build = enu
|
||||
.variants
|
||||
.iter_mut()
|
||||
.map(|variant| {
|
||||
let build = generate_build_cdef(&to_cfields(&mut variant.fields)?)?;
|
||||
Ok(quote! { s.inner_struct(|s| { #build }); })
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(quote! {
|
||||
unsafe impl #ffi::CDef for #ty {
|
||||
fn build(s: &mut #ffi::CDefBuilder) {
|
||||
s.field::<::std::ffi::c_int>("__tag").inner_union(|s| { #(#build)* });
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CField {
|
||||
name: String,
|
||||
ty: Type,
|
||||
attrs: CFieldAttrs,
|
||||
}
|
||||
|
||||
fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
match fields {
|
||||
Fields::Named(fields) => fields.named.iter_mut(),
|
||||
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
|
||||
Fields::Unit => return Ok(vec![]),
|
||||
}
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
Ok(CField {
|
||||
name: match field.ident {
|
||||
Some(ref name) => format!("{name}"),
|
||||
None => format!("__{i}"),
|
||||
},
|
||||
ty: field.ty.clone(),
|
||||
attrs: parse_attrs(&mut field.attrs)?,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct CFieldAttrs {
|
||||
opaque: bool,
|
||||
}
|
||||
|
||||
fn parse_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
|
||||
let mut parsed = CFieldAttrs::default();
|
||||
let mut i = 0;
|
||||
while let Some(attr) = attrs.get(i) {
|
||||
if let Some(name) = attr.path().get_ident() {
|
||||
if name == "opaque" {
|
||||
parsed.opaque = true;
|
||||
attrs.remove(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
|
||||
let mut body = vec![quote! {
|
||||
let mut offset = 0;
|
||||
let mut align = 1;
|
||||
}];
|
||||
|
||||
fn offset(i: usize) -> Ident {
|
||||
format_ident!("offset{i}")
|
||||
}
|
||||
|
||||
let max = quote!(::std::cmp::Ord::max);
|
||||
let size_of = quote!(::std::mem::size_of);
|
||||
let align_of = quote!(::std::mem::align_of);
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let ty = &field.ty;
|
||||
let offset = offset(i);
|
||||
body.push(quote! {
|
||||
// round up current offset to the alignment of field type for field offset
|
||||
offset = (offset + #align_of::<#ty>() - 1) & !(#align_of::<#ty>() - 1);
|
||||
align = #max(align, #align_of::<#ty>());
|
||||
let #offset = offset;
|
||||
offset += #size_of::<#ty>();
|
||||
});
|
||||
}
|
||||
|
||||
body.push(quote! {
|
||||
// round up final offset to the total alignment of struct for struct size
|
||||
let size = (offset + align - 1) & !(align - 1);
|
||||
});
|
||||
|
||||
let len = fields.len();
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let name = &field.name;
|
||||
let ty = &field.ty;
|
||||
if field.attrs.opaque {
|
||||
body.push(if i == len - 1 {
|
||||
let a = offset(i);
|
||||
quote! { s.field_opaque(size - #a); } // last field
|
||||
} else {
|
||||
let a = offset(i);
|
||||
let b = offset(i + 1);
|
||||
quote! { s.field_opaque(#b - #a); }
|
||||
});
|
||||
} else {
|
||||
body.push(quote! { s.field::<#ty>(#name); });
|
||||
}
|
||||
}
|
||||
|
||||
Ok(quote! { #(#body)* })
|
||||
}
|
21
crates/luaffi_impl/src/lib.rs
Normal file
21
crates/luaffi_impl/src/lib.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use proc_macro::TokenStream as TokenStream1;
|
||||
use quote::ToTokens;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
mod cdef;
|
||||
mod metatype;
|
||||
mod utils;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn cdef(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
||||
cdef::transform(parse_macro_input!(input))
|
||||
.unwrap_or_else(|err| err.into_compile_error().into_token_stream())
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn metatype(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
||||
metatype::transform(parse_macro_input!(input))
|
||||
.unwrap_or_else(|err| err.into_compile_error().into_token_stream())
|
||||
.into()
|
||||
}
|
49
crates/luaffi_impl/src/metatype.rs
Normal file
49
crates/luaffi_impl/src/metatype.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use crate::utils::{ffi_crate, syn_assert, syn_error, ty_name};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{spanned::*, *};
|
||||
|
||||
pub fn transform(mut item: ItemImpl) -> Result<TokenStream> {
|
||||
syn_assert!(
|
||||
item.generics.params.is_empty(),
|
||||
item.generics,
|
||||
"cannot be generic (not yet implemented)"
|
||||
);
|
||||
|
||||
let impls = generate_impls(&mut item)?;
|
||||
let mod_name = format_ident!("__metatype__{}", ty_name(&item.self_ty)?);
|
||||
|
||||
Ok(quote! {
|
||||
#item
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(unused, non_snake_case)]
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
#impls
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let ty = &*imp.self_ty;
|
||||
let name = ty_name(&ty)?;
|
||||
|
||||
Ok(quote! {
|
||||
unsafe impl #ffi::Metatype for #ty {
|
||||
type Target = Self;
|
||||
fn build(s: &mut #ffi::MetatypeBuilder) {}
|
||||
}
|
||||
|
||||
impl #ty {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FfiFunction {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LuaFunction {}
|
35
crates/luaffi_impl/src/utils.rs
Normal file
35
crates/luaffi_impl/src/utils.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use std::{env, fmt};
|
||||
use syn::{ext::*, spanned::*, *};
|
||||
|
||||
macro_rules! syn_error {
|
||||
($src:expr, $($fmt:expr),+) => {{
|
||||
return Err(syn::Error::new($src.span(), format!($($fmt),*)));
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! syn_assert {
|
||||
($cond:expr, $src:expr, $($fmt:expr),+) => {{
|
||||
if !$cond {
|
||||
syn_error!($src, $($fmt),+);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use {syn_assert, syn_error};
|
||||
|
||||
pub fn ffi_crate() -> Path {
|
||||
match (
|
||||
env::var("CARGO_PKG_NAME").ok(),
|
||||
env::var("CARGO_TARGET_TMPDIR").ok(), // ignore tests/**/*.rs
|
||||
) {
|
||||
(Some(name), None) if name == "luaffi" => parse_quote!(crate),
|
||||
_ => parse_quote!(::luaffi),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty_name(ty: &Type) -> Result<&Ident> {
|
||||
match ty {
|
||||
Type::Path(path) => path.path.require_ident(),
|
||||
_ => syn_error!(ty, "expected ident"),
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user