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