detached f163b4c

Add struct argument support (#175)

Closes #88
commit
f163b4c2606b7a2d3089335dc55a07cb42ac18c1
author
TotallyGamerJet · · 2024-01-12 12:04 UTC
committer
GitHub · · 2024-01-12 12:04 UTC
parent
dab77e6
signature
GPG Unverified4AEE18F83AFDEB23 The key is unknown
6 files · +1219 -1
M func.go +30 -1
@@ -52,7 +52,7 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
5252 // int64 <=> int64_t
5353 // float32 <=> float
5454 // float64 <=> double
55-// struct <=> struct (WIP)
55+// struct <=> struct (WIP - darwin only)
5656 // func <=> C function
5757 // unsafe.Pointer, *T <=> void*
5858 // []T => void*
@@ -81,6 +81,12 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
8181 // using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
8282 // of the pointer
8383 //
84+// # Structs
85+//
86+// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
87+// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
88+// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
89+//
8490 // # Example
8591 //
8692 // All functions below call this C function:
@@ -133,6 +139,23 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
133139 } else {
134140 stack++
135141 }
142+ case reflect.Struct:
143+ if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
144+ panic("purego: struct arguments are only supported on darwin amd64 & arm64")
145+ }
146+ if arg.Size() == 0 {
147+ continue
148+ }
149+ addInt := func(u uintptr) {
150+ ints++
151+ }
152+ addFloat := func(u uintptr) {
153+ floats++
154+ }
155+ addStack := func(u uintptr) {
156+ stack++
157+ }
158+ _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
136159 default:
137160 panic("purego: unsupported kind " + arg.Kind().String())
138161 }
@@ -228,6 +251,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
228251 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
229252 case reflect.Float64:
230253 addFloat(uintptr(math.Float64bits(v.Float())))
254+ case reflect.Struct:
255+ keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
231256 default:
232257 panic("purego: unsupported kind: " + v.Kind().String())
233258 }
@@ -292,6 +317,10 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
292317 fn.Set(v)
293318 }
294319
320+func roundUpTo8(val uintptr) uintptr {
321+ return (val + 7) &^ 7
322+}
323+
295324 func numOfIntegerRegisters() int {
296325 switch runtime.GOARCH {
297326 case "arm64":
@@ -52,7 +52,7 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
52 // int64 <=> int64_t52 // int64 <=> int64_t
53 // float32 <=> float53 // float32 <=> float
54 // float64 <=> double54 // float64 <=> double
55-// struct <=> struct (WIP)55+// struct <=> struct (WIP - darwin only)
56 // func <=> C function56 // func <=> C function
57 // unsafe.Pointer, *T <=> void*57 // unsafe.Pointer, *T <=> void*
58 // []T => void*58 // []T => void*
@@ -81,6 +81,12 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
81 // using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime81 // using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
82 // of the pointer82 // of the pointer
83 //83 //
84+// # Structs
85+//
86+// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
87+// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
88+// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
89+//
84 // # Example90 // # Example
85 //91 //
86 // All functions below call this C function:92 // All functions below call this C function:
@@ -133,6 +139,23 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
133 } else {139 } else {
134 stack++140 stack++
135 }141 }
142+ case reflect.Struct:
143+ if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
144+ panic("purego: struct arguments are only supported on darwin amd64 & arm64")
145+ }
146+ if arg.Size() == 0 {
147+ continue
148+ }
149+ addInt := func(u uintptr) {
150+ ints++
151+ }
152+ addFloat := func(u uintptr) {
153+ floats++
154+ }
155+ addStack := func(u uintptr) {
156+ stack++
157+ }
158+ _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
136 default:159 default:
137 panic("purego: unsupported kind " + arg.Kind().String())160 panic("purego: unsupported kind " + arg.Kind().String())
138 }161 }
@@ -228,6 +251,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
228 addFloat(uintptr(math.Float32bits(float32(v.Float()))))251 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
229 case reflect.Float64:252 case reflect.Float64:
230 addFloat(uintptr(math.Float64bits(v.Float())))253 addFloat(uintptr(math.Float64bits(v.Float())))
254+ case reflect.Struct:
255+ keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
231 default:256 default:
232 panic("purego: unsupported kind: " + v.Kind().String())257 panic("purego: unsupported kind: " + v.Kind().String())
233 }258 }
@@ -292,6 +317,10 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
292 fn.Set(v)317 fn.Set(v)
293 }318 }
294 319
320+func roundUpTo8(val uintptr) uintptr {
321+ return (val + 7) &^ 7
322+}
323+
295 func numOfIntegerRegisters() int {324 func numOfIntegerRegisters() int {
296 switch runtime.GOARCH {325 switch runtime.GOARCH {
297 case "arm64":326 case "arm64":
A struct_amd64.go +203 -0
@@ -0,0 +1,203 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+package purego
5+
6+import (
7+ "math"
8+ "reflect"
9+)
10+
11+// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
12+// https://gitlab.com/x86-psABIs/x86-64-ABI
13+// Class determines where the 8 byte value goes.
14+// Higher value classes win over lower value classes
15+const (
16+ _NO_CLASS = 0b0000
17+ _SSE = 0b0001
18+ _X87 = 0b0011 // long double not used in Go
19+ _INTEGER = 0b0111
20+ _MEMORY = 0b1111
21+)
22+
23+func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
24+ if v.Type().Size() == 0 {
25+ return keepAlive
26+ }
27+
28+ // if greater than 64 bytes place on stack
29+ if v.Type().Size() > 8*8 {
30+ placeStack(v, addStack)
31+ return keepAlive
32+ }
33+ var (
34+ savedNumFloats = *numFloats
35+ savedNumInts = *numInts
36+ savedNumStack = *numStack
37+ )
38+ placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
39+ if placeOnStack {
40+ // reset any values placed in registers
41+ *numFloats = savedNumFloats
42+ *numInts = savedNumInts
43+ *numStack = savedNumStack
44+ placeStack(v, addStack)
45+ }
46+ return keepAlive
47+}
48+
49+func postMerger(t reflect.Type) bool {
50+ // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
51+ // eightbyte isn’t SSEUP, the whole argument is passed in memory.
52+ if t.Kind() != reflect.Struct {
53+ return false
54+ }
55+ if t.Size() <= 2*8 {
56+ return false
57+ }
58+ first := getFirst(t).Kind()
59+ if first != reflect.Float32 && first != reflect.Float64 {
60+ return false
61+ }
62+ return true
63+}
64+
65+func getFirst(t reflect.Type) reflect.Type {
66+ first := t.Field(0).Type
67+ if first.Kind() == reflect.Struct {
68+ return getFirst(first)
69+ }
70+ return first
71+}
72+
73+func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
74+ ok = true
75+ var val uint64
76+ var shift byte // # of bits to shift
77+ var flushed bool
78+ class := _NO_CLASS
79+ var place func(v reflect.Value)
80+ place = func(v reflect.Value) {
81+ var numFields int
82+ if v.Kind() == reflect.Struct {
83+ numFields = v.Type().NumField()
84+ } else {
85+ numFields = v.Type().Len()
86+ }
87+
88+ for i := 0; i < numFields; i++ {
89+ flushed = false
90+ var f reflect.Value
91+ if v.Kind() == reflect.Struct {
92+ f = v.Field(i)
93+ } else {
94+ f = v.Index(i)
95+ }
96+ if shift >= 64 {
97+ shift = 0
98+ flushed = true
99+ if class == _SSE {
100+ addFloat(uintptr(val))
101+ } else {
102+ addInt(uintptr(val))
103+ }
104+ class = _NO_CLASS
105+ }
106+ switch f.Kind() {
107+ case reflect.Struct:
108+ place(f)
109+ case reflect.Bool:
110+ if f.Bool() {
111+ val |= 1
112+ }
113+ shift += 8
114+ class |= _INTEGER
115+ case reflect.Pointer:
116+ ok = false
117+ return
118+ case reflect.Int8:
119+ val |= uint64(f.Int()&0xFF) << shift
120+ shift += 8
121+ class |= _INTEGER
122+ case reflect.Int16:
123+ val |= uint64(f.Int()&0xFFFF) << shift
124+ shift += 16
125+ class |= _INTEGER
126+ case reflect.Int32:
127+ val |= uint64(f.Int()&0xFFFF_FFFF) << shift
128+ shift += 32
129+ class |= _INTEGER
130+ case reflect.Int64:
131+ addInt(uintptr(f.Int()))
132+ shift = 0
133+ class = _NO_CLASS
134+ flushed = true
135+ case reflect.Uint8:
136+ val |= f.Uint() << shift
137+ shift += 8
138+ class |= _INTEGER
139+ case reflect.Uint16:
140+ val |= f.Uint() << shift
141+ shift += 16
142+ class |= _INTEGER
143+ case reflect.Uint32:
144+ val |= f.Uint() << shift
145+ shift += 32
146+ class |= _INTEGER
147+ case reflect.Uint64:
148+ addInt(uintptr(f.Uint()))
149+ shift = 0
150+ class = _NO_CLASS
151+ flushed = true
152+ case reflect.Float32:
153+ val |= uint64(math.Float32bits(float32(f.Float()))) << shift
154+ shift += 32
155+ class |= _SSE
156+ case reflect.Float64:
157+ if v.Type().Size() > 16 {
158+ ok = false
159+ return
160+ }
161+ addFloat(uintptr(math.Float64bits(f.Float())))
162+ class = _NO_CLASS
163+ flushed = true
164+ case reflect.Array:
165+ place(f)
166+ default:
167+ panic("purego: unsupported kind " + f.Kind().String())
168+ }
169+ }
170+ }
171+
172+ place(v)
173+ if !flushed {
174+ if class == _SSE {
175+ addFloat(uintptr(val))
176+ } else {
177+ addInt(uintptr(val))
178+ }
179+ }
180+ return ok
181+}
182+
183+func placeStack(v reflect.Value, addStack func(uintptr)) {
184+ for i := 0; i < v.Type().NumField(); i++ {
185+ f := v.Field(i)
186+ switch f.Kind() {
187+ case reflect.Pointer:
188+ addStack(f.Pointer())
189+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
190+ addStack(uintptr(f.Int()))
191+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
192+ addStack(uintptr(f.Uint()))
193+ case reflect.Float32:
194+ addStack(uintptr(math.Float32bits(float32(f.Float()))))
195+ case reflect.Float64:
196+ addStack(uintptr(math.Float64bits(f.Float())))
197+ case reflect.Struct:
198+ placeStack(f, addStack)
199+ default:
200+ panic("purego: unsupported kind " + f.Kind().String())
201+ }
202+ }
203+}
@@ -0,0 +1,203 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+package purego
5+
6+import (
7+ "math"
8+ "reflect"
9+)
10+
11+// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
12+// https://gitlab.com/x86-psABIs/x86-64-ABI
13+// Class determines where the 8 byte value goes.
14+// Higher value classes win over lower value classes
15+const (
16+ _NO_CLASS = 0b0000
17+ _SSE = 0b0001
18+ _X87 = 0b0011 // long double not used in Go
19+ _INTEGER = 0b0111
20+ _MEMORY = 0b1111
21+)
22+
23+func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
24+ if v.Type().Size() == 0 {
25+ return keepAlive
26+ }
27+
28+ // if greater than 64 bytes place on stack
29+ if v.Type().Size() > 8*8 {
30+ placeStack(v, addStack)
31+ return keepAlive
32+ }
33+ var (
34+ savedNumFloats = *numFloats
35+ savedNumInts = *numInts
36+ savedNumStack = *numStack
37+ )
38+ placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
39+ if placeOnStack {
40+ // reset any values placed in registers
41+ *numFloats = savedNumFloats
42+ *numInts = savedNumInts
43+ *numStack = savedNumStack
44+ placeStack(v, addStack)
45+ }
46+ return keepAlive
47+}
48+
49+func postMerger(t reflect.Type) bool {
50+ // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
51+ // eightbyte isn’t SSEUP, the whole argument is passed in memory.
52+ if t.Kind() != reflect.Struct {
53+ return false
54+ }
55+ if t.Size() <= 2*8 {
56+ return false
57+ }
58+ first := getFirst(t).Kind()
59+ if first != reflect.Float32 && first != reflect.Float64 {
60+ return false
61+ }
62+ return true
63+}
64+
65+func getFirst(t reflect.Type) reflect.Type {
66+ first := t.Field(0).Type
67+ if first.Kind() == reflect.Struct {
68+ return getFirst(first)
69+ }
70+ return first
71+}
72+
73+func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
74+ ok = true
75+ var val uint64
76+ var shift byte // # of bits to shift
77+ var flushed bool
78+ class := _NO_CLASS
79+ var place func(v reflect.Value)
80+ place = func(v reflect.Value) {
81+ var numFields int
82+ if v.Kind() == reflect.Struct {
83+ numFields = v.Type().NumField()
84+ } else {
85+ numFields = v.Type().Len()
86+ }
87+
88+ for i := 0; i < numFields; i++ {
89+ flushed = false
90+ var f reflect.Value
91+ if v.Kind() == reflect.Struct {
92+ f = v.Field(i)
93+ } else {
94+ f = v.Index(i)
95+ }
96+ if shift >= 64 {
97+ shift = 0
98+ flushed = true
99+ if class == _SSE {
100+ addFloat(uintptr(val))
101+ } else {
102+ addInt(uintptr(val))
103+ }
104+ class = _NO_CLASS
105+ }
106+ switch f.Kind() {
107+ case reflect.Struct:
108+ place(f)
109+ case reflect.Bool:
110+ if f.Bool() {
111+ val |= 1
112+ }
113+ shift += 8
114+ class |= _INTEGER
115+ case reflect.Pointer:
116+ ok = false
117+ return
118+ case reflect.Int8:
119+ val |= uint64(f.Int()&0xFF) << shift
120+ shift += 8
121+ class |= _INTEGER
122+ case reflect.Int16:
123+ val |= uint64(f.Int()&0xFFFF) << shift
124+ shift += 16
125+ class |= _INTEGER
126+ case reflect.Int32:
127+ val |= uint64(f.Int()&0xFFFF_FFFF) << shift
128+ shift += 32
129+ class |= _INTEGER
130+ case reflect.Int64:
131+ addInt(uintptr(f.Int()))
132+ shift = 0
133+ class = _NO_CLASS
134+ flushed = true
135+ case reflect.Uint8:
136+ val |= f.Uint() << shift
137+ shift += 8
138+ class |= _INTEGER
139+ case reflect.Uint16:
140+ val |= f.Uint() << shift
141+ shift += 16
142+ class |= _INTEGER
143+ case reflect.Uint32:
144+ val |= f.Uint() << shift
145+ shift += 32
146+ class |= _INTEGER
147+ case reflect.Uint64:
148+ addInt(uintptr(f.Uint()))
149+ shift = 0
150+ class = _NO_CLASS
151+ flushed = true
152+ case reflect.Float32:
153+ val |= uint64(math.Float32bits(float32(f.Float()))) << shift
154+ shift += 32
155+ class |= _SSE
156+ case reflect.Float64:
157+ if v.Type().Size() > 16 {
158+ ok = false
159+ return
160+ }
161+ addFloat(uintptr(math.Float64bits(f.Float())))
162+ class = _NO_CLASS
163+ flushed = true
164+ case reflect.Array:
165+ place(f)
166+ default:
167+ panic("purego: unsupported kind " + f.Kind().String())
168+ }
169+ }
170+ }
171+
172+ place(v)
173+ if !flushed {
174+ if class == _SSE {
175+ addFloat(uintptr(val))
176+ } else {
177+ addInt(uintptr(val))
178+ }
179+ }
180+ return ok
181+}
182+
183+func placeStack(v reflect.Value, addStack func(uintptr)) {
184+ for i := 0; i < v.Type().NumField(); i++ {
185+ f := v.Field(i)
186+ switch f.Kind() {
187+ case reflect.Pointer:
188+ addStack(f.Pointer())
189+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
190+ addStack(uintptr(f.Int()))
191+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
192+ addStack(uintptr(f.Uint()))
193+ case reflect.Float32:
194+ addStack(uintptr(math.Float32bits(float32(f.Float()))))
195+ case reflect.Float64:
196+ addStack(uintptr(math.Float64bits(f.Float())))
197+ case reflect.Struct:
198+ placeStack(f, addStack)
199+ default:
200+ panic("purego: unsupported kind " + f.Kind().String())
201+ }
202+ }
203+}
A struct_arm64.go +224 -0
@@ -0,0 +1,224 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+package purego
5+
6+import (
7+ "math"
8+ "reflect"
9+)
10+
11+// https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
12+const (
13+ _NO_CLASS = 0b00
14+ _FLOAT = 0b01
15+ _INT = 0b11
16+)
17+
18+func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
19+ if v.Type().Size() == 0 {
20+ return keepAlive
21+ }
22+
23+ if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
24+ // if this doesn't fit entirely in registers then
25+ // each element goes onto the stack
26+ if hfa && *numFloats+v.NumField() > numOfFloats {
27+ *numFloats = numOfFloats
28+ } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
29+ *numInts = numOfIntegerRegisters()
30+ }
31+
32+ placeRegisters(v, addFloat, addInt)
33+ } else {
34+ keepAlive = placeStack(v, keepAlive, addInt)
35+ }
36+ return keepAlive // the struct was allocated so don't panic
37+}
38+
39+func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
40+ var val uint64
41+ var shift byte
42+ var flushed bool
43+ class := _NO_CLASS
44+ var place func(v reflect.Value)
45+ place = func(v reflect.Value) {
46+ var numFields int
47+ if v.Kind() == reflect.Struct {
48+ numFields = v.Type().NumField()
49+ } else {
50+ numFields = v.Type().Len()
51+ }
52+ for k := 0; k < numFields; k++ {
53+ flushed = false
54+ var f reflect.Value
55+ if v.Kind() == reflect.Struct {
56+ f = v.Field(k)
57+ } else {
58+ f = v.Index(k)
59+ }
60+ if shift >= 64 {
61+ shift = 0
62+ flushed = true
63+ if class == _FLOAT {
64+ addFloat(uintptr(val))
65+ } else {
66+ addInt(uintptr(val))
67+ }
68+ }
69+ switch f.Type().Kind() {
70+ case reflect.Struct:
71+ place(f)
72+ case reflect.Bool:
73+ if f.Bool() {
74+ val |= 1
75+ }
76+ shift += 8
77+ class |= _INT
78+ case reflect.Uint8:
79+ val |= f.Uint() << shift
80+ shift += 8
81+ class |= _INT
82+ case reflect.Uint16:
83+ val |= f.Uint() << shift
84+ shift += 16
85+ class |= _INT
86+ case reflect.Uint32:
87+ val |= f.Uint() << shift
88+ shift += 32
89+ class |= _INT
90+ case reflect.Uint64:
91+ addInt(uintptr(f.Uint()))
92+ shift = 0
93+ flushed = true
94+ case reflect.Int8:
95+ val |= uint64(f.Int()&0xFF) << shift
96+ shift += 8
97+ class |= _INT
98+ case reflect.Int16:
99+ val |= uint64(f.Int()&0xFFFF) << shift
100+ shift += 16
101+ class |= _INT
102+ case reflect.Int32:
103+ val |= uint64(f.Int()&0xFFFF_FFFF) << shift
104+ shift += 32
105+ class |= _INT
106+ case reflect.Int64:
107+ addInt(uintptr(f.Int()))
108+ shift = 0
109+ flushed = true
110+ case reflect.Float32:
111+ if class == _FLOAT {
112+ addFloat(uintptr(val))
113+ val = 0
114+ shift = 0
115+ }
116+ val |= uint64(math.Float32bits(float32(f.Float()))) << shift
117+ shift += 32
118+ class |= _FLOAT
119+ case reflect.Float64:
120+ addFloat(uintptr(math.Float64bits(float64(f.Float()))))
121+ shift = 0
122+ flushed = true
123+ case reflect.Array:
124+ place(f)
125+ default:
126+ panic("purego: unsupported kind " + f.Kind().String())
127+ }
128+ }
129+ }
130+ place(v)
131+ if !flushed {
132+ if class == _FLOAT {
133+ addFloat(uintptr(val))
134+ } else {
135+ addInt(uintptr(val))
136+ }
137+ }
138+}
139+
140+func placeStack(v reflect.Value, keepAlive []interface{}, addInt func(uintptr)) []interface{} {
141+ // Struct is too big to be placed in registers.
142+ // Copy to heap and place the pointer in register
143+ ptrStruct := reflect.New(v.Type())
144+ ptrStruct.Elem().Set(v)
145+ ptr := ptrStruct.Elem().Addr().UnsafePointer()
146+ keepAlive = append(keepAlive, ptr)
147+ addInt(uintptr(ptr))
148+ return keepAlive
149+}
150+
151+// isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a
152+// Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]).
153+// This type of struct will be placed more compactly than the individual fields.
154+//
155+// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
156+func isHFA(t reflect.Type) bool {
157+ // round up struct size to nearest 8 see section B.4
158+ structSize := roundUpTo8(t.Size())
159+ if structSize == 0 || t.NumField() > 4 {
160+ return false
161+ }
162+ first := t.Field(0)
163+ switch first.Type.Kind() {
164+ case reflect.Float32, reflect.Float64:
165+ firstKind := first.Type.Kind()
166+ for i := 0; i < t.NumField(); i++ {
167+ if t.Field(i).Type.Kind() != firstKind {
168+ return false
169+ }
170+ }
171+ return true
172+ case reflect.Array:
173+ switch first.Type.Elem().Kind() {
174+ case reflect.Float32, reflect.Float64:
175+ return true
176+ default:
177+ return false
178+ }
179+ case reflect.Struct:
180+ for i := 0; i < first.Type.NumField(); i++ {
181+ if !isHFA(first.Type) {
182+ return false
183+ }
184+ }
185+ return true
186+ default:
187+ return false
188+ }
189+}
190+
191+// isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
192+// and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]).
193+// A short vector is a machine type that is composed of repeated instances of one fundamental integral or
194+// floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]).
195+// This type of struct will be placed more compactly than the individual fields.
196+//
197+// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
198+func isHVA(t reflect.Type) bool {
199+ // round up struct size to nearest 8 see section B.4
200+ structSize := roundUpTo8(t.Size())
201+ if structSize == 0 || (structSize != 8 && structSize != 16) {
202+ return false
203+ }
204+ first := t.Field(0)
205+ switch first.Type.Kind() {
206+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
207+ firstKind := first.Type.Kind()
208+ for i := 0; i < t.NumField(); i++ {
209+ if t.Field(i).Type.Kind() != firstKind {
210+ return false
211+ }
212+ }
213+ return true
214+ case reflect.Array:
215+ switch first.Type.Elem().Kind() {
216+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
217+ return true
218+ default:
219+ return false
220+ }
221+ default:
222+ return false
223+ }
224+}
@@ -0,0 +1,224 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+package purego
5+
6+import (
7+ "math"
8+ "reflect"
9+)
10+
11+// https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
12+const (
13+ _NO_CLASS = 0b00
14+ _FLOAT = 0b01
15+ _INT = 0b11
16+)
17+
18+func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
19+ if v.Type().Size() == 0 {
20+ return keepAlive
21+ }
22+
23+ if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
24+ // if this doesn't fit entirely in registers then
25+ // each element goes onto the stack
26+ if hfa && *numFloats+v.NumField() > numOfFloats {
27+ *numFloats = numOfFloats
28+ } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
29+ *numInts = numOfIntegerRegisters()
30+ }
31+
32+ placeRegisters(v, addFloat, addInt)
33+ } else {
34+ keepAlive = placeStack(v, keepAlive, addInt)
35+ }
36+ return keepAlive // the struct was allocated so don't panic
37+}
38+
39+func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
40+ var val uint64
41+ var shift byte
42+ var flushed bool
43+ class := _NO_CLASS
44+ var place func(v reflect.Value)
45+ place = func(v reflect.Value) {
46+ var numFields int
47+ if v.Kind() == reflect.Struct {
48+ numFields = v.Type().NumField()
49+ } else {
50+ numFields = v.Type().Len()
51+ }
52+ for k := 0; k < numFields; k++ {
53+ flushed = false
54+ var f reflect.Value
55+ if v.Kind() == reflect.Struct {
56+ f = v.Field(k)
57+ } else {
58+ f = v.Index(k)
59+ }
60+ if shift >= 64 {
61+ shift = 0
62+ flushed = true
63+ if class == _FLOAT {
64+ addFloat(uintptr(val))
65+ } else {
66+ addInt(uintptr(val))
67+ }
68+ }
69+ switch f.Type().Kind() {
70+ case reflect.Struct:
71+ place(f)
72+ case reflect.Bool:
73+ if f.Bool() {
74+ val |= 1
75+ }
76+ shift += 8
77+ class |= _INT
78+ case reflect.Uint8:
79+ val |= f.Uint() << shift
80+ shift += 8
81+ class |= _INT
82+ case reflect.Uint16:
83+ val |= f.Uint() << shift
84+ shift += 16
85+ class |= _INT
86+ case reflect.Uint32:
87+ val |= f.Uint() << shift
88+ shift += 32
89+ class |= _INT
90+ case reflect.Uint64:
91+ addInt(uintptr(f.Uint()))
92+ shift = 0
93+ flushed = true
94+ case reflect.Int8:
95+ val |= uint64(f.Int()&0xFF) << shift
96+ shift += 8
97+ class |= _INT
98+ case reflect.Int16:
99+ val |= uint64(f.Int()&0xFFFF) << shift
100+ shift += 16
101+ class |= _INT
102+ case reflect.Int32:
103+ val |= uint64(f.Int()&0xFFFF_FFFF) << shift
104+ shift += 32
105+ class |= _INT
106+ case reflect.Int64:
107+ addInt(uintptr(f.Int()))
108+ shift = 0
109+ flushed = true
110+ case reflect.Float32:
111+ if class == _FLOAT {
112+ addFloat(uintptr(val))
113+ val = 0
114+ shift = 0
115+ }
116+ val |= uint64(math.Float32bits(float32(f.Float()))) << shift
117+ shift += 32
118+ class |= _FLOAT
119+ case reflect.Float64:
120+ addFloat(uintptr(math.Float64bits(float64(f.Float()))))
121+ shift = 0
122+ flushed = true
123+ case reflect.Array:
124+ place(f)
125+ default:
126+ panic("purego: unsupported kind " + f.Kind().String())
127+ }
128+ }
129+ }
130+ place(v)
131+ if !flushed {
132+ if class == _FLOAT {
133+ addFloat(uintptr(val))
134+ } else {
135+ addInt(uintptr(val))
136+ }
137+ }
138+}
139+
140+func placeStack(v reflect.Value, keepAlive []interface{}, addInt func(uintptr)) []interface{} {
141+ // Struct is too big to be placed in registers.
142+ // Copy to heap and place the pointer in register
143+ ptrStruct := reflect.New(v.Type())
144+ ptrStruct.Elem().Set(v)
145+ ptr := ptrStruct.Elem().Addr().UnsafePointer()
146+ keepAlive = append(keepAlive, ptr)
147+ addInt(uintptr(ptr))
148+ return keepAlive
149+}
150+
151+// isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a
152+// Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]).
153+// This type of struct will be placed more compactly than the individual fields.
154+//
155+// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
156+func isHFA(t reflect.Type) bool {
157+ // round up struct size to nearest 8 see section B.4
158+ structSize := roundUpTo8(t.Size())
159+ if structSize == 0 || t.NumField() > 4 {
160+ return false
161+ }
162+ first := t.Field(0)
163+ switch first.Type.Kind() {
164+ case reflect.Float32, reflect.Float64:
165+ firstKind := first.Type.Kind()
166+ for i := 0; i < t.NumField(); i++ {
167+ if t.Field(i).Type.Kind() != firstKind {
168+ return false
169+ }
170+ }
171+ return true
172+ case reflect.Array:
173+ switch first.Type.Elem().Kind() {
174+ case reflect.Float32, reflect.Float64:
175+ return true
176+ default:
177+ return false
178+ }
179+ case reflect.Struct:
180+ for i := 0; i < first.Type.NumField(); i++ {
181+ if !isHFA(first.Type) {
182+ return false
183+ }
184+ }
185+ return true
186+ default:
187+ return false
188+ }
189+}
190+
191+// isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
192+// and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]).
193+// A short vector is a machine type that is composed of repeated instances of one fundamental integral or
194+// floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]).
195+// This type of struct will be placed more compactly than the individual fields.
196+//
197+// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
198+func isHVA(t reflect.Type) bool {
199+ // round up struct size to nearest 8 see section B.4
200+ structSize := roundUpTo8(t.Size())
201+ if structSize == 0 || (structSize != 8 && structSize != 16) {
202+ return false
203+ }
204+ first := t.Field(0)
205+ switch first.Type.Kind() {
206+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
207+ firstKind := first.Type.Kind()
208+ for i := 0; i < t.NumField(); i++ {
209+ if t.Field(i).Type.Kind() != firstKind {
210+ return false
211+ }
212+ }
213+ return true
214+ case reflect.Array:
215+ switch first.Type.Elem().Kind() {
216+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
217+ return true
218+ default:
219+ return false
220+ }
221+ default:
222+ return false
223+ }
224+}
A struct_other.go +12 -0
@@ -0,0 +1,12 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+//go:build !amd64 && !arm64
5+
6+package purego
7+
8+import "reflect"
9+
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")
12+}
@@ -0,0 +1,12 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+//go:build !amd64 && !arm64
5+
6+package purego
7+
8+import "reflect"
9+
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")
12+}
A struct_test.go +431 -0
@@ -0,0 +1,431 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+//go:build darwin && (arm64 || amd64)
5+
6+package purego_test
7+
8+import (
9+ "os"
10+ "path/filepath"
11+ "testing"
12+
13+ "github.com/ebitengine/purego"
14+)
15+
16+func TestRegisterFunc_structArgs(t *testing.T) {
17+ libFileName := filepath.Join(t.TempDir(), "structtest.so")
18+ t.Logf("Build %v", libFileName)
19+
20+ if err := buildSharedLib("CC", libFileName, filepath.Join("structtest", "struct_test.c")); err != nil {
21+ t.Fatal(err)
22+ }
23+ defer os.Remove(libFileName)
24+
25+ lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
26+ if err != nil {
27+ t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
28+ }
29+
30+ const (
31+ expectedUnsigned = 0xdeadbeef
32+ expectedSigned = -123
33+ expectedOdd = 12 + 23 + 46
34+ expectedLong uint64 = 0xdeadbeefcafebabe
35+ expectedFloat float32 = 10
36+ expectedDouble float64 = 10
37+ )
38+
39+ {
40+ type Empty struct{}
41+ var NoStruct func(Empty) int64
42+ purego.RegisterLibFunc(&NoStruct, lib, "NoStruct")
43+ if ret := NoStruct(Empty{}); ret != expectedUnsigned {
44+ t.Fatalf("NoStruct returned %#x wanted %#x", ret, expectedUnsigned)
45+ }
46+ }
47+ {
48+ type EmptyEmpty struct{}
49+ var EmptyEmptyFn func(EmptyEmpty) int64
50+ purego.RegisterLibFunc(&EmptyEmptyFn, lib, "EmptyEmpty")
51+ if ret := EmptyEmptyFn(EmptyEmpty{}); ret != expectedUnsigned {
52+ t.Fatalf("EmptyEmpty returned %#x wanted %#x", ret, expectedUnsigned)
53+ }
54+ var EmptyEmptyWithReg func(uint32, EmptyEmpty, uint32) int64
55+ purego.RegisterLibFunc(&EmptyEmptyWithReg, lib, "EmptyEmptyWithReg")
56+ if ret := EmptyEmptyWithReg(0xdead, EmptyEmpty{}, 0xbeef); ret != expectedUnsigned {
57+ t.Fatalf("EmptyEmptyWithReg returned %#x wanted %#x", ret, expectedUnsigned)
58+ }
59+ }
60+ {
61+ type GreaterThan16Bytes struct {
62+ x, y, z *int64
63+ }
64+ var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000
65+ var GreaterThan16BytesFn func(GreaterThan16Bytes) int64
66+ purego.RegisterLibFunc(&GreaterThan16BytesFn, lib, "GreaterThan16Bytes")
67+ if ret := GreaterThan16BytesFn(GreaterThan16Bytes{x: &x, y: &y, z: &z}); ret != expectedUnsigned {
68+ t.Fatalf("GreaterThan16Bytes returned %#x wanted %#x", ret, expectedUnsigned)
69+ }
70+ }
71+ {
72+ type GreaterThan16BytesStruct struct {
73+ a struct {
74+ x, y, z *int64
75+ }
76+ }
77+ var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000
78+ var GreaterThan16BytesStructFn func(GreaterThan16BytesStruct) int64
79+ purego.RegisterLibFunc(&GreaterThan16BytesStructFn, lib, "GreaterThan16BytesStruct")
80+ if ret := GreaterThan16BytesStructFn(GreaterThan16BytesStruct{a: struct{ x, y, z *int64 }{x: &x, y: &y, z: &z}}); ret != expectedUnsigned {
81+ t.Fatalf("GreaterThan16BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned)
82+ }
83+ }
84+ {
85+ type GreaterThan16Bytes struct {
86+ x, y, z *int64
87+ }
88+ var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000
89+ var AfterRegisters func(a, b, c, d, e, f, g, h int, bytes GreaterThan16Bytes) int64
90+ purego.RegisterLibFunc(&AfterRegisters, lib, "AfterRegisters")
91+ if ret := AfterRegisters(0xD0000000, 0xE000000, 0xA00000, 0xD0000, 0xB000, 0xE00, 0xE0, 0xF, GreaterThan16Bytes{x: &x, y: &y, z: &z}); ret != expectedUnsigned {
92+ t.Fatalf("AfterRegisters returned %#x wanted %#x", ret, expectedUnsigned)
93+ }
94+ var BeforeRegisters func(bytes GreaterThan16Bytes, a, b int64) uint64
95+ z -= 0xFF
96+ purego.RegisterLibFunc(&BeforeRegisters, lib, "BeforeRegisters")
97+ if ret := BeforeRegisters(GreaterThan16Bytes{&x, &y, &z}, 0x0F, 0xF0); ret != expectedUnsigned {
98+ t.Fatalf("BeforeRegisters returned %#x wanted %#x", ret, expectedUnsigned)
99+ }
100+ }
101+ {
102+ type IntLessThan16Bytes struct {
103+ x, y int64
104+ }
105+ var IntLessThan16BytesFn func(bytes IntLessThan16Bytes) int64
106+ purego.RegisterLibFunc(&IntLessThan16BytesFn, lib, "IntLessThan16Bytes")
107+ if ret := IntLessThan16BytesFn(IntLessThan16Bytes{0xDEAD0000, 0xBEEF}); ret != expectedUnsigned {
108+ t.Fatalf("IntLessThan16BytesFn returned %#x wanted %#x", ret, expectedUnsigned)
109+ }
110+ }
111+ {
112+ type FloatLessThan16Bytes struct {
113+ x, y float32
114+ }
115+ var FloatLessThan16BytesFn func(FloatLessThan16Bytes) float32
116+ purego.RegisterLibFunc(&FloatLessThan16BytesFn, lib, "FloatLessThan16Bytes")
117+ if ret := FloatLessThan16BytesFn(FloatLessThan16Bytes{3, 7}); ret != expectedFloat {
118+ t.Fatalf("FloatLessThan16Bytes returned %f wanted %f", ret, expectedFloat)
119+ }
120+ }
121+ {
122+ type FloatAndInt struct {
123+ x float32
124+ y int32
125+ }
126+ var FloatAndIntFn func(FloatAndInt) float32
127+ purego.RegisterLibFunc(&FloatAndIntFn, lib, "FloatAndInt")
128+ if ret := FloatAndIntFn(FloatAndInt{3, 7}); ret != expectedFloat {
129+ t.Fatalf("FloatAndIntFn returned %f wanted %f", ret, expectedFloat)
130+ }
131+ }
132+ {
133+ type DoubleStruct struct {
134+ x float64
135+ }
136+ var DoubleStructFn func(DoubleStruct) float64
137+ purego.RegisterLibFunc(&DoubleStructFn, lib, "DoubleStruct")
138+ if ret := DoubleStructFn(DoubleStruct{10}); ret != expectedDouble {
139+ t.Fatalf("DoubleStruct returned %f wanted %f", ret, expectedDouble)
140+ }
141+ }
142+ {
143+ type TwoDoubleStruct struct {
144+ x, y float64
145+ }
146+ var TwoDoubleStructFn func(TwoDoubleStruct) float64
147+ purego.RegisterLibFunc(&TwoDoubleStructFn, lib, "TwoDoubleStruct")
148+ if ret := TwoDoubleStructFn(TwoDoubleStruct{3, 7}); ret != expectedDouble {
149+ t.Fatalf("TwoDoubleStruct returned %f wanted %f", ret, expectedDouble)
150+ }
151+ }
152+ {
153+ type TwoDoubleTwoStruct struct {
154+ x struct {
155+ x, y float64
156+ }
157+ }
158+ var TwoDoubleTwoStructFn func(TwoDoubleTwoStruct) float64
159+ purego.RegisterLibFunc(&TwoDoubleTwoStructFn, lib, "TwoDoubleTwoStruct")
160+ if ret := TwoDoubleTwoStructFn(TwoDoubleTwoStruct{x: struct{ x, y float64 }{x: 3, y: 7}}); ret != expectedDouble {
161+ t.Fatalf("TwoDoubleTwoStruct returned %f wanted %f", ret, expectedDouble)
162+ }
163+ }
164+ {
165+ type ThreeDoubleStruct struct {
166+ x, y, z float64
167+ }
168+ var ThreeDoubleStructFn func(ThreeDoubleStruct) float64
169+ purego.RegisterLibFunc(&ThreeDoubleStructFn, lib, "ThreeDoubleStruct")
170+ if ret := ThreeDoubleStructFn(ThreeDoubleStruct{1, 3, 6}); ret != expectedDouble {
171+ t.Fatalf("ThreeDoubleStructFn returned %f wanted %f", ret, expectedDouble)
172+ }
173+ }
174+ {
175+ type LargeFloatStruct struct {
176+ a, b, c, d, e, f float64
177+ }
178+ var LargeFloatStructFn func(LargeFloatStruct) float64
179+ purego.RegisterLibFunc(&LargeFloatStructFn, lib, "LargeFloatStruct")
180+ if ret := LargeFloatStructFn(LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble {
181+ t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat)
182+ }
183+ var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64
184+ purego.RegisterLibFunc(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs")
185+ if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble {
186+ t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat)
187+ }
188+ }
189+ {
190+ type Rect struct {
191+ x, y, w, h float64
192+ }
193+ var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64
194+ purego.RegisterLibFunc(&RectangleWithRegs, lib, "RectangleWithRegs")
195+ if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{1, 2, 3, -4}); ret != expectedDouble {
196+ t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble)
197+ }
198+ var RectangleSubtract func(rect Rect) float64
199+ purego.RegisterLibFunc(&RectangleSubtract, lib, "RectangleSubtract")
200+ if ret := RectangleSubtract(Rect{15, 5, 3, 7}); ret != expectedDouble {
201+ t.Fatalf("RectangleSubtract returned %f wanted %f", ret, expectedDouble)
202+ }
203+ var Rectangle func(rect Rect) float64
204+ purego.RegisterLibFunc(&Rectangle, lib, "Rectangle")
205+ if ret := Rectangle(Rect{1, 2, 3, 4}); ret != expectedDouble {
206+ t.Fatalf("Rectangle returned %f wanted %f", ret, expectedFloat)
207+ }
208+ }
209+ {
210+ type FloatArray struct {
211+ a [2]float64
212+ }
213+ var FloatArrayFn func(rect FloatArray) float64
214+ purego.RegisterLibFunc(&FloatArrayFn, lib, "FloatArray")
215+ if ret := FloatArrayFn(FloatArray{a: [2]float64{3, 7}}); ret != expectedDouble {
216+ t.Fatalf("FloatArray returned %f wanted %f", ret, expectedFloat)
217+ }
218+ }
219+ {
220+ type UnsignedChar4Bytes struct {
221+ a, b, c, d byte
222+ }
223+ var UnsignedChar4BytesFn func(UnsignedChar4Bytes) uint32
224+ purego.RegisterLibFunc(&UnsignedChar4BytesFn, lib, "UnsignedChar4Bytes")
225+ if ret := UnsignedChar4BytesFn(UnsignedChar4Bytes{a: 0xDE, b: 0xAD, c: 0xBE, d: 0xEF}); ret != expectedUnsigned {
226+ t.Fatalf("UnsignedChar4BytesFn returned %#x wanted %#x", ret, expectedUnsigned)
227+ }
228+ }
229+ {
230+ type UnsignedChar4BytesStruct struct {
231+ x struct {
232+ a byte
233+ }
234+ y struct {
235+ b byte
236+ }
237+ z struct {
238+ c byte
239+ }
240+ w struct {
241+ d byte
242+ }
243+ }
244+ var UnsignedChar4BytesStructFn func(UnsignedChar4BytesStruct) uint32
245+ purego.RegisterLibFunc(&UnsignedChar4BytesStructFn, lib, "UnsignedChar4BytesStruct")
246+ if ret := UnsignedChar4BytesStructFn(UnsignedChar4BytesStruct{
247+ x: struct{ a byte }{a: 0xDE},
248+ y: struct{ b byte }{b: 0xAD},
249+ z: struct{ c byte }{c: 0xBE},
250+ w: struct{ d byte }{d: 0xEF},
251+ }); ret != expectedUnsigned {
252+ t.Fatalf("UnsignedChar4BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned)
253+ }
254+ }
255+ {
256+ type Short struct {
257+ a, b, c, d uint16
258+ }
259+ var ShortFn func(Short) uint64
260+ purego.RegisterLibFunc(&ShortFn, lib, "Short")
261+ if ret := ShortFn(Short{a: 0xDEAD, b: 0xBEEF, c: 0xCAFE, d: 0xBABE}); ret != expectedLong {
262+ t.Fatalf("ShortFn returned %#x wanted %#x", ret, expectedLong)
263+ }
264+ }
265+ {
266+ type Int struct {
267+ a, b uint32
268+ }
269+ var IntFn func(Int) uint64
270+ purego.RegisterLibFunc(&IntFn, lib, "Int")
271+ if ret := IntFn(Int{a: 0xDEADBEEF, b: 0xCAFEBABE}); ret != expectedLong {
272+ t.Fatalf("IntFn returned %#x wanted %#x", ret, expectedLong)
273+ }
274+ }
275+ {
276+ type Long struct {
277+ a uint64
278+ }
279+ var LongFn func(Long) uint64
280+ purego.RegisterLibFunc(&LongFn, lib, "Long")
281+ if ret := LongFn(Long{a: 0xDEADBEEFCAFEBABE}); ret != expectedLong {
282+ t.Fatalf("LongFn returned %#x wanted %#x", ret, expectedLong)
283+ }
284+ }
285+ {
286+ type Char8Bytes struct {
287+ a, b, c, d, e, f, g, h int8
288+ }
289+ var Char8BytesFn func(Char8Bytes) int32
290+ purego.RegisterLibFunc(&Char8BytesFn, lib, "Char8Bytes")
291+ if ret := Char8BytesFn(Char8Bytes{a: -128, b: 127, c: 3, d: -88, e: -3, f: 34, g: -48, h: -20}); ret != expectedSigned {
292+ t.Fatalf("Char8Bytes returned %d wanted %d", ret, expectedSigned)
293+ }
294+ }
295+ {
296+ type Odd struct {
297+ a, b, c byte
298+ }
299+ var OddFn func(Odd) int32
300+ purego.RegisterLibFunc(&OddFn, lib, "Odd")
301+ if ret := OddFn(Odd{a: 12, b: 23, c: 46}); ret != expectedOdd {
302+ t.Fatalf("OddFn returned %d wanted %d", ret, expectedOdd)
303+ }
304+ }
305+ {
306+ type Char2Short1 struct {
307+ a, b byte
308+ c uint16
309+ }
310+ var Char2Short1s func(Char2Short1) int32
311+ purego.RegisterLibFunc(&Char2Short1s, lib, "Char2Short1s")
312+ if ret := Char2Short1s(Char2Short1{a: 12, b: 23, c: 46}); ret != expectedOdd {
313+ t.Fatalf("Char2Short1s returned %d wanted %d", ret, expectedOdd)
314+ }
315+ }
316+ {
317+ type SignedChar2Short1 struct {
318+ a, b int8
319+ c int16
320+ }
321+ var SignedChar2Short1Fn func(SignedChar2Short1) int32
322+ purego.RegisterLibFunc(&SignedChar2Short1Fn, lib, "SignedChar2Short1")
323+ if ret := SignedChar2Short1Fn(SignedChar2Short1{a: 100, b: -23, c: -200}); ret != expectedSigned {
324+ t.Fatalf("SignedChar2Short1Fn returned %d wanted %d", ret, expectedSigned)
325+ }
326+ }
327+ {
328+ type Array4UnsignedChars struct {
329+ a [4]uint8
330+ }
331+ var Array4UnsignedCharsFn func(chars Array4UnsignedChars) uint32
332+ purego.RegisterLibFunc(&Array4UnsignedCharsFn, lib, "Array4UnsignedChars")
333+ if ret := Array4UnsignedCharsFn(Array4UnsignedChars{a: [...]uint8{0xDE, 0xAD, 0xBE, 0xEF}}); ret != expectedUnsigned {
334+ t.Fatalf("Array4UnsignedCharsFn returned %#x wanted %#x", ret, expectedUnsigned)
335+ }
336+ }
337+ {
338+ type Array3UnsignedChar struct {
339+ a [3]uint8
340+ }
341+ var Array3UnsignedChars func(chars Array3UnsignedChar) uint32
342+ purego.RegisterLibFunc(&Array3UnsignedChars, lib, "Array3UnsignedChars")
343+ if ret := Array3UnsignedChars(Array3UnsignedChar{a: [...]uint8{0xDE, 0xAD, 0xBE}}); ret != expectedUnsigned {
344+ t.Fatalf("Array4UnsignedCharsFn returned %#x wanted %#x", ret, expectedUnsigned)
345+ }
346+ }
347+ {
348+ type Array2UnsignedShort struct {
349+ a [2]uint16
350+ }
351+ var Array2UnsignedShorts func(chars Array2UnsignedShort) uint32
352+ purego.RegisterLibFunc(&Array2UnsignedShorts, lib, "Array2UnsignedShorts")
353+ if ret := Array2UnsignedShorts(Array2UnsignedShort{a: [...]uint16{0xDEAD, 0xBEEF}}); ret != expectedUnsigned {
354+ t.Fatalf("Array4UnsignedCharsFn returned %#x wanted %#x", ret, expectedUnsigned)
355+ }
356+ }
357+ {
358+ type Array4Chars struct {
359+ a [4]int8
360+ }
361+ var Array4CharsFn func(chars Array4Chars) int32
362+ purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars")
363+ if ret := Array4CharsFn(Array4Chars{a: [...]int8{100, -127, 4, -100}}); ret != expectedSigned {
364+ t.Fatalf("Array4CharsFn returned %#x wanted %#x", ret, expectedSigned)
365+ }
366+ }
367+ {
368+ type Array2Short struct {
369+ a [2]int16
370+ }
371+ var Array2Shorts func(chars Array2Short) int32
372+ purego.RegisterLibFunc(&Array2Shorts, lib, "Array2Shorts")
373+ if ret := Array2Shorts(Array2Short{a: [...]int16{-333, 210}}); ret != expectedSigned {
374+ t.Fatalf("Array4Shorts returned %#x wanted %#x", ret, expectedSigned)
375+ }
376+ }
377+ {
378+ type Array3Short struct {
379+ a [3]int16
380+ }
381+ var Array3Shorts func(chars Array3Short) int32
382+ purego.RegisterLibFunc(&Array3Shorts, lib, "Array3Shorts")
383+ if ret := Array3Shorts(Array3Short{a: [...]int16{-333, 100, 110}}); ret != expectedSigned {
384+ t.Fatalf("Array4Shorts returned %#x wanted %#x", ret, expectedSigned)
385+ }
386+ }
387+ {
388+ type BoolStruct struct {
389+ b bool
390+ }
391+ var BoolStructFn func(BoolStruct) bool
392+ purego.RegisterLibFunc(&BoolStructFn, lib, "BoolStruct")
393+ if ret := BoolStructFn(BoolStruct{true}); ret != true {
394+ t.Fatalf("BoolStructFn returned %v wanted %v", ret, true)
395+ }
396+ if ret := BoolStructFn(BoolStruct{false}); ret != false {
397+ t.Fatalf("BoolStructFn returned %v wanted %v", ret, false)
398+ }
399+ }
400+ {
401+ type BoolFloat struct {
402+ b bool
403+ _ [3]byte // purego won't do padding for you so make sure it aligns properly with C struct
404+ f float32
405+ }
406+ var BoolFloatFn func(BoolFloat) float32
407+ purego.RegisterLibFunc(&BoolFloatFn, lib, "BoolFloat")
408+ if ret := BoolFloatFn(BoolFloat{b: true, f: 10}); ret != expectedFloat {
409+ t.Fatalf("BoolFloatFn returned %f wanted %f", ret, expectedFloat)
410+ }
411+ if ret := BoolFloatFn(BoolFloat{b: false, f: 10}); ret != -expectedFloat {
412+ t.Fatalf("BoolFloatFn returned %f wanted %f", ret, -expectedFloat)
413+ }
414+ }
415+ {
416+ type point struct{ x, y float64 }
417+ type size struct{ width, height float64 }
418+ type Content struct {
419+ point point
420+ size size
421+ }
422+ var InitWithContentRect func(*int, Content, int32, int32, bool) uint64
423+ purego.RegisterLibFunc(&InitWithContentRect, lib, "InitWithContentRect")
424+ if ret := InitWithContentRect(new(int),
425+ // These numbers are created so that when added together and then divided by 11 it produces 0xdeadbeef
426+ Content{point{x: 41_000_000_000, y: 95_000_000}, size{width: 214_000, height: 149}},
427+ 15, 4, true); ret != expectedUnsigned {
428+ t.Fatalf("InitWithContentRect returned %d wanted %#x", ret, expectedUnsigned)
429+ }
430+ }
431+}
@@ -0,0 +1,431 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+//go:build darwin && (arm64 || amd64)
5+
6+package purego_test
7+
8+import (
9+ "os"
10+ "path/filepath"
11+ "testing"
12+
13+ "github.com/ebitengine/purego"
14+)
15+
16+func TestRegisterFunc_structArgs(t *testing.T) {
17+ libFileName := filepath.Join(t.TempDir(), "structtest.so")
18+ t.Logf("Build %v", libFileName)
19+
20+ if err := buildSharedLib("CC", libFileName, filepath.Join("structtest", "struct_test.c")); err != nil {
21+ t.Fatal(err)
22+ }
23+ defer os.Remove(libFileName)
24+
25+ lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
26+ if err != nil {
27+ t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
28+ }
29+
30+ const (
31+ expectedUnsigned = 0xdeadbeef
32+ expectedSigned = -123
33+ expectedOdd = 12 + 23 + 46
34+ expectedLong uint64 = 0xdeadbeefcafebabe
35+ expectedFloat float32 = 10
36+ expectedDouble float64 = 10
37+ )
38+
39+ {
40+ type Empty struct{}
41+ var NoStruct func(Empty) int64
42+ purego.RegisterLibFunc(&NoStruct, lib, "NoStruct")
43+ if ret := NoStruct(Empty{}); ret != expectedUnsigned {
44+ t.Fatalf("NoStruct returned %#x wanted %#x", ret, expectedUnsigned)
45+ }
46+ }
47+ {
48+ type EmptyEmpty struct{}
49+ var EmptyEmptyFn func(EmptyEmpty) int64
50+ purego.RegisterLibFunc(&EmptyEmptyFn, lib, "EmptyEmpty")
51+ if ret := EmptyEmptyFn(EmptyEmpty{}); ret != expectedUnsigned {
52+ t.Fatalf("EmptyEmpty returned %#x wanted %#x", ret, expectedUnsigned)
53+ }
54+ var EmptyEmptyWithReg func(uint32, EmptyEmpty, uint32) int64
55+ purego.RegisterLibFunc(&EmptyEmptyWithReg, lib, "EmptyEmptyWithReg")
56+ if ret := EmptyEmptyWithReg(0xdead, EmptyEmpty{}, 0xbeef); ret != expectedUnsigned {
57+ t.Fatalf("EmptyEmptyWithReg returned %#x wanted %#x", ret, expectedUnsigned)
58+ }
59+ }
60+ {
61+ type GreaterThan16Bytes struct {
62+ x, y, z *int64
63+ }
64+ var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000
65+ var GreaterThan16BytesFn func(GreaterThan16Bytes) int64
66+ purego.RegisterLibFunc(&GreaterThan16BytesFn, lib, "GreaterThan16Bytes")
67+ if ret := GreaterThan16BytesFn(GreaterThan16Bytes{x: &x, y: &y, z: &z}); ret != expectedUnsigned {
68+ t.Fatalf("GreaterThan16Bytes returned %#x wanted %#x", ret, expectedUnsigned)
69+ }
70+ }
71+ {
72+ type GreaterThan16BytesStruct struct {
73+ a struct {
74+ x, y, z *int64
75+ }
76+ }
77+ var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000
78+ var GreaterThan16BytesStructFn func(GreaterThan16BytesStruct) int64
79+ purego.RegisterLibFunc(&GreaterThan16BytesStructFn, lib, "GreaterThan16BytesStruct")
80+ if ret := GreaterThan16BytesStructFn(GreaterThan16BytesStruct{a: struct{ x, y, z *int64 }{x: &x, y: &y, z: &z}}); ret != expectedUnsigned {
81+ t.Fatalf("GreaterThan16BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned)
82+ }
83+ }
84+ {
85+ type GreaterThan16Bytes struct {
86+ x, y, z *int64
87+ }
88+ var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000
89+ var AfterRegisters func(a, b, c, d, e, f, g, h int, bytes GreaterThan16Bytes) int64
90+ purego.RegisterLibFunc(&AfterRegisters, lib, "AfterRegisters")
91+ if ret := AfterRegisters(0xD0000000, 0xE000000, 0xA00000, 0xD0000, 0xB000, 0xE00, 0xE0, 0xF, GreaterThan16Bytes{x: &x, y: &y, z: &z}); ret != expectedUnsigned {
92+ t.Fatalf("AfterRegisters returned %#x wanted %#x", ret, expectedUnsigned)
93+ }
94+ var BeforeRegisters func(bytes GreaterThan16Bytes, a, b int64) uint64
95+ z -= 0xFF
96+ purego.RegisterLibFunc(&BeforeRegisters, lib, "BeforeRegisters")
97+ if ret := BeforeRegisters(GreaterThan16Bytes{&x, &y, &z}, 0x0F, 0xF0); ret != expectedUnsigned {
98+ t.Fatalf("BeforeRegisters returned %#x wanted %#x", ret, expectedUnsigned)
99+ }
100+ }
101+ {
102+ type IntLessThan16Bytes struct {
103+ x, y int64
104+ }
105+ var IntLessThan16BytesFn func(bytes IntLessThan16Bytes) int64
106+ purego.RegisterLibFunc(&IntLessThan16BytesFn, lib, "IntLessThan16Bytes")
107+ if ret := IntLessThan16BytesFn(IntLessThan16Bytes{0xDEAD0000, 0xBEEF}); ret != expectedUnsigned {
108+ t.Fatalf("IntLessThan16BytesFn returned %#x wanted %#x", ret, expectedUnsigned)
109+ }
110+ }
111+ {
112+ type FloatLessThan16Bytes struct {
113+ x, y float32
114+ }
115+ var FloatLessThan16BytesFn func(FloatLessThan16Bytes) float32
116+ purego.RegisterLibFunc(&FloatLessThan16BytesFn, lib, "FloatLessThan16Bytes")
117+ if ret := FloatLessThan16BytesFn(FloatLessThan16Bytes{3, 7}); ret != expectedFloat {
118+ t.Fatalf("FloatLessThan16Bytes returned %f wanted %f", ret, expectedFloat)
119+ }
120+ }
121+ {
122+ type FloatAndInt struct {
123+ x float32
124+ y int32
125+ }
126+ var FloatAndIntFn func(FloatAndInt) float32
127+ purego.RegisterLibFunc(&FloatAndIntFn, lib, "FloatAndInt")
128+ if ret := FloatAndIntFn(FloatAndInt{3, 7}); ret != expectedFloat {
129+ t.Fatalf("FloatAndIntFn returned %f wanted %f", ret, expectedFloat)
130+ }
131+ }
132+ {
133+ type DoubleStruct struct {
134+ x float64
135+ }
136+ var DoubleStructFn func(DoubleStruct) float64
137+ purego.RegisterLibFunc(&DoubleStructFn, lib, "DoubleStruct")
138+ if ret := DoubleStructFn(DoubleStruct{10}); ret != expectedDouble {
139+ t.Fatalf("DoubleStruct returned %f wanted %f", ret, expectedDouble)
140+ }
141+ }
142+ {
143+ type TwoDoubleStruct struct {
144+ x, y float64
145+ }
146+ var TwoDoubleStructFn func(TwoDoubleStruct) float64
147+ purego.RegisterLibFunc(&TwoDoubleStructFn, lib, "TwoDoubleStruct")
148+ if ret := TwoDoubleStructFn(TwoDoubleStruct{3, 7}); ret != expectedDouble {
149+ t.Fatalf("TwoDoubleStruct returned %f wanted %f", ret, expectedDouble)
150+ }
151+ }
152+ {
153+ type TwoDoubleTwoStruct struct {
154+ x struct {
155+ x, y float64
156+ }
157+ }
158+ var TwoDoubleTwoStructFn func(TwoDoubleTwoStruct) float64
159+ purego.RegisterLibFunc(&TwoDoubleTwoStructFn, lib, "TwoDoubleTwoStruct")
160+ if ret := TwoDoubleTwoStructFn(TwoDoubleTwoStruct{x: struct{ x, y float64 }{x: 3, y: 7}}); ret != expectedDouble {
161+ t.Fatalf("TwoDoubleTwoStruct returned %f wanted %f", ret, expectedDouble)
162+ }
163+ }
164+ {
165+ type ThreeDoubleStruct struct {
166+ x, y, z float64
167+ }
168+ var ThreeDoubleStructFn func(ThreeDoubleStruct) float64
169+ purego.RegisterLibFunc(&ThreeDoubleStructFn, lib, "ThreeDoubleStruct")
170+ if ret := ThreeDoubleStructFn(ThreeDoubleStruct{1, 3, 6}); ret != expectedDouble {
171+ t.Fatalf("ThreeDoubleStructFn returned %f wanted %f", ret, expectedDouble)
172+ }
173+ }
174+ {
175+ type LargeFloatStruct struct {
176+ a, b, c, d, e, f float64
177+ }
178+ var LargeFloatStructFn func(LargeFloatStruct) float64
179+ purego.RegisterLibFunc(&LargeFloatStructFn, lib, "LargeFloatStruct")
180+ if ret := LargeFloatStructFn(LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble {
181+ t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat)
182+ }
183+ var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64
184+ purego.RegisterLibFunc(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs")
185+ if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble {
186+ t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat)
187+ }
188+ }
189+ {
190+ type Rect struct {
191+ x, y, w, h float64
192+ }
193+ var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64
194+ purego.RegisterLibFunc(&RectangleWithRegs, lib, "RectangleWithRegs")
195+ if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{1, 2, 3, -4}); ret != expectedDouble {
196+ t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble)
197+ }
198+ var RectangleSubtract func(rect Rect) float64
199+ purego.RegisterLibFunc(&RectangleSubtract, lib, "RectangleSubtract")
200+ if ret := RectangleSubtract(Rect{15, 5, 3, 7}); ret != expectedDouble {
201+ t.Fatalf("RectangleSubtract returned %f wanted %f", ret, expectedDouble)
202+ }
203+ var Rectangle func(rect Rect) float64
204+ purego.RegisterLibFunc(&Rectangle, lib, "Rectangle")
205+ if ret := Rectangle(Rect{1, 2, 3, 4}); ret != expectedDouble {
206+ t.Fatalf("Rectangle returned %f wanted %f", ret, expectedFloat)
207+ }
208+ }
209+ {
210+ type FloatArray struct {
211+ a [2]float64
212+ }
213+ var FloatArrayFn func(rect FloatArray) float64
214+ purego.RegisterLibFunc(&FloatArrayFn, lib, "FloatArray")
215+ if ret := FloatArrayFn(FloatArray{a: [2]float64{3, 7}}); ret != expectedDouble {
216+ t.Fatalf("FloatArray returned %f wanted %f", ret, expectedFloat)
217+ }
218+ }
219+ {
220+ type UnsignedChar4Bytes struct {
221+ a, b, c, d byte
222+ }
223+ var UnsignedChar4BytesFn func(UnsignedChar4Bytes) uint32
224+ purego.RegisterLibFunc(&UnsignedChar4BytesFn, lib, "UnsignedChar4Bytes")
225+ if ret := UnsignedChar4BytesFn(UnsignedChar4Bytes{a: 0xDE, b: 0xAD, c: 0xBE, d: 0xEF}); ret != expectedUnsigned {
226+ t.Fatalf("UnsignedChar4BytesFn returned %#x wanted %#x", ret, expectedUnsigned)
227+ }
228+ }
229+ {
230+ type UnsignedChar4BytesStruct struct {
231+ x struct {
232+ a byte
233+ }
234+ y struct {
235+ b byte
236+ }
237+ z struct {
238+ c byte
239+ }
240+ w struct {
241+ d byte
242+ }
243+ }
244+ var UnsignedChar4BytesStructFn func(UnsignedChar4BytesStruct) uint32
245+ purego.RegisterLibFunc(&UnsignedChar4BytesStructFn, lib, "UnsignedChar4BytesStruct")
246+ if ret := UnsignedChar4BytesStructFn(UnsignedChar4BytesStruct{
247+ x: struct{ a byte }{a: 0xDE},
248+ y: struct{ b byte }{b: 0xAD},
249+ z: struct{ c byte }{c: 0xBE},
250+ w: struct{ d byte }{d: 0xEF},
251+ }); ret != expectedUnsigned {
252+ t.Fatalf("UnsignedChar4BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned)
253+ }
254+ }
255+ {
256+ type Short struct {
257+ a, b, c, d uint16
258+ }
259+ var ShortFn func(Short) uint64
260+ purego.RegisterLibFunc(&ShortFn, lib, "Short")
261+ if ret := ShortFn(Short{a: 0xDEAD, b: 0xBEEF, c: 0xCAFE, d: 0xBABE}); ret != expectedLong {
262+ t.Fatalf("ShortFn returned %#x wanted %#x", ret, expectedLong)
263+ }
264+ }
265+ {
266+ type Int struct {
267+ a, b uint32
268+ }
269+ var IntFn func(Int) uint64
270+ purego.RegisterLibFunc(&IntFn, lib, "Int")
271+ if ret := IntFn(Int{a: 0xDEADBEEF, b: 0xCAFEBABE}); ret != expectedLong {
272+ t.Fatalf("IntFn returned %#x wanted %#x", ret, expectedLong)
273+ }
274+ }
275+ {
276+ type Long struct {
277+ a uint64
278+ }
279+ var LongFn func(Long) uint64
280+ purego.RegisterLibFunc(&LongFn, lib, "Long")
281+ if ret := LongFn(Long{a: 0xDEADBEEFCAFEBABE}); ret != expectedLong {
282+ t.Fatalf("LongFn returned %#x wanted %#x", ret, expectedLong)
283+ }
284+ }
285+ {
286+ type Char8Bytes struct {
287+ a, b, c, d, e, f, g, h int8
288+ }
289+ var Char8BytesFn func(Char8Bytes) int32
290+ purego.RegisterLibFunc(&Char8BytesFn, lib, "Char8Bytes")
291+ if ret := Char8BytesFn(Char8Bytes{a: -128, b: 127, c: 3, d: -88, e: -3, f: 34, g: -48, h: -20}); ret != expectedSigned {
292+ t.Fatalf("Char8Bytes returned %d wanted %d", ret, expectedSigned)
293+ }
294+ }
295+ {
296+ type Odd struct {
297+ a, b, c byte
298+ }
299+ var OddFn func(Odd) int32
300+ purego.RegisterLibFunc(&OddFn, lib, "Odd")
301+ if ret := OddFn(Odd{a: 12, b: 23, c: 46}); ret != expectedOdd {
302+ t.Fatalf("OddFn returned %d wanted %d", ret, expectedOdd)
303+ }
304+ }
305+ {
306+ type Char2Short1 struct {
307+ a, b byte
308+ c uint16
309+ }
310+ var Char2Short1s func(Char2Short1) int32
311+ purego.RegisterLibFunc(&Char2Short1s, lib, "Char2Short1s")
312+ if ret := Char2Short1s(Char2Short1{a: 12, b: 23, c: 46}); ret != expectedOdd {
313+ t.Fatalf("Char2Short1s returned %d wanted %d", ret, expectedOdd)
314+ }
315+ }
316+ {
317+ type SignedChar2Short1 struct {
318+ a, b int8
319+ c int16
320+ }
321+ var SignedChar2Short1Fn func(SignedChar2Short1) int32
322+ purego.RegisterLibFunc(&SignedChar2Short1Fn, lib, "SignedChar2Short1")
323+ if ret := SignedChar2Short1Fn(SignedChar2Short1{a: 100, b: -23, c: -200}); ret != expectedSigned {
324+ t.Fatalf("SignedChar2Short1Fn returned %d wanted %d", ret, expectedSigned)
325+ }
326+ }
327+ {
328+ type Array4UnsignedChars struct {
329+ a [4]uint8
330+ }
331+ var Array4UnsignedCharsFn func(chars Array4UnsignedChars) uint32
332+ purego.RegisterLibFunc(&Array4UnsignedCharsFn, lib, "Array4UnsignedChars")
333+ if ret := Array4UnsignedCharsFn(Array4UnsignedChars{a: [...]uint8{0xDE, 0xAD, 0xBE, 0xEF}}); ret != expectedUnsigned {
334+ t.Fatalf("Array4UnsignedCharsFn returned %#x wanted %#x", ret, expectedUnsigned)
335+ }
336+ }
337+ {
338+ type Array3UnsignedChar struct {
339+ a [3]uint8
340+ }
341+ var Array3UnsignedChars func(chars Array3UnsignedChar) uint32
342+ purego.RegisterLibFunc(&Array3UnsignedChars, lib, "Array3UnsignedChars")
343+ if ret := Array3UnsignedChars(Array3UnsignedChar{a: [...]uint8{0xDE, 0xAD, 0xBE}}); ret != expectedUnsigned {
344+ t.Fatalf("Array4UnsignedCharsFn returned %#x wanted %#x", ret, expectedUnsigned)
345+ }
346+ }
347+ {
348+ type Array2UnsignedShort struct {
349+ a [2]uint16
350+ }
351+ var Array2UnsignedShorts func(chars Array2UnsignedShort) uint32
352+ purego.RegisterLibFunc(&Array2UnsignedShorts, lib, "Array2UnsignedShorts")
353+ if ret := Array2UnsignedShorts(Array2UnsignedShort{a: [...]uint16{0xDEAD, 0xBEEF}}); ret != expectedUnsigned {
354+ t.Fatalf("Array4UnsignedCharsFn returned %#x wanted %#x", ret, expectedUnsigned)
355+ }
356+ }
357+ {
358+ type Array4Chars struct {
359+ a [4]int8
360+ }
361+ var Array4CharsFn func(chars Array4Chars) int32
362+ purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars")
363+ if ret := Array4CharsFn(Array4Chars{a: [...]int8{100, -127, 4, -100}}); ret != expectedSigned {
364+ t.Fatalf("Array4CharsFn returned %#x wanted %#x", ret, expectedSigned)
365+ }
366+ }
367+ {
368+ type Array2Short struct {
369+ a [2]int16
370+ }
371+ var Array2Shorts func(chars Array2Short) int32
372+ purego.RegisterLibFunc(&Array2Shorts, lib, "Array2Shorts")
373+ if ret := Array2Shorts(Array2Short{a: [...]int16{-333, 210}}); ret != expectedSigned {
374+ t.Fatalf("Array4Shorts returned %#x wanted %#x", ret, expectedSigned)
375+ }
376+ }
377+ {
378+ type Array3Short struct {
379+ a [3]int16
380+ }
381+ var Array3Shorts func(chars Array3Short) int32
382+ purego.RegisterLibFunc(&Array3Shorts, lib, "Array3Shorts")
383+ if ret := Array3Shorts(Array3Short{a: [...]int16{-333, 100, 110}}); ret != expectedSigned {
384+ t.Fatalf("Array4Shorts returned %#x wanted %#x", ret, expectedSigned)
385+ }
386+ }
387+ {
388+ type BoolStruct struct {
389+ b bool
390+ }
391+ var BoolStructFn func(BoolStruct) bool
392+ purego.RegisterLibFunc(&BoolStructFn, lib, "BoolStruct")
393+ if ret := BoolStructFn(BoolStruct{true}); ret != true {
394+ t.Fatalf("BoolStructFn returned %v wanted %v", ret, true)
395+ }
396+ if ret := BoolStructFn(BoolStruct{false}); ret != false {
397+ t.Fatalf("BoolStructFn returned %v wanted %v", ret, false)
398+ }
399+ }
400+ {
401+ type BoolFloat struct {
402+ b bool
403+ _ [3]byte // purego won't do padding for you so make sure it aligns properly with C struct
404+ f float32
405+ }
406+ var BoolFloatFn func(BoolFloat) float32
407+ purego.RegisterLibFunc(&BoolFloatFn, lib, "BoolFloat")
408+ if ret := BoolFloatFn(BoolFloat{b: true, f: 10}); ret != expectedFloat {
409+ t.Fatalf("BoolFloatFn returned %f wanted %f", ret, expectedFloat)
410+ }
411+ if ret := BoolFloatFn(BoolFloat{b: false, f: 10}); ret != -expectedFloat {
412+ t.Fatalf("BoolFloatFn returned %f wanted %f", ret, -expectedFloat)
413+ }
414+ }
415+ {
416+ type point struct{ x, y float64 }
417+ type size struct{ width, height float64 }
418+ type Content struct {
419+ point point
420+ size size
421+ }
422+ var InitWithContentRect func(*int, Content, int32, int32, bool) uint64
423+ purego.RegisterLibFunc(&InitWithContentRect, lib, "InitWithContentRect")
424+ if ret := InitWithContentRect(new(int),
425+ // These numbers are created so that when added together and then divided by 11 it produces 0xdeadbeef
426+ Content{point{x: 41_000_000_000, y: 95_000_000}, size{width: 214_000, height: 149}},
427+ 15, 4, true); ret != expectedUnsigned {
428+ t.Fatalf("InitWithContentRect returned %d wanted %#x", ret, expectedUnsigned)
429+ }
430+ }
431+}
A structtest/struct_test.c +319 -0
@@ -0,0 +1,319 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+// Empty is empty
5+struct Empty {};
6+
7+// NoStruct tests that an empty struct doesn't cause issues
8+unsigned long NoStruct(struct Empty e) {
9+ return 0xdeadbeef;
10+}
11+
12+struct EmptyEmpty {
13+ struct Empty x;
14+};
15+
16+unsigned long EmptyEmpty(struct EmptyEmpty e) {
17+ return 0xdeadbeef;
18+}
19+
20+unsigned long EmptyEmptyWithReg(unsigned int x, struct EmptyEmpty e, unsigned int y) {
21+ return (x << 16) | y;
22+}
23+
24+// GreaterThan16Bytes is 24 bytes on 64 bit systems
25+struct GreaterThan16Bytes {
26+ long *x, *y, *z;
27+};
28+
29+// GreaterThan16Bytes is a basic test for structs bigger than 16 bytes
30+unsigned long GreaterThan16Bytes(struct GreaterThan16Bytes g) {
31+ return *g.x + *g.y + *g.z;
32+}
33+
34+// AfterRegisters tests to make sure that structs placed on the stack work properly
35+unsigned long AfterRegisters(long a, long b, long c, long d, long e, long f, long g, long h, struct GreaterThan16Bytes bytes) {
36+ long registers = a + b + c + d + e + f + g + h;
37+ long stack = *bytes.x + *bytes.y + *bytes.z;
38+ if (registers != stack) {
39+ return 0xbadbad;
40+ }
41+ if (stack != 0xdeadbeef) {
42+ return 0xcafebad;
43+ }
44+ return stack;
45+}
46+
47+unsigned long BeforeRegisters(struct GreaterThan16Bytes bytes, long a, long b) {
48+ return *bytes.x + *bytes.y + *bytes.z + a + b;
49+}
50+
51+struct GreaterThan16BytesStruct {
52+ struct {
53+ long *x, *y, *z;
54+ } a ;
55+};
56+
57+unsigned long GreaterThan16BytesStruct(struct GreaterThan16BytesStruct g) {
58+ return *(g.a.x) + *(g.a.y) + *(g.a.z);
59+}
60+
61+struct IntLessThan16Bytes {
62+ long x, y;
63+};
64+
65+unsigned long IntLessThan16Bytes(struct IntLessThan16Bytes l) {
66+ return l.x + l.y;
67+}
68+
69+struct FloatLessThan16Bytes {
70+ float x, y;
71+};
72+
73+float FloatLessThan16Bytes(struct FloatLessThan16Bytes f) {
74+ return f.x + f.y;
75+}
76+
77+struct FloatAndInt {
78+ float x;
79+ int y;
80+};
81+
82+float FloatAndInt(struct FloatAndInt f) {
83+ return f.x + f.y;
84+}
85+
86+struct DoubleStruct {
87+ double x;
88+};
89+
90+double DoubleStruct(struct DoubleStruct d) {
91+ return d.x;
92+}
93+
94+struct TwoDoubleStruct {
95+ double x, y;
96+};
97+
98+double TwoDoubleStruct(struct TwoDoubleStruct d) {
99+ return d.x + d.y;
100+}
101+
102+struct TwoDoubleTwoStruct {
103+ struct {
104+ double x, y;
105+ } s;
106+};
107+
108+double TwoDoubleTwoStruct(struct TwoDoubleTwoStruct d) {
109+ return d.s.x + d.s.y;
110+}
111+
112+struct ThreeDoubleStruct {
113+ double x, y, z;
114+};
115+
116+double ThreeDoubleStruct(struct ThreeDoubleStruct d) {
117+ return d.x + d.y + d.z;
118+}
119+
120+struct LargeFloatStruct {
121+ double a, b, c, d, e, f;
122+};
123+
124+double LargeFloatStruct(struct LargeFloatStruct s) {
125+ return s.a + s.b + s.c + s.d + s.e + s.f;
126+}
127+
128+double LargeFloatStructWithRegs(double a, double b, double c, struct LargeFloatStruct s) {
129+ return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f;
130+}
131+
132+struct Rect {
133+ double x, y, w, h;
134+};
135+
136+double Rectangle(struct Rect rect) {
137+ return rect.x + rect.y + rect.w + rect.h;
138+}
139+
140+double RectangleSubtract(struct Rect rect) {
141+ return (rect.x + rect.y) - (rect.w + rect.h);
142+}
143+
144+double RectangleWithRegs(double a, double b, double c, double d, double e, struct Rect rect) {
145+ return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h;
146+}
147+
148+struct FloatArray {
149+ double a[2];
150+};
151+
152+double FloatArray(struct FloatArray f) {
153+ return f.a[0] + f.a[1];
154+}
155+
156+struct UnsignedChar4Bytes {
157+ unsigned char a, b, c, d;
158+};
159+
160+unsigned int UnsignedChar4Bytes(struct UnsignedChar4Bytes b) {
161+ return (((int)b.a)<<24) | (((int)b.b)<<16) | (((int)b.c)<<8) | (((int)b.d)<<0);
162+}
163+
164+struct UnsignedChar4BytesStruct {
165+ struct {
166+ unsigned char a;
167+ } x;
168+ struct {
169+ unsigned char b;
170+ } y;
171+ struct {
172+ unsigned char c;
173+ } z;
174+ struct {
175+ unsigned char d;
176+ } w;
177+};
178+
179+unsigned int UnsignedChar4BytesStruct(struct UnsignedChar4BytesStruct b) {
180+ return (((int)b.x.a)<<24) | (((int)b.y.b)<<16) | (((int)b.z.c)<<8) | (((int)b.w.d)<<0);
181+}
182+
183+struct Short {
184+ unsigned short a, b, c, d;
185+};
186+
187+unsigned long Short(struct Short s) {
188+ return (long)s.a << 48 | (long)s.b << 32 | (long)s.c << 16 | (long)s.d << 0;
189+}
190+
191+struct Int {
192+ unsigned int a, b;
193+};
194+
195+unsigned long Int(struct Int i) {
196+ return (long)i.a << 32 | (long)i.b << 0;
197+}
198+
199+struct Long {
200+ unsigned long a;
201+};
202+
203+unsigned long Long(struct Long l) {
204+ return l.a;
205+}
206+
207+struct Char8Bytes {
208+ signed char a, b, c, d, e, f, g, h;
209+};
210+
211+int Char8Bytes(struct Char8Bytes b) {
212+ return (int)b.a + (int)b.b + (int)b.c + (int)b.d + (int)b.e + (int)b.f + (int)b.g + (int)b.h;
213+}
214+
215+struct Odd {
216+ unsigned char a, b, c;
217+};
218+
219+int Odd(struct Odd o) {
220+ return (int)o.a + (int)o.b + (int)o.c;
221+}
222+
223+struct Char2Short1 {
224+ unsigned char a, b;
225+ unsigned short c;
226+};
227+
228+int Char2Short1s(struct Char2Short1 s) {
229+ return (int)s.a + (int)s.b + (int)s.c;
230+}
231+
232+struct SignedChar2Short1 {
233+ signed char a, b;
234+ signed short c;
235+};
236+
237+int SignedChar2Short1(struct SignedChar2Short1 s) {
238+ return s.a + s.b + s.c;
239+}
240+
241+struct Array4UnsignedChars {
242+ unsigned char a[4];
243+};
244+
245+unsigned int Array4UnsignedChars(struct Array4UnsignedChars a) {
246+ return (((int)a.a[0])<<24) | (((int)a.a[1])<<16) | (((int)a.a[2])<<8) | (((int)a.a[3])<<0);
247+}
248+
249+struct Array3UnsignedChar {
250+ unsigned char a[3];
251+};
252+
253+unsigned int Array3UnsignedChars(struct Array3UnsignedChar a) {
254+ return (((int)a.a[0])<<24) | (((int)a.a[1])<<16) | (((int)a.a[2])<<8) | 0xef;
255+}
256+
257+struct Array2UnsignedShort {
258+ unsigned short a[2];
259+};
260+
261+unsigned int Array2UnsignedShorts(struct Array2UnsignedShort a) {
262+ return (((int)a.a[0])<<16) | (((int)a.a[1])<<0);
263+}
264+
265+struct Array4Chars {
266+ char a[4];
267+};
268+
269+int Array4Chars(struct Array4Chars a) {
270+ return (int)a.a[0] + (int)a.a[1] + (int)a.a[2] + (int)a.a[3];
271+}
272+
273+struct Array2Short {
274+ short a[2];
275+};
276+
277+int Array2Shorts(struct Array2Short a) {
278+ return (int)a.a[0] + (int)a.a[1];
279+}
280+
281+struct Array3Short {
282+ short a[3];
283+};
284+
285+int Array3Shorts(struct Array3Short a) {
286+ return (int)a.a[0] + (int)a.a[1] + (int)a.a[2];
287+}
288+
289+struct BoolStruct {
290+ _Bool b;
291+};
292+
293+_Bool BoolStruct(struct BoolStruct b) {
294+ return b.b;
295+}
296+
297+struct BoolFloat {
298+ _Bool b;
299+ float f;
300+};
301+
302+float BoolFloat(struct BoolFloat s) {
303+ if (s.b)
304+ return s.f;
305+ return -s.f;
306+}
307+
308+struct Content {
309+ struct { double x, y; } point;
310+ struct { double width, height; } size;
311+};
312+
313+unsigned long InitWithContentRect(int *win, struct Content c, int style, int backing, _Bool flag) {
314+ if (win == 0)
315+ return 0xBAD;
316+ if (!flag)
317+ return 0xF1A6; // FLAG
318+ return (unsigned long)(c.point.x + c.point.y + c.size.width + c.size.height) / (style - backing);
319+}
@@ -0,0 +1,319 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3+
4+// Empty is empty
5+struct Empty {};
6+
7+// NoStruct tests that an empty struct doesn't cause issues
8+unsigned long NoStruct(struct Empty e) {
9+ return 0xdeadbeef;
10+}
11+
12+struct EmptyEmpty {
13+ struct Empty x;
14+};
15+
16+unsigned long EmptyEmpty(struct EmptyEmpty e) {
17+ return 0xdeadbeef;
18+}
19+
20+unsigned long EmptyEmptyWithReg(unsigned int x, struct EmptyEmpty e, unsigned int y) {
21+ return (x << 16) | y;
22+}
23+
24+// GreaterThan16Bytes is 24 bytes on 64 bit systems
25+struct GreaterThan16Bytes {
26+ long *x, *y, *z;
27+};
28+
29+// GreaterThan16Bytes is a basic test for structs bigger than 16 bytes
30+unsigned long GreaterThan16Bytes(struct GreaterThan16Bytes g) {
31+ return *g.x + *g.y + *g.z;
32+}
33+
34+// AfterRegisters tests to make sure that structs placed on the stack work properly
35+unsigned long AfterRegisters(long a, long b, long c, long d, long e, long f, long g, long h, struct GreaterThan16Bytes bytes) {
36+ long registers = a + b + c + d + e + f + g + h;
37+ long stack = *bytes.x + *bytes.y + *bytes.z;
38+ if (registers != stack) {
39+ return 0xbadbad;
40+ }
41+ if (stack != 0xdeadbeef) {
42+ return 0xcafebad;
43+ }
44+ return stack;
45+}
46+
47+unsigned long BeforeRegisters(struct GreaterThan16Bytes bytes, long a, long b) {
48+ return *bytes.x + *bytes.y + *bytes.z + a + b;
49+}
50+
51+struct GreaterThan16BytesStruct {
52+ struct {
53+ long *x, *y, *z;
54+ } a ;
55+};
56+
57+unsigned long GreaterThan16BytesStruct(struct GreaterThan16BytesStruct g) {
58+ return *(g.a.x) + *(g.a.y) + *(g.a.z);
59+}
60+
61+struct IntLessThan16Bytes {
62+ long x, y;
63+};
64+
65+unsigned long IntLessThan16Bytes(struct IntLessThan16Bytes l) {
66+ return l.x + l.y;
67+}
68+
69+struct FloatLessThan16Bytes {
70+ float x, y;
71+};
72+
73+float FloatLessThan16Bytes(struct FloatLessThan16Bytes f) {
74+ return f.x + f.y;
75+}
76+
77+struct FloatAndInt {
78+ float x;
79+ int y;
80+};
81+
82+float FloatAndInt(struct FloatAndInt f) {
83+ return f.x + f.y;
84+}
85+
86+struct DoubleStruct {
87+ double x;
88+};
89+
90+double DoubleStruct(struct DoubleStruct d) {
91+ return d.x;
92+}
93+
94+struct TwoDoubleStruct {
95+ double x, y;
96+};
97+
98+double TwoDoubleStruct(struct TwoDoubleStruct d) {
99+ return d.x + d.y;
100+}
101+
102+struct TwoDoubleTwoStruct {
103+ struct {
104+ double x, y;
105+ } s;
106+};
107+
108+double TwoDoubleTwoStruct(struct TwoDoubleTwoStruct d) {
109+ return d.s.x + d.s.y;
110+}
111+
112+struct ThreeDoubleStruct {
113+ double x, y, z;
114+};
115+
116+double ThreeDoubleStruct(struct ThreeDoubleStruct d) {
117+ return d.x + d.y + d.z;
118+}
119+
120+struct LargeFloatStruct {
121+ double a, b, c, d, e, f;
122+};
123+
124+double LargeFloatStruct(struct LargeFloatStruct s) {
125+ return s.a + s.b + s.c + s.d + s.e + s.f;
126+}
127+
128+double LargeFloatStructWithRegs(double a, double b, double c, struct LargeFloatStruct s) {
129+ return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f;
130+}
131+
132+struct Rect {
133+ double x, y, w, h;
134+};
135+
136+double Rectangle(struct Rect rect) {
137+ return rect.x + rect.y + rect.w + rect.h;
138+}
139+
140+double RectangleSubtract(struct Rect rect) {
141+ return (rect.x + rect.y) - (rect.w + rect.h);
142+}
143+
144+double RectangleWithRegs(double a, double b, double c, double d, double e, struct Rect rect) {
145+ return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h;
146+}
147+
148+struct FloatArray {
149+ double a[2];
150+};
151+
152+double FloatArray(struct FloatArray f) {
153+ return f.a[0] + f.a[1];
154+}
155+
156+struct UnsignedChar4Bytes {
157+ unsigned char a, b, c, d;
158+};
159+
160+unsigned int UnsignedChar4Bytes(struct UnsignedChar4Bytes b) {
161+ return (((int)b.a)<<24) | (((int)b.b)<<16) | (((int)b.c)<<8) | (((int)b.d)<<0);
162+}
163+
164+struct UnsignedChar4BytesStruct {
165+ struct {
166+ unsigned char a;
167+ } x;
168+ struct {
169+ unsigned char b;
170+ } y;
171+ struct {
172+ unsigned char c;
173+ } z;
174+ struct {
175+ unsigned char d;
176+ } w;
177+};
178+
179+unsigned int UnsignedChar4BytesStruct(struct UnsignedChar4BytesStruct b) {
180+ return (((int)b.x.a)<<24) | (((int)b.y.b)<<16) | (((int)b.z.c)<<8) | (((int)b.w.d)<<0);
181+}
182+
183+struct Short {
184+ unsigned short a, b, c, d;
185+};
186+
187+unsigned long Short(struct Short s) {
188+ return (long)s.a << 48 | (long)s.b << 32 | (long)s.c << 16 | (long)s.d << 0;
189+}
190+
191+struct Int {
192+ unsigned int a, b;
193+};
194+
195+unsigned long Int(struct Int i) {
196+ return (long)i.a << 32 | (long)i.b << 0;
197+}
198+
199+struct Long {
200+ unsigned long a;
201+};
202+
203+unsigned long Long(struct Long l) {
204+ return l.a;
205+}
206+
207+struct Char8Bytes {
208+ signed char a, b, c, d, e, f, g, h;
209+};
210+
211+int Char8Bytes(struct Char8Bytes b) {
212+ return (int)b.a + (int)b.b + (int)b.c + (int)b.d + (int)b.e + (int)b.f + (int)b.g + (int)b.h;
213+}
214+
215+struct Odd {
216+ unsigned char a, b, c;
217+};
218+
219+int Odd(struct Odd o) {
220+ return (int)o.a + (int)o.b + (int)o.c;
221+}
222+
223+struct Char2Short1 {
224+ unsigned char a, b;
225+ unsigned short c;
226+};
227+
228+int Char2Short1s(struct Char2Short1 s) {
229+ return (int)s.a + (int)s.b + (int)s.c;
230+}
231+
232+struct SignedChar2Short1 {
233+ signed char a, b;
234+ signed short c;
235+};
236+
237+int SignedChar2Short1(struct SignedChar2Short1 s) {
238+ return s.a + s.b + s.c;
239+}
240+
241+struct Array4UnsignedChars {
242+ unsigned char a[4];
243+};
244+
245+unsigned int Array4UnsignedChars(struct Array4UnsignedChars a) {
246+ return (((int)a.a[0])<<24) | (((int)a.a[1])<<16) | (((int)a.a[2])<<8) | (((int)a.a[3])<<0);
247+}
248+
249+struct Array3UnsignedChar {
250+ unsigned char a[3];
251+};
252+
253+unsigned int Array3UnsignedChars(struct Array3UnsignedChar a) {
254+ return (((int)a.a[0])<<24) | (((int)a.a[1])<<16) | (((int)a.a[2])<<8) | 0xef;
255+}
256+
257+struct Array2UnsignedShort {
258+ unsigned short a[2];
259+};
260+
261+unsigned int Array2UnsignedShorts(struct Array2UnsignedShort a) {
262+ return (((int)a.a[0])<<16) | (((int)a.a[1])<<0);
263+}
264+
265+struct Array4Chars {
266+ char a[4];
267+};
268+
269+int Array4Chars(struct Array4Chars a) {
270+ return (int)a.a[0] + (int)a.a[1] + (int)a.a[2] + (int)a.a[3];
271+}
272+
273+struct Array2Short {
274+ short a[2];
275+};
276+
277+int Array2Shorts(struct Array2Short a) {
278+ return (int)a.a[0] + (int)a.a[1];
279+}
280+
281+struct Array3Short {
282+ short a[3];
283+};
284+
285+int Array3Shorts(struct Array3Short a) {
286+ return (int)a.a[0] + (int)a.a[1] + (int)a.a[2];
287+}
288+
289+struct BoolStruct {
290+ _Bool b;
291+};
292+
293+_Bool BoolStruct(struct BoolStruct b) {
294+ return b.b;
295+}
296+
297+struct BoolFloat {
298+ _Bool b;
299+ float f;
300+};
301+
302+float BoolFloat(struct BoolFloat s) {
303+ if (s.b)
304+ return s.f;
305+ return -s.f;
306+}
307+
308+struct Content {
309+ struct { double x, y; } point;
310+ struct { double width, height; } size;
311+};
312+
313+unsigned long InitWithContentRect(int *win, struct Content c, int style, int backing, _Bool flag) {
314+ if (win == 0)
315+ return 0xBAD;
316+ if (!flag)
317+ return 0xF1A6; // FLAG
318+ return (unsigned long)(c.point.x + c.point.y + c.size.width + c.size.height) / (style - backing);
319+}

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