v0.11.0-alpha.3 11272d2

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	"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}

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