Add Data:copyFrom, tests and annotation (#243)

This commit is contained in:
qwreey 2024-10-24 00:26:43 +00:00
parent 34a5b39277
commit 154c68a64e
No known key found for this signature in database
GPG key ID: D28DB79297A214BD
12 changed files with 514 additions and 35 deletions

2
.gitattributes vendored
View file

@ -7,6 +7,6 @@
# Ensure all txt files within tests use LF
tests/**/*.txt eol=lf
# Remove test c, typescript from language list
# Remove test c and typescript from language list
tests/ffi/**/*.c linguist-vendored
tests/ffi/**/*.ts linguist-vendored

View file

@ -6,6 +6,10 @@ See [tests/ffi](../../tests/ffi/README.md)
## TODO
- Deref
- Big endianness / Little endianness variable (On process.endianness)
- CString
- Add buffer for owned data support
@ -25,6 +29,8 @@ See [tests/ffi](../../tests/ffi/README.md)
- Add varargs support
- Array argument in cfn
## Code structure
### /c

View file

@ -1,7 +1,13 @@
use std::{alloc, alloc::Layout, boxed::Box, mem::ManuallyDrop, ptr};
use std::{
alloc::{self, Layout},
boxed::Box,
mem::ManuallyDrop,
ptr,
};
use mlua::prelude::*;
use super::helper::method_provider;
use crate::{
data::{association_names::REF_INNER, RefBounds, RefData, RefFlag},
ffi::{association, bit_mask::*, FfiData},
@ -148,6 +154,7 @@ impl LuaUserData for BoxData {
fields.add_field_method_get("size", |_, this| Ok(this.size()));
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
method_provider::provide_copy_from(methods);
// For convenience, :zero returns self.
methods.add_function_mut("zero", |_, this: LuaAnyUserData| {
this.borrow_mut::<BoxData>()?.zero();

View file

@ -0,0 +1,41 @@
use mlua::prelude::*;
use super::{FfiData, GetFfiData};
pub mod method_provider {
use super::*;
pub fn provide_copy_from<'lua, Target, M>(methods: &mut M)
where
Target: FfiData,
M: LuaUserDataMethods<'lua, Target>,
{
methods.add_method(
"copyFrom",
|_lua,
this,
(src, length, dst_offset, src_offset): (
LuaAnyUserData,
usize,
Option<isize>,
Option<isize>,
)| unsafe {
let dst_offset = dst_offset.unwrap_or(0);
let src_offset = src_offset.unwrap_or(0);
let src = src.get_ffi_data()?;
if !src.check_inner_boundary(src_offset, length) {
return Err(LuaError::external("Source boundary check failed"));
}
if !this.check_inner_boundary(dst_offset, length) {
return Err(LuaError::external("Self boundary check failed"));
}
this.copy_from(&src, length, dst_offset, src_offset);
Ok(())
},
);
}
}

View file

@ -6,6 +6,7 @@ use mlua::prelude::*;
mod box_data;
mod callable_data;
mod closure_data;
mod helper;
mod lib_data;
mod ref_data;

View file

@ -2,6 +2,7 @@ use std::{mem::ManuallyDrop, ptr};
use mlua::prelude::*;
use super::helper::method_provider;
use crate::{
data::association_names::REF_INNER,
ffi::{association, bit_mask::*, FfiData},
@ -91,6 +92,10 @@ impl RefData {
(**self.ptr) as usize == 0
}
pub fn leak(&mut self) {
self.flags = u8_set(self.flags, RefFlag::Leaked.value(), true);
}
pub unsafe fn offset(&self, offset: isize) -> LuaResult<Self> {
u8_test(self.flags, RefFlag::Offsetable.value())
.then_some(())
@ -147,6 +152,8 @@ impl FfiData for RefData {
impl LuaUserData for RefData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
method_provider::provide_copy_from(methods);
// FIXME:
methods.add_function("deref", |lua, this: LuaAnyUserData| {
let inner = association::get(lua, REF_INNER, &this)?;
@ -171,10 +178,12 @@ impl LuaUserData for RefData {
Ok(userdata)
});
// FIXME:
methods.add_function_mut("leak", |lua, this: LuaAnyUserData| {
this.borrow_mut::<RefData>()?.leak();
RefData::luaref(lua, this)
});
methods.add_function("ref", |lua, this: LuaAnyUserData| {
let ffiref = RefData::luaref(lua, this)?;
Ok(ffiref)
RefData::luaref(lua, this)
});
methods.add_method("isNull", |_, this, ()| Ok(this.is_nullptr()));
}

View file

@ -62,6 +62,17 @@ pub trait FfiData {
unsafe fn get_inner_pointer(&self) -> *mut ();
fn is_writable(&self) -> bool;
fn is_readable(&self) -> bool;
unsafe fn copy_from(
&self,
src: &Ref<dyn FfiData>,
length: usize,
dst_offset: isize,
src_offset: isize,
) {
self.get_inner_pointer()
.byte_offset(dst_offset)
.copy_from(src.get_inner_pointer().byte_offset(src_offset), length);
}
}
pub struct FfiArg {

View file

@ -0,0 +1,53 @@
local ffi = require("@lune/ffi")
local ok
-- Case1: Success
ok = pcall(function()
local box = ffi.u8:box(1)
ffi.u8:readData(box)
end)
assert(ok, "assersion failed, Case1 should success")
-- Case2: Fail
ok = pcall(function()
local box = ffi.u8:box(1)
ffi.u16:readData(box)
end)
assert(not ok, "assersion failed, Case2 should fail")
-- Case3: Success
ok = pcall(function()
local box = ffi.box(ffi.u8.size * 2)
ffi.u16:readData(box)
end)
assert(ok, "assersion failed, Case3 should success")
-- Case4: Success
ok = pcall(function()
local box = ffi.box(ffi.u8.size * 2)
ffi.u8:readData(box, ffi.u8.size)
end)
assert(ok, "assersion failed, Case4 should success")
-- Case5: Fail
ok = pcall(function()
local box = ffi.u8:box(1):ref()
ffi.u16:readData(box)
end)
assert(not ok, "assersion failed, Case5 should fail")
-- Case6: Success
ok = pcall(function()
local box = ffi.box(ffi.u8.size * 2):ref()
ffi.u16:readData(box)
end)
assert(ok, "assersion failed, Case6 should success")
-- Case7: Fail
ok = pcall(function()
local box = ffi.box(ffi.u8.size * 2):ref(ffi.u16.size)
ffi.u16:readData(box)
end)
assert(ok, "assersion failed, Case7 should fail")

View file

@ -0,0 +1,46 @@
local ffi = require("@lune/ffi")
local c = ffi.c
local ok
-- Case1: Fail
ok = pcall(function()
local box = ffi.box(c.int.size - 1)
c.int:writeData(box, 10)
end)
assert(not ok, "assersion failed, Case1 should fail")
-- Case2: Success
ok = pcall(function()
local box = ffi.box(c.int.size)
c.int:writeData(box, 10)
end)
assert(ok, "assersion failed, Case2 should success")
-- Case3: Success
ok = pcall(function()
local box = ffi.box(c.int.size * 2)
c.int:writeData(box, 10, c.int.size)
end)
assert(ok, "assersion failed, Case3 should success")
-- Case4: Fail
ok = pcall(function()
local box = ffi.box(c.int.size * 2)
c.int:writeData(box, 10, c.int.size * 2)
end)
assert(not ok, "assersion failed, Case4 should fail")
-- Case5: Success
ok = pcall(function()
local box = ffi.box(c.int.size * 2):ref()
c.int:writeData(box, 10, c.int.size)
end)
assert(ok, "assersion failed, Case5 should success")
-- Case6: Fail
ok = pcall(function()
local box = ffi.box(c.int.size * 2):ref()
c.int:writeData(box, 10, c.int.size * 2)
end)
assert(not ok, "assersion failed, Case6 should fail")

View file

@ -1,11 +1,41 @@
--[=[
@class FFI
Built-in library for foreign function interface
Built-in library for foreign function interface.
### Example usage
lib.c:
```c
int add(int a, int b) {
return a + b;
}
```
init.luau:
```lua
local ffi = require("@lune/ffi")
-- Create function signature
local addSignature = ffi.c.fn({ ffi.c.int, ffi.c.int }, ffi.c.int)
-- Load library
local lib = ffi.open("./lib.so")
-- Get symbol from library
local addSymbol = lib:find("add")
-- Create CallableData
local add = addSignature:callable(addSymbol)
-- Create result box and arguments
local result = ffi.box(ffi.c.int.size)
local a = ffi.c.int:box(1)
local b = ffi.c.int:box(2)
-- Call external function
add(result, a:ref(), b:ref())
-- Get number from result
print(ffi.c.int:readData(result))
```
]=]
local ffi = {}
@ -24,19 +54,83 @@ ffi.c = c
--[=[
@class RefData
A user manageable memory reference
A user manageable memory reference.
]=]
export type RefData = {
--[=[
@within RefData
@tag Method
@method deref
Get a RefData by dereference this reference.
@return A dereferenced `RefData`
]=]
deref: (self: RefData) -> RefData,
--[=[
@within RefData
@tag Method
@method offset
Create a reference with specific offset from this reference.
@param offset Create a reference at the given offset
@return A offseted reference
]=]
offset: (self: RefData, offset: number) -> RefData,
--[=[
@within RefData
@tag Method
@method ref
Create a reference of this reference.
The created reference keeps the box from being garbage collected until the reference itself is collected.
@return A reference of this reference
]=]
ref: (self: RefData) -> RefData,
--[=[
@within RefData
@tag Method
@method leak
Create a reference of this reference after leaking it.
GC doesn't manage destruction after this action. You must free it later.
@return A reference of this reference
]=]
leak: (self: RefData) -> RefData,
--[=[
@within RefData
@tag Method
@method isNull
Check reference is null or not.
@return Whether reference is null or not
]=]
isNull: (self: RefData) -> boolean,
--[=[
@within RefData
@tag Method
@method copyFrom
Copy content from another data.
@param src The source data
@param len The amount of data to copy, in bytes
@param dst_offset The offset in the destination where the data will be pasted
@param src_offset The offset in the source data from where the data will be copied
]=]
copyFrom: (self: RefData, src: (BoxData|RefData), length: number, dst_offset: number, src_offset: number)->();
}
--[=[
@class BoxData
A user manageable heap memory
A user manageable heap memory.
]=]
export type BoxData = {
--[=[
@ -44,7 +138,7 @@ export type BoxData = {
@tag Field
@field size
Size of the box.
The size of the box.
]=]
size: number,
@ -65,8 +159,9 @@ export type BoxData = {
Create a reference of the box after leaking it.
GC doesn't manage destruction after this action. You must free it later
GC doesn't manage destruction after this action. You must free it later.
@param offset Create a reference at the given offset
@return A reference of the box
]=]
leak: (self: BoxData, offset: number?) -> RefData,
@ -77,15 +172,31 @@ export type BoxData = {
Create a reference of the box.
The created reference keeps the box from being garbage collected until the reference itself is collected.
@param offset Create a reference at the given offset
@return A reference of the box
]=]
ref: (self: BoxData, offset: number?) -> RefData,
--[=[
@within BoxData
@tag Method
@method copyFrom
Copy content from another data.
@param src The source data
@param len The amount of data to copy, in bytes
@param dst_offset The offset in the destination where the data will be pasted
@param src_offset The offset in the source data from where the data will be copied
]=]
copyFrom: (self: BoxData, src: (BoxData|RefData), length: number, dst_offset: number, src_offset: number)->();
}
--[=[
@class LibData
A dynamic opened library handle
A dynamic opened library handle.
]=]
export type LibData = {
--[=[
@ -105,9 +216,12 @@ export type LibData = {
--[=[
@class CallableData
@tag unsafe
A callable external function
A callable external function.
To call external function, you should provide memory for the return value and references for the arguments.
If return type is `void`, pass `nil`.
]=]
export type CallableData = (ret: (RefData|BoxData)?, ...RefData)->() & {
-- apply: (self: Callable, args: Args)->AppliedCallable,
@ -116,7 +230,9 @@ export type CallableData = (ret: (RefData|BoxData)?, ...RefData)->() & {
--[=[
@class ClosureData
A lua function wrapper for function pointer
A lua function wrapper for the function pointer.
To return some data, write value into ret reference.
]=]
export type ClosureData = {
--[=[
@ -124,7 +240,9 @@ export type ClosureData = {
@tag Method
@method ref
Create a reference of the closure. usually can be used for passing function pointer as argument
Create a reference of the closure. usually can be used for passing function pointer as argument.
The created reference keeps the closure from being garbage collected until the reference itself is collected.
@return A reference of the closure
]=]
@ -137,7 +255,21 @@ export type ClosureData = {
-- NOTE: T is a unique identifier for the `CType` and R is the closest Lua type.
export type CTypeInfo<T, R> = {
--[=[
@within CTypeInfo
@tag Field
@field size
The size of the type in bytes.
]=]
size: number,
--[=[
@within CTypeInfo
@tag Field
@field signedness
The signedness of the type.
]=]
signedness: boolean,
-- subtype
@ -156,7 +288,23 @@ export type CTypeInfo<T, R> = {
type NumCType<T> = CTypeInfo<T, (number|any)>
export type CPtrInfo<T> = {
--[=[
@within CPtrInfo
@tag Field
@field size
The size of a pointer. should be the same for all pointers.
Equivalent to `ffi.c.usize.size`.
]=]
size: number,
--[=[
@within CPtrInfo
@tag Field
@field inner
The inner type of the pointer.
]=]
inner: T,
-- subtype
@ -169,9 +317,35 @@ export type CPtrInfo<T> = {
writeRef: (self: CPtrInfo<T>, target: (RefData|BoxData), value: (RefData|BoxData), offset: number?) -> (),
}
--[=[
@class CArrInfo
A c sized array type information.
]=]
export type CArrInfo<T, R> = {
--[=[
@within CArrInfo
@tag Field
@field size
The total size of the array in bytes.
]=]
size: number,
--[=[
@within CArrInfo
@tag Field
@field length
The length of the array.
]=]
length: number,
--[=[
@within CArrInfo
@tag Field
@field inner
The inner element type of the array.
]=]
inner: T,
-- subtype
@ -186,15 +360,62 @@ export type CArrInfo<T, R> = {
offset: (self: CArrInfo<T, R>, index: number) -> number,
}
--[=[
@class CFnInfo
A c function signature type information.
]=]
export type CFnInfo = {
--[=[
@within CFnInfo
@tag Method
@method callable
Create a callable from reference.
@return Struct array type
]=]
callable: (self: CFnInfo, functionRef: RefData) -> CallableData,
--[=[
@within CFnInfo
@tag Method
@method closure
Create a closure from lua function.
@return A closure.
]=]
closure: (self: CFnInfo, (ret: RefData, ...RefData)->()) -> ClosureData,
}
--[=[
@class CStructInfo
A c struct type information.
]=]
export type CStructInfo = {
size: number,
--[=[
@within CSturctInfo
@tag Method
@method arr
Create a struct array type.
@param len The length of the array
@return A struct array type
]=]
arr: (self: CStructInfo, len: number) -> CArrInfo<CStructInfo, {any}>,
--[=[
@within CSturctInfo
@tag Method
@method ptr
Create a struct pointer type.
@return A struct pointer type
]=]
ptr: (self: CStructInfo) -> CPtrInfo<CStructInfo>,
box: (self: CStructInfo, table: { any }) -> BoxData,
@ -217,9 +438,9 @@ export type CVoidInfo = {
@tag Method
@method ptr
Create a generic pointer type
Create a generic pointer type.
@return Generic pointer type
@return Generic pointer type, equivalent to `*void` in C.
]=]
ptr: (self: CVoidInfo) -> CPtrInfo<CVoidInfo>,
}
@ -233,7 +454,7 @@ c.void = {} :: CVoidInfo
@class u8
@within FFI
A 8-bit sized unsigned integer, Equivalent to `uint8_t` in `stdint`
A 8-bit sized unsigned integer, Equivalent to `uint8_t` in `stdint`.
]=]
ffi.u8 = {} :: u8
export type u8 = NumCType<"u8">
@ -241,7 +462,7 @@ export type u8 = NumCType<"u8">
@class u16
@within FFI
A 16-bit sized unsigned integer, Equivalent to `uint16_t` in `stdint`
A 16-bit sized unsigned integer, Equivalent to `uint16_t` in `stdint`.
]=]
ffi.u16 = {} :: u16
export type u16 = NumCType<"u16">
@ -249,7 +470,7 @@ export type u16 = NumCType<"u16">
@class u32
@within FFI
A 32-bit sized unsigned integer, Equivalent to `uint32_t` in `stdint`
A 32-bit sized unsigned integer, Equivalent to `uint32_t` in `stdint`.
]=]
ffi.u32 = {} :: u32
export type u32 = NumCType<"u32">
@ -257,7 +478,7 @@ export type u32 = NumCType<"u32">
@class u64
@within FFI
A 64-bit sized unsigned integer, Equivalent to `uint64_t` in `stdint`
A 64-bit sized unsigned integer, Equivalent to `uint64_t` in `stdint`.
]=]
ffi.u64 = {} :: u64
export type u64 = NumCType<"u64">
@ -265,7 +486,7 @@ export type u64 = NumCType<"u64">
@class u128
@within FFI
A 128-bit sized unsigned integer, Equivalent to `uint128_t` in `stdint`
A 128-bit sized unsigned integer, Equivalent to `uint128_t` in `stdint`.
]=]
ffi.u128 = {} :: u128
export type u128 = NumCType<"u128">
@ -273,7 +494,7 @@ export type u128 = NumCType<"u128">
@class i8
@within FFI
A 8-bit sized signed integer, Equivalent to `int8_t` in `stdint`
A 8-bit sized signed integer, Equivalent to `int8_t` in `stdint`.
]=]
ffi.i8 = {} :: i8
export type i8 = NumCType<"i8">
@ -281,7 +502,7 @@ export type i8 = NumCType<"i8">
@class i16
@within FFI
A 16-bit sized signed integer, Equivalent to `int16_t` in `stdint`
A 16-bit sized signed integer, Equivalent to `int16_t` in `stdint`.
]=]
ffi.i16 = {} :: i16
export type i16 = NumCType<"i16">
@ -289,7 +510,7 @@ export type i16 = NumCType<"i16">
@class i32
@within FFI
A 32-bit sized signed integer, Equivalent to `int32_t` in `stdint`
A 32-bit sized signed integer, Equivalent to `int32_t` in `stdint`.
]=]
ffi.i32 = {} :: i32
export type i32 = NumCType<"i32">
@ -297,7 +518,7 @@ export type i32 = NumCType<"i32">
@class i64
@within FFI
A 64-bit sized signed integer, Equivalent to `int64_t` in `stdint`
A 64-bit sized signed integer, Equivalent to `int64_t` in `stdint`.
]=]
ffi.i64 = {} :: i64
export type i64 = NumCType<"i64">
@ -305,7 +526,7 @@ export type i64 = NumCType<"i64">
@class i128
@within FFI
A 128-bit sized signed integer, Equivalent to `int128_t` in `stdint`
A 128-bit sized signed integer, Equivalent to `int128_t` in `stdint`.
]=]
ffi.i128 = {} :: i128
export type i128 = NumCType<"i128">
@ -313,7 +534,7 @@ export type i128 = NumCType<"i128">
@class f32
@within FFI
A single-precision 32-bit sized floating-point, Almost always equivalent to `float` in C
A single-precision 32-bit sized floating-point, Almost always equivalent to `float` in C.
]=]
ffi.f32 = {} :: f32
export type f32 = NumCType<"f32">
@ -321,7 +542,7 @@ export type f32 = NumCType<"f32">
@class f64
@within FFI
A double-precision 64-bit sized floating-point, Almost always equivalent to `double` in C
A double-precision 64-bit sized floating-point, Almost always equivalent to `double` in C.
]=]
ffi.f64 = {} :: f64
export type f64 = NumCType<"f64">
@ -329,7 +550,7 @@ export type f64 = NumCType<"f64">
@class usize
@within FFI
A machine specific pointer sized unsigned integer,
A machine specific pointer sized unsigned integer.
]=]
ffi.usize = {} :: usize
export type usize = NumCType<"usize">
@ -337,7 +558,7 @@ export type usize = NumCType<"usize">
@class isize
@within FFI
A machine specific pointer sized signed integer,
A machine specific pointer sized signed integer.
]=]
ffi.isize = {} :: isize
export type isize = NumCType<"isize">
@ -345,36 +566,120 @@ export type isize = NumCType<"isize">
--#endregion Fixed size Rust-style types --
--#region Variable size C-style types --
--[=[
@class char
@within C
Compiler defined C `char` type.
The signedness may differ depending on the compiler and platform.
You can get signedness by `signedness` field.
]=]
c.char = {} :: char
export type char = NumCType<"char">
-- c.float = {} :: float
-- export type float = NumCType<"float">
-- c.double = {} :: double
-- export type double = NumCType<"double">
--[=[
@class uchar
@within C
Compiler defined C `unsigned char` type.
Mostly equivalent to `u8`.
]=]
c.uchar = {} :: uchar
export type uchar = NumCType<"uchar">
--[=[
@class schar
@within C
Compiler defined C `signed char` type.
]=]
c.schar = {} :: schar
export type schar = NumCType<"schar">
--[=[
@class short
@within C
Compiler defined C `short` type.
]=]
c.short = {} :: short
export type short = NumCType<"short">
--[=[
@class ushort
@within C
Compiler defined C `unsigned short` type.
]=]
c.ushort = {} :: ushort
export type ushort = NumCType<"ushort">
--[=[
@class int
@within C
Compiler defined C `int` type.
The side may differ depending on the compiler and platform.
]=]
c.int = {} :: int
export type int = NumCType<"int">
--[=[
@class uint
@within C
Compiler defined C `unsigned int` type.
The side may differ depending on the compiler and platform.
]=]
c.uint = {} :: uint
export type uint = NumCType<"uint">
--[=[
@class long
@within C
Compiler defined C `long` type.
The side may differ depending on the compiler and platform.
]=]
c.long = {} :: long
export type long = NumCType<"long">
--[=[
@class ulong
@within C
Compiler defined C `unsigned long` type.
The side may differ depending on the compiler and platform.
]=]
c.ulong = {} :: ulong
export type ulong = NumCType<"ulong">
--[=[
@class longlong
@within C
Compiler defined C `unsigned longlong` type.
]=]
c.longlong = {} :: longlong
export type longlong = NumCType<"longlong">
--[=[
@class longlong
@within C
Compiler defined C `unsigned longlong` type.
]=]
c.ulonglong = {} :: ulonglong
export type ulonglong = NumCType<"ulonglong">
--#endregion Variable size C-style types --
--[=[
@class CTypes
All possible C types.
]=]
export type CTypes =
| u8
| u16
@ -427,7 +732,7 @@ end
Create a struct type information.
@param fields An array of CTypes represents the fields of the struct
@param fields An array of CTypes represents the fields of the struct
@return A struct type information
]=]
function c.struct(fields: { CTypes }): CStructInfo
@ -474,7 +779,7 @@ end
--[=[
@within FFI
Return `true` if the second argument is an integer (i32)
Return `true` if the second argument is an integer (i32).
@param val A lua value to check
@return Whether val is an integer or not