detached 9ef47a2

purego: Add struct return support for darwin amd64 and arm64 (#213)

Add support for returning structs who's fields are floats or integer or struct or arrays containing floats or integers

Closes #195
commit
9ef47a2286c0919e2d0129570e89caadfffe882e
author
TotallyGamerJet · · 2024-02-27 03:44 UTC
committer
GitHub · · 2024-02-27 03:44 UTC
parent
be6f20b
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
refs
tag: v0.7.0-alpha.2
11 files · +719 -35
M func.go +86 -20
@@ -6,6 +6,7 @@
66 package purego
77
88 import (
9+ "fmt"
910 "math"
1011 "reflect"
1112 "runtime"
@@ -163,6 +164,18 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
163164 panic("purego: unsupported kind " + arg.Kind().String())
164165 }
165166 }
167+ if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
168+ if runtime.GOOS != "darwin" {
169+ panic("purego: struct return values only supported on darwin arm64 & amd64")
170+ }
171+ outType := ty.Out(0)
172+ checkStructFieldsSupported(outType)
173+ if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
174+ // on amd64 if struct is bigger than 16 bytes allocate the return struct
175+ // and pass it in as a hidden first argument.
176+ ints++
177+ }
178+ }
166179 sizeOfStack := maxArgs - numOfIntegerRegisters()
167180 if stack > sizeOfStack {
168181 panic("purego: too many arguments")
@@ -229,6 +242,21 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
229242 runtime.KeepAlive(keepAlive)
230243 runtime.KeepAlive(args)
231244 }()
245+ var syscall syscall15Args
246+ if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
247+ outType := ty.Out(0)
248+ if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
249+ val := reflect.New(outType)
250+ keepAlive = append(keepAlive, val)
251+ addInt(val.Pointer())
252+ } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
253+ if !isAllSameFloat(outType) || outType.NumField() > 4 {
254+ val := reflect.New(outType)
255+ keepAlive = append(keepAlive, val)
256+ syscall.arm64_r8 = val.Pointer()
257+ }
258+ }
259+ }
232260 for _, v := range args {
233261 switch v.Kind() {
234262 case reflect.String:
@@ -260,25 +288,23 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
260288 panic("purego: unsupported kind: " + v.Kind().String())
261289 }
262290 }
263- // TODO: support structs
264- var r1, r2 uintptr
265291 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
266292 // Use the normal arm64 calling convention even on Windows
267- syscall := syscall15Args{
293+ syscall = syscall15Args{
268294 cfn,
269295 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
270296 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
271297 sysargs[12], sysargs[13], sysargs[14],
272298 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
273- 0, 0, 0,
299+ 0, 0, 0, syscall.arm64_r8,
274300 }
275301 runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
276- r1, r2 = syscall.r1, syscall.r2
277302 } else {
278303 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
279- r1, r2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
304+ syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
280305 sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
281306 sysargs[12], sysargs[13], sysargs[14])
307+ syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
282308 }
283309 if ty.NumOut() == 0 {
284310 return nil
@@ -287,31 +313,33 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
287313 v := reflect.New(outType).Elem()
288314 switch outType.Kind() {
289315 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
290- v.SetUint(uint64(r1))
316+ v.SetUint(uint64(syscall.a1))
291317 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
292- v.SetInt(int64(r1))
318+ v.SetInt(int64(syscall.a1))
293319 case reflect.Bool:
294- v.SetBool(byte(r1) != 0)
320+ v.SetBool(byte(syscall.a1) != 0)
295321 case reflect.UnsafePointer:
296322 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
297- v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
323+ v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
298324 case reflect.Ptr:
299- // It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()
300- v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()
325+ // It is safe to have the address of syscall.r1 not escape because it is immediately dereferenced with .Elem()
326+ v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&syscall.a1))).Elem()
301327 case reflect.Func:
302328 // wrap this C function in a nicely typed Go function
303329 v = reflect.New(outType)
304- RegisterFunc(v.Interface(), r1)
330+ RegisterFunc(v.Interface(), syscall.a1)
305331 case reflect.String:
306- v.SetString(strings.GoString(r1))
332+ v.SetString(strings.GoString(syscall.a1))
307333 case reflect.Float32:
308- // NOTE: r2 is only the floating return value on 64bit platforms.
309- // On 32bit platforms r2 is the upper part of a 64bit return.
310- v.SetFloat(float64(math.Float32frombits(uint32(r2))))
334+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
335+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
336+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
311337 case reflect.Float64:
312- // NOTE: r2 is only the floating return value on 64bit platforms.
313- // On 32bit platforms r2 is the upper part of a 64bit return.
314- v.SetFloat(math.Float64frombits(uint64(r2)))
338+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
339+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
340+ v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
341+ case reflect.Struct:
342+ v = getStruct(outType, syscall)
315343 default:
316344 panic("purego: unsupported return kind: " + outType.Kind().String())
317345 }
@@ -320,6 +348,44 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
320348 fn.Set(v)
321349 }
322350
351+// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
352+// if it is bigger than this than enough space must be allocated on the heap and then passed into
353+// the function as the first parameter on amd64 or in R8 on arm64.
354+const maxRegAllocStructSize = 16
355+
356+func isAllSameFloat(ty reflect.Type) bool {
357+ first := ty.Field(0).Type.Kind()
358+ if first != reflect.Float32 && first != reflect.Float64 {
359+ return false
360+ }
361+ for i := 0; i < ty.NumField(); i++ {
362+ f := ty.Field(i)
363+ if f.Type.Kind() != first {
364+ return false
365+ }
366+ }
367+ return true
368+}
369+
370+func checkStructFieldsSupported(ty reflect.Type) {
371+ for i := 0; i < ty.NumField(); i++ {
372+ f := ty.Field(i).Type
373+ if f.Kind() == reflect.Array {
374+ f = f.Elem()
375+ } else if f.Kind() == reflect.Struct {
376+ checkStructFieldsSupported(f)
377+ continue
378+ }
379+ switch f.Kind() {
380+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
381+ reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
382+ reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32:
383+ default:
384+ panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
385+ }
386+ }
387+}
388+
323389 const is32bit = unsafe.Sizeof(uintptr(0)) == 4
324390
325391 func roundUpTo8(val uintptr) uintptr {
@@ -6,6 +6,7 @@
6 package purego6 package purego
7 7
8 import (8 import (
9+ "fmt"
9 "math"10 "math"
10 "reflect"11 "reflect"
11 "runtime"12 "runtime"
@@ -163,6 +164,18 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
163 panic("purego: unsupported kind " + arg.Kind().String())164 panic("purego: unsupported kind " + arg.Kind().String())
164 }165 }
165 }166 }
167+ if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
168+ if runtime.GOOS != "darwin" {
169+ panic("purego: struct return values only supported on darwin arm64 & amd64")
170+ }
171+ outType := ty.Out(0)
172+ checkStructFieldsSupported(outType)
173+ if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
174+ // on amd64 if struct is bigger than 16 bytes allocate the return struct
175+ // and pass it in as a hidden first argument.
176+ ints++
177+ }
178+ }
166 sizeOfStack := maxArgs - numOfIntegerRegisters()179 sizeOfStack := maxArgs - numOfIntegerRegisters()
167 if stack > sizeOfStack {180 if stack > sizeOfStack {
168 panic("purego: too many arguments")181 panic("purego: too many arguments")
@@ -229,6 +242,21 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
229 runtime.KeepAlive(keepAlive)242 runtime.KeepAlive(keepAlive)
230 runtime.KeepAlive(args)243 runtime.KeepAlive(args)
231 }()244 }()
245+ var syscall syscall15Args
246+ if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
247+ outType := ty.Out(0)
248+ if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
249+ val := reflect.New(outType)
250+ keepAlive = append(keepAlive, val)
251+ addInt(val.Pointer())
252+ } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
253+ if !isAllSameFloat(outType) || outType.NumField() > 4 {
254+ val := reflect.New(outType)
255+ keepAlive = append(keepAlive, val)
256+ syscall.arm64_r8 = val.Pointer()
257+ }
258+ }
259+ }
232 for _, v := range args {260 for _, v := range args {
233 switch v.Kind() {261 switch v.Kind() {
234 case reflect.String:262 case reflect.String:
@@ -260,25 +288,23 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
260 panic("purego: unsupported kind: " + v.Kind().String())288 panic("purego: unsupported kind: " + v.Kind().String())
261 }289 }
262 }290 }
263- // TODO: support structs
264- var r1, r2 uintptr
265 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {291 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
266 // Use the normal arm64 calling convention even on Windows292 // Use the normal arm64 calling convention even on Windows
267- syscall := syscall15Args{293+ syscall = syscall15Args{
268 cfn,294 cfn,
269 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],295 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
270 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],296 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
271 sysargs[12], sysargs[13], sysargs[14],297 sysargs[12], sysargs[13], sysargs[14],
272 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],298 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
273- 0, 0, 0,299+ 0, 0, 0, syscall.arm64_r8,
274 }300 }
275 runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))301 runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
276- r1, r2 = syscall.r1, syscall.r2
277 } else {302 } else {
278 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats303 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
279- r1, r2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],304+ syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
280 sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],305 sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
281 sysargs[12], sysargs[13], sysargs[14])306 sysargs[12], sysargs[13], sysargs[14])
307+ syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
282 }308 }
283 if ty.NumOut() == 0 {309 if ty.NumOut() == 0 {
284 return nil310 return nil
@@ -287,31 +313,33 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
287 v := reflect.New(outType).Elem()313 v := reflect.New(outType).Elem()
288 switch outType.Kind() {314 switch outType.Kind() {
289 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:315 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
290- v.SetUint(uint64(r1))316+ v.SetUint(uint64(syscall.a1))
291 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:317 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
292- v.SetInt(int64(r1))318+ v.SetInt(int64(syscall.a1))
293 case reflect.Bool:319 case reflect.Bool:
294- v.SetBool(byte(r1) != 0)320+ v.SetBool(byte(syscall.a1) != 0)
295 case reflect.UnsafePointer:321 case reflect.UnsafePointer:
296 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer322 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
297- v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))323+ v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
298 case reflect.Ptr:324 case reflect.Ptr:
299- // It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()325+ // It is safe to have the address of syscall.r1 not escape because it is immediately dereferenced with .Elem()
300- v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()326+ v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&syscall.a1))).Elem()
301 case reflect.Func:327 case reflect.Func:
302 // wrap this C function in a nicely typed Go function328 // wrap this C function in a nicely typed Go function
303 v = reflect.New(outType)329 v = reflect.New(outType)
304- RegisterFunc(v.Interface(), r1)330+ RegisterFunc(v.Interface(), syscall.a1)
305 case reflect.String:331 case reflect.String:
306- v.SetString(strings.GoString(r1))332+ v.SetString(strings.GoString(syscall.a1))
307 case reflect.Float32:333 case reflect.Float32:
308- // NOTE: r2 is only the floating return value on 64bit platforms.334+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
309- // On 32bit platforms r2 is the upper part of a 64bit return.335+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
310- v.SetFloat(float64(math.Float32frombits(uint32(r2))))336+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
311 case reflect.Float64:337 case reflect.Float64:
312- // NOTE: r2 is only the floating return value on 64bit platforms.338+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
313- // On 32bit platforms r2 is the upper part of a 64bit return.339+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
314- v.SetFloat(math.Float64frombits(uint64(r2)))340+ v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
341+ case reflect.Struct:
342+ v = getStruct(outType, syscall)
315 default:343 default:
316 panic("purego: unsupported return kind: " + outType.Kind().String())344 panic("purego: unsupported return kind: " + outType.Kind().String())
317 }345 }
@@ -320,6 +348,44 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
320 fn.Set(v)348 fn.Set(v)
321 }349 }
322 350
351+// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
352+// if it is bigger than this than enough space must be allocated on the heap and then passed into
353+// the function as the first parameter on amd64 or in R8 on arm64.
354+const maxRegAllocStructSize = 16
355+
356+func isAllSameFloat(ty reflect.Type) bool {
357+ first := ty.Field(0).Type.Kind()
358+ if first != reflect.Float32 && first != reflect.Float64 {
359+ return false
360+ }
361+ for i := 0; i < ty.NumField(); i++ {
362+ f := ty.Field(i)
363+ if f.Type.Kind() != first {
364+ return false
365+ }
366+ }
367+ return true
368+}
369+
370+func checkStructFieldsSupported(ty reflect.Type) {
371+ for i := 0; i < ty.NumField(); i++ {
372+ f := ty.Field(i).Type
373+ if f.Kind() == reflect.Array {
374+ f = f.Elem()
375+ } else if f.Kind() == reflect.Struct {
376+ checkStructFieldsSupported(f)
377+ continue
378+ }
379+ switch f.Kind() {
380+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
381+ reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
382+ reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32:
383+ default:
384+ panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
385+ }
386+ }
387+}
388+
323 const is32bit = unsafe.Sizeof(uintptr(0)) == 4389 const is32bit = unsafe.Sizeof(uintptr(0)) == 4
324 390
325 func roundUpTo8(val uintptr) uintptr {391 func roundUpTo8(val uintptr) uintptr {
M struct_amd64.go +65 -0
@@ -6,8 +6,73 @@ package purego
66 import (
77 "math"
88 "reflect"
9+ "unsafe"
910 )
1011
12+func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
13+ outSize := outType.Size()
14+ switch {
15+ case outSize == 0:
16+ return reflect.New(outType).Elem()
17+ case outSize <= 8:
18+ if isAllFloats(outType) {
19+ // 2 float32s or 1 float64s are return in the float register
20+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem()
21+ }
22+ // up to 8 bytes is returned in RAX
23+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
24+ case outSize <= 16:
25+ r1, r2 := syscall.a1, syscall.a2
26+ if isAllFloats(outType) {
27+ r1 = syscall.f1
28+ r2 = syscall.f2
29+ } else {
30+ // check first 8 bytes if it's floats
31+ hasFirstFloat := false
32+ f1 := outType.Field(0).Type
33+ if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 {
34+ r1 = syscall.f1
35+ hasFirstFloat = true
36+ }
37+
38+ // find index of the field that starts the second 8 bytes
39+ var i int
40+ for i = 0; i < outType.NumField(); i++ {
41+ if outType.Field(i).Offset == 8 {
42+ break
43+ }
44+ }
45+
46+ // check last 8 bytes if they are floats
47+ f1 = outType.Field(i).Type
48+ if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() {
49+ r2 = syscall.f1
50+ } else if hasFirstFloat {
51+ // if the first field was a float then that means the second integer field
52+ // comes from the first integer register
53+ r2 = syscall.a1
54+ }
55+ }
56+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
57+ default:
58+ // create struct from the Go pointer created above
59+ // weird pointer dereference to circumvent go vet
60+ return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
61+ }
62+}
63+
64+func isAllFloats(ty reflect.Type) bool {
65+ for i := 0; i < ty.NumField(); i++ {
66+ f := ty.Field(i)
67+ switch f.Type.Kind() {
68+ case reflect.Float64, reflect.Float32:
69+ default:
70+ return false
71+ }
72+ }
73+ return true
74+}
75+
1176 // https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
1277 // https://gitlab.com/x86-psABIs/x86-64-ABI
1378 // Class determines where the 8 byte value goes.
@@ -6,8 +6,73 @@ package purego
6 import (6 import (
7 "math"7 "math"
8 "reflect"8 "reflect"
9+ "unsafe"
9 )10 )
10 11
12+func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
13+ outSize := outType.Size()
14+ switch {
15+ case outSize == 0:
16+ return reflect.New(outType).Elem()
17+ case outSize <= 8:
18+ if isAllFloats(outType) {
19+ // 2 float32s or 1 float64s are return in the float register
20+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem()
21+ }
22+ // up to 8 bytes is returned in RAX
23+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
24+ case outSize <= 16:
25+ r1, r2 := syscall.a1, syscall.a2
26+ if isAllFloats(outType) {
27+ r1 = syscall.f1
28+ r2 = syscall.f2
29+ } else {
30+ // check first 8 bytes if it's floats
31+ hasFirstFloat := false
32+ f1 := outType.Field(0).Type
33+ if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 {
34+ r1 = syscall.f1
35+ hasFirstFloat = true
36+ }
37+
38+ // find index of the field that starts the second 8 bytes
39+ var i int
40+ for i = 0; i < outType.NumField(); i++ {
41+ if outType.Field(i).Offset == 8 {
42+ break
43+ }
44+ }
45+
46+ // check last 8 bytes if they are floats
47+ f1 = outType.Field(i).Type
48+ if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() {
49+ r2 = syscall.f1
50+ } else if hasFirstFloat {
51+ // if the first field was a float then that means the second integer field
52+ // comes from the first integer register
53+ r2 = syscall.a1
54+ }
55+ }
56+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
57+ default:
58+ // create struct from the Go pointer created above
59+ // weird pointer dereference to circumvent go vet
60+ return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
61+ }
62+}
63+
64+func isAllFloats(ty reflect.Type) bool {
65+ for i := 0; i < ty.NumField(); i++ {
66+ f := ty.Field(i)
67+ switch f.Type.Kind() {
68+ case reflect.Float64, reflect.Float32:
69+ default:
70+ return false
71+ }
72+ }
73+ return true
74+}
75+
11 // https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf76 // https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
12 // https://gitlab.com/x86-psABIs/x86-64-ABI77 // https://gitlab.com/x86-psABIs/x86-64-ABI
13 // Class determines where the 8 byte value goes.78 // Class determines where the 8 byte value goes.
M struct_arm64.go +50 -0
@@ -6,8 +6,58 @@ package purego
66 import (
77 "math"
88 "reflect"
9+ "unsafe"
910 )
1011
12+func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
13+ outSize := outType.Size()
14+ switch {
15+ case outSize == 0:
16+ return reflect.New(outType).Elem()
17+ case outSize <= 8:
18+ r1 := syscall.a1
19+ if isAllSameFloat(outType) {
20+ r1 = syscall.f1
21+ if outType.NumField() == 2 {
22+ r1 = syscall.f2<<32 | syscall.f1
23+ }
24+ }
25+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
26+ case outSize <= 16:
27+ r1, r2 := syscall.a1, syscall.a2
28+ if isAllSameFloat(outType) {
29+ switch outType.NumField() {
30+ case 4:
31+ r1 = syscall.f2<<32 | syscall.f1
32+ r2 = syscall.f4<<32 | syscall.f3
33+ case 3:
34+ r1 = syscall.f2<<32 | syscall.f1
35+ r2 = syscall.f3
36+ case 2:
37+ r1 = syscall.f1
38+ r2 = syscall.f2
39+ default:
40+ panic("unreachable")
41+ }
42+ }
43+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
44+ default:
45+ if isAllSameFloat(outType) && outType.NumField() <= 4 {
46+ switch outType.NumField() {
47+ case 4:
48+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
49+ case 3:
50+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem()
51+ default:
52+ panic("unreachable")
53+ }
54+ }
55+ // create struct from the Go pointer created in arm64_r8
56+ // weird pointer dereference to circumvent go vet
57+ return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem()
58+ }
59+}
60+
1161 // https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
1262 const (
1363 _NO_CLASS = 0b00
@@ -6,8 +6,58 @@ package purego
6 import (6 import (
7 "math"7 "math"
8 "reflect"8 "reflect"
9+ "unsafe"
9 )10 )
10 11
12+func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
13+ outSize := outType.Size()
14+ switch {
15+ case outSize == 0:
16+ return reflect.New(outType).Elem()
17+ case outSize <= 8:
18+ r1 := syscall.a1
19+ if isAllSameFloat(outType) {
20+ r1 = syscall.f1
21+ if outType.NumField() == 2 {
22+ r1 = syscall.f2<<32 | syscall.f1
23+ }
24+ }
25+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
26+ case outSize <= 16:
27+ r1, r2 := syscall.a1, syscall.a2
28+ if isAllSameFloat(outType) {
29+ switch outType.NumField() {
30+ case 4:
31+ r1 = syscall.f2<<32 | syscall.f1
32+ r2 = syscall.f4<<32 | syscall.f3
33+ case 3:
34+ r1 = syscall.f2<<32 | syscall.f1
35+ r2 = syscall.f3
36+ case 2:
37+ r1 = syscall.f1
38+ r2 = syscall.f2
39+ default:
40+ panic("unreachable")
41+ }
42+ }
43+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
44+ default:
45+ if isAllSameFloat(outType) && outType.NumField() <= 4 {
46+ switch outType.NumField() {
47+ case 4:
48+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
49+ case 3:
50+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem()
51+ default:
52+ panic("unreachable")
53+ }
54+ }
55+ // create struct from the Go pointer created in arm64_r8
56+ // weird pointer dereference to circumvent go vet
57+ return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem()
58+ }
59+}
60+
11 // https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst61 // https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
12 const (62 const (
13 _NO_CLASS = 0b0063 _NO_CLASS = 0b00
M struct_other.go +4 -0
@@ -10,3 +10,7 @@ import "reflect"
1010 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
1111 panic("purego: struct arguments are not supported")
1212 }
13+
14+func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
15+ panic("purego: struct returns are not supported")
16+}
@@ -10,3 +10,7 @@ import "reflect"
10 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {10 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
11 panic("purego: struct arguments are not supported")11 panic("purego: struct arguments are not supported")
12 }12 }
13+
14+func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
15+ panic("purego: struct returns are not supported")
16+}
M struct_test.go +263 -0
@@ -8,7 +8,9 @@ package purego_test
88 import (
99 "os"
1010 "path/filepath"
11+ "runtime"
1112 "testing"
13+ "unsafe"
1214
1315 "github.com/ebitengine/purego"
1416 )
@@ -439,3 +441,264 @@ func TestRegisterFunc_structArgs(t *testing.T) {
439441 }
440442 }
441443 }
444+
445+func TestRegisterFunc_structReturns(t *testing.T) {
446+ libFileName := filepath.Join(t.TempDir(), "structreturntest.so")
447+ t.Logf("Build %v", libFileName)
448+
449+ if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "structtest", "structreturn_test.c")); err != nil {
450+ t.Fatal(err)
451+ }
452+ defer os.Remove(libFileName)
453+
454+ lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
455+ if err != nil {
456+ t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
457+ }
458+
459+ {
460+ type Empty struct{}
461+ var ReturnEmpty func() Empty
462+ purego.RegisterLibFunc(&ReturnEmpty, lib, "ReturnEmpty")
463+ ret := ReturnEmpty()
464+ _ = ret
465+ }
466+ {
467+ type inner struct{ a int16 }
468+ type StructInStruct struct {
469+ a inner
470+ b inner
471+ c inner
472+ }
473+ var ReturnStructInStruct func(a, b, c int16) StructInStruct
474+ purego.RegisterLibFunc(&ReturnStructInStruct, lib, "ReturnStructInStruct")
475+ expected := StructInStruct{inner{^int16(0)}, inner{2}, inner{3}}
476+ if ret := ReturnStructInStruct(^int16(0), 2, 3); ret != expected {
477+ t.Fatalf("StructInStruct returned %+v wanted %+v", ret, expected)
478+ }
479+ }
480+ {
481+ type ThreeShorts struct{ a, b, c int16 }
482+ var ReturnThreeShorts func(a, b, c int16) ThreeShorts
483+ purego.RegisterLibFunc(&ReturnThreeShorts, lib, "ReturnThreeShorts")
484+ expected := ThreeShorts{^int16(0), 2, 3}
485+ if ret := ReturnThreeShorts(^int16(0), 2, 3); ret != expected {
486+ t.Fatalf("ReturnThreeShorts returned %+v wanted %+v", ret, expected)
487+ }
488+ }
489+ {
490+ type FourShorts struct{ a, b, c, d int16 }
491+ var ReturnFourShorts func(a, b, c, d int16) FourShorts
492+ purego.RegisterLibFunc(&ReturnFourShorts, lib, "ReturnFourShorts")
493+ expected := FourShorts{^int16(0), 2, 3, 4}
494+ if ret := ReturnFourShorts(^int16(0), 2, 3, 4); ret != expected {
495+ t.Fatalf("ReturnFourShorts returned %+v wanted %+v", ret, expected)
496+ }
497+ }
498+ {
499+ type OneLong struct{ a int64 }
500+ var ReturnOneLong func(a int64) OneLong
501+ purego.RegisterLibFunc(&ReturnOneLong, lib, "ReturnOneLong")
502+ expected := OneLong{5}
503+ if ret := ReturnOneLong(5); ret != expected {
504+ t.Fatalf("ReturnOneLong returned %+v wanted %+v", ret, expected)
505+ }
506+ }
507+ {
508+ type TwoLongs struct{ a, b int64 }
509+ var ReturnTwoLongs func(a, b int64) TwoLongs
510+ purego.RegisterLibFunc(&ReturnTwoLongs, lib, "ReturnTwoLongs")
511+ expected := TwoLongs{1, 2}
512+ if ret := ReturnTwoLongs(1, 2); ret != expected {
513+ t.Fatalf("ReturnTwoLongs returned %+v wanted %+v", ret, expected)
514+ }
515+ }
516+ {
517+ type ThreeLongs struct{ a, b, c int64 }
518+ var ReturnThreeLongs func(a, b, c int64) ThreeLongs
519+ purego.RegisterLibFunc(&ReturnThreeLongs, lib, "ReturnThreeLongs")
520+ expected := ThreeLongs{1, 2, 3}
521+ if ret := ReturnThreeLongs(1, 2, 3); ret != expected {
522+ t.Fatalf("ReturnThreeLongs returned %+v wanted %+v", ret, expected)
523+ }
524+ }
525+ {
526+ type OneFloat struct{ a float32 }
527+ var ReturnOneFloat func(a float32) OneFloat
528+ purego.RegisterLibFunc(&ReturnOneFloat, lib, "ReturnOneFloat")
529+ expected := OneFloat{1}
530+ if ret := ReturnOneFloat(1); ret != expected {
531+ t.Fatalf("ReturnOneFloat returned %+v wanted %+v", ret, expected)
532+ }
533+ }
534+ {
535+ type TwoFloats struct{ a, b float32 }
536+ var ReturnTwoFloats func(a, b float32) TwoFloats
537+ purego.RegisterLibFunc(&ReturnTwoFloats, lib, "ReturnTwoFloats")
538+ expected := TwoFloats{3, 10}
539+ if ret := ReturnTwoFloats(5, 2); ret != expected {
540+ t.Fatalf("ReturnTwoFloats returned %+v wanted %+v", ret, expected)
541+ }
542+ }
543+ {
544+ type ThreeFloats struct{ a, b, c float32 }
545+ var ReturnThreeFloats func(a, b, c float32) ThreeFloats
546+ purego.RegisterLibFunc(&ReturnThreeFloats, lib, "ReturnThreeFloats")
547+ expected := ThreeFloats{1, 2, 3}
548+ if ret := ReturnThreeFloats(1, 2, 3); ret != expected {
549+ t.Fatalf("ReturnThreeFloats returned %+v wanted %+v", ret, expected)
550+ }
551+ }
552+ {
553+ type OneDouble struct{ a float64 }
554+ var ReturnOneDouble func(a float64) OneDouble
555+ purego.RegisterLibFunc(&ReturnOneDouble, lib, "ReturnOneDouble")
556+ expected := OneDouble{1}
557+ if ret := ReturnOneDouble(1); ret != expected {
558+ t.Fatalf("ReturnOneDouble returned %+v wanted %+v", ret, expected)
559+ }
560+ }
561+ {
562+ type TwoDoubles struct{ a, b float64 }
563+ var ReturnTwoDoubles func(a, b float64) TwoDoubles
564+ purego.RegisterLibFunc(&ReturnTwoDoubles, lib, "ReturnTwoDoubles")
565+ expected := TwoDoubles{1, 2}
566+ if ret := ReturnTwoDoubles(1, 2); ret != expected {
567+ t.Fatalf("ReturnTwoDoubles returned %+v wanted %+v", ret, expected)
568+ }
569+ }
570+ {
571+ type ThreeDoubles struct{ a, b, c float64 }
572+ var ReturnThreeDoubles func(a, b, c float64) ThreeDoubles
573+ purego.RegisterLibFunc(&ReturnThreeDoubles, lib, "ReturnThreeDoubles")
574+ expected := ThreeDoubles{1, 2, 3}
575+ if ret := ReturnThreeDoubles(1, 2, 3); ret != expected {
576+ t.Fatalf("ReturnThreeDoubles returned %+v wanted %+v", ret, expected)
577+ }
578+ }
579+ {
580+ type FourDoubles struct{ a, b, c, d float64 }
581+ var ReturnFourDoubles func(a, b, c, d float64) FourDoubles
582+ purego.RegisterLibFunc(&ReturnFourDoubles, lib, "ReturnFourDoubles")
583+ expected := FourDoubles{1, 2, 3, 4}
584+ if ret := ReturnFourDoubles(1, 2, 3, 4); ret != expected {
585+ t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected)
586+ }
587+ }
588+ {
589+ type FiveDoubles struct{ a, b, c, d, e float64 }
590+ var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles
591+ purego.RegisterLibFunc(&ReturnFiveDoubles, lib, "ReturnFiveDoubles")
592+ expected := FiveDoubles{1, 2, 3, 4, 5}
593+ if ret := ReturnFiveDoubles(1, 2, 3, 4, 5); ret != expected {
594+ t.Fatalf("ReturnFiveDoubles returned %+v wanted %+v", ret, expected)
595+ }
596+ }
597+ {
598+ type OneFloatOneDouble struct {
599+ a float32
600+ _ float32
601+ b float64
602+ }
603+ var ReturnOneFloatOneDouble func(a float32, b float64) OneFloatOneDouble
604+ purego.RegisterLibFunc(&ReturnOneFloatOneDouble, lib, "ReturnOneFloatOneDouble")
605+ expected := OneFloatOneDouble{a: 1, b: 2}
606+ if ret := ReturnOneFloatOneDouble(1, 2); ret != expected {
607+ t.Fatalf("ReturnOneFloatOneDouble returned %+v wanted %+v", ret, expected)
608+ }
609+ }
610+ {
611+ type OneDoubleOneFloat struct {
612+ a float64
613+ b float32
614+ }
615+ var ReturnOneDoubleOneFloat func(a float64, b float32) OneDoubleOneFloat
616+ purego.RegisterLibFunc(&ReturnOneDoubleOneFloat, lib, "ReturnOneDoubleOneFloat")
617+ expected := OneDoubleOneFloat{1, 2}
618+ if ret := ReturnOneDoubleOneFloat(1, 2); ret != expected {
619+ t.Fatalf("ReturnOneDoubleOneFloat returned %+v wanted %+v", ret, expected)
620+ }
621+ }
622+ {
623+ type Unaligned1 struct {
624+ a int8
625+ _ [1]int8
626+ b int16
627+ _ [1]int32
628+ c int64
629+ }
630+ var ReturnUnaligned1 func(a int8, b int16, c int64) Unaligned1
631+ purego.RegisterLibFunc(&ReturnUnaligned1, lib, "ReturnUnaligned1")
632+ expected := Unaligned1{a: 1, b: 2, c: 3}
633+ if ret := ReturnUnaligned1(1, 2, 3); ret != expected {
634+ t.Fatalf("ReturnUnaligned1 returned %+v wanted %+v", ret, expected)
635+ }
636+ }
637+ {
638+ type Mixed1 struct {
639+ a float32
640+ b int32
641+ }
642+ var ReturnMixed1 func(a float32, b int32) Mixed1
643+ purego.RegisterLibFunc(&ReturnMixed1, lib, "ReturnMixed1")
644+ expected := Mixed1{1, 2}
645+ if ret := ReturnMixed1(1, 2); ret != expected {
646+ t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected)
647+ }
648+ }
649+ {
650+ type Mixed2 struct {
651+ a float32
652+ b int32
653+ c float32
654+ d int32
655+ }
656+ var ReturnMixed2 func(a float32, b int32, c float32, d int32) Mixed2
657+ purego.RegisterLibFunc(&ReturnMixed2, lib, "ReturnMixed2")
658+ expected := Mixed2{1, 2, 3, 4}
659+ if ret := ReturnMixed2(1, 2, 3, 4); ret != expected {
660+ t.Fatalf("ReturnMixed2 returned %+v wanted %+v", ret, expected)
661+ }
662+ }
663+ {
664+ type Mixed3 struct {
665+ a float32
666+ b uint32
667+ c float64
668+ }
669+ var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3
670+ purego.RegisterLibFunc(&ReturnMixed3, lib, "ReturnMixed3")
671+ expected := Mixed3{1, 2, 3}
672+ if ret := ReturnMixed3(1, 2, 3); ret != expected {
673+ t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected)
674+ }
675+ }
676+ {
677+ type Mixed4 struct {
678+ a float64
679+ b uint32
680+ c float32
681+ }
682+ var ReturnMixed4 func(a float64, b uint32, c float32) Mixed4
683+ purego.RegisterLibFunc(&ReturnMixed4, lib, "ReturnMixed4")
684+ expected := Mixed4{1, 2, 3}
685+ if ret := ReturnMixed4(1, 2, 3); ret != expected {
686+ t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected)
687+ }
688+ }
689+ {
690+ type Ptr1 struct {
691+ a *int64
692+ b unsafe.Pointer
693+ }
694+ var ReturnPtr1 func(a *int64, b unsafe.Pointer) Ptr1
695+ purego.RegisterLibFunc(&ReturnPtr1, lib, "ReturnPtr1")
696+ a, b := new(int64), new(struct{})
697+ expected := Ptr1{a, unsafe.Pointer(b)}
698+ if ret := ReturnPtr1(a, unsafe.Pointer(b)); ret != expected {
699+ t.Fatalf("ReturnPtr1 returned %+v wanted %+v", ret, expected)
700+ }
701+ runtime.KeepAlive(a)
702+ runtime.KeepAlive(b)
703+ }
704+}
@@ -8,7 +8,9 @@ package purego_test
8 import (8 import (
9 "os"9 "os"
10 "path/filepath"10 "path/filepath"
11+ "runtime"
11 "testing"12 "testing"
13+ "unsafe"
12 14
13 "github.com/ebitengine/purego"15 "github.com/ebitengine/purego"
14 )16 )
@@ -439,3 +441,264 @@ func TestRegisterFunc_structArgs(t *testing.T) {
439 }441 }
440 }442 }
441 }443 }
444+
445+func TestRegisterFunc_structReturns(t *testing.T) {
446+ libFileName := filepath.Join(t.TempDir(), "structreturntest.so")
447+ t.Logf("Build %v", libFileName)
448+
449+ if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "structtest", "structreturn_test.c")); err != nil {
450+ t.Fatal(err)
451+ }
452+ defer os.Remove(libFileName)
453+
454+ lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
455+ if err != nil {
456+ t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
457+ }
458+
459+ {
460+ type Empty struct{}
461+ var ReturnEmpty func() Empty
462+ purego.RegisterLibFunc(&ReturnEmpty, lib, "ReturnEmpty")
463+ ret := ReturnEmpty()
464+ _ = ret
465+ }
466+ {
467+ type inner struct{ a int16 }
468+ type StructInStruct struct {
469+ a inner
470+ b inner
471+ c inner
472+ }
473+ var ReturnStructInStruct func(a, b, c int16) StructInStruct
474+ purego.RegisterLibFunc(&ReturnStructInStruct, lib, "ReturnStructInStruct")
475+ expected := StructInStruct{inner{^int16(0)}, inner{2}, inner{3}}
476+ if ret := ReturnStructInStruct(^int16(0), 2, 3); ret != expected {
477+ t.Fatalf("StructInStruct returned %+v wanted %+v", ret, expected)
478+ }
479+ }
480+ {
481+ type ThreeShorts struct{ a, b, c int16 }
482+ var ReturnThreeShorts func(a, b, c int16) ThreeShorts
483+ purego.RegisterLibFunc(&ReturnThreeShorts, lib, "ReturnThreeShorts")
484+ expected := ThreeShorts{^int16(0), 2, 3}
485+ if ret := ReturnThreeShorts(^int16(0), 2, 3); ret != expected {
486+ t.Fatalf("ReturnThreeShorts returned %+v wanted %+v", ret, expected)
487+ }
488+ }
489+ {
490+ type FourShorts struct{ a, b, c, d int16 }
491+ var ReturnFourShorts func(a, b, c, d int16) FourShorts
492+ purego.RegisterLibFunc(&ReturnFourShorts, lib, "ReturnFourShorts")
493+ expected := FourShorts{^int16(0), 2, 3, 4}
494+ if ret := ReturnFourShorts(^int16(0), 2, 3, 4); ret != expected {
495+ t.Fatalf("ReturnFourShorts returned %+v wanted %+v", ret, expected)
496+ }
497+ }
498+ {
499+ type OneLong struct{ a int64 }
500+ var ReturnOneLong func(a int64) OneLong
501+ purego.RegisterLibFunc(&ReturnOneLong, lib, "ReturnOneLong")
502+ expected := OneLong{5}
503+ if ret := ReturnOneLong(5); ret != expected {
504+ t.Fatalf("ReturnOneLong returned %+v wanted %+v", ret, expected)
505+ }
506+ }
507+ {
508+ type TwoLongs struct{ a, b int64 }
509+ var ReturnTwoLongs func(a, b int64) TwoLongs
510+ purego.RegisterLibFunc(&ReturnTwoLongs, lib, "ReturnTwoLongs")
511+ expected := TwoLongs{1, 2}
512+ if ret := ReturnTwoLongs(1, 2); ret != expected {
513+ t.Fatalf("ReturnTwoLongs returned %+v wanted %+v", ret, expected)
514+ }
515+ }
516+ {
517+ type ThreeLongs struct{ a, b, c int64 }
518+ var ReturnThreeLongs func(a, b, c int64) ThreeLongs
519+ purego.RegisterLibFunc(&ReturnThreeLongs, lib, "ReturnThreeLongs")
520+ expected := ThreeLongs{1, 2, 3}
521+ if ret := ReturnThreeLongs(1, 2, 3); ret != expected {
522+ t.Fatalf("ReturnThreeLongs returned %+v wanted %+v", ret, expected)
523+ }
524+ }
525+ {
526+ type OneFloat struct{ a float32 }
527+ var ReturnOneFloat func(a float32) OneFloat
528+ purego.RegisterLibFunc(&ReturnOneFloat, lib, "ReturnOneFloat")
529+ expected := OneFloat{1}
530+ if ret := ReturnOneFloat(1); ret != expected {
531+ t.Fatalf("ReturnOneFloat returned %+v wanted %+v", ret, expected)
532+ }
533+ }
534+ {
535+ type TwoFloats struct{ a, b float32 }
536+ var ReturnTwoFloats func(a, b float32) TwoFloats
537+ purego.RegisterLibFunc(&ReturnTwoFloats, lib, "ReturnTwoFloats")
538+ expected := TwoFloats{3, 10}
539+ if ret := ReturnTwoFloats(5, 2); ret != expected {
540+ t.Fatalf("ReturnTwoFloats returned %+v wanted %+v", ret, expected)
541+ }
542+ }
543+ {
544+ type ThreeFloats struct{ a, b, c float32 }
545+ var ReturnThreeFloats func(a, b, c float32) ThreeFloats
546+ purego.RegisterLibFunc(&ReturnThreeFloats, lib, "ReturnThreeFloats")
547+ expected := ThreeFloats{1, 2, 3}
548+ if ret := ReturnThreeFloats(1, 2, 3); ret != expected {
549+ t.Fatalf("ReturnThreeFloats returned %+v wanted %+v", ret, expected)
550+ }
551+ }
552+ {
553+ type OneDouble struct{ a float64 }
554+ var ReturnOneDouble func(a float64) OneDouble
555+ purego.RegisterLibFunc(&ReturnOneDouble, lib, "ReturnOneDouble")
556+ expected := OneDouble{1}
557+ if ret := ReturnOneDouble(1); ret != expected {
558+ t.Fatalf("ReturnOneDouble returned %+v wanted %+v", ret, expected)
559+ }
560+ }
561+ {
562+ type TwoDoubles struct{ a, b float64 }
563+ var ReturnTwoDoubles func(a, b float64) TwoDoubles
564+ purego.RegisterLibFunc(&ReturnTwoDoubles, lib, "ReturnTwoDoubles")
565+ expected := TwoDoubles{1, 2}
566+ if ret := ReturnTwoDoubles(1, 2); ret != expected {
567+ t.Fatalf("ReturnTwoDoubles returned %+v wanted %+v", ret, expected)
568+ }
569+ }
570+ {
571+ type ThreeDoubles struct{ a, b, c float64 }
572+ var ReturnThreeDoubles func(a, b, c float64) ThreeDoubles
573+ purego.RegisterLibFunc(&ReturnThreeDoubles, lib, "ReturnThreeDoubles")
574+ expected := ThreeDoubles{1, 2, 3}
575+ if ret := ReturnThreeDoubles(1, 2, 3); ret != expected {
576+ t.Fatalf("ReturnThreeDoubles returned %+v wanted %+v", ret, expected)
577+ }
578+ }
579+ {
580+ type FourDoubles struct{ a, b, c, d float64 }
581+ var ReturnFourDoubles func(a, b, c, d float64) FourDoubles
582+ purego.RegisterLibFunc(&ReturnFourDoubles, lib, "ReturnFourDoubles")
583+ expected := FourDoubles{1, 2, 3, 4}
584+ if ret := ReturnFourDoubles(1, 2, 3, 4); ret != expected {
585+ t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected)
586+ }
587+ }
588+ {
589+ type FiveDoubles struct{ a, b, c, d, e float64 }
590+ var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles
591+ purego.RegisterLibFunc(&ReturnFiveDoubles, lib, "ReturnFiveDoubles")
592+ expected := FiveDoubles{1, 2, 3, 4, 5}
593+ if ret := ReturnFiveDoubles(1, 2, 3, 4, 5); ret != expected {
594+ t.Fatalf("ReturnFiveDoubles returned %+v wanted %+v", ret, expected)
595+ }
596+ }
597+ {
598+ type OneFloatOneDouble struct {
599+ a float32
600+ _ float32
601+ b float64
602+ }
603+ var ReturnOneFloatOneDouble func(a float32, b float64) OneFloatOneDouble
604+ purego.RegisterLibFunc(&ReturnOneFloatOneDouble, lib, "ReturnOneFloatOneDouble")
605+ expected := OneFloatOneDouble{a: 1, b: 2}
606+ if ret := ReturnOneFloatOneDouble(1, 2); ret != expected {
607+ t.Fatalf("ReturnOneFloatOneDouble returned %+v wanted %+v", ret, expected)
608+ }
609+ }
610+ {
611+ type OneDoubleOneFloat struct {
612+ a float64
613+ b float32
614+ }
615+ var ReturnOneDoubleOneFloat func(a float64, b float32) OneDoubleOneFloat
616+ purego.RegisterLibFunc(&ReturnOneDoubleOneFloat, lib, "ReturnOneDoubleOneFloat")
617+ expected := OneDoubleOneFloat{1, 2}
618+ if ret := ReturnOneDoubleOneFloat(1, 2); ret != expected {
619+ t.Fatalf("ReturnOneDoubleOneFloat returned %+v wanted %+v", ret, expected)
620+ }
621+ }
622+ {
623+ type Unaligned1 struct {
624+ a int8
625+ _ [1]int8
626+ b int16
627+ _ [1]int32
628+ c int64
629+ }
630+ var ReturnUnaligned1 func(a int8, b int16, c int64) Unaligned1
631+ purego.RegisterLibFunc(&ReturnUnaligned1, lib, "ReturnUnaligned1")
632+ expected := Unaligned1{a: 1, b: 2, c: 3}
633+ if ret := ReturnUnaligned1(1, 2, 3); ret != expected {
634+ t.Fatalf("ReturnUnaligned1 returned %+v wanted %+v", ret, expected)
635+ }
636+ }
637+ {
638+ type Mixed1 struct {
639+ a float32
640+ b int32
641+ }
642+ var ReturnMixed1 func(a float32, b int32) Mixed1
643+ purego.RegisterLibFunc(&ReturnMixed1, lib, "ReturnMixed1")
644+ expected := Mixed1{1, 2}
645+ if ret := ReturnMixed1(1, 2); ret != expected {
646+ t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected)
647+ }
648+ }
649+ {
650+ type Mixed2 struct {
651+ a float32
652+ b int32
653+ c float32
654+ d int32
655+ }
656+ var ReturnMixed2 func(a float32, b int32, c float32, d int32) Mixed2
657+ purego.RegisterLibFunc(&ReturnMixed2, lib, "ReturnMixed2")
658+ expected := Mixed2{1, 2, 3, 4}
659+ if ret := ReturnMixed2(1, 2, 3, 4); ret != expected {
660+ t.Fatalf("ReturnMixed2 returned %+v wanted %+v", ret, expected)
661+ }
662+ }
663+ {
664+ type Mixed3 struct {
665+ a float32
666+ b uint32
667+ c float64
668+ }
669+ var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3
670+ purego.RegisterLibFunc(&ReturnMixed3, lib, "ReturnMixed3")
671+ expected := Mixed3{1, 2, 3}
672+ if ret := ReturnMixed3(1, 2, 3); ret != expected {
673+ t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected)
674+ }
675+ }
676+ {
677+ type Mixed4 struct {
678+ a float64
679+ b uint32
680+ c float32
681+ }
682+ var ReturnMixed4 func(a float64, b uint32, c float32) Mixed4
683+ purego.RegisterLibFunc(&ReturnMixed4, lib, "ReturnMixed4")
684+ expected := Mixed4{1, 2, 3}
685+ if ret := ReturnMixed4(1, 2, 3); ret != expected {
686+ t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected)
687+ }
688+ }
689+ {
690+ type Ptr1 struct {
691+ a *int64
692+ b unsafe.Pointer
693+ }
694+ var ReturnPtr1 func(a *int64, b unsafe.Pointer) Ptr1
695+ purego.RegisterLibFunc(&ReturnPtr1, lib, "ReturnPtr1")
696+ a, b := new(int64), new(struct{})
697+ expected := Ptr1{a, unsafe.Pointer(b)}
698+ if ret := ReturnPtr1(a, unsafe.Pointer(b)); ret != expected {
699+ t.Fatalf("ReturnPtr1 returned %+v wanted %+v", ret, expected)
700+ }
701+ runtime.KeepAlive(a)
702+ runtime.KeepAlive(b)
703+ }
704+}
M sys_amd64.s +4 -2
@@ -86,8 +86,10 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
8686 CALL R10
8787
8888 MOVQ PTR_ADDRESS(BP), DI // get the pointer back
89- MOVQ AX, syscall15Args_r1(DI) // r1
90- MOVQ X0, syscall15Args_r2(DI) // r2
89+ MOVQ AX, syscall15Args_a1(DI) // r1
90+ MOVQ DX, syscall15Args_a2(DI) // r3
91+ MOVQ X0, syscall15Args_f1(DI) // f1
92+ MOVQ X1, syscall15Args_f2(DI) // f2
9193
9294 XORL AX, AX // no error (it's ignored anyway)
9395 ADDQ $STACK_SIZE, SP
@@ -86,8 +86,10 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
86 CALL R1086 CALL R10
87 87
88 MOVQ PTR_ADDRESS(BP), DI // get the pointer back88 MOVQ PTR_ADDRESS(BP), DI // get the pointer back
89- MOVQ AX, syscall15Args_r1(DI) // r189+ MOVQ AX, syscall15Args_a1(DI) // r1
90- MOVQ X0, syscall15Args_r2(DI) // r290+ MOVQ DX, syscall15Args_a2(DI) // r3
91+ MOVQ X0, syscall15Args_f1(DI) // f1
92+ MOVQ X1, syscall15Args_f2(DI) // f2
91 93
92 XORL AX, AX // no error (it's ignored anyway)94 XORL AX, AX // no error (it's ignored anyway)
93 ADDQ $STACK_SIZE, SP95 ADDQ $STACK_SIZE, SP
M sys_arm64.s +19 -12
@@ -51,14 +51,15 @@ TEXT syscall15X(SB), NOSPLIT, $0
5151 FMOVD syscall15Args_f7(R9), F6 // f7
5252 FMOVD syscall15Args_f8(R9), F7 // f8
5353
54- MOVD syscall15Args_a1(R9), R0 // a1
55- MOVD syscall15Args_a2(R9), R1 // a2
56- MOVD syscall15Args_a3(R9), R2 // a3
57- MOVD syscall15Args_a4(R9), R3 // a4
58- MOVD syscall15Args_a5(R9), R4 // a5
59- MOVD syscall15Args_a6(R9), R5 // a6
60- MOVD syscall15Args_a7(R9), R6 // a7
61- MOVD syscall15Args_a8(R9), R7 // a8
54+ MOVD syscall15Args_a1(R9), R0 // a1
55+ MOVD syscall15Args_a2(R9), R1 // a2
56+ MOVD syscall15Args_a3(R9), R2 // a3
57+ MOVD syscall15Args_a4(R9), R3 // a4
58+ MOVD syscall15Args_a5(R9), R4 // a5
59+ MOVD syscall15Args_a6(R9), R5 // a6
60+ MOVD syscall15Args_a7(R9), R6 // a7
61+ MOVD syscall15Args_a8(R9), R7 // a8
62+ MOVD syscall15Args_arm64_r8(R9), R8 // r8
6263
6364 MOVD syscall15Args_a9(R9), R10
6465 MOVD R10, 0(RSP) // push a9 onto stack
@@ -78,8 +79,14 @@ TEXT syscall15X(SB), NOSPLIT, $0
7879 MOVD syscall15Args_fn(R9), R10 // fn
7980 BL (R10)
8081
81- MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer
82- ADD $STACK_SIZE, RSP
83- MOVD R0, syscall15Args_r1(R2) // save r1
84- FMOVD F0, syscall15Args_r2(R2) // save r2
82+ MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer
83+ ADD $STACK_SIZE, RSP
84+
85+ MOVD R0, syscall15Args_a1(R2) // save r1
86+ MOVD R1, syscall15Args_a2(R2) // save r3
87+ FMOVD F0, syscall15Args_f1(R2) // save f0
88+ FMOVD F1, syscall15Args_f2(R2) // save f1
89+ FMOVD F2, syscall15Args_f3(R2) // save f2
90+ FMOVD F3, syscall15Args_f4(R2) // save f3
91+
8592 RET
@@ -51,14 +51,15 @@ TEXT syscall15X(SB), NOSPLIT, $0
51 FMOVD syscall15Args_f7(R9), F6 // f751 FMOVD syscall15Args_f7(R9), F6 // f7
52 FMOVD syscall15Args_f8(R9), F7 // f852 FMOVD syscall15Args_f8(R9), F7 // f8
53 53
54- MOVD syscall15Args_a1(R9), R0 // a154+ MOVD syscall15Args_a1(R9), R0 // a1
55- MOVD syscall15Args_a2(R9), R1 // a255+ MOVD syscall15Args_a2(R9), R1 // a2
56- MOVD syscall15Args_a3(R9), R2 // a356+ MOVD syscall15Args_a3(R9), R2 // a3
57- MOVD syscall15Args_a4(R9), R3 // a457+ MOVD syscall15Args_a4(R9), R3 // a4
58- MOVD syscall15Args_a5(R9), R4 // a558+ MOVD syscall15Args_a5(R9), R4 // a5
59- MOVD syscall15Args_a6(R9), R5 // a659+ MOVD syscall15Args_a6(R9), R5 // a6
60- MOVD syscall15Args_a7(R9), R6 // a760+ MOVD syscall15Args_a7(R9), R6 // a7
61- MOVD syscall15Args_a8(R9), R7 // a861+ MOVD syscall15Args_a8(R9), R7 // a8
62+ MOVD syscall15Args_arm64_r8(R9), R8 // r8
62 63
63 MOVD syscall15Args_a9(R9), R1064 MOVD syscall15Args_a9(R9), R10
64 MOVD R10, 0(RSP) // push a9 onto stack65 MOVD R10, 0(RSP) // push a9 onto stack
@@ -78,8 +79,14 @@ TEXT syscall15X(SB), NOSPLIT, $0
78 MOVD syscall15Args_fn(R9), R10 // fn79 MOVD syscall15Args_fn(R9), R10 // fn
79 BL (R10)80 BL (R10)
80 81
81- MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer82+ MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer
82- ADD $STACK_SIZE, RSP83+ ADD $STACK_SIZE, RSP
83- MOVD R0, syscall15Args_r1(R2) // save r184+
84- FMOVD F0, syscall15Args_r2(R2) // save r285+ MOVD R0, syscall15Args_a1(R2) // save r1
86+ MOVD R1, syscall15Args_a2(R2) // save r3
87+ FMOVD F0, syscall15Args_f1(R2) // save f0
88+ FMOVD F1, syscall15Args_f2(R2) // save f1
89+ FMOVD F2, syscall15Args_f3(R2) // save f2
90+ FMOVD F3, syscall15Args_f4(R2) // save f3
91+
85 RET92 RET
M syscall_cgo_linux.go +1 -0
@@ -18,6 +18,7 @@ type syscall15Args struct {
1818 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
1919 f1, f2, f3, f4, f5, f6, f7, f8 uintptr
2020 r1, r2, err uintptr
21+ arm64_r8 uintptr
2122 }
2223
2324 //go:nosplit
@@ -18,6 +18,7 @@ type syscall15Args struct {
18 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr18 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
19 f1, f2, f3, f4, f5, f6, f7, f8 uintptr19 f1, f2, f3, f4, f5, f6, f7, f8 uintptr
20 r1, r2, err uintptr20 r1, r2, err uintptr
21+ arm64_r8 uintptr
21 }22 }
22 23
23 //go:nosplit24 //go:nosplit
M syscall_sysv.go +2 -1
@@ -18,6 +18,7 @@ type syscall15Args struct {
1818 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
1919 f1, f2, f3, f4, f5, f6, f7, f8 uintptr
2020 r1, r2, err uintptr
21+ arm64_r8 uintptr
2122 }
2223
2324 //go:nosplit
@@ -25,7 +26,7 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a
2526 args := syscall15Args{
2627 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15,
2728 a1, a2, a3, a4, a5, a6, a7, a8,
28- r1, r2, err,
29+ r1, r2, err, 0,
2930 }
3031 runtime_cgocall(syscall15XABI0, unsafe.Pointer(&args))
3132 return args.r1, args.r2, args.err
@@ -18,6 +18,7 @@ type syscall15Args struct {
18 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr18 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
19 f1, f2, f3, f4, f5, f6, f7, f8 uintptr19 f1, f2, f3, f4, f5, f6, f7, f8 uintptr
20 r1, r2, err uintptr20 r1, r2, err uintptr
21+ arm64_r8 uintptr
21 }22 }
22 23
23 //go:nosplit24 //go:nosplit
@@ -25,7 +26,7 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a
25 args := syscall15Args{26 args := syscall15Args{
26 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15,27 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15,
27 a1, a2, a3, a4, a5, a6, a7, a8,28 a1, a2, a3, a4, a5, a6, a7, a8,
28- r1, r2, err,29+ r1, r2, err, 0,
29 }30 }
30 runtime_cgocall(syscall15XABI0, unsafe.Pointer(&args))31 runtime_cgocall(syscall15XABI0, unsafe.Pointer(&args))
31 return args.r1, args.r2, args.err32 return args.r1, args.r2, args.err
M syscall_windows.go +1 -0
@@ -16,6 +16,7 @@ type syscall15Args struct {
1616 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
1717 f1, f2, f3, f4, f5, f6, f7, f8 uintptr
1818 r1, r2, err uintptr
19+ arm64_r8 uintptr
1920 }
2021
2122 func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
@@ -16,6 +16,7 @@ type syscall15Args struct {
16 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr16 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
17 f1, f2, f3, f4, f5, f6, f7, f8 uintptr17 f1, f2, f3, f4, f5, f6, f7, f8 uintptr
18 r1, r2, err uintptr18 r1, r2, err uintptr
19+ arm64_r8 uintptr
19 }20 }
20 21
21 func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {22 func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
A testdata/structtest/structreturn_test.c +224 -0
@@ -0,0 +1,224 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+#include <stdint.h>
5+
6+struct Empty{};
7+
8+struct Empty ReturnEmpty() {
9+ struct Empty e = {};
10+ return e;
11+}
12+
13+struct StructInStruct{
14+ struct{ int16_t a; } a;
15+ struct{ int16_t b; } b;
16+ struct{ int16_t c; } c;
17+};
18+
19+struct StructInStruct ReturnStructInStruct(int16_t a, int16_t b, int16_t c) {
20+ struct StructInStruct e = {{a}, {b}, {c}};
21+ return e;
22+}
23+
24+struct ThreeShorts{
25+ int16_t a, b, c;
26+};
27+
28+struct ThreeShorts ReturnThreeShorts(int16_t a, int16_t b, int16_t c) {
29+ struct ThreeShorts e = {a, b, c};
30+ return e;
31+}
32+
33+struct FourShorts{
34+ int16_t a, b, c, d;
35+};
36+
37+struct FourShorts ReturnFourShorts(int16_t a, int16_t b, int16_t c, int16_t d) {
38+ struct FourShorts e = {a, b, c, d};
39+ return e;
40+}
41+
42+struct OneLong{
43+ int64_t a;
44+};
45+
46+struct OneLong ReturnOneLong(int64_t a) {
47+ struct OneLong e = {a};
48+ return e;
49+}
50+
51+struct TwoLongs{
52+ int64_t a, b;
53+};
54+
55+struct TwoLongs ReturnTwoLongs(int64_t a, int64_t b) {
56+ struct TwoLongs e = {a, b};
57+ return e;
58+}
59+
60+struct ThreeLongs{
61+ int64_t a, b, c;
62+};
63+
64+struct ThreeLongs ReturnThreeLongs(int64_t a, int64_t b, int64_t c) {
65+ struct ThreeLongs e = {a, b, c};
66+ return e;
67+}
68+
69+struct OneFloat{
70+ float a;
71+};
72+
73+struct TwoFloats{
74+ float a, b;
75+};
76+
77+struct TwoFloats ReturnTwoFloats(float a, float b) {
78+ struct TwoFloats e = {a-b, a*b};
79+ return e;
80+}
81+
82+struct ThreeFloats{
83+ float a, b, c;
84+};
85+
86+struct ThreeFloats ReturnThreeFloats(float a, float b, float c) {
87+ struct ThreeFloats e = {a, b, c};
88+ return e;
89+}
90+
91+struct OneFloat ReturnOneFloat(float a) {
92+ struct OneFloat e = {a};
93+ return e;
94+}
95+
96+struct OneDouble{
97+ double a;
98+};
99+
100+struct OneDouble ReturnOneDouble(double a) {
101+ struct OneDouble e = {a};
102+ return e;
103+}
104+
105+struct TwoDoubles{
106+ double a, b;
107+};
108+
109+struct TwoDoubles ReturnTwoDoubles(double a, double b) {
110+ struct TwoDoubles e = {a, b};
111+ return e;
112+}
113+
114+struct ThreeDoubles{
115+ double a, b, c;
116+};
117+
118+struct ThreeDoubles ReturnThreeDoubles(double a, double b, double c) {
119+ struct ThreeDoubles e = {a, b, c};
120+ return e;
121+}
122+
123+struct FourDoubles{
124+ double a, b, c, d;
125+};
126+
127+struct FourDoubles ReturnFourDoubles(double a, double b, double c, double d) {
128+ struct FourDoubles e = {a, b, c, d};
129+ return e;
130+}
131+
132+struct FiveDoubles{
133+ double a, b, c, d, e;
134+};
135+
136+struct FiveDoubles ReturnFiveDoubles(double a, double b, double c, double d, double e) {
137+ struct FiveDoubles s = {a, b, c, d, e};
138+ return s;
139+}
140+
141+struct OneFloatOneDouble{
142+ float a;
143+ double b;
144+};
145+
146+struct OneFloatOneDouble ReturnOneFloatOneDouble(float a, double b) {
147+ struct OneFloatOneDouble e = {a, b};
148+ return e;
149+}
150+
151+struct OneDoubleOneFloat{
152+ double a;
153+ float b;
154+};
155+
156+struct OneDoubleOneFloat ReturnOneDoubleOneFloat(double a, float b) {
157+ struct OneDoubleOneFloat e = {a, b};
158+ return e;
159+}
160+
161+struct Unaligned1{
162+ int8_t a;
163+ int16_t b;
164+ int64_t c;
165+};
166+
167+struct Unaligned1 ReturnUnaligned1(int8_t a, int16_t b, int64_t c) {
168+ struct Unaligned1 e = {a, b, c};
169+ return e;
170+}
171+
172+struct Mixed1{
173+ float a;
174+ int32_t b;
175+};
176+
177+struct Mixed1 ReturnMixed1(float a, int32_t b) {
178+ struct Mixed1 e = {a, b};
179+ return e;
180+}
181+
182+struct Mixed2{
183+ float a;
184+ int32_t b;
185+ float c;
186+ int32_t d;
187+};
188+
189+struct Mixed2 ReturnMixed2(float a, int32_t b, float c, int32_t d) {
190+ struct Mixed2 e = {a, b, c, d};
191+ return e;
192+}
193+
194+struct Mixed3{
195+ float a;
196+ uint32_t b;
197+ double c;
198+};
199+
200+struct Mixed3 ReturnMixed3(float a, uint32_t b, double c) {
201+ struct Mixed3 s = {a, b, c};
202+ return s;
203+}
204+
205+struct Mixed4{
206+ double a;
207+ uint32_t b;
208+ float c;
209+};
210+
211+struct Mixed4 ReturnMixed4(double a, uint32_t b, float c) {
212+ struct Mixed4 s = {a, b, c};
213+ return s;
214+}
215+
216+struct Ptr1{
217+ int64_t *a;
218+ void *b;
219+};
220+
221+struct Ptr1 ReturnPtr1(int64_t *a, void *b) {
222+ struct Ptr1 s = {a, b};
223+ return s;
224+}
@@ -0,0 +1,224 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+#include <stdint.h>
5+
6+struct Empty{};
7+
8+struct Empty ReturnEmpty() {
9+ struct Empty e = {};
10+ return e;
11+}
12+
13+struct StructInStruct{
14+ struct{ int16_t a; } a;
15+ struct{ int16_t b; } b;
16+ struct{ int16_t c; } c;
17+};
18+
19+struct StructInStruct ReturnStructInStruct(int16_t a, int16_t b, int16_t c) {
20+ struct StructInStruct e = {{a}, {b}, {c}};
21+ return e;
22+}
23+
24+struct ThreeShorts{
25+ int16_t a, b, c;
26+};
27+
28+struct ThreeShorts ReturnThreeShorts(int16_t a, int16_t b, int16_t c) {
29+ struct ThreeShorts e = {a, b, c};
30+ return e;
31+}
32+
33+struct FourShorts{
34+ int16_t a, b, c, d;
35+};
36+
37+struct FourShorts ReturnFourShorts(int16_t a, int16_t b, int16_t c, int16_t d) {
38+ struct FourShorts e = {a, b, c, d};
39+ return e;
40+}
41+
42+struct OneLong{
43+ int64_t a;
44+};
45+
46+struct OneLong ReturnOneLong(int64_t a) {
47+ struct OneLong e = {a};
48+ return e;
49+}
50+
51+struct TwoLongs{
52+ int64_t a, b;
53+};
54+
55+struct TwoLongs ReturnTwoLongs(int64_t a, int64_t b) {
56+ struct TwoLongs e = {a, b};
57+ return e;
58+}
59+
60+struct ThreeLongs{
61+ int64_t a, b, c;
62+};
63+
64+struct ThreeLongs ReturnThreeLongs(int64_t a, int64_t b, int64_t c) {
65+ struct ThreeLongs e = {a, b, c};
66+ return e;
67+}
68+
69+struct OneFloat{
70+ float a;
71+};
72+
73+struct TwoFloats{
74+ float a, b;
75+};
76+
77+struct TwoFloats ReturnTwoFloats(float a, float b) {
78+ struct TwoFloats e = {a-b, a*b};
79+ return e;
80+}
81+
82+struct ThreeFloats{
83+ float a, b, c;
84+};
85+
86+struct ThreeFloats ReturnThreeFloats(float a, float b, float c) {
87+ struct ThreeFloats e = {a, b, c};
88+ return e;
89+}
90+
91+struct OneFloat ReturnOneFloat(float a) {
92+ struct OneFloat e = {a};
93+ return e;
94+}
95+
96+struct OneDouble{
97+ double a;
98+};
99+
100+struct OneDouble ReturnOneDouble(double a) {
101+ struct OneDouble e = {a};
102+ return e;
103+}
104+
105+struct TwoDoubles{
106+ double a, b;
107+};
108+
109+struct TwoDoubles ReturnTwoDoubles(double a, double b) {
110+ struct TwoDoubles e = {a, b};
111+ return e;
112+}
113+
114+struct ThreeDoubles{
115+ double a, b, c;
116+};
117+
118+struct ThreeDoubles ReturnThreeDoubles(double a, double b, double c) {
119+ struct ThreeDoubles e = {a, b, c};
120+ return e;
121+}
122+
123+struct FourDoubles{
124+ double a, b, c, d;
125+};
126+
127+struct FourDoubles ReturnFourDoubles(double a, double b, double c, double d) {
128+ struct FourDoubles e = {a, b, c, d};
129+ return e;
130+}
131+
132+struct FiveDoubles{
133+ double a, b, c, d, e;
134+};
135+
136+struct FiveDoubles ReturnFiveDoubles(double a, double b, double c, double d, double e) {
137+ struct FiveDoubles s = {a, b, c, d, e};
138+ return s;
139+}
140+
141+struct OneFloatOneDouble{
142+ float a;
143+ double b;
144+};
145+
146+struct OneFloatOneDouble ReturnOneFloatOneDouble(float a, double b) {
147+ struct OneFloatOneDouble e = {a, b};
148+ return e;
149+}
150+
151+struct OneDoubleOneFloat{
152+ double a;
153+ float b;
154+};
155+
156+struct OneDoubleOneFloat ReturnOneDoubleOneFloat(double a, float b) {
157+ struct OneDoubleOneFloat e = {a, b};
158+ return e;
159+}
160+
161+struct Unaligned1{
162+ int8_t a;
163+ int16_t b;
164+ int64_t c;
165+};
166+
167+struct Unaligned1 ReturnUnaligned1(int8_t a, int16_t b, int64_t c) {
168+ struct Unaligned1 e = {a, b, c};
169+ return e;
170+}
171+
172+struct Mixed1{
173+ float a;
174+ int32_t b;
175+};
176+
177+struct Mixed1 ReturnMixed1(float a, int32_t b) {
178+ struct Mixed1 e = {a, b};
179+ return e;
180+}
181+
182+struct Mixed2{
183+ float a;
184+ int32_t b;
185+ float c;
186+ int32_t d;
187+};
188+
189+struct Mixed2 ReturnMixed2(float a, int32_t b, float c, int32_t d) {
190+ struct Mixed2 e = {a, b, c, d};
191+ return e;
192+}
193+
194+struct Mixed3{
195+ float a;
196+ uint32_t b;
197+ double c;
198+};
199+
200+struct Mixed3 ReturnMixed3(float a, uint32_t b, double c) {
201+ struct Mixed3 s = {a, b, c};
202+ return s;
203+}
204+
205+struct Mixed4{
206+ double a;
207+ uint32_t b;
208+ float c;
209+};
210+
211+struct Mixed4 ReturnMixed4(double a, uint32_t b, float c) {
212+ struct Mixed4 s = {a, b, c};
213+ return s;
214+}
215+
216+struct Ptr1{
217+ int64_t *a;
218+ void *b;
219+};
220+
221+struct Ptr1 ReturnPtr1(int64_t *a, void *b) {
222+ struct Ptr1 s = {a, b};
223+ return s;
224+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close