diff --git a/func.go b/func.go index bc9687ca..194744ed 100644 --- a/func.go +++ b/func.go @@ -24,6 +24,14 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) { RegisterFunc(fptr, sym) } +func Symbol(handle uintptr, name string) uintptr { + sym, err := loadSymbol(handle, name) + if err != nil { + panic(err) + } + return sym +} + // RegisterFunc takes a pointer to a Go function representing the calling convention of the C function. // fptr will be set to a function that when called will call the C function given by cfn with the // parameters passed in the correct registers and stack. @@ -290,12 +298,587 @@ func numOfIntegerRegisters() int { return 8 case "amd64": return 6 - // TODO: figure out why 386 tests are not working - /*case "386": - return 0 - case "arm": - return 4*/ + // TODO: figure out why 386 tests are not working + /*case "386": + return 0 + case "arm": + return 4*/ default: panic("purego: unknown GOARCH (" + runtime.GOARCH + ")") } } + +// WIP: Less reflection below + +type syscallStack interface { + SysArgs() []uintptr + Floats() []uintptr + + addStack(x uintptr) + addInt(x uintptr) + addFloat(x uintptr) +} + +type syscallStackArm64NoWin [1 + maxArgs + numOfFloats]uintptr + +func (ss *syscallStackArm64NoWin) numStack() uintptr { + return ss[0] & 0b1111 +} + +func (ss *syscallStackArm64NoWin) numInts() uintptr { + return (ss[0] >> 4) & 0b1111 +} + +func (ss *syscallStackArm64NoWin) numFloats() uintptr { + return (ss[0] >> 8) & 0b1111 +} + +func (ss *syscallStackArm64NoWin) addStack(x uintptr) { + n := ss.numStack() + ss[1+n] = x + ss[0] = (ss[0] - n) | (n + 1) +} + +func (ss *syscallStackArm64NoWin) addInt(x uintptr) { + n := ss.numInts() + if int(n) >= numOfIntegerRegisters() { + ss.addStack(x) + } else { + ss[1+n] = x + ss[0] = (ss[0] - (n << 4)) | ((n + 1) << 4) + } +} + +func (ss *syscallStackArm64NoWin) addFloat(x uintptr) { + n := ss.numFloats() + if int(n) < numOfFloats { + ss[1+maxArgs+n] = x + ss[0] = (ss[0] - (n << 8)) | ((n + 1) << 8) + } else { + ss.addStack(x) + } +} + +func (ss *syscallStackArm64NoWin) SysArgs() []uintptr { + return ss[1:] +} + +func (ss *syscallStackArm64NoWin) Floats() []uintptr { + return ss[1+maxArgs:] +} + +type syscallStackAmd64OrWin syscallStackArm64NoWin + +func (ss *syscallStackAmd64OrWin) numStack() uintptr { + return ss[0] & 0b1111 +} + +func (ss *syscallStackAmd64OrWin) numInts() uintptr { + return (ss[0] >> 4) & 0b1111 +} + +func (ss *syscallStackAmd64OrWin) numFloats() uintptr { + return (ss[0] >> 8) & 0b1111 +} + +func (ss *syscallStackAmd64OrWin) addStack(x uintptr) { + n := ss.numStack() + ss[1+n] = x + ss[0] = (ss[0] - n) | (n + 1) +} + +func (ss *syscallStackAmd64OrWin) addInt(x uintptr) { + ss.addStack(x) +} + +func (ss *syscallStackAmd64OrWin) addFloat(x uintptr) { + ss.addStack(x) +} + +func (ss *syscallStackAmd64OrWin) SysArgs() []uintptr { + return ss[1:] +} + +func (ss *syscallStackAmd64OrWin) Floats() []uintptr { + return ss[1+maxArgs:] +} + +func newSyscallStack() syscallStack { + if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { + return &syscallStackArm64NoWin{} + } + return &syscallStackAmd64OrWin{} +} + +func uintToPtr[T ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr](v T) uintptr { + return uintptr(uint64(v)) +} + +func intToPtr[T ~int | ~int8 | ~int16 | ~int32 | ~int64](v T) uintptr { + return uintptr(int64(v)) +} + +func getAddFunc[T any]() func(syscallStack, T) { + // TODO: support structs + var v T + switch any(v).(type) { + case int: + return func(r syscallStack, x T) { + r.addInt(intToPtr(any(x).(int))) + } + case int8: + return func(r syscallStack, x T) { + r.addInt(intToPtr(any(x).(int8))) + } + case int16: + return func(r syscallStack, x T) { + r.addInt(intToPtr(any(x).(int16))) + } + case int32: + return func(r syscallStack, x T) { + r.addInt(intToPtr(any(x).(int32))) + } + case int64: + return func(r syscallStack, x T) { + r.addInt(intToPtr(any(x).(int64))) + } + case uint: + return func(r syscallStack, x T) { + r.addInt(uintToPtr(any(x).(uint))) + } + case uint8: + return func(r syscallStack, x T) { + r.addInt(uintToPtr(any(x).(uint8))) + } + case uint16: + return func(r syscallStack, x T) { + r.addInt(uintToPtr(any(x).(uint16))) + } + case uint32: + return func(r syscallStack, x T) { + r.addInt(uintToPtr(any(x).(uint32))) + } + case uint64: + return func(r syscallStack, x T) { + r.addInt(uintToPtr(any(x).(uint64))) + } + case uintptr: + return func(r syscallStack, x T) { + r.addInt(uintToPtr(any(x).(uintptr))) + } + case float32: + return func(r syscallStack, x T) { + r.addFloat(uintptr(math.Float32bits(any(x).(float32)))) + } + case float64: + return func(r syscallStack, x T) { + r.addFloat(uintptr(math.Float64bits(any(x).(float64)))) + } + case bool: + return func(r syscallStack, x T) { + if any(x).(bool) { + r.addInt(1) + } else { + r.addInt(0) + } + } + case string: + return func(r syscallStack, x T) { + ptr := strings.CString(any(x).(string)) + r.addInt(uintptr(unsafe.Pointer(ptr))) + } + + default: + return func(r syscallStack, x T) { + rv := reflect.ValueOf(x) + switch rv.Kind() { + case reflect.Ptr, reflect.UnsafePointer, reflect.Slice: + // There is no need to keepAlive this pointer separately because it is kept alive in the args variable + r.addInt(rv.Pointer()) + case reflect.Func: + r.addInt(NewCallback(rv.Interface())) + default: + panic("purego: unsupported kind: " + rv.Kind().String()) + } + } + } +} + +func retInts[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr, V any](r1, r2 uintptr) V { + return any(T(r1)).(V) +} + +func retBool[V any](r1, r2 uintptr) V { + return any(r1 != 0).(V) +} + +func getReturnFunc[T any]() func(r1, r2 uintptr) T { + var v T + switch any(v).(type) { + case int: + return retInts[int, T] + case int8: + return retInts[int8, T] + case int16: + return retInts[int16, T] + case int32: + return retInts[int32, T] + case int64: + return retInts[int64, T] + case uint: + return retInts[uint, T] + case uint8: + return retInts[uint8, T] + case uint16: + return retInts[uint16, T] + case uint32: + return retInts[uint32, T] + case uint64: + return retInts[uint64, T] + case uintptr: + return retInts[uintptr, T] + case float32: + return func(r1, r2 uintptr) T { + return any(math.Float32frombits(uint32(r2))).(T) + } + case float64: + return func(r1, r2 uintptr) T { + return any(math.Float64frombits(uint64(r2))).(T) + } + case bool: + return retBool[T] + case string: + return func(r1, r2 uintptr) T { + return any(strings.GoString(r1)).(T) + } + case unsafe.Pointer: + // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer + return func(r1, r2 uintptr) T { + return any(*(*unsafe.Pointer)(unsafe.Pointer(&r1))).(T) + } + // Note: funcs and ptrs handled via reflect + default: + // TODO: below + /*u := reflect.ValueOf(v) + switch v.Elem().Type().Kind() { + case reflect.Ptr: + // It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem() + v.Set(reflect.NewAt(v.Type(), runtime_noescape(unsafe.Pointer(&r1))).Elem()) + case reflect.Func: + // wrap this C function in a nicely typed Go function + fv := reflect.New(v.Type()) + // Note: cannot use a generic one unfortunately + RegisterFunc(fv.Interface(), r1) + v.Set(fv) + default: + panic("purego: unsupported return kind: " + v.Type().Kind().String()) + }*/ + } + + return nil +} + +func argsCheck(fptr any, cfn uintptr) { + if cfn == 0 { + panic("purego: cfn is nil") + } + // this code checks how many registers and stack this function will use + // to avoid crashing with too many arguments + var ints, floats, stack int + + ty := reflect.ValueOf(fptr).Elem().Type() + for i := 0; i < ty.NumIn(); i++ { + arg := ty.In(i) + switch arg.Kind() { + case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice, + reflect.Func, reflect.Bool: + if ints < numOfIntegerRegisters() { + ints++ + } else { + stack++ + } + case reflect.Float32, reflect.Float64: + if floats < numOfFloats { + floats++ + } else { + stack++ + } + default: + panic("purego: unsupported kind " + arg.Kind().String()) + } + } + sizeOfStack := maxArgs - numOfIntegerRegisters() + if stack > sizeOfStack { + panic("purego: too many arguments") + } +} + +// Convenience to avoid code repetition in all instances of RegisterFuncI_O +func runtime_call(ss syscallStack, cfn uintptr) (uintptr, uintptr) { + var r1, r2 uintptr + sysargs, floats := ss.SysArgs(), ss.Floats() + if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { + // Use the normal arm64 calling convention even on Windows + syscall := syscall9Args{ + cfn, + sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8], + floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], + 0, 0, 0, + } + runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall)) + r1, r2 = syscall.r1, syscall.r2 + } else { + // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats + r1, r2, _ = syscall_syscall9X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8]) + } + + return r1, r2 +} + +// No return value + +func RegisterFunc1_0[I0 any](fptr *func(I0), cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + func0 := getAddFunc[I0]() + // Create new function + *fptr = func(i0 I0) { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + // Function call + runtime_call(ss, cfn) + } +} + +func RegisterFunc2_0[I0, I1 any](fptr *func(I0, I1), cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + func0 := getAddFunc[I0]() + func1 := getAddFunc[I1]() + // Create new function + *fptr = func(i0 I0, i1 I1) { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + func1(ss, i1) + // Function call + runtime_call(ss, cfn) + } +} + +func RegisterFunc3_0[I0, I1, I2 any](fptr *func(I0, I1, I2), cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + func0 := getAddFunc[I0]() + func1 := getAddFunc[I1]() + func2 := getAddFunc[I2]() + // Create new function + *fptr = func(i0 I0, i1 I1, i2 I2) { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + func1(ss, i1) + func2(ss, i2) + // Function call + runtime_call(ss, cfn) + } +} + +func RegisterFunc4_0[I0, I1, I2, I3 any](fptr *func(I0, I1, I2, I3), cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + func0 := getAddFunc[I0]() + func1 := getAddFunc[I1]() + func2 := getAddFunc[I2]() + func3 := getAddFunc[I3]() + // Create new function + *fptr = func(i0 I0, i1 I1, i2 I2, i3 I3) { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + func1(ss, i1) + func2(ss, i2) + func3(ss, i3) + // Function call + runtime_call(ss, cfn) + } +} + +func RegisterFunc5_0[I0, I1, I2, I3, I4 any](fptr *func(I0, I1, I2, I3, I4), cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + func0 := getAddFunc[I0]() + func1 := getAddFunc[I1]() + func2 := getAddFunc[I2]() + func3 := getAddFunc[I3]() + func4 := getAddFunc[I4]() + // Create new function + *fptr = func(i0 I0, i1 I1, i2 I2, i3 I3, i4 I4) { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + func1(ss, i1) + func2(ss, i2) + func3(ss, i3) + func4(ss, i4) + // Function call + runtime_call(ss, cfn) + } +} + +// .. so on + +// 1 return value + +func RegisterFunc1_1[I0, O any](fptr *func(I0) O, cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + returnFunc := getReturnFunc[O]() + func0 := getAddFunc[I0]() + // Create new function + *fptr = func(i0 I0) O { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + // Function call + r1, r2 := runtime_call(ss, cfn) + + return returnFunc(r1, r2) + } +} + +func RegisterFunc2_1[I0, I1, O any](fptr *func(I0, I1) O, cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + returnFunc := getReturnFunc[O]() + func0 := getAddFunc[I0]() + func1 := getAddFunc[I1]() + // Create new function + *fptr = func(i0 I0, i1 I1) O { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + func1(ss, i1) + // Function call + r1, r2 := runtime_call(ss, cfn) + + return returnFunc(r1, r2) + } +} + +func RegisterFunc3_1[I0, I1, I2, O any](fptr *func(I0, I1, I2) O, cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + returnFunc := getReturnFunc[O]() + func0 := getAddFunc[I0]() + func1 := getAddFunc[I1]() + func2 := getAddFunc[I2]() + // Create new function + *fptr = func(i0 I0, i1 I1, i2 I2) O { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + func1(ss, i1) + func2(ss, i2) + // Function call + r1, r2 := runtime_call(ss, cfn) + + return returnFunc(r1, r2) + } +} + +func RegisterFunc4_1[I0, I1, I2, I3, O any](fptr *func(I0, I1, I2, I3) O, cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + returnFunc := getReturnFunc[O]() + func0 := getAddFunc[I0]() + func1 := getAddFunc[I1]() + func2 := getAddFunc[I2]() + func3 := getAddFunc[I3]() + // Create new function + *fptr = func(i0 I0, i1 I1, i2 I2, i3 I3) O { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + func1(ss, i1) + func2(ss, i2) + func3(ss, i3) + // Function call + r1, r2 := runtime_call(ss, cfn) + + return returnFunc(r1, r2) + } +} + +func RegisterFunc5_1[I0, I1, I2, I3, I4, O any](fptr *func(I0, I1, I2, I3, I4) O, cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + returnFunc := getReturnFunc[O]() + func0 := getAddFunc[I0]() + func1 := getAddFunc[I1]() + func2 := getAddFunc[I2]() + func3 := getAddFunc[I3]() + func4 := getAddFunc[I4]() + // Create new function + *fptr = func(i0 I0, i1 I1, i2 I2, i3 I3, i4 I4) O { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + func1(ss, i1) + func2(ss, i2) + func3(ss, i3) + func4(ss, i4) + // Function call + r1, r2 := runtime_call(ss, cfn) + + return returnFunc(r1, r2) + } +} + +// TODO: missing 6-8 + +func RegisterFunc9_1[I0, I1, I2, I3, I4, I5, I6, I7, I8, O any](fptr *func(I0, I1, I2, I3, I4, I5, I6, I7, I8) O, cfn uintptr) { + // Prevent too many registers and check func address is okay + argsCheck(fptr, cfn) + returnFunc := getReturnFunc[O]() + func0 := getAddFunc[I0]() + func1 := getAddFunc[I1]() + func2 := getAddFunc[I2]() + func3 := getAddFunc[I3]() + func4 := getAddFunc[I4]() + func5 := getAddFunc[I5]() + func6 := getAddFunc[I6]() + func7 := getAddFunc[I7]() + func8 := getAddFunc[I8]() + // Create new function + *fptr = func(i0 I0, i1 I1, i2 I2, i3 I3, i4 I4, i5 I5, i6 I6, i7 I7, i8 I8) O { + // Create new syscall stack + ss := newSyscallStack() + // Add inputs in registers + func0(ss, i0) + func1(ss, i1) + func2(ss, i2) + func3(ss, i3) + func4(ss, i4) + func5(ss, i5) + func6(ss, i6) + func7(ss, i7) + func8(ss, i8) + // Function call + r1, r2 := runtime_call(ss, cfn) + + return returnFunc(r1, r2) + } +} diff --git a/func_test.go b/func_test.go index 11dd07f8..9cd49f80 100644 --- a/func_test.go +++ b/func_test.go @@ -5,11 +5,13 @@ package purego_test import ( "fmt" + "math" "runtime" "testing" "unsafe" "github.com/ebitengine/purego" + "github.com/ebitengine/purego/internal/strings" ) // This is an internal OS-dependent function for getting the handle to a library @@ -32,57 +34,480 @@ func getSystemLibrary() (string, error) { } } -func TestRegisterFunc(t *testing.T) { - library, err := getSystemLibrary() - if err != nil { - t.Errorf("couldn't get system library: %s", err) - } - libc, err := openLibrary(library) - if err != nil { - t.Errorf("failed to dlopen: %s", err) - } - var puts func(string) - purego.RegisterLibFunc(&puts, libc, "puts") - puts("Calling C from from Go without Cgo!") -} +// NewCallBack + +func Test_NewCallBack(t *testing.T) { + // Original + t.Run("RegisterFunc(original)", func(t *testing.T) { + cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int { + fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9) + return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + }) + + var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int + purego.RegisterFunc(&fn, cb) + + ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9) + fmt.Println(ret) + + // Output: 1 2 3 4 5 6 7 8 9 + // 45 + }) + // New + t.Run("RegisterFunc9_1", func(t *testing.T) { + cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int { + fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9) + return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + }) -func ExampleNewCallback() { - cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int { - fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9) - return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int + purego.RegisterFunc9_1(&fn, cb) + + ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9) + fmt.Println(ret) + + // Output: 1 2 3 4 5 6 7 8 9 + // 45 }) +} - var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int - purego.RegisterFunc(&fn, cb) +func Benchmark_NewCallBack(b *testing.B) { + // Original + b.Run("RegisterFunc(original)", func(b *testing.B) { + // 1000000, 1111 ns/op, 328 B/op, 12 allocs/op + cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int { + return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + }) - ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9) - fmt.Println(ret) + var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int + purego.RegisterFunc(&fn, cb) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = fn(1, 2, 3, 4, 5, 6, 7, 8, 9) + } + }) + // New + b.Run("RegisterFunc9_1(new)", func(b *testing.B) { + // 3153188, 383.6 ns/op, 144 B/op, 1 allocs/op + cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int { + return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + }) - // Output: 1 2 3 4 5 6 7 8 9 - // 45 + var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int + purego.RegisterFunc9_1(&fn, cb) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = fn(1, 2, 3, 4, 5, 6, 7, 8, 9) + } + }) } +// qsort + func Test_qsort(t *testing.T) { - library, err := getSystemLibrary() - if err != nil { - t.Errorf("couldn't get system library: %s", err) - } - libc, err := openLibrary(library) - if err != nil { - t.Errorf("failed to dlopen: %s", err) - } + // Original + t.Run("RegisterFunc(original)", func(t *testing.T) { + library, err := getSystemLibrary() + if err != nil { + t.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + t.Errorf("failed to dlopen: %s", err) + } - data := []int{88, 56, 100, 2, 25} - sorted := []int{2, 25, 56, 88, 100} - compare := func(a, b *int) int { - return *a - *b - } - var qsort func(data []int, nitms uintptr, size uintptr, compar func(a, b *int) int) - purego.RegisterLibFunc(&qsort, libc, "qsort") - qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare) - for i := range data { - if data[i] != sorted[i] { - t.Errorf("got %d wanted %d at %d", data[i], sorted[i], i) + data := []int{88, 56, 100, 2, 25} + sorted := []int{2, 25, 56, 88, 100} + compare := func(a, b *int) int { + return *a - *b } - } + var qsort func(data []int, nitms uintptr, size uintptr, compar func(a, b *int) int) + purego.RegisterLibFunc(&qsort, libc, "qsort") + qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare) + for i := range data { + if data[i] != sorted[i] { + t.Errorf("got %d wanted %d at %d", data[i], sorted[i], i) + } + } + }) + // New + t.Run("RegisterFunc4_0(new)", func(t *testing.T) { + library, err := getSystemLibrary() + if err != nil { + t.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + t.Errorf("failed to dlopen: %s", err) + } + + data := []int{88, 56, 100, 2, 25} + sorted := []int{2, 25, 56, 88, 100} + compare := func(a, b *int) int { + return *a - *b + } + var qsort func(data []int, nitms uintptr, size uintptr, compar func(a, b *int) int) + symbol := purego.Symbol(libc, "qsort") + purego.RegisterFunc4_0(&qsort, symbol) + qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare) + for i := range data { + if data[i] != sorted[i] { + t.Errorf("got %d wanted %d at %d", data[i], sorted[i], i) + } + } + }) +} + +func Benchmark_qsort(b *testing.B) { + // Original + b.Run("RegisterFunc(original)", func(b *testing.B) { + // 558027, 2067 ns/op, 264 B/op, 6 allocs/op + library, err := getSystemLibrary() + if err != nil { + b.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + b.Errorf("failed to dlopen: %s", err) + } + + data := []int{88, 56, 100, 2, 25} + compare := func(a, b *int) int { + return *a - *b + } + var qsort func(data []int, nitms uintptr, size uintptr, compar func(a, b *int) int) + purego.RegisterLibFunc(&qsort, libc, "qsort") + b.ResetTimer() + for n := 0; n < b.N; n++ { + qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare) + } + }) + // New + b.Run("RegisterFunc4_0(new)", func(b *testing.B) { + // 648578, 1806 ns/op, 296 B/op, 4 allocs/op + library, err := getSystemLibrary() + if err != nil { + b.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + b.Errorf("failed to dlopen: %s", err) + } + + data := []int{88, 56, 100, 2, 25} + compare := func(a, b *int) int { + return *a - *b + } + var qsort func(data []int, nitms uintptr, size uintptr, compar func(a, b *int) int) + symbol := purego.Symbol(libc, "qsort") + purego.RegisterFunc4_0(&qsort, symbol) + b.ResetTimer() + for n := 0; n < b.N; n++ { + qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare) + } + }) +} + +// puts + +func Test_puts(t *testing.T) { + // Original + t.Run("RegisterFunc(original)", func(t *testing.T) { + library, err := getSystemLibrary() + if err != nil { + t.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + t.Errorf("failed to dlopen: %s", err) + } + var puts func(string) + purego.RegisterLibFunc(&puts, libc, "puts") + puts("Calling C from from Go without Cgo! (original)") + }) + // New + t.Run("RegisterFunc1_0(new)", func(t *testing.T) { + library, err := getSystemLibrary() + if err != nil { + t.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + t.Errorf("failed to dlopen: %s", err) + } + var puts func(string) + symbol := purego.Symbol(libc, "puts") + purego.RegisterFunc1_0(&puts, symbol) + puts("Calling C from from Go without Cgo! (new)") + }) +} + +// strlen + +func Test_strlen(t *testing.T) { + t.Run("RegisterFunc1_1(new)", func(t *testing.T) { + library, err := getSystemLibrary() + if err != nil { + t.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + t.Errorf("failed to dlopen: %s", err) + } + var strlen func(string) int + symbol := purego.Symbol(libc, "strlen") + purego.RegisterFunc1_1(&strlen, symbol) + count := strlen("abcdefghijklmnopqrstuvwxyz") + if count != 26 { + t.Errorf("strlen(0): expected 26 but got %d", count) + } + count = strlen("abcdefghijklmnopqrstuvwxyz") + if count != 26 { + t.Errorf("strlen(1): expected 26 but got %d", count) + } + }) +} + +func Benchmark_strlen(b *testing.B) { + // Current + b.Run("RegisterFunc(original)", func(b *testing.B) { + // 2411634 - 490.4 ns/op - 120 B/op - 6 allocs/op + library, err := getSystemLibrary() + if err != nil { + b.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + b.Errorf("failed to dlopen: %s", err) + } + var strlen func(string) int + purego.RegisterLibFunc(&strlen, libc, "strlen") + b.ResetTimer() + for n := 0; n < b.N; n++ { + strlen("abcdefghijklmnopqrstuvwxyz") + } + }) + // New + b.Run("RegisterFunc1_1(new)", func(b *testing.B) { + // 7690965 - 157.0 ns/op - 176 B/op - 2 allocs/op + library, err := getSystemLibrary() + if err != nil { + b.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + b.Errorf("failed to dlopen: %s", err) + } + var strlen func(string) int + symbol := purego.Symbol(libc, "strlen") + purego.RegisterFunc1_1(&strlen, symbol) + b.ResetTimer() + for n := 0; n < b.N; n++ { + strlen("abcdefghijklmnopqrstuvwxyz") + } + }) + // Direct + b.Run("SyscallN", func(b *testing.B) { + // 8449221, 142.2 ns/op, 112 B/op, 2 allocs/op + library, err := getSystemLibrary() + if err != nil { + b.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + b.Errorf("failed to dlopen: %s", err) + } + symbol := purego.Symbol(libc, "strlen") + b.ResetTimer() + for n := 0; n < b.N; n++ { + ptr := strings.CString("abcdefghijklmnopqrstuvwxyz") + sysargs := [9]uintptr{ + uintptr(unsafe.Pointer(ptr)), + } + _, _, _ = purego.SyscallN(symbol, sysargs[:]...) + } + }) +} + +// cos + +func Test_cos(t *testing.T) { + // Original + t.Run("RegisterFunc(original)", func(t *testing.T) { + library, err := getSystemLibrary() + if err != nil { + t.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + t.Errorf("failed to dlopen: %s", err) + } + var cos func(float64) float64 + purego.RegisterLibFunc(&cos, libc, "cos") + // 0.05428962282295477 + const v = 1.51648 + expected := math.Cos(v) + actual := cos(v) + if expected != actual { + t.Errorf("cos(%.8f): expected %.8f but got %.8f", v, expected, actual) + } + }) + // New + t.Run("RegisterFunc1_1(new)", func(t *testing.T) { + library, err := getSystemLibrary() + if err != nil { + t.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + t.Errorf("failed to dlopen: %s", err) + } + var cos func(float64) float64 + symbol := purego.Symbol(libc, "cos") + purego.RegisterFunc1_1(&cos, symbol) + // 0.05428962282295477 + const v = 1.51648 + expected := math.Cos(v) + actual := cos(v) + if expected != actual { + t.Errorf("cos(%.8f): expected %.8f but got %.8f", v, expected, actual) + } + }) +} + +func Benchmark_cos(b *testing.B) { + // Original + b.Run("RegisterFunc(original)", func(b *testing.B) { + // 3337392, 362.0 ns/op, 64 B/op, 4 allocs/op + library, err := getSystemLibrary() + if err != nil { + b.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + b.Errorf("failed to dlopen: %s", err) + } + var cos func(float64) float64 + purego.RegisterLibFunc(&cos, libc, "cos") + // 0.05428962282295477 + const v = 1.51648 + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = cos(v) + } + }) + // New + b.Run("RegisterFunc1_1(new)", func(b *testing.B) { + // 9300645, 129.0 ns/op, 144 B/op, 1 allocs/op + library, err := getSystemLibrary() + if err != nil { + b.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + b.Errorf("failed to dlopen: %s", err) + } + var cos func(float64) float64 + symbol := purego.Symbol(libc, "cos") + purego.RegisterFunc1_1(&cos, symbol) + // 0.05428962282295477 + const v = 1.51648 + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = cos(v) + } + }) + // Go + b.Run("Go", func(b *testing.B) { + const v = 1.51648 + for i := 0; i < b.N; i++ { + _ = math.Cos(v) + } + }) +} + +// isupper + +func Test_isupper(t *testing.T) { + // Original + t.Run("RegisterFunc(original)", func(t *testing.T) { + library, err := getSystemLibrary() + if err != nil { + t.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + t.Errorf("failed to dlopen: %s", err) + } + var isupper func(c rune) bool + purego.RegisterLibFunc(&isupper, libc, "isupper") + actual := isupper('A') + if !actual { + t.Errorf("isupper('%c'): expected true but got false", 'A') + } + actual = isupper('a') + if actual { + t.Errorf("isupper('%c'): expected false but got true", 'a') + } + }) + // New + t.Run("RegisterFunc1_1(new)", func(t *testing.T) { + library, err := getSystemLibrary() + if err != nil { + t.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + t.Errorf("failed to dlopen: %s", err) + } + var isupper func(c rune) bool + symbol := purego.Symbol(libc, "isupper") + purego.RegisterFunc(&isupper, symbol) + actual := isupper('A') + if !actual { + t.Errorf("isupper('%c'): expected true but got false", 'A') + } + actual = isupper('a') + if actual { + t.Errorf("isupper('%c'): expected false but got true", 'a') + } + }) +} + +func Benchmark_isupper(b *testing.B) { + // Original + b.Run("RegisterFunc(original)", func(b *testing.B) { + // 3037436, 395.6 ns/op, 56 B/op, 4 allocs/op + library, err := getSystemLibrary() + if err != nil { + b.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + b.Errorf("failed to dlopen: %s", err) + } + var isupper func(c rune) bool + purego.RegisterLibFunc(&isupper, libc, "isupper") + for i := 0; i < b.N; i++ { + _ = isupper('A') + } + }) + // New + b.Run("RegisterFunc1_1(new)", func(b *testing.B) { + // 10082194, 121.2 ns/op, 144 B/op, 1 allocs/op + library, err := getSystemLibrary() + if err != nil { + b.Errorf("couldn't get system library: %s", err) + } + libc, err := openLibrary(library) + if err != nil { + b.Errorf("failed to dlopen: %s", err) + } + var isupper func(c rune) bool + symbol := purego.Symbol(libc, "isupper") + purego.RegisterFunc1_1(&isupper, symbol) + for i := 0; i < b.N; i++ { + _ = isupper('A') + } + }) }