v0.11.0-alpha.6 d60df56

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

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