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