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 generated file: collapsed
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 renamed, 85% similar
@@ -55,9 +55,10 @@ func NewCallback(fn any) uintptr {
5555 const maxCB = 2000
5656
5757 var cbs struct {
58- lock sync.Mutex
59- numFn int // the number of functions currently in cbs.funcs
60- funcs [maxCB]reflect.Value // the saved callbacks
58+ lock sync.RWMutex
59+ numFn int // the number of functions currently in cbs.funcs
60+ funcs [maxCB]reflect.Value // the saved callbacks
61+ argPools [maxCB]*sync.Pool // pre-allocated argument buffers per callback
6162 }
6263
6364 func compileCallback(fn any) uintptr {
@@ -76,7 +77,9 @@ func compileCallback(fn any) uintptr {
7677 if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) {
7778 continue
7879 }
79- fallthrough
80+ ensureStructSupported()
81+ checkStructFieldsSupported(in)
82+ continue
8083 case reflect.Interface, reflect.Func, reflect.Slice,
8184 reflect.Chan, reflect.Complex64, reflect.Complex128,
8285 reflect.String, reflect.Map, reflect.Invalid:
@@ -89,7 +92,7 @@ output:
8992 switch ty.Out(0).Kind() {
9093 case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
9194 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
92- reflect.Bool, reflect.UnsafePointer:
95+ reflect.Bool, reflect.UnsafePointer, reflect.Struct:
9396 break output
9497 }
9598 panic("purego: unsupported return type: " + ty.String())
@@ -101,9 +104,16 @@ output:
101104 if cbs.numFn >= maxCB {
102105 panic("purego: the maximum number of callbacks has been reached")
103106 }
104- cbs.funcs[cbs.numFn] = val
107+ index := cbs.numFn
108+ cbs.funcs[index] = val
109+ numIn := ty.NumIn()
110+ cbs.argPools[index] = &sync.Pool{
111+ New: func() any {
112+ return make([]reflect.Value, numIn)
113+ },
114+ }
105115 cbs.numFn++
106- return callbackasmAddr(cbs.numFn - 1)
116+ return callbackasmAddr(index)
107117 }
108118
109119 const ptrSize = unsafe.Sizeof((*int)(nil))
@@ -125,11 +135,18 @@ var callbackWrap_call = callbackWrap
125135 // callbackWrap is called by assembly code which determines which Go function to call.
126136 // This function takes the arguments and passes them to the Go function and returns the result.
127137 func callbackWrap(a *callbackArgs) {
128- cbs.lock.Lock()
138+ cbs.lock.RLock()
129139 fn := cbs.funcs[a.index]
130- cbs.lock.Unlock()
140+ pool := cbs.argPools[a.index]
141+ cbs.lock.RUnlock()
131142 fnType := fn.Type()
132- args := make([]reflect.Value, fnType.NumIn())
143+ args := pool.Get().([]reflect.Value)
144+ defer func() {
145+ for i := range args {
146+ args[i] = reflect.Value{}
147+ }
148+ pool.Put(args)
149+ }()
133150 frame := (*[callbackMaxFrame]uintptr)(a.args)
134151 // stackFrame points to stack-passed arguments. On most architectures this is
135152 // contiguous with frame (after register args), but on ppc64le it's separate.
@@ -142,6 +159,14 @@ func callbackWrap(a *callbackArgs) {
142159 // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers).
143160 var floatsN int
144161 var intsN int
162+ // On amd64/loong64/ppc64le/riscv64/s390x, when returning a struct larger than
163+ // maxRegAllocStructSize, the caller passes a hidden pointer in the first integer
164+ // register. Skip it to avoid misreading it as the first function argument.
165+ if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") &&
166+ fnType.NumOut() == 1 && fnType.Out(0).Kind() == reflect.Struct &&
167+ fnType.Out(0).Size() > maxRegAllocStructSize {
168+ intsN = 1
169+ }
145170 // stackSlot points to the index into frame (or stackFrame) of the current stack element.
146171 // When stackFrame is nil, stack begins after float and integer registers in frame.
147172 // When stackFrame is not nil (ppc64le), stackSlot indexes into stackFrame starting at 0.
@@ -187,8 +212,16 @@ func callbackWrap(a *callbackArgs) {
187212 }
188213 floatsN += slots
189214 case reflect.Struct:
190- // This is the CDecl field
191- args[i] = reflect.Zero(inType)
215+ if i == 0 && inType.AssignableTo(reflect.TypeOf(CDecl{})) {
216+ args[i] = reflect.Zero(inType)
217+ continue
218+ }
219+ if inType.Size() == 0 {
220+ args[i] = reflect.New(inType).Elem()
221+ continue
222+ }
223+ args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset)
224+ continue
192225 default:
193226 slots = int((inType.Size() + ptrSize - 1) / ptrSize)
194227 if intsN+slots > numOfIntegerRegisters() {
@@ -225,19 +258,21 @@ func callbackWrap(a *callbackArgs) {
225258 if len(ret) > 0 {
226259 switch k := ret[0].Kind(); k {
227260 case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
228- a.result = uintptr(ret[0].Uint())
261+ a.result[0] = uintptr(ret[0].Uint())
229262 case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
230- a.result = uintptr(ret[0].Int())
263+ a.result[0] = uintptr(ret[0].Int())
231264 case reflect.Bool:
232265 if ret[0].Bool() {
233- a.result = 1
266+ a.result[0] = 1
234267 } else {
235- a.result = 0
268+ a.result[0] = 0
236269 }
237270 case reflect.Pointer:
238- a.result = ret[0].Pointer()
271+ a.result[0] = ret[0].Pointer()
239272 case reflect.UnsafePointer:
240- a.result = ret[0].Pointer()
273+ a.result[0] = ret[0].Pointer()
274+ case reflect.Struct:
275+ setStruct(a, ret[0])
241276 default:
242277 panic("purego: unsupported kind: " + k.String())
243278 }
@@ -55,9 +55,10 @@ func NewCallback(fn any) uintptr {
55 const maxCB = 200055 const maxCB = 2000
56 56
57 var cbs struct {57 var cbs struct {
58- lock sync.Mutex58+ lock sync.RWMutex
59- numFn int // the number of functions currently in cbs.funcs59+ numFn int // the number of functions currently in cbs.funcs
60- funcs [maxCB]reflect.Value // the saved callbacks60+ funcs [maxCB]reflect.Value // the saved callbacks
61+ argPools [maxCB]*sync.Pool // pre-allocated argument buffers per callback
61 }62 }
62 63
63 func compileCallback(fn any) uintptr {64 func compileCallback(fn any) uintptr {
@@ -76,7 +77,9 @@ func compileCallback(fn any) uintptr {
76 if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) {77 if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) {
77 continue78 continue
78 }79 }
79- fallthrough80+ ensureStructSupported()
81+ checkStructFieldsSupported(in)
82+ continue
80 case reflect.Interface, reflect.Func, reflect.Slice,83 case reflect.Interface, reflect.Func, reflect.Slice,
81 reflect.Chan, reflect.Complex64, reflect.Complex128,84 reflect.Chan, reflect.Complex64, reflect.Complex128,
82 reflect.String, reflect.Map, reflect.Invalid:85 reflect.String, reflect.Map, reflect.Invalid:
@@ -89,7 +92,7 @@ output:
89 switch ty.Out(0).Kind() {92 switch ty.Out(0).Kind() {
90 case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,93 case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
91 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,94 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
92- reflect.Bool, reflect.UnsafePointer:95+ reflect.Bool, reflect.UnsafePointer, reflect.Struct:
93 break output96 break output
94 }97 }
95 panic("purego: unsupported return type: " + ty.String())98 panic("purego: unsupported return type: " + ty.String())
@@ -101,9 +104,16 @@ output:
101 if cbs.numFn >= maxCB {104 if cbs.numFn >= maxCB {
102 panic("purego: the maximum number of callbacks has been reached")105 panic("purego: the maximum number of callbacks has been reached")
103 }106 }
104- cbs.funcs[cbs.numFn] = val107+ index := cbs.numFn
108+ cbs.funcs[index] = val
109+ numIn := ty.NumIn()
110+ cbs.argPools[index] = &sync.Pool{
111+ New: func() any {
112+ return make([]reflect.Value, numIn)
113+ },
114+ }
105 cbs.numFn++115 cbs.numFn++
106- return callbackasmAddr(cbs.numFn - 1)116+ return callbackasmAddr(index)
107 }117 }
108 118
109 const ptrSize = unsafe.Sizeof((*int)(nil))119 const ptrSize = unsafe.Sizeof((*int)(nil))
@@ -125,11 +135,18 @@ var callbackWrap_call = callbackWrap
125 // callbackWrap is called by assembly code which determines which Go function to call.135 // callbackWrap is called by assembly code which determines which Go function to call.
126 // This function takes the arguments and passes them to the Go function and returns the result.136 // This function takes the arguments and passes them to the Go function and returns the result.
127 func callbackWrap(a *callbackArgs) {137 func callbackWrap(a *callbackArgs) {
128- cbs.lock.Lock()138+ cbs.lock.RLock()
129 fn := cbs.funcs[a.index]139 fn := cbs.funcs[a.index]
130- cbs.lock.Unlock()140+ pool := cbs.argPools[a.index]
141+ cbs.lock.RUnlock()
131 fnType := fn.Type()142 fnType := fn.Type()
132- args := make([]reflect.Value, fnType.NumIn())143+ args := pool.Get().([]reflect.Value)
144+ defer func() {
145+ for i := range args {
146+ args[i] = reflect.Value{}
147+ }
148+ pool.Put(args)
149+ }()
133 frame := (*[callbackMaxFrame]uintptr)(a.args)150 frame := (*[callbackMaxFrame]uintptr)(a.args)
134 // stackFrame points to stack-passed arguments. On most architectures this is151 // stackFrame points to stack-passed arguments. On most architectures this is
135 // contiguous with frame (after register args), but on ppc64le it's separate.152 // contiguous with frame (after register args), but on ppc64le it's separate.
@@ -142,6 +159,14 @@ func callbackWrap(a *callbackArgs) {
142 // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers).159 // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers).
143 var floatsN int160 var floatsN int
144 var intsN int161 var intsN int
162+ // On amd64/loong64/ppc64le/riscv64/s390x, when returning a struct larger than
163+ // maxRegAllocStructSize, the caller passes a hidden pointer in the first integer
164+ // register. Skip it to avoid misreading it as the first function argument.
165+ if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") &&
166+ fnType.NumOut() == 1 && fnType.Out(0).Kind() == reflect.Struct &&
167+ fnType.Out(0).Size() > maxRegAllocStructSize {
168+ intsN = 1
169+ }
145 // stackSlot points to the index into frame (or stackFrame) of the current stack element.170 // stackSlot points to the index into frame (or stackFrame) of the current stack element.
146 // When stackFrame is nil, stack begins after float and integer registers in frame.171 // When stackFrame is nil, stack begins after float and integer registers in frame.
147 // When stackFrame is not nil (ppc64le), stackSlot indexes into stackFrame starting at 0.172 // When stackFrame is not nil (ppc64le), stackSlot indexes into stackFrame starting at 0.
@@ -187,8 +212,16 @@ func callbackWrap(a *callbackArgs) {
187 }212 }
188 floatsN += slots213 floatsN += slots
189 case reflect.Struct:214 case reflect.Struct:
190- // This is the CDecl field215+ if i == 0 && inType.AssignableTo(reflect.TypeOf(CDecl{})) {
191- args[i] = reflect.Zero(inType)216+ args[i] = reflect.Zero(inType)
217+ continue
218+ }
219+ if inType.Size() == 0 {
220+ args[i] = reflect.New(inType).Elem()
221+ continue
222+ }
223+ args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset)
224+ continue
192 default:225 default:
193 slots = int((inType.Size() + ptrSize - 1) / ptrSize)226 slots = int((inType.Size() + ptrSize - 1) / ptrSize)
194 if intsN+slots > numOfIntegerRegisters() {227 if intsN+slots > numOfIntegerRegisters() {
@@ -225,19 +258,21 @@ func callbackWrap(a *callbackArgs) {
225 if len(ret) > 0 {258 if len(ret) > 0 {
226 switch k := ret[0].Kind(); k {259 switch k := ret[0].Kind(); k {
227 case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:260 case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
228- a.result = uintptr(ret[0].Uint())261+ a.result[0] = uintptr(ret[0].Uint())
229 case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:262 case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
230- a.result = uintptr(ret[0].Int())263+ a.result[0] = uintptr(ret[0].Int())
231 case reflect.Bool:264 case reflect.Bool:
232 if ret[0].Bool() {265 if ret[0].Bool() {
233- a.result = 1266+ a.result[0] = 1
234 } else {267 } else {
235- a.result = 0268+ a.result[0] = 0
236 }269 }
237 case reflect.Pointer:270 case reflect.Pointer:
238- a.result = ret[0].Pointer()271+ a.result[0] = ret[0].Pointer()
239 case reflect.UnsafePointer:272 case reflect.UnsafePointer:
240- a.result = ret[0].Pointer()273+ a.result[0] = ret[0].Pointer()
274+ case reflect.Struct:
275+ setStruct(a, ret[0])
241 default:276 default:
242 panic("purego: unsupported kind: " + k.String())277 panic("purego: unsupported kind: " + k.String())
243 }278 }
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