detached 9fea51b

test(syscall): prove pointer liveness across Syscall6

commit
9fea51b06733dd19f9573dcb56c9983948b45263
author
brice · · 2026-09-27 18:01 UTC
committer
brice · · 2026-09-27 18:45 UTC
parent
d3eb2c6
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
checks
0/25
refs
tag: v0.13.0-bnema.1
2 files · +92 -0
A syscall_fixed_test.go +82 -0
@@ -0,0 +1,82 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !windows && !plan9
5+
6+package purego_test
7+
8+import (
9+ "path/filepath"
10+ "runtime"
11+ "runtime/debug"
12+ "sync"
13+ "testing"
14+ "unsafe"
15+
16+ "github.com/bnema/purego"
17+ "github.com/bnema/purego/internal/load"
18+)
19+
20+//go:noinline
21+func writeDeep(sym uintptr, depth int) bool {
22+ var pad [1024]byte
23+ pad[0] = byte(depth)
24+ if depth > 0 {
25+ ok := writeDeep(sym, depth-1)
26+ runtime.KeepAlive(pad)
27+ return ok
28+ }
29+ var local uint64
30+ purego.Syscall6(sym, uintptr(unsafe.Pointer(&local)), 42, 0, 0, 0, 0)
31+ runtime.KeepAlive(&local)
32+ runtime.KeepAlive(pad)
33+ return local == 42
34+}
35+
36+func TestSyscall6PointerLiveness(t *testing.T) {
37+ libFile := filepath.Join(t.TempDir(), "libbenchmark.so")
38+ if err := buildSharedLib(t, "CC", libFile, filepath.Join("testdata", "benchmarktest", "benchmark.c")); err != nil {
39+ t.Fatal(err)
40+ }
41+ lib, err := load.OpenLibrary(libFile)
42+ if err != nil {
43+ t.Fatal(err)
44+ }
45+ defer load.CloseLibrary(lib)
46+ sym, err := load.OpenSymbol(lib, "write_through")
47+ if err != nil {
48+ t.Fatal(err)
49+ }
50+
51+ stop := make(chan struct{})
52+ var wg sync.WaitGroup
53+ wg.Add(1)
54+ go func() {
55+ defer wg.Done()
56+ for {
57+ select {
58+ case <-stop:
59+ return
60+ default:
61+ runtime.GC()
62+ debug.FreeOSMemory()
63+ }
64+ }
65+ }()
66+ defer func() { close(stop); wg.Wait() }()
67+
68+ for i := 0; i < 32; i++ {
69+ if !writeDeep(sym, 0) {
70+ t.Fatal("stack local not updated")
71+ }
72+ }
73+ if !writeDeep(sym, 64) {
74+ t.Fatal("deep stack local not updated")
75+ }
76+ heap := new(uint64)
77+ purego.Syscall6(sym, uintptr(unsafe.Pointer(heap)), 42, 0, 0, 0, 0)
78+ runtime.KeepAlive(heap)
79+ if *heap != 42 {
80+ t.Fatalf("heap value = %d", *heap)
81+ }
82+}
@@ -0,0 +1,82 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !windows && !plan9
5+
6+package purego_test
7+
8+import (
9+ "path/filepath"
10+ "runtime"
11+ "runtime/debug"
12+ "sync"
13+ "testing"
14+ "unsafe"
15+
16+ "github.com/bnema/purego"
17+ "github.com/bnema/purego/internal/load"
18+)
19+
20+//go:noinline
21+func writeDeep(sym uintptr, depth int) bool {
22+ var pad [1024]byte
23+ pad[0] = byte(depth)
24+ if depth > 0 {
25+ ok := writeDeep(sym, depth-1)
26+ runtime.KeepAlive(pad)
27+ return ok
28+ }
29+ var local uint64
30+ purego.Syscall6(sym, uintptr(unsafe.Pointer(&local)), 42, 0, 0, 0, 0)
31+ runtime.KeepAlive(&local)
32+ runtime.KeepAlive(pad)
33+ return local == 42
34+}
35+
36+func TestSyscall6PointerLiveness(t *testing.T) {
37+ libFile := filepath.Join(t.TempDir(), "libbenchmark.so")
38+ if err := buildSharedLib(t, "CC", libFile, filepath.Join("testdata", "benchmarktest", "benchmark.c")); err != nil {
39+ t.Fatal(err)
40+ }
41+ lib, err := load.OpenLibrary(libFile)
42+ if err != nil {
43+ t.Fatal(err)
44+ }
45+ defer load.CloseLibrary(lib)
46+ sym, err := load.OpenSymbol(lib, "write_through")
47+ if err != nil {
48+ t.Fatal(err)
49+ }
50+
51+ stop := make(chan struct{})
52+ var wg sync.WaitGroup
53+ wg.Add(1)
54+ go func() {
55+ defer wg.Done()
56+ for {
57+ select {
58+ case <-stop:
59+ return
60+ default:
61+ runtime.GC()
62+ debug.FreeOSMemory()
63+ }
64+ }
65+ }()
66+ defer func() { close(stop); wg.Wait() }()
67+
68+ for i := 0; i < 32; i++ {
69+ if !writeDeep(sym, 0) {
70+ t.Fatal("stack local not updated")
71+ }
72+ }
73+ if !writeDeep(sym, 64) {
74+ t.Fatal("deep stack local not updated")
75+ }
76+ heap := new(uint64)
77+ purego.Syscall6(sym, uintptr(unsafe.Pointer(heap)), 42, 0, 0, 0, 0)
78+ runtime.KeepAlive(heap)
79+ if *heap != 42 {
80+ t.Fatalf("heap value = %d", *heap)
81+ }
82+}
M testdata/benchmarktest/benchmark.c +10 -0
@@ -2,6 +2,16 @@
22 // SPDX-FileCopyrightText: 2026 The Ebitengine Authors
33
44 #include <stdint.h>
5+#include <time.h>
6+
7+void write_through(uint64_t *p, uint64_t v) {
8+ *p = v;
9+ struct timespec start, now;
10+ clock_gettime(CLOCK_MONOTONIC, &start);
11+ do {
12+ clock_gettime(CLOCK_MONOTONIC, &now);
13+ } while ((now.tv_sec - start.tv_sec) * 1000000000L + now.tv_nsec - start.tv_nsec < 2000000L);
14+}
515
616 int64_t sum1_c(int64_t a1) { return a1; }
717
@@ -2,6 +2,16 @@
2 // SPDX-FileCopyrightText: 2026 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3 3
4 #include <stdint.h>4 #include <stdint.h>
5+#include <time.h>
6+
7+void write_through(uint64_t *p, uint64_t v) {
8+ *p = v;
9+ struct timespec start, now;
10+ clock_gettime(CLOCK_MONOTONIC, &start);
11+ do {
12+ clock_gettime(CLOCK_MONOTONIC, &now);
13+ } while ((now.tv_sec - start.tv_sec) * 1000000000L + now.tv_nsec - start.tv_nsec < 2000000L);
14+}
5 15
6 int64_t sum1_c(int64_t a1) { return a1; }16 int64_t sum1_c(int64_t a1) { return a1; }
7 17

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