1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3
4//go:build darwin && (arm64 || amd64)
5
6package purego_test
7
8import (
9 "os"
10 "path/filepath"
11 "testing"
12
13 "github.com/ebitengine/purego"
14)
15
16func 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}