diff --git a/objc/block.go b/objc/block.go index ad6c3575..3456b212 100644 --- a/objc/block.go +++ b/objc/block.go @@ -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) @@ -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") } @@ -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") } diff --git a/objc/call.go b/objc/call.go index 26a608e6..2afdcbb4 100644 --- a/objc/call.go +++ b/objc/call.go @@ -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) @@ -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") } @@ -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") } diff --git a/objc/ffi/ffi.go b/objc/ffi/ffi.go index 19da4e1a..a69ee719 100644 --- a/objc/ffi/ffi.go +++ b/objc/ffi/ffi.go @@ -8,18 +8,13 @@ package ffi // #cgo LDFLAGS: -l ffi // #import // #import -// 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 @@ -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 } @@ -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) @@ -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) } diff --git a/objc/ffi/ffi.m b/objc/ffi/ffi.m deleted file mode 100644 index 31370189..00000000 --- a/objc/ffi/ffi.m +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2021 Liu Dong. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#import -#import -#include "_cgo_export.h" - - -ffi_status ffi_prep_cif0(uintptr_t cif, ffi_abi abi, unsigned int nargs, uintptr_t rtype, uintptr_t atypes) { - return ffi_prep_cif((ffi_cif *)cif, abi, nargs, (ffi_type *)rtype, (ffi_type **)atypes); -} -void ffi_call0(uintptr_t cif, void* fn, uintptr_t rvalue, uintptr_t avalue) { - return ffi_call((ffi_cif *)cif, fn, (void *)rvalue, (void **)avalue); -} -void *ffi_closure_alloc0(uintptr_t code) { - return ffi_closure_alloc(sizeof(ffi_closure), (void **)code); -} -ffi_status ffi_prep_closure_loc0(void* closure, uintptr_t cif, void* fn, uintptr_t user_data, void *codeloc) { - return ffi_prep_closure_loc((ffi_closure *)closure, (ffi_cif *)cif, fn, (void *)user_data, codeloc); -} - -void forward_to_go(ffi_cif *cif, void *ret, void* args[], void * user_data) { - handleClosure(cif, ret, args, user_data); -} \ No newline at end of file diff --git a/objc/protocol.go b/objc/protocol.go index 0c66b018..2517c2da 100644 --- a/objc/protocol.go +++ b/objc/protocol.go @@ -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") } @@ -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)