From ed09efd73eea8060e1f6809433bb2dfe639781b9 Mon Sep 17 00:00:00 2001 From: gysddn Date: Thu, 16 Jan 2025 19:38:34 +0300 Subject: [PATCH] purego: Replace cgo function calls with purego - Added purego the ffi submodule - Loading `libffi.dylib` at init of the module - Replaced all cgo function calls with purego bindings - Added structs and enums needed for these functions - Updated some calls to use the new enums - Used purego callback system for handleClosure - Removed ffi.m file This commit leaves some parts of cgo to not break the api but all these parts can be removed by updating the appropriate files using them. cgo, in some places, is exposed to the rest of the api, most notibly the cgo.Handle. --- objc/block.go | 6 +- objc/call.go | 6 +- objc/ffi/ffi.go | 177 ++++++++++++++++++++++++++++++++++++++++++----- objc/ffi/ffi.m | 25 ------- objc/protocol.go | 4 +- 5 files changed, 168 insertions(+), 50 deletions(-) delete mode 100644 objc/ffi/ffi.m 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)