1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4//go:build darwin || linux
5
6package purego
7
8import (
9 "math"
10 "reflect"
11 "runtime"
12 "unsafe"
13
14 "github.com/ebitengine/purego/internal/strings"
15)
16
17// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
18// It panics if Dlsym fails.
19//
20// Windows does not support this function.
21func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
22 sym, err := Dlsym(handle, name)
23 if err != nil {
24 panic(err)
25 }
26 RegisterFunc(fptr, sym)
27}
28
29// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
30// fptr will be set to a function that when called will call the C function given by cfn with the
31// parameters passed in the correct registers and stack.
32//
33// A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
34//
35// These conversions describe how a Go type in the fptr will be used to call
36// the C function. It is important to note that there is no way to verify that fptr
37// matches the C function. This also holds true for struct types where the padding
38// needs to be ensured to match that of C; RegisterFunc does not verify this.
39//
40// Type Conversions (Go => C)
41//
42// string <=> char*
43// bool <=> _Bool
44// uintptr <=> uintptr_t
45// uint <=> uint32_t or uint64_t
46// uint8 <=> uint8_t
47// uint16 <=> uint16_t
48// uint32 <=> uint32_t
49// uint64 <=> uint64_t
50// int <=> int32_t or int64_t
51// int8 <=> int8_t
52// int16 <=> int16_t
53// int32 <=> int32_t
54// int64 <=> int64_t
55// float32 <=> float (WIP)
56// float64 <=> double (WIP)
57// struct <=> struct (WIP)
58// func <=> C function
59// unsafe.Pointer, *T <=> void*
60// []T => void*
61//
62// There is a special case when the last argument of fptr is a variadic interface (or []interface}
63// it will be expanded into a call to the C function as if it had the arguments in that slice.
64// This means that using arg ...interface{} is like a cast to the function with the arguments inside arg.
65// This is not the same as C variadic.
66//
67// There are some limitations when using RegisterFunc on Linux. First, there is no support for function arguments.
68// Second, float32 and float64 arguments and return values do not work when CGO_ENABLED=1. Otherwise, Linux
69// has the same feature parity as Darwin.
70//
71// Windows does not support this function.
72func RegisterFunc(fptr interface{}, cfn uintptr) {
73 fn := reflect.ValueOf(fptr).Elem()
74 ty := fn.Type()
75 if ty.Kind() != reflect.Func {
76 panic("purego: fptr must be a function pointer")
77 }
78 if ty.NumOut() > 1 {
79 panic("purego: function can only return zero or one values")
80 }
81 if cfn == 0 {
82 panic("purego: cfn is nil")
83 }
84 {
85 // this code checks how many registers and stack this function will use
86 // to avoid crashing with too many arguments
87 var ints int
88 var floats int
89 var stack int
90 for i := 0; i < ty.NumIn(); i++ {
91 arg := ty.In(i)
92 switch arg.Kind() {
93 case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
94 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
95 reflect.Func, reflect.Bool:
96 if ints < numOfIntegerRegisters() {
97 ints++
98 } else {
99 stack++
100 }
101 case reflect.Float32, reflect.Float64:
102 if floats < numOfFloats {
103 floats++
104 } else {
105 stack++
106 }
107 default:
108 panic("purego: unsupported kind " + arg.Kind().String())
109 }
110 }
111 sizeOfStack := maxArgs - numOfIntegerRegisters()
112 if stack > sizeOfStack {
113 panic("purego: too many arguments")
114 }
115 }
116 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
117 if len(args) > 0 {
118 if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
119 // subtract one from args bc the last argument in args is []interface{}
120 // which we are currently expanding
121 tmp := make([]reflect.Value, len(args)-1+len(variadic))
122 n := copy(tmp, args[:len(args)-1])
123 for i, v := range variadic {
124 tmp[n+i] = reflect.ValueOf(v)
125 }
126 args = tmp
127 }
128 }
129 var sysargs [maxArgs]uintptr
130 stack := sysargs[numOfIntegerRegisters():]
131 var floats [numOfFloats]uintptr
132 var numInts int
133 var numFloats int
134 var numStack int
135 addStack := func(x uintptr) {
136 stack[numStack] = x
137 numStack++
138 }
139 addInt := func(x uintptr) {
140 if numInts >= numOfIntegerRegisters() {
141 addStack(x)
142 } else {
143 sysargs[numInts] = x
144 numInts++
145 }
146 }
147 var keepAlive []interface{}
148 defer func() {
149 runtime.KeepAlive(keepAlive)
150 runtime.KeepAlive(args)
151 }()
152 for _, v := range args {
153 switch v.Kind() {
154 case reflect.String:
155 ptr := strings.CString(v.String())
156 keepAlive = append(keepAlive, ptr)
157 addInt(uintptr(unsafe.Pointer(ptr)))
158 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
159 addInt(uintptr(v.Uint()))
160 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
161 addInt(uintptr(v.Int()))
162 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
163 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
164 addInt(v.Pointer())
165 case reflect.Func:
166 addInt(NewCallback(v.Interface()))
167 case reflect.Bool:
168 if v.Bool() {
169 addInt(1)
170 } else {
171 addInt(0)
172 }
173 case reflect.Float32:
174 if numFloats < len(floats) {
175 floats[numFloats] = uintptr(math.Float32bits(float32(v.Float())))
176 numFloats++
177 } else {
178 addStack(uintptr(math.Float32bits(float32(v.Float()))))
179 }
180 case reflect.Float64:
181 if numFloats < len(floats) {
182 floats[numFloats] = uintptr(math.Float64bits(v.Float()))
183 numFloats++
184 } else {
185 addStack(uintptr(math.Float64bits(v.Float())))
186 }
187 default:
188 panic("purego: unsupported kind: " + v.Kind().String())
189 }
190 }
191 // TODO: support structs
192 syscall := syscall9Args{
193 cfn,
194 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
195 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
196 0, 0, 0,
197 }
198 runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
199 r1, r2 := syscall.r1, syscall.r2
200
201 if ty.NumOut() == 0 {
202 return nil
203 }
204 outType := ty.Out(0)
205 v := reflect.New(outType).Elem()
206 switch outType.Kind() {
207 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
208 v.SetUint(uint64(r1))
209 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
210 v.SetInt(int64(r1))
211 case reflect.Bool:
212 v.SetBool(r1 != 0)
213 case reflect.UnsafePointer:
214 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
215 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
216 case reflect.Ptr:
217 // It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()
218 v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()
219 case reflect.Func:
220 // wrap this C function in a nicely typed Go function
221 v = reflect.New(outType)
222 RegisterFunc(v.Interface(), r1)
223 case reflect.String:
224 v.SetString(strings.GoString(r1))
225 case reflect.Float32, reflect.Float64:
226 // NOTE: r2 is only the floating return value on 64bit platforms.
227 // On 32bit platforms r2 is the upper part of a 64bit return.
228 v.SetFloat(math.Float64frombits(uint64(r2)))
229 default:
230 panic("purego: unsupported return kind: " + outType.Kind().String())
231 }
232 return []reflect.Value{v}
233 })
234 fn.Set(v)
235}
236
237func numOfIntegerRegisters() int {
238 switch runtime.GOARCH {
239 case "arm64":
240 return 8
241 case "amd64":
242 return 6
243 default:
244 panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")
245 }
246}