Add ffi crate
This commit is contained in:
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) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user