diff --git a/crates/lune-std-ffi/src/c/fn_info.rs b/crates/lune-std-ffi/src/c/fn_info.rs
index 43b7f54..2994499 100644
--- a/crates/lune-std-ffi/src/c/fn_info.rs
+++ b/crates/lune-std-ffi/src/c/fn_info.rs
@@ -43,7 +43,6 @@ impl FfiSignedness for CFnInfo {
         false
     }
 }
-
 impl FfiSize for CFnInfo {
     fn get_size(&self) -> usize {
         SIZE_OF_POINTER
@@ -203,6 +202,9 @@ impl CFnInfo {
 }
 
 impl LuaUserData for CFnInfo {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("size", |_, _| Ok(SIZE_OF_POINTER));
+    }
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
         // Subtype
         method_provider::provide_ptr(methods);
diff --git a/crates/lune-std-ffi/src/c/ptr_info.rs b/crates/lune-std-ffi/src/c/ptr_info.rs
index f39b425..0758ba8 100644
--- a/crates/lune-std-ffi/src/c/ptr_info.rs
+++ b/crates/lune-std-ffi/src/c/ptr_info.rs
@@ -124,7 +124,7 @@ impl CPtrInfo {
 
 impl LuaUserData for CPtrInfo {
     fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
-        fields.add_field_method_get("size", |_, _| Ok(size_of::<usize>()));
+        fields.add_field_method_get("size", |_, _| Ok(SIZE_OF_POINTER));
         fields.add_field_function_get("inner", |lua, this| {
             let inner = association::get(lua, CPTR_INNER, this)?
                 .ok_or_else(|| LuaError::external("inner type not found"))?;
diff --git a/crates/lune-std-ffi/src/c/void_info.rs b/crates/lune-std-ffi/src/c/void_info.rs
index 05ad57d..97a2fa2 100644
--- a/crates/lune-std-ffi/src/c/void_info.rs
+++ b/crates/lune-std-ffi/src/c/void_info.rs
@@ -31,6 +31,9 @@ impl CVoidInfo {
 }
 
 impl LuaUserData for CVoidInfo {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("size", |_, _| Ok(0));
+    }
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
         method_provider::provide_to_string(methods);
         method_provider::provide_ptr(methods);
diff --git a/crates/lune/src/tests.rs b/crates/lune/src/tests.rs
index d5f5640..acce10e 100644
--- a/crates/lune/src/tests.rs
+++ b/crates/lune/src/tests.rs
@@ -99,6 +99,24 @@ create_tests! {
     datetime_to_universal_time: "datetime/toUniversalTime",
 }
 
+#[cfg(feature = "std-ffi")]
+create_tests! {
+    ffi_external_closure_call_closure: "ffi/external_closure/callClosure",
+    ffi_external_closure_call_closure_with_pointer: "ffi/external_closure/callClosureWithPointer",
+    ffi_external_closure_call_hello_world: "ffi/external_closure/callHelloWorld",
+    ffi_external_math_add_int: "ffi/external_math/addInt",
+    ffi_external_math_mul_int: "ffi/external_math/mulInt",
+    ffi_external_pointer_pointer_read: "ffi/external_pointer/pointerRead",
+    ffi_external_pointer_pointer_write: "ffi/external_pointer/pointerWrite",
+    ffi_external_print_hello_world: "ffi/external_print/helloWorld",
+    ffi_external_struct_ab: "ffi/external_struct/ab",
+    ffi_cast: "ffi/cast",
+    ffi_is_integer: "ffi/isInteger",
+    ffi_pretty_print: "ffi/prettyPrint",
+    ffi_read_boundary: "ffi/readBoundary",
+    ffi_write_boundary: "ffi/writeBoundary",
+}
+
 #[cfg(feature = "std-fs")]
 create_tests! {
     fs_files: "fs/files",
diff --git a/print-ignore-me.luau b/print-ignore-me.luau
deleted file mode 100644
index 3686c9e..0000000
--- a/print-ignore-me.luau
+++ /dev/null
@@ -1,12 +0,0 @@
-local ffi = require("@lune/ffi")
-
-print(ffi.int)
-print(ffi.int:ptr())
-print(ffi.int:arr(5):ptr())
-print(ffi.int:arr(5))
-
-print(ffi.funcInfo({ ffi.int }, ffi.int))
-print(ffi.funcInfo({ ffi.int, ffi.double }, ffi.int:ptr()))
-print(ffi.funcInfo({ ffi.int, ffi.double }, ffi.int:ptr():ptr()))
-
-print(ffi.structInfo({ ffi.int, ffi.char }))
diff --git a/tests/ffi/benchmark/external_call/deno.ts b/tests/ffi/benchmark/external_call/deno.ts
index 46b56e0..7332ae1 100644
--- a/tests/ffi/benchmark/external_call/deno.ts
+++ b/tests/ffi/benchmark/external_call/deno.ts
@@ -1,5 +1,5 @@
-import { libSuffix } from "../../utility/deno.ts";
-import { get_clock, get_offset } from "../../utility/proc_clock/deno.ts";
+import { libSuffix } from "../../utils/libSuffix.ts";
+import { get_clock, get_offset } from "../../utils/proc_clock/deno.ts";
 
 const library_file = "./tests/ffi/benchmark/external_call/lib."+libSuffix;
 // @ts-ignore
diff --git a/tests/ffi/benchmark/external_call/init.luau b/tests/ffi/benchmark/external_call/init.luau
index 3c4eef6..6df592b 100644
--- a/tests/ffi/benchmark/external_call/init.luau
+++ b/tests/ffi/benchmark/external_call/init.luau
@@ -1,35 +1,27 @@
 local ffi = require("@lune/ffi")
+local lib = require("../../utility/compile")("./tests/ffi/benchmark/external_call/lib.c")
+local process = require("@lune/process")
 local c = ffi.c
+local BENCH_SCALE: number = tonumber(process.env.BENCH_SCALE) or 1000000
 
-local proc_clock = require("../../utility/proc_clock")
-local before, after = proc_clock.newBox()
-local get_clock = proc_clock.getClock
+-- Get clock provider
+local procClock = require("../../utility/proc_clock")
+local before, after = procClock.newBox()
+local getClock = procClock.getClock
 
-local testdir = "./tests/ffi/benchmark/external_call"
-local compile = require("../../utility/compile")
-compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
-local lib = ffi.open(`{testdir}/lib.so`)
+local add = c.fn({ c.int, c.int }, c.int):callable(lib:find("add"))
 
-local function bench_add(bench_scale: number)
-	local add_info = c.fn({ c.int, c.int }, c.int)
+local a = c.int:box(0)
+local delta = c.int:box(1)
+local a_ref = a:ref()
+local delta_ref = delta:ref()
 
-	local add_callable = add_info:callable(lib:find("add"))
-
-	local a = c.int:box(0)
-	local delta = c.int:box(1)
-
-	local a_ref = a:ref()
-	local delta_ref = delta:ref()
-
-	get_clock(before)
-	for i = 1, bench_scale do
-		add_callable(a, a_ref, delta_ref)
-	end
-	get_clock(after)
-	print(proc_clock.getOffset(before, after))
-
-	local result = c.int:readData(a)
-	assert(result == bench_scale, `bench_add failed. result expected {bench_scale}, got {result}`)
+getClock(before)
+for i = 1, BENCH_SCALE do
+	add(a, a_ref, delta_ref)
 end
+getClock(after)
 
-bench_add(1000000)
+print(procClock.getOffset(before, after))
+local result = c.int:readData(a)
+assert(result == BENCH_SCALE, `bench_add failed. result expected {BENCH_SCALE}, got {result}`)
diff --git a/tests/ffi/benchmark/external_call/luajit.lua b/tests/ffi/benchmark/external_call/luajit.lua
index e5dc41a..b1eb6f4 100644
--- a/tests/ffi/benchmark/external_call/luajit.lua
+++ b/tests/ffi/benchmark/external_call/luajit.lua
@@ -2,26 +2,23 @@
 --!nocheck
 
 local ffi = require("ffi")
+local BENCH_SCALE = 1000000
 
-local function bench_add(bench_scale)
-	ffi.cdef([[
-		int add(int a, int b);
-    ]])
-	local lib = ffi.load("./tests/ffi/benchmark/external_call/lib.so")
-	local add = lib.add
-	local a = 0
+ffi.cdef([[
+	int add(int a, int b);
+]])
+local lib = ffi.load("./tests/ffi/benchmark/external_call/lib.so")
+local add = lib.add
+local a = 0
 
-	local before = os.clock()
-	for i = 1, bench_scale do
-		a = add(a, 1)
-	end
-	local after = os.clock()
-
-	print(after - before)
-	assert(
-		a == bench_scale,
-		string.format("bench_add failed. result expected %d, got %d", bench_scale, a)
-	)
+local before = os.clock()
+for i = 1, BENCH_SCALE do
+	a = add(a, 1)
 end
+local after = os.clock()
 
-bench_add(1000000)
+print(after - before)
+assert(
+	a == BENCH_SCALE,
+	string.format("bench_add failed. result expected %d, got %d", BENCH_SCALE, a)
+)
diff --git a/tests/ffi/external_closure/callClosure.luau b/tests/ffi/external_closure/callClosure.luau
new file mode 100644
index 0000000..b73fe6c
--- /dev/null
+++ b/tests/ffi/external_closure/callClosure.luau
@@ -0,0 +1,14 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_closure/lib.c")
+local c = ffi.c
+
+-- Create closure
+local closureInfo = c.fn({ c.int, c.int }, c.int)
+local closure = closureInfo:closure(function(ret, a, b)
+	c.int:writeData(ret, c.int:readData(a) + c.int:readData(b))
+end)
+
+local callClosure = callableWrapper(lib:find("call_closure"), { closureInfo }, c.int)
+local result = callClosure(closure:ref())
+assert(result == 72, `callClosure failed. result expected 20000, got {result}`)
diff --git a/tests/ffi/external_closure/callClosureWithPointer.luau b/tests/ffi/external_closure/callClosureWithPointer.luau
new file mode 100644
index 0000000..5fd47b9
--- /dev/null
+++ b/tests/ffi/external_closure/callClosureWithPointer.luau
@@ -0,0 +1,15 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_closure/lib.c")
+local c = ffi.c
+
+-- Create closure
+local closureWithPointerInfo = c.fn({ c.int, c.int:ptr() }, c.int)
+local closureWithPointer = closureWithPointerInfo:closure(function(returnRef, aRef, bRef)
+	c.int:writeData(returnRef, c.int:readData(aRef) + c.int:readData(bRef:deref()))
+end)
+
+local callClosureWithPointer =
+	callableWrapper(lib:find("call_closure_with_pointer"), { closureWithPointerInfo }, c.int)
+local result = callClosureWithPointer(closureWithPointer:ref())
+assert(result == 72, `closureWithPointer failed. result expected 20000, got {result}`)
diff --git a/tests/ffi/external_closure/callHelloWorld.luau b/tests/ffi/external_closure/callHelloWorld.luau
new file mode 100644
index 0000000..811601d
--- /dev/null
+++ b/tests/ffi/external_closure/callHelloWorld.luau
@@ -0,0 +1,12 @@
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_closure/lib.c")
+local c = ffi.c
+
+-- Create closure
+local helloWorldInfo = c.fn({}, c.void)
+local helloWorld = helloWorldInfo:closure(function()
+	print("Hello world in lua closure!")
+end)
+
+local callHelloWorld = c.fn({ helloWorldInfo }, c.void):callable(lib:find("call_hello_world"))
+callHelloWorld(nil, helloWorld:ref())
diff --git a/tests/ffi/external_closure/init.luau b/tests/ffi/external_closure/init.luau
deleted file mode 100644
index f442b9e..0000000
--- a/tests/ffi/external_closure/init.luau
+++ /dev/null
@@ -1,52 +0,0 @@
-local ffi = require("@lune/ffi")
-local c = ffi.c
-
-local testdir = "./tests/ffi/external_closure"
-local compile = require("../utility/compile")
-compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
-local lib = ffi.open(`{testdir}/lib.so`)
-
-local function test_callClosure()
-	local closureInfo = c.fn({ c.int, c.int }, c.int)
-	local closure = closureInfo:closure(function(ret, a, b)
-		c.int:writeData(ret, c.int:readData(a) + c.int:readData(b))
-	end)
-
-	local callClosure = c.fn({ closureInfo }, c.int):callable(lib:find("call_closure"))
-
-	local resultBox = ffi.box(c.int.size)
-	callClosure(resultBox, closure:ref())
-	local result = c.int:readData(resultBox)
-	assert(result == 72, `test_callClosure failed. result expected 20000, got {result}`)
-end
-
-test_callClosure()
-
-local function test_helloWorld()
-	local helloWorldInfo = c.fn({}, c.void)
-	local helloWorld = helloWorldInfo:closure(function()
-		print("Hello world in lua closure!")
-	end)
-
-	local callHelloWorld = c.fn({ helloWorldInfo }, c.void):callable(lib:find("call_hello_world"))
-	callHelloWorld(nil, helloWorld:ref())
-end
-
-test_helloWorld()
-
-local function test_closureWithPointer()
-	local closureWithPointerInfo = c.fn({ c.int, c.int:ptr() }, c.int)
-	local closureWithPointer = closureWithPointerInfo:closure(function(returnRef, aRef, bRef)
-		c.int:writeData(returnRef, c.int:readData(aRef) + c.int:readData(bRef:deref()))
-	end)
-
-	local callClosureWithPointer = c.fn({ closureWithPointerInfo }, c.int)
-		:callable(lib:find("call_closure_with_pointer"))
-
-	local resultBox = ffi.box(c.int.size)
-	callClosureWithPointer(resultBox, closureWithPointer:ref())
-	local result = c.int:readData(resultBox)
-	assert(result == 72, `test_closureWithPointer failed. result expected 20000, got {result}`)
-end
-
-test_closureWithPointer()
diff --git a/tests/ffi/external_math/addInt.luau b/tests/ffi/external_math/addInt.luau
new file mode 100644
index 0000000..d63f313
--- /dev/null
+++ b/tests/ffi/external_math/addInt.luau
@@ -0,0 +1,9 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_math/lib.c")
+local c = ffi.c
+
+local addInt = callableWrapper(lib:find("add_int"), { c.int, c.int }, c.int)
+local result = addInt(100, 200)
+
+assert(result == 300, `test_addInt failed. result expected 300, got {result}`)
diff --git a/tests/ffi/external_math/init.luau b/tests/ffi/external_math/init.luau
deleted file mode 100644
index f4ce95f..0000000
--- a/tests/ffi/external_math/init.luau
+++ /dev/null
@@ -1,37 +0,0 @@
-local ffi = require("@lune/ffi")
-local c = ffi.c
-
-local testdir = "./tests/ffi/external_math"
-local compile = require("../utility/compile")
-compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
-local lib = ffi.open(`{testdir}/lib.so`)
-
-local function test_addInt()
-	local addInt = c.fn({ c.int, c.int }, c.int):callable(lib:find("add_int"))
-
-	local resultBox = ffi.box(c.int.size)
-	local arg1 = c.int:box(100)
-	local arg2 = c.int:box(200)
-
-	addInt(resultBox, arg1:ref(), arg2:ref())
-	local result = c.int:readData(resultBox)
-
-	assert(result == 300, `test_addInt failed. result expected 300, got {result}`)
-end
-
-test_addInt()
-
-local function test_mulInt()
-	local mulInt = c.fn({ c.int, c.int }, c.int):callable(lib:find("mul_int"))
-
-	local resultBox = ffi.box(c.int.size)
-	local arg1 = c.int:box(100)
-	local arg2 = c.int:box(200)
-
-	mulInt(resultBox, arg1:ref(), arg2:ref())
-	local result = c.int:readData(resultBox)
-
-	assert(result == 20000, `test_mulInt failed. result expected 20000, got {result}`)
-end
-
-test_mulInt()
diff --git a/tests/ffi/external_math/mulInt.luau b/tests/ffi/external_math/mulInt.luau
new file mode 100644
index 0000000..e14a86b
--- /dev/null
+++ b/tests/ffi/external_math/mulInt.luau
@@ -0,0 +1,8 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_math/lib.c")
+local c = ffi.c
+
+local mulInt = callableWrapper(lib:find("mul_int"), { c.int, c.int }, c.int)
+local result = mulInt(100, 200)
+assert(result == 20000, `test_mulInt failed. result expected 20000, got {result}`)
diff --git a/tests/ffi/external_pointer/init.luau b/tests/ffi/external_pointer/init.luau
deleted file mode 100644
index 8e0e988..0000000
--- a/tests/ffi/external_pointer/init.luau
+++ /dev/null
@@ -1,20 +0,0 @@
-local ffi = require("@lune/ffi")
-local c = ffi.c
-
-local testdir = "./tests/ffi/external_pointer"
-local compile = require("../utility/compile")
-compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
-local lib = ffi.open(`{testdir}/lib.so`)
-
-local function test_pointerWrite()
-	local pointerWrite = c.fn({ c.int:ptr() }, c.void):callable(lib:find("pointer_write"))
-
-	local aBox = ffi.box(c.int.size)
-	pointerWrite(nil, aBox:ref():ref())
-
-	local result = c.int:readData(aBox)
-
-	assert(result == 123, `pointer failed. result expected 123, got {result}`)
-end
-
-test_pointerWrite()
diff --git a/tests/ffi/external_pointer/lib.c b/tests/ffi/external_pointer/lib.c
index 697fd09..7d7440a 100644
--- a/tests/ffi/external_pointer/lib.c
+++ b/tests/ffi/external_pointer/lib.c
@@ -1,3 +1,7 @@
 void pointer_write(int *a) {
     *a = 123;
 }
+
+int pointer_read(int *a) {
+    return *a;
+}
diff --git a/tests/ffi/external_pointer/pointerRead.luau b/tests/ffi/external_pointer/pointerRead.luau
new file mode 100644
index 0000000..e2490a3
--- /dev/null
+++ b/tests/ffi/external_pointer/pointerRead.luau
@@ -0,0 +1,8 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_pointer/lib.c")
+local c = ffi.c
+
+local pointerRead = callableWrapper(lib:find("pointer_read"), { c.int:ptr() }, c.int)
+local result = pointerRead(c.int:box(123):ref():ref())
+assert(result == 123, `pointerRead failed. result expected 123, got {result}`)
diff --git a/tests/ffi/external_pointer/pointerWrite.luau b/tests/ffi/external_pointer/pointerWrite.luau
new file mode 100644
index 0000000..72a9b40
--- /dev/null
+++ b/tests/ffi/external_pointer/pointerWrite.luau
@@ -0,0 +1,9 @@
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_pointer/lib.c")
+local c = ffi.c
+
+local pointerWrite = c.fn({ c.int:ptr() }, c.void):callable(lib:find("pointer_write"))
+local aBox = ffi.box(c.int.size)
+pointerWrite(nil, aBox:ref():ref())
+local result = c.int:readData(aBox)
+assert(result == 123, `pointerWrite failed. result expected 123, got {result}`)
diff --git a/tests/ffi/external_print/helloWorld.luau b/tests/ffi/external_print/helloWorld.luau
new file mode 100644
index 0000000..665283b
--- /dev/null
+++ b/tests/ffi/external_print/helloWorld.luau
@@ -0,0 +1,5 @@
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_print/lib.c")
+local c = ffi.c
+
+c.fn({}, c.void):callable(lib:find("hello_world"))(nil)
diff --git a/tests/ffi/external_print/init.luau b/tests/ffi/external_print/init.luau
deleted file mode 100644
index 174fdc2..0000000
--- a/tests/ffi/external_print/init.luau
+++ /dev/null
@@ -1,17 +0,0 @@
-local ffi = require("@lune/ffi")
-local c = ffi.c
-
-local testdir = "./tests/ffi/external_print"
-local compile = require("../utility/compile")
-compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
-local lib = ffi.open(`{testdir}/lib.so`)
-
-local function test_hello_world()
-	local hello_world_info = c.fn({}, c.void)
-
-	local hello_world_callable = hello_world_info:callable(lib:find("hello_world"))
-
-	hello_world_callable(nil)
-end
-
-test_hello_world()
diff --git a/tests/ffi/external_struct/ab.luau b/tests/ffi/external_struct/ab.luau
new file mode 100644
index 0000000..114b74c
--- /dev/null
+++ b/tests/ffi/external_struct/ab.luau
@@ -0,0 +1,12 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_struct/lib.c")
+local c = ffi.c
+
+local argStructInfo = c.struct({ c.int, c.int:ptr() })
+local resultStructInfo = c.struct({ c.int, c.int })
+
+local ab = callableWrapper(lib:find("ab"), { argStructInfo }, resultStructInfo)
+local result = ab({ 100, c.int:box(200):ref() } :: { any })
+assert(result[1] == 300, `ab failed. result expected 300, got {result[1]}`)
+assert(result[2] == 20000, `ab failed. result expected 300, got {result[2]}`)
diff --git a/tests/ffi/external_struct/init.luau b/tests/ffi/external_struct/init.luau
deleted file mode 100644
index 109ff49..0000000
--- a/tests/ffi/external_struct/init.luau
+++ /dev/null
@@ -1,28 +0,0 @@
-local ffi = require("@lune/ffi")
-local c = ffi.c
-
-local testdir = "./tests/ffi/external_struct"
-local compile = require("../utility/compile")
-compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
-local lib = ffi.open(`{testdir}/lib.so`)
-
-local function test_AB()
-	local argStructInfo = c.struct({ c.int, c.int:ptr() })
-	local resultStructInfo = c.struct({ c.int, c.int })
-
-	local AB = c.fn({ argStructInfo }, resultStructInfo)
-
-	local AB_callable = AB:callable(lib:find("AB"))
-
-	local resultBox = ffi.box(resultStructInfo.size)
-	local b = c.int:box(200)
-	local argStruct = argStructInfo:box({ 100, b:ref() })
-
-	AB_callable(resultBox, argStruct:ref())
-	local result = resultStructInfo:readData(resultBox)
-
-	assert(result[1] == 300, `AB failed. result expected 300, got {result[1]}`)
-	assert(result[2] == 20000, `AB failed. result expected 300, got {result[2]}`)
-end
-
-test_AB()
diff --git a/tests/ffi/external_struct/lib.c b/tests/ffi/external_struct/lib.c
index b542d5d..f68a067 100644
--- a/tests/ffi/external_struct/lib.c
+++ b/tests/ffi/external_struct/lib.c
@@ -8,7 +8,7 @@ typedef struct {
     int mul;
 } ResultStruct;
 
-ResultStruct AB(ArgStruct t) {
+ResultStruct ab(ArgStruct t) {
     ResultStruct result = { t.a+ * t.b, t.a * (*t.b) };
     return result;
 }
diff --git a/tests/ffi/pretty_print.luau b/tests/ffi/prettyPrint.luau
similarity index 100%
rename from tests/ffi/pretty_print.luau
rename to tests/ffi/prettyPrint.luau
diff --git a/tests/ffi/read_boundary.luau b/tests/ffi/readBoundary.luau
similarity index 99%
rename from tests/ffi/read_boundary.luau
rename to tests/ffi/readBoundary.luau
index bc57357..dd8e3d2 100644
--- a/tests/ffi/read_boundary.luau
+++ b/tests/ffi/readBoundary.luau
@@ -1,5 +1,4 @@
 local ffi = require("@lune/ffi")
-
 local ok
 
 -- Case1: Success
@@ -24,7 +23,6 @@ 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)
diff --git a/tests/ffi/utility/compile.luau b/tests/ffi/utility/compile.luau
deleted file mode 100644
index 5cb2434..0000000
--- a/tests/ffi/utility/compile.luau
+++ /dev/null
@@ -1,9 +0,0 @@
-local process = require("@lune/process")
-local function compile(file, out)
-	local gcc = process.exec("gcc", { "-shared", "-o", out, "-fPIC", file })
-	if not gcc.ok then
-		error("Failed to execute gcc command\n" .. gcc.stdout .. gcc.stderr)
-	end
-end
-
-return compile
diff --git a/tests/ffi/utils/callableWrapper.luau b/tests/ffi/utils/callableWrapper.luau
new file mode 100644
index 0000000..ffa9597
--- /dev/null
+++ b/tests/ffi/utils/callableWrapper.luau
@@ -0,0 +1,38 @@
+--!nocheck
+local ffi = require("@lune/ffi")
+
+local function callableWrapper(
+	functionRef: ffi.RefData,
+	argTypeList: { ffi.CTypes },
+	retType: ffi.CTypes
+): (...any) -> any
+	local callable = ffi.c.fn(argTypeList, retType):callable(functionRef)
+
+	return function(...)
+		local argValues = table.create(#argTypeList + 1)
+
+		local resultBox
+		if retType ~= ffi.c.void then
+			resultBox = ffi.box(retType.size)
+		end
+		argValues[1] = resultBox
+
+		for index, argType in argTypeList do
+			local arg = select(index, ...)
+			if type(arg) == "userdata" then
+				argValues[index + 1] = arg
+			else
+				argValues[index + 1] = argType:box(arg):ref()
+			end
+		end
+
+		callable(table.unpack(argValues, 1, #argTypeList + 1))
+
+		if retType == ffi.c.void then
+			return nil
+		end
+		return retType:readData(resultBox)
+	end
+end
+
+return callableWrapper
diff --git a/tests/ffi/utils/compile.luau b/tests/ffi/utils/compile.luau
new file mode 100644
index 0000000..624ddaf
--- /dev/null
+++ b/tests/ffi/utils/compile.luau
@@ -0,0 +1,25 @@
+local ffi = require("@lune/ffi")
+local process = require("@lune/process")
+
+local function getLibSuffix(): string
+	if process.os == "linux" then
+		return "so"
+	elseif process.os == "windows" then
+		return "dll"
+	elseif process.os == "macos" then
+		return "dylib"
+	end
+	error("Unknown OS")
+end
+
+local function compile(file: string): ffi.LibData
+	local out = file:gsub("%.c$", "." .. getLibSuffix())
+	local gcc = process.exec("gcc", { "-shared", "-o", out, "-fPIC", file })
+	if not gcc.ok then
+		error("Failed to execute gcc command\n" .. gcc.stdout .. gcc.stderr)
+	end
+
+	return ffi.open(out)
+end
+
+return compile
diff --git a/tests/ffi/utility/deno.ts b/tests/ffi/utils/libSuffix.ts
similarity index 100%
rename from tests/ffi/utility/deno.ts
rename to tests/ffi/utils/libSuffix.ts
diff --git a/tests/ffi/utility/proc_clock/deno.ts b/tests/ffi/utils/proc_clock/deno.ts
similarity index 83%
rename from tests/ffi/utility/proc_clock/deno.ts
rename to tests/ffi/utils/proc_clock/deno.ts
index d5c8d14..23ea8d4 100644
--- a/tests/ffi/utility/proc_clock/deno.ts
+++ b/tests/ffi/utils/proc_clock/deno.ts
@@ -1,6 +1,6 @@
-import { libSuffix } from "../deno.ts";
+import { libSuffix } from "../libSuffix.ts";
 
-const library_file = "./tests/ffi/utility/proc_clock/lib."+libSuffix;
+const library_file = "./tests/ffi/utils/proc_clock/lib."+libSuffix;
 // @ts-ignore
 let library = Deno.dlopen(library_file, {
     sizeof_clock: {
diff --git a/tests/ffi/utility/proc_clock/init.luau b/tests/ffi/utils/proc_clock/init.luau
similarity index 89%
rename from tests/ffi/utility/proc_clock/init.luau
rename to tests/ffi/utils/proc_clock/init.luau
index 720cbd6..8877a4c 100644
--- a/tests/ffi/utility/proc_clock/init.luau
+++ b/tests/ffi/utils/proc_clock/init.luau
@@ -7,10 +7,7 @@ local c = ffi.c
 
 local procClock = {}
 
-local libdir = "./tests/ffi/utility/proc_clock"
-local compile = require("../compile")
-compile(`{libdir}/lib.c`, `{libdir}/lib.so`)
-local lib = ffi.open(`{libdir}/lib.so`)
+local lib = require("../compile")("./tests/ffi/utils/proc_clock/lib.c")
 
 -- sizeof_clock
 local sizeofClock = c.fn({}, c.int):callable(lib:find("sizeof_clock"))
diff --git a/tests/ffi/utility/proc_clock/lib.c b/tests/ffi/utils/proc_clock/lib.c
similarity index 100%
rename from tests/ffi/utility/proc_clock/lib.c
rename to tests/ffi/utils/proc_clock/lib.c
diff --git a/tests/ffi/write_boundary.luau b/tests/ffi/writeBoundary.luau
similarity index 99%
rename from tests/ffi/write_boundary.luau
rename to tests/ffi/writeBoundary.luau
index 53291d6..afb2b16 100644
--- a/tests/ffi/write_boundary.luau
+++ b/tests/ffi/writeBoundary.luau
@@ -1,6 +1,5 @@
 local ffi = require("@lune/ffi")
 local c = ffi.c
-
 local ok
 
 -- Case1: Fail
diff --git a/types/ffi.luau b/types/ffi.luau
index f21b4bf..a60c6ee 100644
--- a/types/ffi.luau
+++ b/types/ffi.luau
@@ -42,15 +42,13 @@ local ffi = {}
 
 --[=[
 	@class C
-	@within FFI
 
 	Namespace for compile time sized c types.
 ]=]
 local c = {}
 ffi.c = c
 
---#region Data --
-
+--#region Data
 --[=[
 	@class RefData
 	
@@ -124,7 +122,13 @@ export type RefData = {
 		@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)->();
+	copyFrom: (
+		self: RefData,
+		src: BoxData | RefData,
+		length: number,
+		dst_offset: number,
+		src_offset: number
+	) -> (),
 }
 
 --[=[
@@ -190,7 +194,13 @@ export type BoxData = {
 		@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)->();
+	copyFrom: (
+		self: BoxData,
+		src: BoxData | RefData,
+		length: number,
+		dst_offset: number,
+		src_offset: number
+	) -> (),
 }
 
 --[=[
@@ -223,7 +233,10 @@ export type LibData = {
 
 	If return type is `void`, pass `nil`.
 ]=]
-export type CallableData = (ret: (RefData|BoxData)?, ...RefData)->() & {
+export type CallableData = (
+	ret: (RefData | BoxData)?,
+	...RefData
+) -> () & {
 	-- apply: (self: Callable, args: Args)->AppliedCallable,
 }
 
@@ -246,13 +259,11 @@ export type ClosureData = {
 
 		@return A reference of the closure
 	]=]
-	ref: (self: ClosureData)->RefData,
+	ref: (self: ClosureData) -> RefData,
 }
+--#endregion Data
 
---#endregion Data --
-
---#region C ABI Type Infos --
-
+--#region C ABI Type Infos
 -- NOTE: T is a unique identifier for the `CType` and R is the closest Lua type.
 export type CTypeInfo<T, R> = {
 	--[=[
@@ -278,14 +289,19 @@ export type CTypeInfo<T, R> = {
 
 	-- realize
 	box: (self: CTypeInfo<T, R>, val: R) -> BoxData,
-	readData: (self: CTypeInfo<T, R>, target: (RefData|BoxData), offset: number?) -> R,
-	writeData: (self: CTypeInfo<T, R>, target: (RefData|BoxData), value: R, offset: number?) -> (),
-	stringifyData: (self: CTypeInfo<T, R>, target: (RefData|BoxData), offset: number?) -> string,
+	readData: (self: CTypeInfo<T, R>, target: RefData | BoxData, offset: number?) -> R,
+	writeData: (self: CTypeInfo<T, R>, target: RefData | BoxData, value: R, offset: number?) -> (),
+	stringifyData: (self: CTypeInfo<T, R>, target: RefData | BoxData, offset: number?) -> string,
 
 	-- FIXME: recursive types; 'intoType' should be CTypes
-	cast: (self: CTypeInfo<T, R>, intoType: any, fromData: (RefData|BoxData), intoData: (RefData|BoxData)) -> (),
+	cast: (
+		self: CTypeInfo<T, R>,
+		intoType: any,
+		fromData: RefData | BoxData,
+		intoData: RefData | BoxData
+	) -> (),
 } & { ["__phantom"]: T }
-type NumCType<T> = CTypeInfo<T, (number|any)>
+type NumCType<T> = CTypeInfo<T, (number | any)>
 
 export type CPtrInfo<T> = {
 	--[=[
@@ -313,8 +329,13 @@ export type CPtrInfo<T> = {
 	-- FIXME: recursive types; result 'any' should be CPtrInfo<CPtrInfo<T>>
 	ptr: (self: CPtrInfo<T>) -> any,
 
-	readRef: (self: CPtrInfo<T>, target: (RefData|BoxData), offset: number?) -> RefData,
-	writeRef: (self: CPtrInfo<T>, target: (RefData|BoxData), value: (RefData|BoxData), offset: number?) -> (),
+	readRef: (self: CPtrInfo<T>, target: RefData | BoxData, offset: number?) -> RefData,
+	writeRef: (
+		self: CPtrInfo<T>,
+		target: RefData | BoxData,
+		value: RefData | BoxData,
+		offset: number?
+	) -> (),
 }
 
 --[=[
@@ -353,9 +374,20 @@ export type CArrInfo<T, R> = {
 
 	-- realize
 	box: (self: CArrInfo<T, R>, table: { T }) -> BoxData,
-	readData: (self: CArrInfo<T, R>, target: (RefData|BoxData), offset: number?) -> { T },
-	writeData: (self: CArrInfo<T, R>, target: (RefData|BoxData), value: { R }, target_offset: number?) -> (),
-	copyData: (self: CArrInfo<T, R>, dst: (RefData|BoxData), src: (RefData|BoxData), dst_offset: number?, src_offset: number?) -> (),
+	readData: (self: CArrInfo<T, R>, target: RefData | BoxData, offset: number?) -> { T },
+	writeData: (
+		self: CArrInfo<T, R>,
+		target: RefData | BoxData,
+		value: { R },
+		target_offset: number?
+	) -> (),
+	copyData: (
+		self: CArrInfo<T, R>,
+		dst: RefData | BoxData,
+		src: RefData | BoxData,
+		dst_offset: number?,
+		src_offset: number?
+	) -> (),
 
 	offset: (self: CArrInfo<T, R>, index: number) -> number,
 }
@@ -366,6 +398,16 @@ export type CArrInfo<T, R> = {
 	A c function signature type information.
 ]=]
 export type CFnInfo = {
+	--[=[
+		@within CFnInfo
+		@tag Field
+		@field size
+
+		The size of a function pointer.
+
+		Equivalent to `ffi.c.usize.size`.
+	]=]
+	size: number,
 	--[=[
 		@within CFnInfo
 		@tag Method
@@ -385,7 +427,7 @@ export type CFnInfo = {
 
 		@return A closure.
 	]=]
-	closure: (self: CFnInfo, (ret: RefData, ...RefData)->()) -> ClosureData,
+	closure: (self: CFnInfo, (ret: RefData, ...RefData) -> ()) -> ClosureData,
 }
 
 --[=[
@@ -394,6 +436,13 @@ export type CFnInfo = {
 	A c struct type information.
 ]=]
 export type CStructInfo = {
+	--[=[
+		@within CStructInfo
+		@tag Field
+		@field size
+
+		The size of a struct, including padding.
+	]=]
 	size: number,
 
 	--[=[
@@ -406,7 +455,7 @@ export type CStructInfo = {
 		@param len The length of the array
 		@return A struct array type
 	]=]
-	arr: (self: CStructInfo, len: number) -> CArrInfo<CStructInfo, {any}>,
+	arr: (self: CStructInfo, len: number) -> CArrInfo<CStructInfo, { any }>,
 	--[=[
 		@within CSturctInfo
 		@tag Method
@@ -419,9 +468,20 @@ export type CStructInfo = {
 	ptr: (self: CStructInfo) -> CPtrInfo<CStructInfo>,
 
 	box: (self: CStructInfo, table: { any }) -> BoxData,
-	readData: (self: CStructInfo, target: (RefData|BoxData), offset: number?) -> { any },
-	writeData: (self: CStructInfo, target: (RefData|BoxData), table: { any }, offset: number?) -> (),
-	copyData: (self: CStructInfo, dst: (RefData|BoxData), src: (RefData|BoxData), dst_offset: number?, src_offset: number?) -> (),
+	readData: (self: CStructInfo, target: RefData | BoxData, offset: number?) -> { any },
+	writeData: (
+		self: CStructInfo,
+		target: RefData | BoxData,
+		table: { any },
+		offset: number?
+	) -> (),
+	copyData: (
+		self: CStructInfo,
+		dst: RefData | BoxData,
+		src: RefData | BoxData,
+		dst_offset: number?,
+		src_offset: number?
+	) -> (),
 
 	offset: (self: CStructInfo, index: number) -> number,
 	field: (self: CStructInfo, index: number) -> CTypes,
@@ -433,6 +493,14 @@ export type CStructInfo = {
 	A type that represents c void. can only be used for the function return type.
 ]=]
 export type CVoidInfo = {
+	--[=[
+		@within CVoidInfo
+		@tag Field
+		@field size
+
+		The size of the void type. It is always 0.
+	]=]
+	size: number,
 	--[=[
 		@within CVoidInfo
 		@tag Method
@@ -445,13 +513,11 @@ export type CVoidInfo = {
 	ptr: (self: CVoidInfo) -> CPtrInfo<CVoidInfo>,
 }
 c.void = {} :: CVoidInfo
+--#endregion C ABI Type Infos
 
---#endregion C ABI Type Infos --
-
---#region Fixed size Rust-style types --
-
+--#region Fixed size Rust-style types
 --[=[
-	@class u8
+	@prop u8 NumCType
 	@within FFI
 
 	A 8-bit sized unsigned integer, Equivalent to `uint8_t` in `stdint`.
@@ -459,7 +525,7 @@ c.void = {} :: CVoidInfo
 ffi.u8 = {} :: u8
 export type u8 = NumCType<"u8">
 --[=[
-	@class u16
+	@prop u16 NumCType
 	@within FFI
 
 	A 16-bit sized unsigned integer, Equivalent to `uint16_t` in `stdint`.
@@ -467,7 +533,7 @@ export type u8 = NumCType<"u8">
 ffi.u16 = {} :: u16
 export type u16 = NumCType<"u16">
 --[=[
-	@class u32
+	@prop u32 NumCType
 	@within FFI
 
 	A 32-bit sized unsigned integer, Equivalent to `uint32_t` in `stdint`.
@@ -475,7 +541,7 @@ export type u16 = NumCType<"u16">
 ffi.u32 = {} :: u32
 export type u32 = NumCType<"u32">
 --[=[
-	@class u64
+	@prop u64 NumCType
 	@within FFI
 
 	A 64-bit sized unsigned integer, Equivalent to `uint64_t` in `stdint`.
@@ -483,7 +549,7 @@ export type u32 = NumCType<"u32">
 ffi.u64 = {} :: u64
 export type u64 = NumCType<"u64">
 --[=[
-	@class u128
+	@prop u128 NumCType
 	@within FFI
 
 	A 128-bit sized unsigned integer, Equivalent to `uint128_t` in `stdint`.
@@ -491,7 +557,7 @@ export type u64 = NumCType<"u64">
 ffi.u128 = {} :: u128
 export type u128 = NumCType<"u128">
 --[=[
-	@class i8
+	@prop i8 NumCType
 	@within FFI
 
 	A 8-bit sized signed integer, Equivalent to `int8_t` in `stdint`.
@@ -499,7 +565,7 @@ export type u128 = NumCType<"u128">
 ffi.i8 = {} :: i8
 export type i8 = NumCType<"i8">
 --[=[
-	@class i16
+	@prop i16 NumCType
 	@within FFI
 
 	A 16-bit sized signed integer, Equivalent to `int16_t` in `stdint`.
@@ -507,7 +573,7 @@ export type i8 = NumCType<"i8">
 ffi.i16 = {} :: i16
 export type i16 = NumCType<"i16">
 --[=[
-	@class i32
+	@prop i32 NumCType
 	@within FFI
 
 	A 32-bit sized signed integer, Equivalent to `int32_t` in `stdint`.
@@ -515,7 +581,7 @@ export type i16 = NumCType<"i16">
 ffi.i32 = {} :: i32
 export type i32 = NumCType<"i32">
 --[=[
-	@class i64
+	@prop i64 NumCType
 	@within FFI
 
 	A 64-bit sized signed integer, Equivalent to `int64_t` in `stdint`.
@@ -523,7 +589,7 @@ export type i32 = NumCType<"i32">
 ffi.i64 = {} :: i64
 export type i64 = NumCType<"i64">
 --[=[
-	@class i128
+	@prop i128 NumCType
 	@within FFI
 
 	A 128-bit sized signed integer, Equivalent to `int128_t` in `stdint`.
@@ -531,7 +597,7 @@ export type i64 = NumCType<"i64">
 ffi.i128 = {} :: i128
 export type i128 = NumCType<"i128">
 --[=[
-	@class f32
+	@prop f32 NumCType
 	@within FFI
 
 	A single-precision 32-bit sized floating-point, Almost always equivalent to `float` in C.
@@ -539,7 +605,7 @@ export type i128 = NumCType<"i128">
 ffi.f32 = {} :: f32
 export type f32 = NumCType<"f32">
 --[=[
-	@class f64
+	@prop f64 NumCType
 	@within FFI
 
 	A double-precision 64-bit sized floating-point, Almost always equivalent to `double` in C.
@@ -547,7 +613,7 @@ export type f32 = NumCType<"f32">
 ffi.f64 = {} :: f64
 export type f64 = NumCType<"f64">
 --[=[
-	@class usize
+	@prop usize NumCType
 	@within FFI
 
 	A machine specific pointer sized unsigned integer.
@@ -555,19 +621,18 @@ export type f64 = NumCType<"f64">
 ffi.usize = {} :: usize
 export type usize = NumCType<"usize">
 --[=[
-	@class isize
+	@prop isize NumCType
 	@within FFI
 
 	A machine specific pointer sized signed integer.
 ]=]
 ffi.isize = {} :: isize
 export type isize = NumCType<"isize">
+--#endregion Fixed size Rust-style types
 
---#endregion Fixed size Rust-style types --
-
---#region Variable size C-style types --
+--#region Variable size C-style types
 --[=[
-	@class char
+	@prop char NumCType
 	@within C
 
 	Compiler defined C `char` type.
@@ -578,12 +643,8 @@ export type isize = NumCType<"isize">
 ]=]
 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
+	@prop uchar NumCType
 	@within C
 
 	Compiler defined C `unsigned char` type.
@@ -593,7 +654,7 @@ export type char = NumCType<"char">
 c.uchar = {} :: uchar
 export type uchar = NumCType<"uchar">
 --[=[
-	@class schar
+	@prop schar NumCType
 	@within C
 
 	Compiler defined C `signed char` type.
@@ -601,7 +662,7 @@ export type uchar = NumCType<"uchar">
 c.schar = {} :: schar
 export type schar = NumCType<"schar">
 --[=[
-	@class short
+	@prop short NumCType
 	@within C
 
 	Compiler defined C `short` type.
@@ -609,7 +670,7 @@ export type schar = NumCType<"schar">
 c.short = {} :: short
 export type short = NumCType<"short">
 --[=[
-	@class ushort
+	@prop ushort NumCType
 	@within C
 
 	Compiler defined C `unsigned short` type.
@@ -617,7 +678,7 @@ export type short = NumCType<"short">
 c.ushort = {} :: ushort
 export type ushort = NumCType<"ushort">
 --[=[
-	@class int
+	@prop int NumCType
 	@within C
 
 	Compiler defined C `int` type.
@@ -627,7 +688,7 @@ export type ushort = NumCType<"ushort">
 c.int = {} :: int
 export type int = NumCType<"int">
 --[=[
-	@class uint
+	@prop uint NumCType
 	@within C
 
 	Compiler defined C `unsigned int` type.
@@ -637,7 +698,7 @@ export type int = NumCType<"int">
 c.uint = {} :: uint
 export type uint = NumCType<"uint">
 --[=[
-	@class long
+	@prop long NumCType
 	@within C
 
 	Compiler defined C `long` type.
@@ -647,7 +708,7 @@ export type uint = NumCType<"uint">
 c.long = {} :: long
 export type long = NumCType<"long">
 --[=[
-	@class ulong
+	@prop ulong NumCType
 	@within C
 
 	Compiler defined C `unsigned long` type.
@@ -657,7 +718,7 @@ export type long = NumCType<"long">
 c.ulong = {} :: ulong
 export type ulong = NumCType<"ulong">
 --[=[
-	@class longlong
+	@prop longlong NumCType
 	@within C
 
 	Compiler defined C `unsigned longlong` type.
@@ -665,15 +726,14 @@ export type ulong = NumCType<"ulong">
 c.longlong = {} :: longlong
 export type longlong = NumCType<"longlong">
 --[=[
-	@class longlong
+	@prop longlong NumCType
 	@within C
 
 	Compiler defined C `unsigned longlong` type.
 ]=]
 c.ulonglong = {} :: ulonglong
 export type ulonglong = NumCType<"ulonglong">
-
---#endregion Variable size C-style types --
+--#endregion Variable size C-style types
 
 --[=[
 	@class CTypes
@@ -681,7 +741,7 @@ export type ulonglong = NumCType<"ulonglong">
 	All possible C types.
 ]=]
 export type CTypes =
-	| u8
+	u8
 	| u16
 	| u32
 	| u64
@@ -696,8 +756,6 @@ export type CTypes =
 	| usize
 	| isize
 	| char
-	-- | float
-	-- | double
 	| uchar
 	| schar
 	| short