Skip to content

ffi purego #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions objc/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func callBlock(b Block, params []reflect.Value, rt reflect.Type) reflect.Value {
}

cif, status := ffi.PrepCIF(retType, argTypes)
if status != ffi.OK {
if status != ffi.FFIStatusOK {
panic("ffi prep cif status not ok")
}
ffi.Call(cif, fn, retPtr, args)
Expand Down Expand Up @@ -190,7 +190,7 @@ func wrapGoFuncAsBlockIMP(rf reflect.Value) (imp IMP, handle cgo.Handle) {
}

cif, status := ffi.PrepCIF(retType, objcArgTypes)
if status != ffi.OK {
if status != ffi.FFIStatusOK {
panic("ffi prep cif status not ok")
}

Expand All @@ -204,7 +204,7 @@ func wrapGoFuncAsBlockIMP(rf reflect.Value) (imp IMP, handle cgo.Handle) {
setGoValueToObjcPointer(results[0], ret)
}
})
if status != ffi.OK {
if status != ffi.FFIStatusOK {
panic("ffi prep closure status not ok")
}

Expand Down
6 changes: 3 additions & 3 deletions objc/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func Call[T any](o Handle, selector Selector, params ...any) T {
return ret
}
cif, status := ffi.PrepCIF(retType, argTypes)
if status != ffi.OK {
if status != ffi.FFIStatusOK {
panic("ffi prep cif status not ok")
}
ffi.Call(cif, imp.ptr, retPtr, args)
Expand Down Expand Up @@ -200,7 +200,7 @@ func wrapGoFuncAsMethodIMP(rf reflect.Value) (imp IMP, handle cgo.Handle) {
}

cif, status := ffi.PrepCIF(retType, objcArgTypes)
if status != ffi.OK {
if status != ffi.FFIStatusOK {
panic("ffi prep cif status not ok")
}

Expand All @@ -216,7 +216,7 @@ func wrapGoFuncAsMethodIMP(rf reflect.Value) (imp IMP, handle cgo.Handle) {
setGoValueToObjcPointer(results[0], ret)
}
})
if status != ffi.OK {
if status != ffi.FFIStatusOK {
panic("ffi prep closure status not ok")
}

Expand Down
177 changes: 160 additions & 17 deletions objc/ffi/ffi.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@ package ffi
// #cgo LDFLAGS: -l ffi
// #import <ffi/ffi.h>
// #import <stdint.h>
// ffi_status ffi_prep_cif0(uintptr_t cif, ffi_abi abi, unsigned int nargs, uintptr_t rtype, uintptr_t atypes);
// void ffi_call0(uintptr_t cif, void* fn, uintptr_t rvalue, uintptr_t avalue);
// void *ffi_closure_alloc0(uintptr_t code);
// ffi_status ffi_prep_closure_loc0(void* closure, uintptr_t cif, void* fn, uintptr_t user_data, void *codeloc);
//
// void forward_to_go(ffi_cif *cif, void *ret, void* args[], void * user_data);
//
import "C"
import (
"runtime"
"runtime/cgo"
"unsafe"

"github.com/ebitengine/purego"
)

type Type = C.ffi_type
Expand Down Expand Up @@ -48,6 +43,152 @@ var TypeFloat *Type = &C.ffi_type_float
var TypeDouble *Type = &C.ffi_type_double
var TypePointer *Type = &C.ffi_type_pointer

var (
FFITypeVoid uintptr
FFITypeUint8 uintptr
FFITypeSint8 uintptr
FFITypeUint16 uintptr
FFITypeSint16 uintptr
FFITypeUint32 uintptr
FFITypeSint32 uintptr
FFITypeUint64 uintptr
FFITypeSint64 uintptr
FFITypeFloat uintptr
FFITypeDouble uintptr
FFITypePointer uintptr
)

// TODO size_t depends on architecture
type FFIType struct {
size int
alignment uint16
// **FFIType array
elements unsafe.Pointer
}

type FFICif struct {
abi FFIABI
nargs uint32
argTypes []*FFIType
rtype *FFIType
bytes uint32
flags uint32
}

// TODO intel won't work
// TODO naming
// Default alignment is 8, nothing needed here for
// __attribute__((aligned(8))) on ffi.h
type FFIClosure struct {
trampoline_table uintptr
trampoline_table_entry uintptr
cif *FFICif
fun func(FFICif, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer)
user_data uintptr
}

// TODO const naming
type FFIStatus uint32
const (
FFIStatusOK = iota
FFIStatusBadTypedef
FFIStatusBadABI
FFIStatusBadArgType
)

type FFIABI uint32
const (
FFIFirstABI = iota
FFISysV
FFIWin64
FFILastABI
FFIDefaultABI = FFISysV
)

var libffi uintptr

// TODO passing array pointer to C
// TODO usage comments not correct
var (
// FFICall: FFI call function
// @param cif: CIF pointer
// @param fn: function pointer
// @param rvalue: return value
// @param avalues: arguments
// @return: void
FFICall func(cif unsafe.Pointer, fn unsafe.Pointer, rvalue unsafe.Pointer, avalues []unsafe.Pointer)

// TODO atypes argument
// FFIPrepCIF: FFI prepare CIF function
// @param cif: CIF pointer
// @param abi: ABI
// @param nargs: number of arguments
// @param rtype: return type
// @param atypes: argument types
// @return: status
FFIPrepCIF func(cif unsafe.Pointer, abi FFIABI, nargs uint32, rtype unsafe.Pointer, atypes unsafe.Pointer) FFIStatus

// FFIClosureAlloc: FFI closure alloc function
// @param size: size
// @param code: code pointer
// @return: closure pointer
FFIClosureAlloc func(size uint32, code unsafe.Pointer) unsafe.Pointer

// TODO update typed pointers and function pointers
// FFIPrepClosureLoc: FFI prepare closure loc function
// @param closure: closure pointer
// @param cif: CIF pointer
// @param fun: function pointer
// @param user_data: user data
// @param codeloc: code location
// @return: status
FFIPrepClosureLoc func(closure unsafe.Pointer, cif unsafe.Pointer, fun uintptr, user_data unsafe.Pointer, codeloc unsafe.Pointer) FFIStatus

// FFIClosureFree: FFI closure free function
// @param closure: closure pointer
// @return: void
FFIClosureFree func(closure unsafe.Pointer)
)

func LoadFFI() {
libffi, err := purego.Dlopen("libffi.dylib", purego.RTLD_NOW | purego.RTLD_GLOBAL)
if err != nil {
panic(err)
}

purego.RegisterLibFunc(&FFICall, libffi, "ffi_call")
purego.RegisterLibFunc(&FFIPrepCIF, libffi, "ffi_prep_cif")
purego.RegisterLibFunc(&FFIClosureAlloc, libffi, "ffi_closure_alloc")
purego.RegisterLibFunc(&FFIPrepClosureLoc, libffi, "ffi_prep_closure_loc")
purego.RegisterLibFunc(&FFIClosureFree, libffi, "ffi_closure_free")

loadExternSymbol := func (symbol string) uintptr {
ptr, err := purego.Dlsym(libffi, symbol)
if err != nil {
panic(err)
}
return ptr
}

FFITypeVoid = loadExternSymbol("ffi_type_void")
FFITypeUint8 = loadExternSymbol("ffi_type_uint8")
FFITypeSint8 = loadExternSymbol("ffi_type_sint8")
FFITypeUint16 = loadExternSymbol("ffi_type_uint16")
FFITypeSint16 = loadExternSymbol("ffi_type_sint16")
FFITypeUint32 = loadExternSymbol("ffi_type_uint32")
FFITypeSint32 = loadExternSymbol("ffi_type_sint32")
FFITypeUint64 = loadExternSymbol("ffi_type_uint64")
FFITypeSint64 = loadExternSymbol("ffi_type_sint64")
FFITypeFloat = loadExternSymbol("ffi_type_float")
FFITypeDouble = loadExternSymbol("ffi_type_double")
FFITypePointer = loadExternSymbol("ffi_type_pointer")
}

// TODO move this to an appropriate place
func init() {
LoadFFI()
}

func IsStruct(t *Type) bool {
return t._type == C.FFI_TYPE_STRUCT
}
Expand All @@ -63,16 +204,16 @@ func MakeStructType(types []*Type) *Type {
}
}

func PrepCIF(rtype *Type, argtypes []*Type) (*CIF, Status) {
func PrepCIF(rtype *Type, argtypes []*Type) (*CIF, FFIStatus) {
var cif CIF
s := C.ffi_prep_cif0(toUintptrT(&cif), DEFAULT_ABI, C.uint(len(argtypes)), toUintptrT(rtype), toUintptrT(&argtypes[0]))
s := FFIPrepCIF(unsafe.Pointer(&cif), FFIDefaultABI, uint32(len(argtypes)), unsafe.Pointer(rtype), unsafe.Pointer(&argtypes[0]))
runtime.KeepAlive(rtype)
runtime.KeepAlive(argtypes)
return &cif, s
}

func Call(cif *CIF, fn unsafe.Pointer, rvalue unsafe.Pointer, avalues []unsafe.Pointer) {
C.ffi_call0(toUintptrT(cif), fn, C.uintptr_t(uintptr(rvalue)), toUintptrT(&avalues[0]))
FFICall(unsafe.Pointer(cif), fn, rvalue, avalues)
runtime.KeepAlive(cif)
runtime.KeepAlive(avalues)
runtime.KeepAlive(rvalue)
Expand All @@ -90,26 +231,28 @@ type UserData struct {
guard *int // used to free resource when is gced
}

func CreateClosure(cif *CIF, f ClosureHandle) (codeloc unsafe.Pointer, udHandle cgo.Handle, status Status) {
closure := C.ffi_closure_alloc0(toUintptrT(&codeloc))
func CreateClosure(cif *CIF, f ClosureHandle) (codeloc unsafe.Pointer, udHandle cgo.Handle, status FFIStatus) {
closure := FFIClosureAlloc(uint32(unsafe.Sizeof(FFIClosure{})), unsafe.Pointer(&codeloc))
guard := new(int)
userData := UserData{
cif: cif,
handle: f,
guard: guard,
}
runtime.KeepAlive(userData)
runtime.SetFinalizer(guard, func(v *int) {
C.ffi_closure_free(closure)
FFIClosureFree(closure)
})
// keep this for now to not break the api
// but this is no longer needed
udHandle = cgo.NewHandle(userData)
status = C.ffi_prep_closure_loc0(closure, toUintptrT(cif), C.forward_to_go, C.uintptr_t(udHandle), codeloc)
status = FFIPrepClosureLoc(closure, unsafe.Pointer(cif), purego.NewCallback(handleClosure), unsafe.Pointer(&userData), codeloc)
return
}

//export handleClosure
func handleClosure(cif *CIF, ret unsafe.Pointer, args unsafe.Pointer, userData unsafe.Pointer) {
goUserData := cgo.Handle(userData).Value().(UserData)
userDataVal := *(*UserData)(userData)
argsNum := int(cif.nargs)
argS := unsafe.Slice((*unsafe.Pointer)(args), argsNum)
goUserData.handle(cif, ret, argS)
userDataVal.handle(cif, ret, argS)
}
25 changes: 0 additions & 25 deletions objc/ffi/ffi.m

This file was deleted.

4 changes: 2 additions & 2 deletions objc/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ func addProtocolMethod(class IClass, md methodDescription, method reflect.Method
}

cif, status := ffi.PrepCIF(retType, objcArgTypes)
if status != ffi.OK {
if status != ffi.FFIStatusOK {
panic("ffi prep cif status not ok")
}

Expand All @@ -293,7 +293,7 @@ func addProtocolMethod(class IClass, md methodDescription, method reflect.Method
}
})
_ = handle // never free
if status != ffi.OK {
if status != ffi.FFIStatusOK {
panic("ffi prep closure status not ok")
}
flag := class.AddMethod(md.Name, IMPFrom(fn), md.Types)
Expand Down