Improve ctype export and Refactor (#243)

This commit is contained in:
qwreey 2024-08-25 14:37:31 +00:00
parent 6f131e9512
commit 26706d9355
No known key found for this signature in database
GPG key ID: D28DB79297A214BD
14 changed files with 308 additions and 254 deletions

View file

@ -21,7 +21,7 @@ use crate::ffi::ffi_association::{get_association, set_association};
// There is no problem even if you create a struct with n fields of a single type within the struct. Array adheres to the condition that there is no additional padding between each element. Padding to a struct is padding inside the struct. Simply think of the padding byte as a trailing unnamed field.
pub struct CArr {
libffi_type: Type,
element_type: Type,
struct_type: Type,
length: usize,
field_size: usize,
@ -29,12 +29,12 @@ pub struct CArr {
}
impl CArr {
pub fn new(libffi_type: Type, length: usize) -> LuaResult<Self> {
let struct_type = Type::structure(vec![libffi_type.clone(); length]);
let field_size = get_ensured_size(libffi_type.as_raw_ptr())?;
pub fn new(element_type: Type, length: usize) -> LuaResult<Self> {
let struct_type = Type::structure(vec![element_type.clone(); length]);
let field_size = get_ensured_size(element_type.as_raw_ptr())?;
Ok(Self {
libffi_type,
element_type,
struct_type,
length,
field_size,
@ -50,14 +50,18 @@ impl CArr {
let fields = type_from_userdata(luatype)?;
let carr = lua.create_userdata(Self::new(fields, length)?)?;
set_association(lua, CARR_INNER, carr.clone(), luatype)?;
set_association(lua, CARR_INNER, &carr, luatype)?;
Ok(carr)
}
pub fn get_type(&self) -> Type {
self.libffi_type.clone()
self.struct_type.clone()
}
// pub fn get_element_type(&self) -> Type {
// self.element_type.clone()
// }
// Stringify cstruct for pretty printing something like:
// <CStruct( u8, i32, size = 8 )>
pub fn stringify(userdata: &LuaAnyUserData) -> LuaResult<String> {

View file

@ -1,7 +1,5 @@
#![allow(clippy::cargo_common_metadata)]
use std::borrow::Borrow;
use libffi::middle::Type;
use mlua::prelude::*;
@ -21,7 +19,7 @@ impl CPtr {
) -> LuaResult<LuaValue<'lua>> {
let value = Self().into_lua(lua)?;
set_association(lua, CPTR_INNER, value.borrow(), inner)?;
set_association(lua, CPTR_INNER, &value, inner)?;
Ok(value)
}

View file

@ -1,6 +1,6 @@
#![allow(clippy::cargo_common_metadata)]
use std::vec::Vec;
use std::{borrow::Borrow, vec::Vec};
use libffi::{
low,
@ -18,17 +18,17 @@ use crate::ffi::ffi_association::{get_association, set_association};
use crate::ffi::ffi_helper::FFI_STATUS_NAMES;
pub struct CStruct {
libffi_cif: Cif,
libffi_type: Type,
// libffi_cif: Cif,
fields: Vec<Type>,
libffi_type: Type,
offsets: Vec<usize>,
size: usize,
}
impl CStruct {
pub fn new(fields: Vec<Type>) -> LuaResult<Self> {
let libffi_type = Type::structure(fields.clone());
let libffi_cfi = Cif::new(vec![libffi_type.clone()], Type::void());
let libffi_type = Type::structure(fields.iter().cloned());
// let libffi_cfi = Cif::new(vec![libffi_type.clone()], Type::void());
// Get field offsets with ffi_get_struct_offsets
let mut offsets = Vec::<usize>::with_capacity(fields.len());
@ -52,9 +52,9 @@ impl CStruct {
let size = unsafe { (*libffi_type.as_raw_ptr()).size };
Ok(Self {
libffi_cif: libffi_cfi,
libffi_type,
// libffi_cif: libffi_cfi,
fields,
libffi_type,
offsets,
size,
})
@ -69,7 +69,7 @@ impl CStruct {
let fields = type_list_from_table(&table)?;
let cstruct = lua.create_userdata(Self::new(fields)?)?;
table.set_readonly(true);
set_association(lua, CSTRUCT_INNER, cstruct.clone(), table)?;
set_association(lua, CSTRUCT_INNER, &cstruct, table)?;
Ok(cstruct)
}

View file

@ -1,44 +1,38 @@
#![allow(clippy::cargo_common_metadata)]
use core::ffi::{
c_char, c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint,
c_ulong, c_ulonglong, c_ushort, c_void,
};
use libffi::middle::{Cif, Type};
use libffi::middle::Type;
use mlua::prelude::*;
use super::c_arr::CArr;
use super::c_helper::get_ensured_size;
use super::c_ptr::CPtr;
use crate::ffi::ffi_helper::get_ptr_from_userdata;
use crate::ffi::ffi_platform::CHAR_IS_SIGNED;
// use libffi::raw::{ffi_cif, ffi_ptrarray_to_raw};
pub struct CType {
libffi_cif: Cif,
// for ffi_ptrarray_to_raw?
// libffi_cif: Cif,
libffi_type: Type,
size: usize,
name: Option<String>,
// Write converted data from luavalue into some ptr
pub luavalue_into_ptr: fn(value: LuaValue, ptr: *mut c_void) -> LuaResult<()>,
pub luavalue_into_ptr: fn(value: LuaValue, ptr: *mut ()) -> LuaResult<()>,
// Read luavalue from some ptr
pub ptr_into_luavalue: fn(lua: &Lua, ptr: *mut c_void) -> LuaResult<LuaValue>,
pub ptr_into_luavalue: fn(lua: &Lua, ptr: *mut ()) -> LuaResult<LuaValue>,
}
impl CType {
pub fn new(
libffi_type: Type,
name: Option<String>,
luavalue_into_ptr: fn(value: LuaValue, ptr: *mut c_void) -> LuaResult<()>,
ptr_into_luavalue: fn(lua: &Lua, ptr: *mut c_void) -> LuaResult<LuaValue>,
luavalue_into_ptr: fn(value: LuaValue, ptr: *mut ()) -> LuaResult<()>,
ptr_into_luavalue: fn(lua: &Lua, ptr: *mut ()) -> LuaResult<LuaValue>,
) -> LuaResult<Self> {
let libffi_cfi = Cif::new(vec![libffi_type.clone()], Type::void());
// let libffi_cfi = Cif::new(vec![libffi_type.clone()], Type::void());
let size = get_ensured_size(libffi_type.as_raw_ptr())?;
Ok(Self {
libffi_cif: libffi_cfi,
// libffi_cif: libffi_cfi,
libffi_type,
size,
name,
@ -117,122 +111,3 @@ impl LuaUserData for CType {
});
}
}
// export all default c-types
#[allow(clippy::too_many_lines)]
pub fn create_all_types(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
Ok(vec![
(
"int",
CType::new(
Type::c_int(),
Some(String::from("int")),
|data, ptr| {
let value = match data {
LuaValue::Integer(t) => t,
_ => {
return Err(LuaError::external(format!(
"Integer expected, got {}",
data.type_name()
)))
}
} as c_int;
unsafe {
*(ptr.cast::<c_int>()) = value;
}
Ok(())
},
|lua: &Lua, ptr: *mut c_void| {
let value = unsafe { (*ptr.cast::<c_int>()).into_lua(lua)? };
Ok(value)
},
)?
.into_lua(lua)?,
),
(
"long",
CType::new(
Type::c_long(),
Some(String::from("long")),
|data, ptr| {
let value = match data {
LuaValue::Integer(t) => t,
_ => {
return Err(LuaError::external(format!(
"Integer expected, got {}",
data.type_name()
)))
}
} as c_long;
unsafe {
*(ptr.cast::<c_long>()) = value;
}
Ok(())
},
|lua: &Lua, ptr: *mut c_void| {
let value = unsafe { (*ptr.cast::<c_long>()).into_lua(lua)? };
Ok(value)
},
)?
.into_lua(lua)?,
),
(
"longlong",
CType::new(
Type::c_longlong(),
Some(String::from("longlong")),
|data, ptr| {
let value = match data {
LuaValue::Integer(t) => t,
_ => {
return Err(LuaError::external(format!(
"Integer expected, got {}",
data.type_name()
)))
}
} as c_longlong;
unsafe {
*(ptr.cast::<c_longlong>()) = value;
}
Ok(())
},
|lua: &Lua, ptr: *mut c_void| {
let value = unsafe { (*ptr.cast::<c_longlong>()).into_lua(lua)? };
Ok(value)
},
)?
.into_lua(lua)?,
),
(
"char",
CType::new(
if CHAR_IS_SIGNED {
Type::c_schar()
} else {
Type::c_uchar()
},
Some(String::from("char")),
|data, ptr| {
let value = match data {
LuaValue::Integer(t) => t,
_ => {
return Err(LuaError::external(format!(
"Integer expected, got {}",
data.type_name()
)))
}
} as c_char;
unsafe {
*(ptr.cast::<c_char>()) = value;
}
Ok(())
},
|lua: &Lua, ptr: *mut c_void| {
let value = unsafe { (*ptr.cast::<c_char>()).into_lua(lua)? };
Ok(value)
},
)?
.into_lua(lua)?,
),
])
}

View file

@ -12,3 +12,133 @@ mod association_names {
pub const CARR_INNER: &str = "__carr_inner";
pub const CSTRUCT_INNER: &str = "__cstruct_inner";
}
use core::ffi::{
c_char, c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint,
c_ulong, c_ulonglong, c_ushort, c_void,
};
use libffi::middle::Type;
use mlua::prelude::*;
use self::c_type::CType;
use crate::ffi::ffi_platform::CHAR_IS_SIGNED;
// export all default c-types
#[allow(clippy::too_many_lines)]
pub fn create_all_types(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
Ok(vec![
(
"int",
CType::new(
Type::c_int(),
Some(String::from("int")),
|data, ptr| {
let value = match data {
LuaValue::Integer(t) => t,
_ => {
return Err(LuaError::external(format!(
"Integer expected, got {}",
data.type_name()
)))
}
} as c_int;
unsafe {
*(ptr.cast::<c_int>()) = value;
}
Ok(())
},
|lua: &Lua, ptr: *mut ()| {
let value = unsafe { (*ptr.cast::<c_int>()).into_lua(lua)? };
Ok(value)
},
)?
.into_lua(lua)?,
),
(
"long",
CType::new(
Type::c_long(),
Some(String::from("long")),
|data, ptr| {
let value = match data {
LuaValue::Integer(t) => t,
_ => {
return Err(LuaError::external(format!(
"Integer expected, got {}",
data.type_name()
)))
}
} as c_long;
unsafe {
*(ptr.cast::<c_long>()) = value;
}
Ok(())
},
|lua: &Lua, ptr: *mut ()| {
let value = unsafe { (*ptr.cast::<c_long>()).into_lua(lua)? };
Ok(value)
},
)?
.into_lua(lua)?,
),
(
"longlong",
CType::new(
Type::c_longlong(),
Some(String::from("longlong")),
|data, ptr| {
let value = match data {
LuaValue::Integer(t) => t,
_ => {
return Err(LuaError::external(format!(
"Integer expected, got {}",
data.type_name()
)))
}
} as c_longlong;
unsafe {
*(ptr.cast::<c_longlong>()) = value;
}
Ok(())
},
|lua: &Lua, ptr: *mut ()| {
let value = unsafe { (*ptr.cast::<c_longlong>()).into_lua(lua)? };
Ok(value)
},
)?
.into_lua(lua)?,
),
(
"char",
CType::new(
if CHAR_IS_SIGNED {
Type::c_schar()
} else {
Type::c_uchar()
},
Some(String::from("char")),
|data, ptr| {
let value = match data {
LuaValue::Integer(t) => t,
_ => {
return Err(LuaError::external(format!(
"Integer expected, got {}",
data.type_name()
)))
}
} as c_char;
unsafe {
*(ptr.cast::<c_char>()) = value;
}
Ok(())
},
|lua: &Lua, ptr: *mut ()| {
let value = unsafe { (*ptr.cast::<c_char>()).into_lua(lua)? };
Ok(value)
},
)?
.into_lua(lua)?,
),
])
}

View file

@ -0,0 +1,67 @@
// Memory range for ref or box data. For boundary checking
pub struct FfiRefBounds {
// Indicates how much data is above the pointer
pub(crate) high: usize,
// Indicates how much data is below the pointer
pub(crate) low: usize,
}
impl FfiRefBounds {
pub fn new(high: usize, low: usize) -> Self {
Self { high, low }
}
// Check boundary
pub fn check(&self, offset: isize) -> bool {
let sign = offset.signum();
let offset_abs = offset.unsigned_abs();
if sign == -1 {
self.high >= offset_abs
} else if sign == 1 {
self.low >= offset_abs
} else {
// sign == 0
true
}
}
// Check boundary
pub fn check_sized(&self, offset: isize, size: usize) -> bool {
let end = offset + (size as isize) - 1;
let sign = end.signum();
let end_abs = end.unsigned_abs();
if sign == -1 {
self.high >= end_abs
} else if sign == 1 {
self.low >= end_abs
} else {
// sign == 0
true
}
}
// Calculate new bounds from bounds and offset
// No boundary checking in here
pub fn offset(&self, offset: isize) -> Self {
let sign = offset.signum();
let offset_abs = offset.unsigned_abs();
let high: usize = if sign == -1 {
self.high - offset_abs
} else if sign == 1 {
self.high + offset_abs
} else {
self.high
};
let low: usize = if sign == -1 {
self.low + offset_abs
} else if sign == 1 {
self.low - offset_abs
} else {
self.low
};
Self { high, low }
}
}

View file

@ -11,12 +11,11 @@
use std::boxed::Box;
use core::ffi::c_void;
use mlua::prelude::*;
use super::association_names::BOX_REF_INNER;
use super::association_names::REF_INNER;
use super::ffi_association::set_association;
use super::ffi_ref::FfiRange;
use super::ffi_bounds::FfiRefBounds;
use super::ffi_ref::FfiRef;
pub struct FfiBox(Box<[u8]>);
@ -36,29 +35,10 @@ impl FfiBox {
Self(vec_heap.into_boxed_slice())
}
pub fn size(&self) -> usize {
self.0.len()
}
// pub fn copy(&self, target: &mut FfiBox) {}
pub fn get_ptr(&self) -> *mut c_void {
self.0.as_ptr() as *mut c_void
}
// Todo: if too big, print as another format
pub fn stringify(&self) -> String {
let mut buff = String::from(" ");
for i in &self.0 {
buff.push_str(i.to_string().as_str());
buff.push_str(", ");
}
buff.pop();
buff.pop();
buff.push(' ');
buff
}
pub fn binary_print(&self) -> String {
let mut buff: String = String::with_capacity(self.size() * 10 - 2);
for (pos, value) in self.0.iter().enumerate() {
for i in 0..8 {
@ -75,42 +55,51 @@ impl FfiBox {
buff
}
// bad naming. i have no idea what should i use
// Make FfiRef from box, with boundary checking
pub fn luaref<'lua>(
lua: &'lua Lua,
this: LuaAnyUserData<'lua>,
offset: Option<isize>,
) -> LuaResult<LuaAnyUserData<'lua>> {
let target = this.borrow::<FfiBox>()?;
let ptr = if let Some(t) = offset {
if t < 0 || t >= (target.size() as isize) {
let mut target = this.borrow_mut::<FfiBox>()?;
let mut bounds = FfiRefBounds::new(0, target.size());
let mut ptr = target.get_ptr();
// Calculate offset
if let Some(t) = offset {
if !bounds.check(t) {
return Err(LuaError::external(format!(
"Offset is out of bounds. box.size: {}. offset got {}",
target.size(),
t
)));
}
unsafe { target.get_ptr().offset(t) }
} else {
target.get_ptr()
};
ptr = unsafe { target.get_ptr().offset(t) };
bounds = bounds.offset(t);
}
let luaref = lua.create_userdata(FfiRef::new(
ptr,
Some(FfiRange {
low: 0,
high: target.size() as isize,
}),
))?;
let luaref = lua.create_userdata(FfiRef::new(ptr.cast(), Some(bounds)))?;
set_association(lua, BOX_REF_INNER, luaref.clone(), this.clone())?;
// Makes box alive longer then ref
set_association(lua, REF_INNER, &luaref, &this)?;
Ok(luaref)
}
// Fill every field with 0
pub fn zero(&mut self) {
self.0.fill(0u8);
}
// Get size of box
pub fn size(&self) -> usize {
self.0.len()
}
// Get raw ptr
pub fn get_ptr(&mut self) -> *mut u8 {
self.0.as_mut_ptr()
}
}
impl LuaUserData for FfiBox {
@ -119,6 +108,7 @@ impl LuaUserData for FfiBox {
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// For convenience, :zero returns self.
methods.add_function_mut("zero", |_, this: LuaAnyUserData| {
this.borrow_mut::<FfiBox>()?.zero();
Ok(this)
@ -130,8 +120,6 @@ impl LuaUserData for FfiBox {
Ok(luaref)
},
);
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
Ok(this.binary_print())
});
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(this.stringify()));
}
}

View file

@ -1,5 +1,3 @@
use std::ffi::c_void;
use mlua::prelude::*;
use super::ffi_box::FfiBox;
@ -13,12 +11,14 @@ pub const FFI_STATUS_NAMES: [&str; 4] = [
"ffi_status_FFI_BAD_ARGTYPE",
];
// Get raw pointer from userdata
// TODO: boundary check
pub unsafe fn get_ptr_from_userdata(
userdata: &LuaAnyUserData,
offset: Option<isize>,
) -> LuaResult<*mut c_void> {
) -> LuaResult<*mut ()> {
let ptr = if userdata.is::<FfiBox>() {
userdata.borrow::<FfiBox>()?.get_ptr()
userdata.borrow_mut::<FfiBox>()?.get_ptr().cast()
} else if userdata.is::<FfiRef>() {
userdata.borrow::<FfiRef>()?.get_ptr()
} else {
@ -26,7 +26,7 @@ pub unsafe fn get_ptr_from_userdata(
};
let ptr = if let Some(t) = offset {
ptr.offset(t)
ptr.cast::<u8>().offset(t).cast()
} else {
ptr
};

View file

@ -39,9 +39,9 @@ impl FfiLib {
.map_err(|err| LuaError::external(format!("{err}")))?
};
let luasym = lua.create_userdata(FfiRef::new(*sym, None))?;
let luasym = lua.create_userdata(FfiRef::new((*sym).cast(), None))?;
set_association(lua, SYM_INNER, luasym.clone(), this.clone())?;
set_association(lua, SYM_INNER, &luasym, &this)?;
Ok(luasym)
}

View file

@ -1,5 +1,5 @@
use core::ffi::c_void;
use std::{convert, mem::transmute, ptr};
// use core::ffi::c_void;
// use std::{convert, mem::transmute, ptr};
// This is raw data coming from outside.
// Users must convert it to a Lua value, reference, or box to use it.

View file

@ -1,10 +1,10 @@
use core::ffi::c_void;
use std::ptr;
use mlua::prelude::*;
use super::association_names::REF_INNER;
use super::ffi_association::set_association;
use super::ffi_association::{get_association, set_association};
use super::ffi_bounds::FfiRefBounds;
// A referenced space. It is possible to read and write through types.
// This operation is not safe. This may cause a memory error in Lua
@ -12,22 +12,17 @@ use super::ffi_association::set_association;
// If it references an area managed by Lua,
// the box will remain as long as this reference is alive.
pub struct FfiRange {
pub(crate) high: isize,
pub(crate) low: isize,
}
pub struct FfiRef {
ptr: *mut c_void,
range: Option<FfiRange>,
ptr: *mut (),
range: Option<FfiRefBounds>,
}
impl FfiRef {
pub fn new(ptr: *mut c_void, range: Option<FfiRange>) -> Self {
pub fn new(ptr: *mut (), range: Option<FfiRefBounds>) -> Self {
Self { ptr, range }
}
// bad naming. i have no idea what should i use
// Make FfiRef from ref
pub fn luaref<'lua>(
lua: &'lua Lua,
this: LuaAnyUserData<'lua>,
@ -35,52 +30,59 @@ impl FfiRef {
let target = this.borrow::<FfiRef>()?;
let luaref = lua.create_userdata(FfiRef::new(
ptr::from_ref(&target.ptr) as *mut c_void,
Some(FfiRange {
ptr::from_ref(&target.ptr) as *mut (),
Some(FfiRefBounds {
low: 0,
high: size_of::<usize>() as isize,
high: size_of::<usize>(),
}),
))?;
set_association(lua, REF_INNER, luaref.clone(), this.clone())?;
// If the ref holds a box, make sure the new ref also holds the box
if let Some(t) = get_association(lua, REF_INNER, &this)? {
set_association(lua, REF_INNER, &luaref, t)?;
}
Ok(luaref)
}
pub fn get_ptr(&self) -> *mut c_void {
pub fn get_ptr(&self) -> *mut () {
self.ptr
}
pub unsafe fn deref(&self) -> Self {
Self::new(*self.ptr.cast::<*mut c_void>(), None)
Self::new(*self.ptr.cast::<*mut ()>(), None)
}
pub unsafe fn offset(&self, offset: isize) -> LuaResult<Self> {
let range = if let Some(ref t) = self.range {
let high = t.high - offset;
let low = t.low - offset;
if low > 0 || high < 0 {
if let Some(ref t) = self.range {
if !t.check(offset) {
return Err(LuaError::external(format!(
"Offset is out of bounds. low: {}, high: {}. offset got {}",
t.low, t.high, offset
"Offset is out of bounds. high: {}, low: {}. offset got {}",
t.high, t.low, offset
)));
}
}
let range = self.range.as_ref().map(|t| t.offset(offset));
Some(FfiRange { high, low })
} else {
None
};
Ok(Self::new(self.ptr.offset(offset), range))
Ok(Self::new(
self.ptr.cast::<u8>().offset(offset).cast(),
range,
))
}
}
impl LuaUserData for FfiRef {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("deref", |_, this, ()| {
let ffiref = unsafe { this.deref() };
Ok(ffiref)
methods.add_function("deref", |lua, this: LuaAnyUserData| {
let inner = get_association(lua, REF_INNER, &this)?;
let ffiref = this.borrow::<FfiRef>()?;
let result = lua.create_userdata(unsafe { ffiref.deref() })?;
if let Some(t) = inner {
set_association(lua, REF_INNER, &result, &t)?;
}
Ok(result)
});
methods.add_method("offset", |_, this, offset: isize| {
let ffiref = unsafe { this.offset(offset)? };

View file

@ -1,4 +1,5 @@
pub(super) mod ffi_association;
pub(super) mod ffi_bounds;
pub(super) mod ffi_box;
pub(super) mod ffi_helper;
pub(super) mod ffi_lib;
@ -8,6 +9,5 @@ pub(super) mod ffi_ref;
// Named registry table names
mod association_names {
pub const BOX_REF_INNER: &str = "__box_ref";
pub const REF_INNER: &str = "__ref_inner";
}

View file

@ -6,13 +6,10 @@ use mlua::prelude::*;
mod c;
mod ffi;
use crate::c::c_fn::CFn;
use crate::c::c_struct::CStruct;
use crate::c::c_type::create_all_types;
use crate::ffi::ffi_association::get_table;
use crate::ffi::ffi_box::FfiBox;
use crate::ffi::ffi_lib::FfiLib;
use crate::ffi::ffi_platform::get_platform_value;
use crate::c::{c_fn::CFn, c_struct::CStruct, create_all_types};
use crate::ffi::{
ffi_association::get_table, ffi_box::FfiBox, ffi_lib::FfiLib, ffi_platform::get_platform_value,
};
/**
Creates the `ffi` standard library module.

View file

@ -1,11 +1,3 @@
use libffi::raw::{ffi_cif, ffi_ptrarray_to_raw};
// pub fn ffi_get_struct_offsets(
// abi: ffi_abi,
// struct_type: *mut ffi_type,
// offsets: *mut usize,
// ) -> ffi_status;
- last thing to do
- [ ] Add tests
- [ ] Add docs
@ -23,8 +15,9 @@ use libffi::raw::{ffi_cif, ffi_ptrarray_to_raw};
- [x] ffi.box(size)
- [x] .size
- [x] :zero()
- [?] :ref(offset?=0) => ref
- offset is not impled
- [x] :ref(offset?=0) => ref
- [x] tostring
- [~] :copy(box,size?=-1,offset?=0)
- working on it