1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3
4package purego_test
5
6import (
7 "bytes"
8 "errors"
9 "fmt"
10 "os"
11 "os/exec"
12 "path/filepath"
13 "runtime"
14 "strings"
15 "sync"
16 "testing"
17 "unsafe"
18
19 "github.com/ebitengine/purego"
20 "github.com/ebitengine/purego/internal/load"
21)
22
23func getSystemLibrary() (string, error) {
24 switch runtime.GOOS {
25 case "android":
26 return "libc.so", nil
27 case "darwin", "ios":
28 return "/usr/lib/libSystem.B.dylib", nil
29 case "freebsd":
30 return "libc.so.7", nil
31 case "linux":
32 return "libc.so.6", nil
33 case "netbsd":
34 return "libc.so", nil
35 case "windows":
36 return "ucrtbase.dll", nil
37 default:
38 return "", fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)
39 }
40}
41
42func TestRegisterFunc_ConcurrentPointerReturn(t *testing.T) {
43 library, err := getSystemLibrary()
44 if err != nil {
45 t.Fatalf("couldn't get system library: %s", err)
46 }
47 libc, err := load.OpenLibrary(library)
48 if err != nil {
49 t.Fatalf("failed to dlopen: %s", err)
50 }
51
52 var alloc func(uint64) *byte
53 var free func(*byte)
54 purego.RegisterLibFunc(&alloc, libc, "malloc")
55 purego.RegisterLibFunc(&free, libc, "free")
56
57 var wg sync.WaitGroup
58
59 for i := 0; i < runtime.NumCPU(); i++ {
60 wg.Add(1)
61 go func(id int) {
62 defer wg.Done()
63 for range 400_000 {
64 ptr := alloc(5)
65 if ptr == nil {
66 continue
67 }
68 free(ptr)
69 }
70 }(i)
71 }
72
73 wg.Wait()
74}
75
76func TestRegisterFunc(t *testing.T) {
77 library, err := getSystemLibrary()
78 if err != nil {
79 t.Fatalf("couldn't get system library: %s", err)
80 }
81 libc, err := load.OpenLibrary(library)
82 if err != nil {
83 t.Fatalf("failed to dlopen: %s", err)
84 }
85 var puts func(string)
86 purego.RegisterLibFunc(&puts, libc, "puts")
87 puts("Calling C from from Go without Cgo!")
88}
89
90func Test_qsort(t *testing.T) {
91 if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" {
92 t.Skip("Platform doesn't support Floats")
93 return
94 }
95 library, err := getSystemLibrary()
96 if err != nil {
97 t.Fatalf("couldn't get system library: %s", err)
98 }
99 libc, err := load.OpenLibrary(library)
100 if err != nil {
101 t.Fatalf("failed to dlopen: %s", err)
102 }
103
104 data := []int{88, 56, 100, 2, 25}
105 sorted := []int{2, 25, 56, 88, 100}
106 compare := func(_ purego.CDecl, a, b *int) int {
107 return *a - *b
108 }
109 var qsort func(data []int, nitms uintptr, size uintptr, compar func(_ purego.CDecl, a, b *int) int)
110 purego.RegisterLibFunc(&qsort, libc, "qsort")
111 qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare)
112 for i := range data {
113 if data[i] != sorted[i] {
114 t.Errorf("got %d wanted %d at %d", data[i], sorted[i], i)
115 }
116 }
117}
118
119func TestRegisterFunc_Floats(t *testing.T) {
120 if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" {
121 t.Skip("Platform doesn't support Floats")
122 return
123 }
124 if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
125 t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test
126 }
127 library, err := getSystemLibrary()
128 if err != nil {
129 t.Fatalf("couldn't get system library: %s", err)
130 }
131 libc, err := load.OpenLibrary(library)
132 if err != nil {
133 t.Fatalf("failed to dlopen: %s", err)
134 }
135 {
136 var strtof func(arg string) float32
137 purego.RegisterLibFunc(&strtof, libc, "strtof")
138 const (
139 arg = "2"
140 )
141 got := strtof(arg)
142 expected := float32(2)
143 if got != expected {
144 t.Errorf("strtof failed. got %f but wanted %f", got, expected)
145 }
146 }
147 {
148 var strtod func(arg string, ptr **byte) float64
149 purego.RegisterLibFunc(&strtod, libc, "strtod")
150 const (
151 arg = "1"
152 )
153 got := strtod(arg, nil)
154 expected := float64(1)
155 if got != expected {
156 t.Errorf("strtod failed. got %f but wanted %f", got, expected)
157 }
158 }
159}
160
161func TestRegisterLibFunc_Bool(t *testing.T) {
162 if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" {
163 t.Skip("Platform doesn't support callbacks")
164 return
165 }
166 // this callback recreates the state where the return register
167 // contains other information but the least significant byte is false
168 cbFalse := purego.NewCallback(func() uintptr {
169 x := uint64(0x7F5948AE9A00)
170 return uintptr(x)
171 })
172 var runFalse func() bool
173 purego.RegisterFunc(&runFalse, cbFalse)
174 expected := false
175 if got := runFalse(); got != expected {
176 t.Errorf("runFalse failed. got %t but wanted %t", got, expected)
177 }
178}
179
180func TestABI(t *testing.T) {
181 libFileName := filepath.Join(t.TempDir(), "abitest.so")
182 t.Logf("Build %v", libFileName)
183
184 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil {
185 t.Fatal(err)
186 }
187
188 lib, err := load.OpenLibrary(libFileName)
189 if err != nil {
190 t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
191 }
192 defer func() {
193 if err := load.CloseLibrary(lib); err != nil {
194 t.Fatalf("failed to close library: %s", err)
195 }
196 }()
197 {
198 const cName = "stack_uint8_t"
199 const expect = 2047
200 var fn func(a, b, c, d, e, f, g, h uint32, i, j uint8, k uint32) uint32
201 purego.RegisterLibFunc(&fn, lib, cName)
202 res := fn(256, 512, 4, 8, 16, 32, 64, 128, 1, 2, 1024)
203 if res != expect {
204 t.Fatalf("%s: got %d, want %d", cName, res, expect)
205 }
206 }
207 {
208 const cName = "reg_uint8_t"
209 const expect = 1027
210 var fn func(a, b uint8, c uint32) uint32
211 purego.RegisterLibFunc(&fn, lib, cName)
212 res := fn(1, 2, 1024)
213 if res != expect {
214 t.Fatalf("%s: got %d, want %d", cName, res, expect)
215 }
216 }
217 {
218 const cName = "stack_string"
219 const expect = 255
220 var fn func(a, b, c, d, e, f, g, h uint32, i string) uint32
221 purego.RegisterLibFunc(&fn, lib, cName)
222 res := fn(1, 2, 4, 8, 16, 32, 64, 128, "test")
223 if res != expect {
224 t.Fatalf("%s: got %d, want %d", cName, res, expect)
225 }
226 }
227 {
228 const cName = "stack_8i32_3strings"
229 var fn func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, string, string, string)
230 purego.RegisterLibFunc(&fn, lib, cName)
231 buf := make([]byte, 256)
232 fn(&buf[0], uintptr(len(buf)), 1, 2, 3, 4, 5, 6, 7, 8, "foo", "bar", "baz")
233 res := string(buf[:bytes.IndexByte(buf, 0)])
234 const want = "1:2:3:4:5:6:7:8:foo:bar:baz"
235 if res != want {
236 t.Fatalf("%s: got %q, want %q", cName, res, want)
237 }
238 }
239}
240
241func TestABI_ArgumentPassing(t *testing.T) {
242 libFileName := filepath.Join(t.TempDir(), "abitest.so")
243 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil {
244 t.Fatal(err)
245 }
246 lib, err := load.OpenLibrary(libFileName)
247 if err != nil {
248 t.Fatalf("Failed to open library %q: %v", libFileName, err)
249 }
250 t.Cleanup(func() {
251 if err := load.CloseLibrary(lib); err != nil {
252 t.Errorf("Failed to close library: %v", err)
253 }
254 })
255
256 tests := []struct {
257 name string
258 fn any
259 cFn string
260 call func(any) string
261 want string
262 }{
263 {
264 name: "10_int32_baseline",
265 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32)),
266 cFn: "stack_10_int32",
267 call: func(f any) string {
268 buf := make([]byte, 256)
269 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32)))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
270 return string(buf[:bytes.IndexByte(buf, 0)])
271 },
272 want: "1:2:3:4:5:6:7:8:9:10",
273 },
274 {
275 name: "11_int32",
276 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32)),
277 cFn: "stack_11_int32",
278 call: func(f any) string {
279 buf := make([]byte, 256)
280 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32)))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
281 return string(buf[:bytes.IndexByte(buf, 0)])
282 },
283 want: "1:2:3:4:5:6:7:8:9:10:11",
284 },
285 {
286 name: "10_float32",
287 fn: new(func(*byte, uintptr, float32, float32, float32, float32, float32, float32, float32, float32, float32, float32)),
288 cFn: "stack_10_float32",
289 call: func(f any) string {
290 buf := make([]byte, 256)
291 (*f.(*func(*byte, uintptr, float32, float32, float32, float32, float32, float32, float32, float32, float32, float32)))(&buf[0], 256, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)
292 return string(buf[:bytes.IndexByte(buf, 0)])
293 },
294 want: "1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0",
295 },
296 {
297 name: "mixed_stack_strings",
298 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, string, bool, int32, string)),
299 cFn: "stack_mixed_stack_4args",
300 call: func(f any) string {
301 buf := make([]byte, 256)
302 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, string, bool, int32, string)))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, "foo", false, 99, "bar")
303 return string(buf[:bytes.IndexByte(buf, 0)])
304 },
305 want: "1:2:3:4:5:6:7:8:foo:0:99:bar",
306 },
307 {
308 name: "20_int32",
309 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32)),
310 cFn: "stack_20_int32",
311 call: func(f any) string {
312 buf := make([]byte, 512)
313 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32)))(&buf[0], 512, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
314 return string(buf[:bytes.IndexByte(buf, 0)])
315 },
316 want: "1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20",
317 },
318 {
319 name: "8int_hfa2_stack",
320 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y float32 })),
321 cFn: "stack_8int_hfa2_stack",
322 call: func(f any) string {
323 buf := make([]byte, 256)
324 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y float32 })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct{ x, y float32 }{10.0, 20.0})
325 return string(buf[:bytes.IndexByte(buf, 0)])
326 },
327 want: "1:2:3:4:5:6:7:8:10.0:20.0",
328 },
329 {
330 name: "8int_2structs_stack",
331 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y int32 }, struct{ x, y int32 })),
332 cFn: "stack_8int_2structs_stack",
333 call: func(f any) string {
334 buf := make([]byte, 256)
335 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y int32 }, struct{ x, y int32 })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct{ x, y int32 }{9, 10}, struct{ x, y int32 }{11, 12})
336 return string(buf[:bytes.IndexByte(buf, 0)])
337 },
338 want: "1:2:3:4:5:6:7:8:9:10:11:12",
339 },
340 {
341 name: "8float_hfa2_stack",
342 fn: new(func(*byte, uintptr, float32, float32, float32, float32, float32, float32, float32, float32, struct{ x, y float32 })),
343 cFn: "stack_8float_hfa2_stack",
344 call: func(f any) string {
345 buf := make([]byte, 256)
346 (*f.(*func(*byte, uintptr, float32, float32, float32, float32, float32, float32, float32, float32, struct{ x, y float32 })))(&buf[0], 256, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, struct{ x, y float32 }{9.0, 10.0})
347 return string(buf[:bytes.IndexByte(buf, 0)])
348 },
349 want: "1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0",
350 },
351 {
352 name: "8int_hfa2_floatregs",
353 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y float32 })),
354 cFn: "stack_8int_hfa2_floatregs",
355 call: func(f any) string {
356 buf := make([]byte, 256)
357 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y float32 })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct{ x, y float32 }{10.0, 20.0})
358 return string(buf[:bytes.IndexByte(buf, 0)])
359 },
360 want: "1:2:3:4:5:6:7:8:10.0:20.0",
361 },
362 {
363 name: "8int_int_struct_int",
364 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y int32 }, int32)),
365 cFn: "stack_8int_int_struct_int",
366 call: func(f any) string {
367 buf := make([]byte, 256)
368 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y int32 }, int32)))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, 9, struct{ x, y int32 }{10, 11}, 12)
369 return string(buf[:bytes.IndexByte(buf, 0)])
370 },
371 want: "1:2:3:4:5:6:7:8:9:10:11:12",
372 },
373 {
374 name: "8int_hfa4_stack",
375 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y, z, w float32 })),
376 cFn: "stack_8int_hfa4_stack",
377 call: func(f any) string {
378 buf := make([]byte, 256)
379 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y, z, w float32 })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct{ x, y, z, w float32 }{10.0, 20.0, 30.0, 40.0})
380 return string(buf[:bytes.IndexByte(buf, 0)])
381 },
382 want: "1:2:3:4:5:6:7:8:10.0:20.0:30.0:40.0",
383 },
384 {
385 name: "8int_mixed_struct",
386 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct {
387 a int32
388 b float32
389 })),
390 cFn: "stack_8int_mixed_struct",
391 call: func(f any) string {
392 buf := make([]byte, 256)
393 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct {
394 a int32
395 b float32
396 })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct {
397 a int32
398 b float32
399 }{9, 10.0})
400 return string(buf[:bytes.IndexByte(buf, 0)])
401 },
402 want: "1:2:3:4:5:6:7:8:9:10.0",
403 },
404 }
405
406 for _, tt := range tests {
407 t.Run(tt.name, func(t *testing.T) {
408 if tt.name == "20_int32" && runtime.GOARCH == "ppc64le" {
409 t.Skip("ppc64le retains the 15-argument limit")
410 }
411 if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") {
412 t.Skip("float32 stack arguments not yet supported on this platform")
413 }
414 // Struct tests require Darwin ARM64 or AMD64
415 if strings.HasPrefix(tt.name, "8int_") && (runtime.GOOS != "darwin" || (runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64")) {
416 t.Skip("struct argument tests only supported on Darwin ARM64/AMD64")
417 }
418 if strings.HasPrefix(tt.name, "8float_") && (runtime.GOOS != "darwin" || (runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64")) {
419 t.Skip("struct argument tests only supported on Darwin ARM64/AMD64")
420 }
421
422 purego.RegisterLibFunc(tt.fn, lib, tt.cFn)
423 got := tt.call(tt.fn)
424 if got != tt.want {
425 t.Errorf("%s\n got: %q\n want: %q", tt.cFn, got, tt.want)
426 }
427 })
428 }
429
430 t.Run("20_uintptr", func(t *testing.T) {
431 if runtime.GOARCH == "ppc64le" {
432 t.Skip("ppc64le retains the 15-argument limit")
433 }
434 var fn func(uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr) uintptr
435 purego.RegisterLibFunc(&fn, lib, "stack_20_uintptr")
436 got := fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
437 const want = uintptr(210)
438 if got != want {
439 t.Fatalf("stack_20_uintptr: got %d, want %d", got, want)
440 }
441 })
442
443 t.Run("32_uintptr", func(t *testing.T) {
444 if runtime.GOARCH == "ppc64le" {
445 t.Skip("ppc64le retains the 15-argument limit")
446 }
447 var fn func(
448 uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
449 uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
450 uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
451 uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
452 ) uintptr
453 purego.RegisterLibFunc(&fn, lib, "stack_32_uintptr")
454 got := fn(
455 1, 2, 3, 4, 5, 6, 7, 8,
456 9, 10, 11, 12, 13, 14, 15, 16,
457 17, 18, 19, 20, 21, 22, 23, 24,
458 25, 26, 27, 28, 29, 30, 31, 32,
459 )
460 const want = uintptr(528)
461 if got != want {
462 t.Fatalf("stack_32_uintptr: got %d, want %d", got, want)
463 }
464 })
465
466 t.Run("syscalln_20_uintptr", func(t *testing.T) {
467 if runtime.GOARCH == "ppc64le" {
468 t.Skip("ppc64le retains the 15-argument limit")
469 }
470 fn, err := load.OpenSymbol(lib, "stack_20_uintptr")
471 if err != nil {
472 t.Fatalf("OpenSymbol(stack_20_uintptr) failed: %v", err)
473 }
474 got, _, _ := purego.SyscallN(fn,
475 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
476 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
477 )
478 const want = uintptr(210)
479 if got != want {
480 t.Fatalf("stack_20_uintptr SyscallN: got %d, want %d", got, want)
481 }
482 })
483
484 t.Run("syscalln_32_uintptr", func(t *testing.T) {
485 if runtime.GOARCH == "ppc64le" {
486 t.Skip("ppc64le retains the 15-argument limit")
487 }
488 fn, err := load.OpenSymbol(lib, "stack_32_uintptr")
489 if err != nil {
490 t.Fatalf("OpenSymbol(stack_32_uintptr) failed: %v", err)
491 }
492 got, _, _ := purego.SyscallN(fn,
493 1, 2, 3, 4, 5, 6, 7, 8,
494 9, 10, 11, 12, 13, 14, 15, 16,
495 17, 18, 19, 20, 21, 22, 23, 24,
496 25, 26, 27, 28, 29, 30, 31, 32,
497 )
498 const want = uintptr(528)
499 if got != want {
500 t.Fatalf("stack_32_uintptr SyscallN: got %d, want %d", got, want)
501 }
502 })
503
504 t.Run("32_mixed_int_float", func(t *testing.T) {
505 if unsafe.Sizeof(uintptr(0)) == 4 {
506 t.Skip("requires 64-bit uintptr slots")
507 }
508 if runtime.GOARCH == "ppc64le" {
509 t.Skip("mixed int/float stack arguments are not yet supported on ppc64le")
510 }
511
512 var fn func(
513 uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
514 uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
515 float64, float64, float64, float64, float64, float64, float64, float64,
516 float64, float64, float64, float64, float64, float64, float64, float64,
517 ) float64
518 purego.RegisterLibFunc(&fn, lib, "stack_32_mixed_int_float")
519 got := fn(
520 1, 2, 3, 4, 5, 6, 7, 8,
521 9, 10, 11, 12, 13, 14, 15, 16,
522 1, 2, 3, 4, 5, 6, 7, 8,
523 9, 10, 11, 12, 13, 14, 15, 16,
524 )
525 const want = 5168.0
526 if got != want {
527 t.Fatalf("stack_32_mixed_int_float: got %f, want %f", got, want)
528 }
529 })
530}
531
532func TestABI_TooManyArguments(t *testing.T) {
533 mustPanic := func(t *testing.T, want string, f func()) {
534 t.Helper()
535 defer func() {
536 r := recover()
537 if r == nil {
538 t.Fatalf("expected panic %q, got none", want)
539 }
540 got := fmt.Sprint(r)
541 if got != want {
542 t.Fatalf("panic mismatch:\n got: %q\n want: %q", got, want)
543 }
544 }()
545 f()
546 }
547
548 // 33 int64 parameters exceeds maxArgs=32.
549 t.Run("registerfunc_33_int64_exceeds_limit", func(t *testing.T) {
550 mustPanic(t, "purego: too many stack arguments", func() {
551 var fn func(
552 int64, int64, int64, int64, int64, int64, int64, int64,
553 int64, int64, int64, int64, int64, int64, int64, int64,
554 int64, int64, int64, int64, int64, int64, int64, int64,
555 int64, int64, int64, int64, int64, int64, int64, int64,
556 int64,
557 )
558 purego.RegisterFunc(&fn, 1)
559 })
560 })
561
562 t.Run("registerfunc_16_int64_exceeds_ppc64le_limit", func(t *testing.T) {
563 if runtime.GOARCH != "ppc64le" {
564 t.Skip("ppc64le retains the 15-argument limit")
565 }
566 mustPanic(t, "purego: too many stack arguments", func() {
567 var fn func(
568 int64, int64, int64, int64, int64, int64, int64, int64,
569 int64, int64, int64, int64, int64, int64, int64, int64,
570 )
571 purego.RegisterFunc(&fn, 1)
572 })
573 })
574
575 t.Run("syscalln_33_uintptr_exceeds_limit", func(t *testing.T) {
576 mustPanic(t, "purego: too many arguments to SyscallN", func() {
577 purego.SyscallN(1,
578 1, 2, 3, 4, 5, 6, 7, 8,
579 9, 10, 11, 12, 13, 14, 15, 16,
580 17, 18, 19, 20, 21, 22, 23, 24,
581 25, 26, 27, 28, 29, 30, 31, 32,
582 33,
583 )
584 })
585 })
586
587 t.Run("syscalln_16_uintptr_exceeds_ppc64le_limit", func(t *testing.T) {
588 if runtime.GOARCH != "ppc64le" {
589 t.Skip("ppc64le retains the 15-argument limit")
590 }
591 mustPanic(t, "purego: too many arguments to SyscallN", func() {
592 purego.SyscallN(1,
593 1, 2, 3, 4, 5, 6, 7, 8,
594 9, 10, 11, 12, 13, 14, 15, 16,
595 )
596 })
597 })
598}
599
600func buildSharedLib(tb testing.TB, compilerEnv, libFile string, sources ...string) error {
601 tb.Helper()
602 // When PUREGO_TEST_PREBUILT_LIBDIR is set, the shared library has been
603 // cross-compiled ahead of time and placed in that directory under the
604 // base name of libFile. This allows running the tests on a target that
605 // has no C toolchain, such as an Android emulator.
606 if dir := os.Getenv("PUREGO_TEST_PREBUILT_LIBDIR"); dir != "" {
607 data, err := os.ReadFile(filepath.Join(dir, filepath.Base(libFile)))
608 if err != nil {
609 return fmt.Errorf("prebuilt lib: %w", err)
610 }
611 if err := os.WriteFile(libFile, data, 0o755); err != nil {
612 return fmt.Errorf("prebuilt lib: %w", err)
613 }
614 return nil
615 }
616
617 // Compiling the library needs a C toolchain targeting GOARCH. CI has none
618 // for Windows on 386 or arm64, so skip those (the prebuilt path above
619 // avoids the toolchain).
620 if runtime.GOOS == "windows" {
621 switch runtime.GOARCH {
622 case "386":
623 tb.Skip("need a 386 C toolchain to run this test") // TODO: find a 386 C toolchain for test
624 case "arm64":
625 tb.Skip("need an arm64 C toolchain to run this test")
626 }
627 }
628
629 out, err := exec.Command("go", "env", compilerEnv).Output()
630 if err != nil {
631 return fmt.Errorf("go env %s error: %w", compilerEnv, err)
632 }
633
634 compiler := strings.TrimSpace(string(out))
635 if compiler == "" {
636 return errors.New("compiler not found")
637 }
638
639 args := []string{"-shared", "-Wall", "-Werror", "-fPIC", "-o", libFile}
640 if runtime.GOARCH == "386" {
641 args = append(args, "-m32")
642 }
643 // macOS arm64 can run amd64 tests through Rossetta.
644 // Build the shared library based on the GOARCH and not
645 // the default behavior of the compiler.
646 if runtime.GOOS == "darwin" {
647 var arch string
648 switch runtime.GOARCH {
649 case "arm64":
650 arch = "arm64"
651 case "amd64":
652 arch = "x86_64"
653 default:
654 return fmt.Errorf("unknown macOS architecture %s", runtime.GOARCH)
655 }
656 args = append(args, "-arch", arch)
657 }
658 cmd := exec.Command(compiler, append(args, sources...)...)
659 if out, err := cmd.CombinedOutput(); err != nil {
660 return fmt.Errorf("compile lib: %w\n%q\n%s", err, cmd, string(out))
661 }
662
663 return nil
664}