Skip to content

Commit

Permalink
all: improve memory usage (#282)
Browse files Browse the repository at this point in the history
Don't allocate when being called by objc.Send. This is done by just inlining the expansion of the parameters directly into the argument list instead of making a tmp slice.

Use a sync.Pool for syscall15Args struct to save on allocations
  • Loading branch information
TotallyGamerJet authored Oct 18, 2024
1 parent 7402fed commit 0196f2d
Showing 1 changed file with 59 additions and 45 deletions.
104 changes: 59 additions & 45 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ import (
"math"
"reflect"
"runtime"
"sync"
"unsafe"

"github.com/ebitengine/purego/internal/strings"
)

var thePool = sync.Pool{New: func() any {
return new(syscall15Args)
}}

// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
// It panics if it can't find the name symbol.
func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
Expand Down Expand Up @@ -200,18 +205,6 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
}
}
v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
if len(args) > 0 {
if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
// subtract one from args bc the last argument in args is []interface{}
// which we are currently expanding
tmp := make([]reflect.Value, len(args)-1+len(variadic))
n := copy(tmp, args[:len(args)-1])
for i, v := range variadic {
tmp[n+i] = reflect.ValueOf(v)
}
args = tmp
}
}
var sysargs [maxArgs]uintptr
stack := sysargs[numOfIntegerRegisters():]
var floats [numOfFloats]uintptr
Expand Down Expand Up @@ -260,7 +253,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
runtime.KeepAlive(keepAlive)
runtime.KeepAlive(args)
}()
var syscall syscall15Args
syscall := thePool.Get().(*syscall15Args)
defer thePool.Put(syscall)
if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
outType := ty.Out(0)
if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
Expand All @@ -276,48 +270,29 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
}
}
}
for _, v := range args {
switch v.Kind() {
case reflect.String:
ptr := strings.CString(v.String())
keepAlive = append(keepAlive, ptr)
addInt(uintptr(unsafe.Pointer(ptr)))
case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
addInt(uintptr(v.Uint()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
addInt(uintptr(v.Int()))
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
addInt(v.Pointer())
case reflect.Func:
addInt(NewCallback(v.Interface()))
case reflect.Bool:
if v.Bool() {
addInt(1)
} else {
addInt(0)
for i, v := range args {
if variadic, ok := args[i].Interface().([]interface{}); ok {
if i != len(args)-1 {
panic("purego: can only expand last parameter")
}
case reflect.Float32:
addFloat(uintptr(math.Float32bits(float32(v.Float()))))
case reflect.Float64:
addFloat(uintptr(math.Float64bits(v.Float())))
case reflect.Struct:
keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
default:
panic("purego: unsupported kind: " + v.Kind().String())
for _, x := range variadic {
keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
}
continue
}
keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
}
if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
// Use the normal arm64 calling convention even on Windows
syscall = syscall15Args{
*syscall = syscall15Args{
cfn,
sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
sysargs[12], sysargs[13], sysargs[14],
floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
syscall.arm64_r8,
}
runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
} else {
// This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
Expand Down Expand Up @@ -357,15 +332,54 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
case reflect.Struct:
v = getStruct(outType, syscall)
v = getStruct(outType, *syscall)
default:
panic("purego: unsupported return kind: " + outType.Kind().String())
}
return []reflect.Value{v}
if len(args) > 0 {
// reuse args slice instead of allocating one when possible
args[0] = v
return args[:1]
} else {
return []reflect.Value{v}
}
})
fn.Set(v)
}

func addValue(v reflect.Value, keepAlive []interface{}, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []interface{} {
switch v.Kind() {
case reflect.String:
ptr := strings.CString(v.String())
keepAlive = append(keepAlive, ptr)
addInt(uintptr(unsafe.Pointer(ptr)))
case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
addInt(uintptr(v.Uint()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
addInt(uintptr(v.Int()))
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
addInt(v.Pointer())
case reflect.Func:
addInt(NewCallback(v.Interface()))
case reflect.Bool:
if v.Bool() {
addInt(1)
} else {
addInt(0)
}
case reflect.Float32:
addFloat(uintptr(math.Float32bits(float32(v.Float()))))
case reflect.Float64:
addFloat(uintptr(math.Float64bits(v.Float())))
case reflect.Struct:
keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
default:
panic("purego: unsupported kind: " + v.Kind().String())
}
return keepAlive
}

// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
// if it is bigger than this than enough space must be allocated on the heap and then passed into
// the function as the first parameter on amd64 or in R8 on arm64.
Expand Down

0 comments on commit 0196f2d

Please sign in to comment.