detached 8b02958

chore: switch purego dependency to bnema/purego fork

Replace local path replace directive with GitHub URL pointing to
bnema/purego@91aa177 to avoid accidental pushes to upstream.
commit
8b029581aa8aebd90aa84a2b638058062afa9bdd
author
brice · · 2026-03-23 19:51 UTC
parent
f14f9b6
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
51 files · +1860 -488
M go.mod +2 -0
@@ -3,3 +3,5 @@ module github.com/bnema/puregotk
33 go 1.26.1
44
55 require github.com/ebitengine/purego v0.10.0
6+
7+replace github.com/ebitengine/purego => github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704
@@ -3,3 +3,5 @@ module github.com/bnema/puregotk
3 go 1.26.13 go 1.26.1
4 4
5 require github.com/ebitengine/purego v0.10.05 require github.com/ebitengine/purego v0.10.0
6+
7+replace github.com/ebitengine/purego => github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704
M go.sum +2 -2 generated file: collapsed
M vendor/github.com/ebitengine/purego/README.md +3 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/dlfcn_nocgo_netbsd.go +2 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/func.go +220 -107 generated file: collapsed
A vendor/github.com/ebitengine/purego/hostlayout_go123.go +12 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/hostlayout_nogo123.go +11 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/abi_riscv64.h +72 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/asm_riscv64.s +14 -55 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd.go +4 -25 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux.go +4 -25 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/go_netbsd.go +4 -25 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_netbsd.go +2 -2 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/linux.go +184 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/netbsd.go +1 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_386.s +18 -4 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s +1 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm.s +44 -3 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s +1 -4 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_386.s +78 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_amd64.s +69 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_arm.s +69 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_arm64.s +60 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_loong64.s +60 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_ppc64le.s +69 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_riscv64.s +60 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_loong64.s +1 -11 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_ppc64le.s +30 -129 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_riscv64.s +11 -7 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_linux.go +110 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_linux.s +30 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_386.go +12 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_amd64.go +162 -0
@@ -6,6 +6,7 @@ package purego
66 import (
77 "math"
88 "reflect"
9+ "runtime"
910 "unsafe"
1011 )
1112
@@ -284,3 +285,164 @@ func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int
284285 func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
285286 panic("purego: bundleStackArgs should not be called on amd64")
286287 }
288+
289+// getCallbackStruct reads a struct argument from the callback frame on amd64.
290+// It mirrors the SysV AMD64 ABI rules used by addStruct for the Go→C path.
291+//
292+// getCallbackStruct is only used on Unix. On Windows, callbacks are handled by
293+// the runtime's own callback mechanism, so this function is compiled but unused.
294+//
295+// SysV AMD64 struct argument passing rules:
296+// - Struct > 16 bytes (postMerger): passed on the stack as raw bytes
297+// - Struct ≤ 16 bytes: classify each eightbyte (INTEGER or SSE),
298+// read from the appropriate register class
299+// - If not enough registers for all eightbytes: entire struct goes on the stack
300+func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
301+ switch runtime.GOOS {
302+ case "darwin", "freebsd", "linux", "netbsd":
303+ default:
304+ panic("purego: getCallbackStruct is not supported on " + runtime.GOOS)
305+ }
306+
307+ f := (*[callbackMaxFrame]uintptr)(frame)
308+ size := inType.Size()
309+
310+ // Structs > 16 bytes are passed on the stack as raw bytes (SysV ABI MEMORY class).
311+ if postMerger(inType) {
312+ numSlots := int((size + 7) / 8)
313+ v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem()
314+ *stackSlot += numSlots
315+ return v
316+ }
317+
318+ // Struct ≤ 16 bytes: classify each eightbyte and read from the appropriate register.
319+ numEightbytes := int((size + 7) / 8)
320+
321+ // Count how many integer and SSE registers this struct needs.
322+ var needInts, needFloats int
323+ for i := 0; i < numEightbytes; i++ {
324+ class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
325+ if class == _SSE {
326+ needFloats++
327+ } else {
328+ needInts++
329+ }
330+ }
331+
332+ // If not enough registers for all eightbytes, the entire struct goes on the stack.
333+ if *intsN+needInts > numOfIntegerRegisters() || *floatsN+needFloats > numOfFloatRegisters() {
334+ v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem()
335+ *stackSlot += numEightbytes
336+ return v
337+ }
338+
339+ // Read each eightbyte from its appropriate register class.
340+ var r1, r2 uintptr
341+ for i := 0; i < numEightbytes; i++ {
342+ class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
343+ if class == _SSE {
344+ if i == 0 {
345+ r1 = f[*floatsN]
346+ } else {
347+ r2 = f[*floatsN]
348+ }
349+ *floatsN++
350+ } else {
351+ if i == 0 {
352+ r1 = f[numOfFloatRegisters()+*intsN]
353+ } else {
354+ r2 = f[numOfFloatRegisters()+*intsN]
355+ }
356+ *intsN++
357+ }
358+ }
359+
360+ if numEightbytes == 1 {
361+ return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
362+ }
363+ return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
364+}
365+
366+func setStruct(a *callbackArgs, ret reflect.Value) {
367+ outSize := ret.Type().Size()
368+ switch {
369+ case outSize == 0:
370+ return
371+ case outSize <= 16:
372+ // Copy the struct's raw bytes (including padding) into a buffer.
373+ var buf [2]uintptr
374+ reflect.NewAt(ret.Type(), unsafe.Pointer(&buf[0])).Elem().Set(ret)
375+ // Classify each eightbyte by the SysV ABI rules (§3.2.3, rule 4d
376+ // of https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf).
377+ // INTEGER wins over SSE for mixed eightbytes. Place INTEGER eightbytes in
378+ // result[0]/result[1] (AX/DX) and SSE eightbytes in result[2]/result[3]
379+ // (XMM0/XMM1).
380+ // Assign each eightbyte to the next available register of the
381+ // appropriate class. The ABI counts integer (AX, DX) and SSE
382+ // (XMM0, XMM1) return registers independently.
383+ var numInts int
384+ var numFloats int
385+ for i := 0; i < 2 && uintptr(i)*8 < outSize; i++ {
386+ class := classifyEightbyte(ret.Type(), uintptr(i)*8, uintptr(i)*8+8)
387+ if class == _SSE {
388+ switch numFloats {
389+ case 0:
390+ a.result[2] = buf[i]
391+ case 1:
392+ a.result[3] = buf[i]
393+ }
394+ numFloats++
395+ } else {
396+ switch numInts {
397+ case 0:
398+ a.result[0] = buf[i]
399+ case 1:
400+ a.result[1] = buf[i]
401+ }
402+ numInts++
403+ }
404+ }
405+ default:
406+ // Structs > 16 bytes are returned by hidden pointer.
407+ // a.result[0] contains the pointer passed by the caller in RDI.
408+ // Write the struct through this pointer.
409+ reflect.NewAt(ret.Type(), *(*unsafe.Pointer)(unsafe.Pointer(&a.result[0]))).Elem().Set(ret)
410+ }
411+}
412+
413+// classifyEightbyte returns the SysV ABI class for the byte range [start, end)
414+// within a type, by examining all scalar fields that overlap that range.
415+func classifyEightbyte(t reflect.Type, start, end uintptr) int {
416+ return doClassifyEightbyte(t, 0, start, end)
417+}
418+
419+func doClassifyEightbyte(t reflect.Type, base, start, end uintptr) int {
420+ switch t.Kind() {
421+ case reflect.Struct:
422+ class := _NO_CLASS
423+ for i := 0; i < t.NumField(); i++ {
424+ f := t.Field(i)
425+ class |= doClassifyEightbyte(f.Type, base+f.Offset, start, end)
426+ }
427+ return class
428+ case reflect.Array:
429+ class := _NO_CLASS
430+ elemSize := t.Elem().Size()
431+ for i := 0; i < t.Len(); i++ {
432+ class |= doClassifyEightbyte(t.Elem(), base+uintptr(i)*elemSize, start, end)
433+ }
434+ return class
435+ default:
436+ fStart := base
437+ fEnd := base + t.Size()
438+ if fStart >= end || fEnd <= start {
439+ return _NO_CLASS
440+ }
441+ switch t.Kind() {
442+ case reflect.Float32, reflect.Float64:
443+ return _SSE
444+ default:
445+ return _INTEGER
446+ }
447+ }
448+}
@@ -6,6 +6,7 @@ package purego
6 import (6 import (
7 "math"7 "math"
8 "reflect"8 "reflect"
9+ "runtime"
9 "unsafe"10 "unsafe"
10 )11 )
11 12
@@ -284,3 +285,164 @@ func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int
284 func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {285 func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
285 panic("purego: bundleStackArgs should not be called on amd64")286 panic("purego: bundleStackArgs should not be called on amd64")
286 }287 }
288+
289+// getCallbackStruct reads a struct argument from the callback frame on amd64.
290+// It mirrors the SysV AMD64 ABI rules used by addStruct for the Go→C path.
291+//
292+// getCallbackStruct is only used on Unix. On Windows, callbacks are handled by
293+// the runtime's own callback mechanism, so this function is compiled but unused.
294+//
295+// SysV AMD64 struct argument passing rules:
296+// - Struct > 16 bytes (postMerger): passed on the stack as raw bytes
297+// - Struct ≤ 16 bytes: classify each eightbyte (INTEGER or SSE),
298+// read from the appropriate register class
299+// - If not enough registers for all eightbytes: entire struct goes on the stack
300+func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
301+ switch runtime.GOOS {
302+ case "darwin", "freebsd", "linux", "netbsd":
303+ default:
304+ panic("purego: getCallbackStruct is not supported on " + runtime.GOOS)
305+ }
306+
307+ f := (*[callbackMaxFrame]uintptr)(frame)
308+ size := inType.Size()
309+
310+ // Structs > 16 bytes are passed on the stack as raw bytes (SysV ABI MEMORY class).
311+ if postMerger(inType) {
312+ numSlots := int((size + 7) / 8)
313+ v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem()
314+ *stackSlot += numSlots
315+ return v
316+ }
317+
318+ // Struct ≤ 16 bytes: classify each eightbyte and read from the appropriate register.
319+ numEightbytes := int((size + 7) / 8)
320+
321+ // Count how many integer and SSE registers this struct needs.
322+ var needInts, needFloats int
323+ for i := 0; i < numEightbytes; i++ {
324+ class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
325+ if class == _SSE {
326+ needFloats++
327+ } else {
328+ needInts++
329+ }
330+ }
331+
332+ // If not enough registers for all eightbytes, the entire struct goes on the stack.
333+ if *intsN+needInts > numOfIntegerRegisters() || *floatsN+needFloats > numOfFloatRegisters() {
334+ v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem()
335+ *stackSlot += numEightbytes
336+ return v
337+ }
338+
339+ // Read each eightbyte from its appropriate register class.
340+ var r1, r2 uintptr
341+ for i := 0; i < numEightbytes; i++ {
342+ class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
343+ if class == _SSE {
344+ if i == 0 {
345+ r1 = f[*floatsN]
346+ } else {
347+ r2 = f[*floatsN]
348+ }
349+ *floatsN++
350+ } else {
351+ if i == 0 {
352+ r1 = f[numOfFloatRegisters()+*intsN]
353+ } else {
354+ r2 = f[numOfFloatRegisters()+*intsN]
355+ }
356+ *intsN++
357+ }
358+ }
359+
360+ if numEightbytes == 1 {
361+ return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
362+ }
363+ return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
364+}
365+
366+func setStruct(a *callbackArgs, ret reflect.Value) {
367+ outSize := ret.Type().Size()
368+ switch {
369+ case outSize == 0:
370+ return
371+ case outSize <= 16:
372+ // Copy the struct's raw bytes (including padding) into a buffer.
373+ var buf [2]uintptr
374+ reflect.NewAt(ret.Type(), unsafe.Pointer(&buf[0])).Elem().Set(ret)
375+ // Classify each eightbyte by the SysV ABI rules (§3.2.3, rule 4d
376+ // of https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf).
377+ // INTEGER wins over SSE for mixed eightbytes. Place INTEGER eightbytes in
378+ // result[0]/result[1] (AX/DX) and SSE eightbytes in result[2]/result[3]
379+ // (XMM0/XMM1).
380+ // Assign each eightbyte to the next available register of the
381+ // appropriate class. The ABI counts integer (AX, DX) and SSE
382+ // (XMM0, XMM1) return registers independently.
383+ var numInts int
384+ var numFloats int
385+ for i := 0; i < 2 && uintptr(i)*8 < outSize; i++ {
386+ class := classifyEightbyte(ret.Type(), uintptr(i)*8, uintptr(i)*8+8)
387+ if class == _SSE {
388+ switch numFloats {
389+ case 0:
390+ a.result[2] = buf[i]
391+ case 1:
392+ a.result[3] = buf[i]
393+ }
394+ numFloats++
395+ } else {
396+ switch numInts {
397+ case 0:
398+ a.result[0] = buf[i]
399+ case 1:
400+ a.result[1] = buf[i]
401+ }
402+ numInts++
403+ }
404+ }
405+ default:
406+ // Structs > 16 bytes are returned by hidden pointer.
407+ // a.result[0] contains the pointer passed by the caller in RDI.
408+ // Write the struct through this pointer.
409+ reflect.NewAt(ret.Type(), *(*unsafe.Pointer)(unsafe.Pointer(&a.result[0]))).Elem().Set(ret)
410+ }
411+}
412+
413+// classifyEightbyte returns the SysV ABI class for the byte range [start, end)
414+// within a type, by examining all scalar fields that overlap that range.
415+func classifyEightbyte(t reflect.Type, start, end uintptr) int {
416+ return doClassifyEightbyte(t, 0, start, end)
417+}
418+
419+func doClassifyEightbyte(t reflect.Type, base, start, end uintptr) int {
420+ switch t.Kind() {
421+ case reflect.Struct:
422+ class := _NO_CLASS
423+ for i := 0; i < t.NumField(); i++ {
424+ f := t.Field(i)
425+ class |= doClassifyEightbyte(f.Type, base+f.Offset, start, end)
426+ }
427+ return class
428+ case reflect.Array:
429+ class := _NO_CLASS
430+ elemSize := t.Elem().Size()
431+ for i := 0; i < t.Len(); i++ {
432+ class |= doClassifyEightbyte(t.Elem(), base+uintptr(i)*elemSize, start, end)
433+ }
434+ return class
435+ default:
436+ fStart := base
437+ fEnd := base + t.Size()
438+ if fStart >= end || fEnd <= start {
439+ return _NO_CLASS
440+ }
441+ switch t.Kind() {
442+ case reflect.Float32, reflect.Float64:
443+ return _SSE
444+ default:
445+ return _INTEGER
446+ }
447+ }
448+}
M vendor/github.com/ebitengine/purego/struct_arm.go +8 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_arm64.go +203 -7 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_loong64.go +9 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_ppc64le.go +9 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_riscv64.go +9 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_s390x.go +9 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/sys_amd64.s +16 -16 generated file: collapsed
M vendor/github.com/ebitengine/purego/sys_arm64.s +2 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/sys_unix_arm64.s +8 -3 generated file: collapsed
M vendor/github.com/ebitengine/purego/sys_unix_loong64.s +9 -5 generated file: collapsed
M vendor/github.com/ebitengine/purego/syscall.go +21 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/syscall_32bit.go +21 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/syscall_notstackargs.go +38 -0 generated file: collapsed
R vendor/github.com/ebitengine/purego/syscall_sysv_stackargs.go → vendor/github.com/ebitengine/purego/syscall_stackargs.go +13 -3 generated file: collapsed · renamed, 54% similar
D vendor/github.com/ebitengine/purego/syscall_sysv_others.go +0 -28 generated file: collapsed
R vendor/github.com/ebitengine/purego/syscall_sysv.go → vendor/github.com/ebitengine/purego/syscall_unix.go +53 -18 generated file: collapsed · renamed, 85% similar
M vendor/github.com/ebitengine/purego/syscall_windows.go +8 -0 generated file: collapsed
M vendor/modules.txt +2 -1 generated file: collapsed

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