v0.11.0-alpha.2 fa49e74

func_test.go RawHistoryFolder

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}

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