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
@@ -159,8 +159,8 @@ func RegisterFunc(fptr any, cfn uintptr) {
159159 }
160160 }
161161 case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
162- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer,
163- reflect.Slice, reflect.Bool:
162+ reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer,
163+ reflect.Slice, reflect.Array, reflect.Bool:
164164 if ints < numOfIntegerRegisters() {
165165 ints++
166166 } else {
@@ -173,7 +173,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
173173 stack++
174174 }
175175 case reflect.Struct:
176- ensureStructSupportedForRegisterFunc()
176+ ensureStructSupported()
177177 if arg.Size() == 0 {
178178 continue
179179 }
@@ -192,7 +192,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
192192 }
193193 }
194194 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
195- ensureStructSupportedForRegisterFunc()
195+ ensureStructSupported()
196196 outType := ty.Out(0)
197197 checkStructFieldsSupported(outType)
198198 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
@@ -218,6 +218,29 @@ func RegisterFunc(fptr any, cfn uintptr) {
218218 }
219219 }
220220
221+ // Task 3: Analyze types at RegisterFunc time to determine if keepAlive is needed.
222+ // keepAlive is only used for: string args (CString alloc), struct args (addStruct),
223+ // struct returns > maxRegAllocStructSize (hidden pointer), array args (addressable copy),
224+ // and variadic []any args (unknown types at registration time).
225+ needsKeepAlive := false
226+ for i := 0; i < ty.NumIn(); i++ {
227+ inKind := ty.In(i).Kind()
228+ switch inKind {
229+ case reflect.String, reflect.Struct, reflect.Array, reflect.Slice:
230+ // Slice covers []string (CString allocs) and variadic ...any ([]any)
231+ needsKeepAlive = true
232+ case reflect.Interface:
233+ needsKeepAlive = true
234+ }
235+ }
236+ if ty.NumOut() == 1 {
237+ switch ty.Out(0).Kind() {
238+ case reflect.Struct:
239+ // struct returns may need hidden pointer allocation stored in keepAlive
240+ needsKeepAlive = true
241+ }
242+ }
243+
221244 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
222245 var sysargs [maxArgs]uintptr
223246 // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,
@@ -264,132 +287,191 @@ func RegisterFunc(fptr any, cfn uintptr) {
264287 addFloat = addStack
265288 }
266289
267- var keepAlive []any
268- defer func() {
269- runtime.KeepAlive(keepAlive)
270- runtime.KeepAlive(args)
271- }()
290+ if needsKeepAlive {
291+ // Slow path: allocate keepAlive slice and defer cleanup.
292+ // Used when args include strings, structs, arrays, or variadic []any.
293+ var keepAlive []any
294+ defer func() {
295+ runtime.KeepAlive(keepAlive)
296+ runtime.KeepAlive(args)
297+ }()
272298
273- var arm64_r8 uintptr
274- if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
275- outType := ty.Out(0)
276- if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize {
277- val := reflect.New(outType)
278- keepAlive = append(keepAlive, val)
279- addInt(val.Pointer())
280- } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
281- isAllFloats, numFields := isAllSameFloat(outType)
282- if !isAllFloats || numFields > 4 {
299+ var arm64_r8 uintptr
300+ if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
301+ outType := ty.Out(0)
302+ if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize {
283303 val := reflect.New(outType)
284304 keepAlive = append(keepAlive, val)
285- arm64_r8 = val.Pointer()
305+ addInt(val.Pointer())
306+ } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
307+ isAllFloats, numFields := isAllSameFloat(outType)
308+ if !isAllFloats || numFields > 4 {
309+ val := reflect.New(outType)
310+ keepAlive = append(keepAlive, val)
311+ arm64_r8 = val.Pointer()
312+ }
286313 }
287314 }
315+ for i, v := range args {
316+ if variadic, ok := xreflect.TypeAssert[[]any](v); ok {
317+ if i != len(args)-1 {
318+ panic("purego: can only expand last parameter")
319+ }
320+ for _, x := range variadic {
321+ keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
322+ }
323+ continue
324+ }
325+ // Check if we need to start Darwin ARM64 C-style stack packing
326+ if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {
327+ // Collect and separate remaining args into register vs stack
328+ stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats,
329+ keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
330+ keepAlive = newKeepAlive
331+
332+ // Bundle stack arguments with C-style packing
333+ bundleStackArgs(stackArgs, addStack)
334+ break
335+ }
336+ keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
337+ }
338+ return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, arm64_r8, args)
288339 }
340+
341+ // Fast path: no keepAlive needed (pointer/integer/bool/float args only).
342+ // Skip keepAlive slice allocation and defer overhead.
343+ defer runtime.KeepAlive(args)
344+
289345 for i, v := range args {
290- if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok {
346+ if variadic, ok := xreflect.TypeAssert[[]any](v); ok {
291347 if i != len(args)-1 {
292348 panic("purego: can only expand last parameter")
293349 }
294350 for _, x := range variadic {
295- keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
351+ addValue(reflect.ValueOf(x), nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
296352 }
297353 continue
298354 }
299355 // Check if we need to start Darwin ARM64 C-style stack packing
300356 if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {
301- // Collect and separate remaining args into register vs stack
302- stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats,
303- keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
304- keepAlive = newKeepAlive
305-
306- // Bundle stack arguments with C-style packing
357+ stackArgs, _ := collectStackArgs(args, i, numInts, numFloats,
358+ nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
307359 bundleStackArgs(stackArgs, addStack)
308360 break
309361 }
310- keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
362+ addValue(v, nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
311363 }
312364
313- syscall := thePool.Get().(*syscall15Args)
314- defer thePool.Put(syscall)
365+ return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, 0, args)
366+ })
367+ fn.Set(v)
368+}
369+
370+// callAndReturn performs the actual C function call and constructs the return value.
371+// It is shared between the fast path (no keepAlive) and slow path (with keepAlive).
372+func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs]uintptr, floats *[maxArgs]uintptr, arm64_r8 uintptr, args []reflect.Value) []reflect.Value {
373+ syscall := thePool.Get().(*syscall15Args)
374+ defer thePool.Put(syscall)
315375
316- if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {
317- syscall.Set(cfn, sysargs[:], floats[:], 0)
318- runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
319- } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
320- // Use the normal arm64 calling convention even on Windows
321- syscall.Set(cfn, sysargs[:], floats[:], arm64_r8)
322- runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
323- } else {
324- *syscall = syscall15Args{}
325- // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
326- syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
327- sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
328- sysargs[12], sysargs[13], sysargs[14])
329- syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
376+ if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {
377+ syscall.Set(cfn, sysargs[:], floats[:], 0)
378+ runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
379+ } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
380+ // Use the normal arm64 calling convention even on Windows
381+ syscall.Set(cfn, sysargs[:], floats[:], arm64_r8)
382+ runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
383+ } else {
384+ *syscall = syscall15Args{}
385+ // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
386+ syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
387+ sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
388+ sysargs[12], sysargs[13], sysargs[14])
389+ syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
390+ }
391+ if ty.NumOut() == 0 {
392+ return nil
393+ }
394+ outType := ty.Out(0)
395+ v := reflect.New(outType).Elem()
396+ switch outType.Kind() {
397+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
398+ v.SetUint(uint64(syscall.a1))
399+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
400+ v.SetInt(int64(syscall.a1))
401+ case reflect.Bool:
402+ v.SetBool(byte(syscall.a1) != 0)
403+ case reflect.UnsafePointer:
404+ // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
405+ v = reflect.New(outType).Elem()
406+ v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
407+ case reflect.Pointer:
408+ v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
409+ case reflect.Func:
410+ // wrap this C function in a nicely typed Go function
411+ v = reflect.New(outType)
412+ RegisterFunc(v.Interface(), syscall.a1)
413+ case reflect.String:
414+ v = reflect.ValueOf(strings.GoString(syscall.a1))
415+ case reflect.Float32:
416+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
417+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
418+ // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
419+ // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
420+ // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register.
421+ v = reflect.New(outType).Elem()
422+ switch runtime.GOARCH {
423+ case "386":
424+ v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
425+ case "ppc64le":
426+ v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
427+ case "s390x":
428+ // S390X is big-endian: float32 in upper 32 bits of 64-bit register
429+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32))))
430+ default:
431+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
330432 }
331- if ty.NumOut() == 0 {
332- return nil
433+ case reflect.Float64:
434+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
435+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
436+ v = reflect.New(outType).Elem()
437+ if is32bit {
438+ v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
439+ } else {
440+ v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
333441 }
334- outType := ty.Out(0)
335- v := reflect.New(outType).Elem()
336- switch outType.Kind() {
337- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
338- v.SetUint(uint64(syscall.a1))
339- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
340- v.SetInt(int64(syscall.a1))
341- case reflect.Bool:
342- v.SetBool(byte(syscall.a1) != 0)
343- case reflect.UnsafePointer:
344- // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
345- v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
346- case reflect.Ptr:
347- v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
348- case reflect.Func:
349- // wrap this C function in a nicely typed Go function
350- v = reflect.New(outType)
351- RegisterFunc(v.Interface(), syscall.a1)
352- case reflect.String:
353- v.SetString(strings.GoString(syscall.a1))
354- case reflect.Float32:
355- // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
356- // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
357- // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
358- // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
359- // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register.
360- switch runtime.GOARCH {
361- case "386":
362- v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
363- case "ppc64le":
364- v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
365- case "s390x":
366- // S390X is big-endian: float32 in upper 32 bits of 64-bit register
367- v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32))))
368- default:
369- v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
370- }
371- case reflect.Float64:
372- // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
373- // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
374- if is32bit {
375- v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
442+ case reflect.Struct:
443+ v = getStruct(outType, *syscall)
444+ case reflect.Slice:
445+ if outType.Elem().Kind() == reflect.String {
446+ // C returns char** (NULL-terminated array of strings)
447+ if syscall.a1 == 0 {
448+ v = reflect.Zero(outType)
376449 } else {
377- v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
450+ var result []string
451+ ptr := syscall.a1
452+ for {
453+ strPtr := *(*uintptr)(unsafe.Pointer(ptr))
454+ if strPtr == 0 {
455+ break
456+ }
457+ result = append(result, strings.GoString(strPtr))
458+ ptr += unsafe.Sizeof(uintptr(0))
459+ }
460+ v = reflect.ValueOf(result)
378461 }
379- case reflect.Struct:
380- v = getStruct(outType, *syscall)
381- default:
382- panic("purego: unsupported return kind: " + outType.Kind().String())
383- }
384- if len(args) > 0 {
385- // reuse args slice instead of allocating one when possible
386- args[0] = v
387- return args[:1]
388462 } else {
389- return []reflect.Value{v}
463+ panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String())
390464 }
391- })
392- fn.Set(v)
465+ default:
466+ panic("purego: unsupported return kind: " + outType.Kind().String())
467+ }
468+ if len(args) > 0 {
469+ // reuse args slice instead of allocating one when possible
470+ args[0] = v
471+ return args[:1]
472+ } else {
473+ return []reflect.Value{v}
474+ }
393475 }
394476
395477 func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any {
@@ -403,9 +485,29 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
403485 addInt(uintptr(v.Uint()))
404486 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
405487 addInt(uintptr(v.Int()))
406- case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
488+ case reflect.Pointer, reflect.UnsafePointer:
407489 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
408490 addInt(v.Pointer())
491+ case reflect.Slice:
492+ if v.Type().Elem().Kind() == reflect.String {
493+ ss := v.Interface().([]string)
494+ if ss == nil {
495+ addInt(0)
496+ } else {
497+ res := make([]*byte, len(ss)+1)
498+ for i, s := range ss {
499+ ptr := strings.CString(s)
500+ keepAlive = append(keepAlive, ptr)
501+ res[i] = ptr
502+ }
503+ // NULL-terminated for C
504+ res[len(ss)] = nil
505+ keepAlive = append(keepAlive, res)
506+ addInt(uintptr(unsafe.Pointer(&res[0])))
507+ }
508+ } else {
509+ addInt(v.Pointer())
510+ }
409511 case reflect.Func:
410512 addInt(NewCallback(v.Interface()))
411513 case reflect.Bool:
@@ -429,6 +531,17 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
429531 } else {
430532 addFloat(uintptr(math.Float64bits(v.Float())))
431533 }
534+ case reflect.Array:
535+ // Task 7: Support fixed-size array arguments by passing a pointer to the array data.
536+ if v.CanAddr() {
537+ addInt(v.Addr().Pointer())
538+ } else {
539+ // Array is not addressable (e.g. passed by value); make an addressable copy.
540+ tmp := reflect.New(v.Type()).Elem()
541+ tmp.Set(v)
542+ keepAlive = append(keepAlive, tmp)
543+ addInt(tmp.Addr().Pointer())
544+ }
432545 case reflect.Struct:
433546 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
434547 default:
@@ -482,7 +595,7 @@ func checkStructFieldsSupported(ty reflect.Type) {
482595 switch f.Kind() {
483596 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
484597 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
485- reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32,
598+ reflect.Uintptr, reflect.Pointer, reflect.UnsafePointer, reflect.Float64, reflect.Float32,
486599 reflect.Bool:
487600 default:
488601 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
@@ -490,12 +603,12 @@ func checkStructFieldsSupported(ty reflect.Type) {
490603 }
491604 }
492605
493-func ensureStructSupportedForRegisterFunc() {
606+func ensureStructSupported() {
494607 if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
495- panic("purego: struct arguments are only supported on amd64 and arm64")
608+ panic("purego: struct arguments/returns are only supported on amd64 and arm64")
496609 }
497610 if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
498- panic("purego: struct arguments are only supported on darwin and linux")
611+ panic("purego: struct arguments/returns are only supported on darwin and linux")
499612 }
500613 }
501614
@@ -159,8 +159,8 @@ func RegisterFunc(fptr any, cfn uintptr) {
159 }159 }
160 }160 }
161 case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,161 case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
162- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer,162+ reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer,
163- reflect.Slice, reflect.Bool:163+ reflect.Slice, reflect.Array, reflect.Bool:
164 if ints < numOfIntegerRegisters() {164 if ints < numOfIntegerRegisters() {
165 ints++165 ints++
166 } else {166 } else {
@@ -173,7 +173,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
173 stack++173 stack++
174 }174 }
175 case reflect.Struct:175 case reflect.Struct:
176- ensureStructSupportedForRegisterFunc()176+ ensureStructSupported()
177 if arg.Size() == 0 {177 if arg.Size() == 0 {
178 continue178 continue
179 }179 }
@@ -192,7 +192,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
192 }192 }
193 }193 }
194 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {194 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
195- ensureStructSupportedForRegisterFunc()195+ ensureStructSupported()
196 outType := ty.Out(0)196 outType := ty.Out(0)
197 checkStructFieldsSupported(outType)197 checkStructFieldsSupported(outType)
198 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {198 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
@@ -218,6 +218,29 @@ func RegisterFunc(fptr any, cfn uintptr) {
218 }218 }
219 }219 }
220 220
221+ // Task 3: Analyze types at RegisterFunc time to determine if keepAlive is needed.
222+ // keepAlive is only used for: string args (CString alloc), struct args (addStruct),
223+ // struct returns > maxRegAllocStructSize (hidden pointer), array args (addressable copy),
224+ // and variadic []any args (unknown types at registration time).
225+ needsKeepAlive := false
226+ for i := 0; i < ty.NumIn(); i++ {
227+ inKind := ty.In(i).Kind()
228+ switch inKind {
229+ case reflect.String, reflect.Struct, reflect.Array, reflect.Slice:
230+ // Slice covers []string (CString allocs) and variadic ...any ([]any)
231+ needsKeepAlive = true
232+ case reflect.Interface:
233+ needsKeepAlive = true
234+ }
235+ }
236+ if ty.NumOut() == 1 {
237+ switch ty.Out(0).Kind() {
238+ case reflect.Struct:
239+ // struct returns may need hidden pointer allocation stored in keepAlive
240+ needsKeepAlive = true
241+ }
242+ }
243+
221 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {244 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
222 var sysargs [maxArgs]uintptr245 var sysargs [maxArgs]uintptr
223 // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,246 // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,
@@ -264,132 +287,191 @@ func RegisterFunc(fptr any, cfn uintptr) {
264 addFloat = addStack287 addFloat = addStack
265 }288 }
266 289
267- var keepAlive []any290+ if needsKeepAlive {
268- defer func() {291+ // Slow path: allocate keepAlive slice and defer cleanup.
269- runtime.KeepAlive(keepAlive)292+ // Used when args include strings, structs, arrays, or variadic []any.
270- runtime.KeepAlive(args)293+ var keepAlive []any
271- }()294+ defer func() {
295+ runtime.KeepAlive(keepAlive)
296+ runtime.KeepAlive(args)
297+ }()
272 298
273- var arm64_r8 uintptr299+ var arm64_r8 uintptr
274- if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {300+ if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
275- outType := ty.Out(0)301+ outType := ty.Out(0)
276- if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize {302+ if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize {
277- val := reflect.New(outType)
278- keepAlive = append(keepAlive, val)
279- addInt(val.Pointer())
280- } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
281- isAllFloats, numFields := isAllSameFloat(outType)
282- if !isAllFloats || numFields > 4 {
283 val := reflect.New(outType)303 val := reflect.New(outType)
284 keepAlive = append(keepAlive, val)304 keepAlive = append(keepAlive, val)
285- arm64_r8 = val.Pointer()305+ addInt(val.Pointer())
306+ } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
307+ isAllFloats, numFields := isAllSameFloat(outType)
308+ if !isAllFloats || numFields > 4 {
309+ val := reflect.New(outType)
310+ keepAlive = append(keepAlive, val)
311+ arm64_r8 = val.Pointer()
312+ }
286 }313 }
287 }314 }
315+ for i, v := range args {
316+ if variadic, ok := xreflect.TypeAssert[[]any](v); ok {
317+ if i != len(args)-1 {
318+ panic("purego: can only expand last parameter")
319+ }
320+ for _, x := range variadic {
321+ keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
322+ }
323+ continue
324+ }
325+ // Check if we need to start Darwin ARM64 C-style stack packing
326+ if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {
327+ // Collect and separate remaining args into register vs stack
328+ stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats,
329+ keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
330+ keepAlive = newKeepAlive
331+
332+ // Bundle stack arguments with C-style packing
333+ bundleStackArgs(stackArgs, addStack)
334+ break
335+ }
336+ keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
337+ }
338+ return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, arm64_r8, args)
288 }339 }
340+
341+ // Fast path: no keepAlive needed (pointer/integer/bool/float args only).
342+ // Skip keepAlive slice allocation and defer overhead.
343+ defer runtime.KeepAlive(args)
344+
289 for i, v := range args {345 for i, v := range args {
290- if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok {346+ if variadic, ok := xreflect.TypeAssert[[]any](v); ok {
291 if i != len(args)-1 {347 if i != len(args)-1 {
292 panic("purego: can only expand last parameter")348 panic("purego: can only expand last parameter")
293 }349 }
294 for _, x := range variadic {350 for _, x := range variadic {
295- keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)351+ addValue(reflect.ValueOf(x), nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
296 }352 }
297 continue353 continue
298 }354 }
299 // Check if we need to start Darwin ARM64 C-style stack packing355 // Check if we need to start Darwin ARM64 C-style stack packing
300 if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {356 if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {
301- // Collect and separate remaining args into register vs stack357+ stackArgs, _ := collectStackArgs(args, i, numInts, numFloats,
302- stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats,358+ nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
303- keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
304- keepAlive = newKeepAlive
305-
306- // Bundle stack arguments with C-style packing
307 bundleStackArgs(stackArgs, addStack)359 bundleStackArgs(stackArgs, addStack)
308 break360 break
309 }361 }
310- keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)362+ addValue(v, nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
311 }363 }
312 364
313- syscall := thePool.Get().(*syscall15Args)365+ return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, 0, args)
314- defer thePool.Put(syscall)366+ })
367+ fn.Set(v)
368+}
369+
370+// callAndReturn performs the actual C function call and constructs the return value.
371+// It is shared between the fast path (no keepAlive) and slow path (with keepAlive).
372+func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs]uintptr, floats *[maxArgs]uintptr, arm64_r8 uintptr, args []reflect.Value) []reflect.Value {
373+ syscall := thePool.Get().(*syscall15Args)
374+ defer thePool.Put(syscall)
315 375
316- if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {376+ if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {
317- syscall.Set(cfn, sysargs[:], floats[:], 0)377+ syscall.Set(cfn, sysargs[:], floats[:], 0)
318- runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))378+ runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
319- } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {379+ } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
320- // Use the normal arm64 calling convention even on Windows380+ // Use the normal arm64 calling convention even on Windows
321- syscall.Set(cfn, sysargs[:], floats[:], arm64_r8)381+ syscall.Set(cfn, sysargs[:], floats[:], arm64_r8)
322- runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))382+ runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
323- } else {383+ } else {
324- *syscall = syscall15Args{}384+ *syscall = syscall15Args{}
325- // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats385+ // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
326- syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],386+ syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
327- sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],387+ sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
328- sysargs[12], sysargs[13], sysargs[14])388+ sysargs[12], sysargs[13], sysargs[14])
329- syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support389+ syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
390+ }
391+ if ty.NumOut() == 0 {
392+ return nil
393+ }
394+ outType := ty.Out(0)
395+ v := reflect.New(outType).Elem()
396+ switch outType.Kind() {
397+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
398+ v.SetUint(uint64(syscall.a1))
399+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
400+ v.SetInt(int64(syscall.a1))
401+ case reflect.Bool:
402+ v.SetBool(byte(syscall.a1) != 0)
403+ case reflect.UnsafePointer:
404+ // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
405+ v = reflect.New(outType).Elem()
406+ v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
407+ case reflect.Pointer:
408+ v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
409+ case reflect.Func:
410+ // wrap this C function in a nicely typed Go function
411+ v = reflect.New(outType)
412+ RegisterFunc(v.Interface(), syscall.a1)
413+ case reflect.String:
414+ v = reflect.ValueOf(strings.GoString(syscall.a1))
415+ case reflect.Float32:
416+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
417+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
418+ // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
419+ // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
420+ // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register.
421+ v = reflect.New(outType).Elem()
422+ switch runtime.GOARCH {
423+ case "386":
424+ v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
425+ case "ppc64le":
426+ v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
427+ case "s390x":
428+ // S390X is big-endian: float32 in upper 32 bits of 64-bit register
429+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32))))
430+ default:
431+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
330 }432 }
331- if ty.NumOut() == 0 {433+ case reflect.Float64:
332- return nil434+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
435+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
436+ v = reflect.New(outType).Elem()
437+ if is32bit {
438+ v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
439+ } else {
440+ v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
333 }441 }
334- outType := ty.Out(0)442+ case reflect.Struct:
335- v := reflect.New(outType).Elem()443+ v = getStruct(outType, *syscall)
336- switch outType.Kind() {444+ case reflect.Slice:
337- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:445+ if outType.Elem().Kind() == reflect.String {
338- v.SetUint(uint64(syscall.a1))446+ // C returns char** (NULL-terminated array of strings)
339- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:447+ if syscall.a1 == 0 {
340- v.SetInt(int64(syscall.a1))448+ v = reflect.Zero(outType)
341- case reflect.Bool:
342- v.SetBool(byte(syscall.a1) != 0)
343- case reflect.UnsafePointer:
344- // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
345- v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
346- case reflect.Ptr:
347- v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
348- case reflect.Func:
349- // wrap this C function in a nicely typed Go function
350- v = reflect.New(outType)
351- RegisterFunc(v.Interface(), syscall.a1)
352- case reflect.String:
353- v.SetString(strings.GoString(syscall.a1))
354- case reflect.Float32:
355- // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
356- // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
357- // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
358- // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
359- // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register.
360- switch runtime.GOARCH {
361- case "386":
362- v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
363- case "ppc64le":
364- v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
365- case "s390x":
366- // S390X is big-endian: float32 in upper 32 bits of 64-bit register
367- v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32))))
368- default:
369- v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
370- }
371- case reflect.Float64:
372- // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
373- // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
374- if is32bit {
375- v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
376 } else {449 } else {
377- v.SetFloat(math.Float64frombits(uint64(syscall.f1)))450+ var result []string
451+ ptr := syscall.a1
452+ for {
453+ strPtr := *(*uintptr)(unsafe.Pointer(ptr))
454+ if strPtr == 0 {
455+ break
456+ }
457+ result = append(result, strings.GoString(strPtr))
458+ ptr += unsafe.Sizeof(uintptr(0))
459+ }
460+ v = reflect.ValueOf(result)
378 }461 }
379- case reflect.Struct:
380- v = getStruct(outType, *syscall)
381- default:
382- panic("purego: unsupported return kind: " + outType.Kind().String())
383- }
384- if len(args) > 0 {
385- // reuse args slice instead of allocating one when possible
386- args[0] = v
387- return args[:1]
388 } else {462 } else {
389- return []reflect.Value{v}463+ panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String())
390 }464 }
391- })465+ default:
392- fn.Set(v)466+ panic("purego: unsupported return kind: " + outType.Kind().String())
467+ }
468+ if len(args) > 0 {
469+ // reuse args slice instead of allocating one when possible
470+ args[0] = v
471+ return args[:1]
472+ } else {
473+ return []reflect.Value{v}
474+ }
393 }475 }
394 476
395 func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any {477 func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any {
@@ -403,9 +485,29 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
403 addInt(uintptr(v.Uint()))485 addInt(uintptr(v.Uint()))
404 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:486 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
405 addInt(uintptr(v.Int()))487 addInt(uintptr(v.Int()))
406- case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:488+ case reflect.Pointer, reflect.UnsafePointer:
407 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable489 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
408 addInt(v.Pointer())490 addInt(v.Pointer())
491+ case reflect.Slice:
492+ if v.Type().Elem().Kind() == reflect.String {
493+ ss := v.Interface().([]string)
494+ if ss == nil {
495+ addInt(0)
496+ } else {
497+ res := make([]*byte, len(ss)+1)
498+ for i, s := range ss {
499+ ptr := strings.CString(s)
500+ keepAlive = append(keepAlive, ptr)
501+ res[i] = ptr
502+ }
503+ // NULL-terminated for C
504+ res[len(ss)] = nil
505+ keepAlive = append(keepAlive, res)
506+ addInt(uintptr(unsafe.Pointer(&res[0])))
507+ }
508+ } else {
509+ addInt(v.Pointer())
510+ }
409 case reflect.Func:511 case reflect.Func:
410 addInt(NewCallback(v.Interface()))512 addInt(NewCallback(v.Interface()))
411 case reflect.Bool:513 case reflect.Bool:
@@ -429,6 +531,17 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
429 } else {531 } else {
430 addFloat(uintptr(math.Float64bits(v.Float())))532 addFloat(uintptr(math.Float64bits(v.Float())))
431 }533 }
534+ case reflect.Array:
535+ // Task 7: Support fixed-size array arguments by passing a pointer to the array data.
536+ if v.CanAddr() {
537+ addInt(v.Addr().Pointer())
538+ } else {
539+ // Array is not addressable (e.g. passed by value); make an addressable copy.
540+ tmp := reflect.New(v.Type()).Elem()
541+ tmp.Set(v)
542+ keepAlive = append(keepAlive, tmp)
543+ addInt(tmp.Addr().Pointer())
544+ }
432 case reflect.Struct:545 case reflect.Struct:
433 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)546 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
434 default:547 default:
@@ -482,7 +595,7 @@ func checkStructFieldsSupported(ty reflect.Type) {
482 switch f.Kind() {595 switch f.Kind() {
483 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,596 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
484 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,597 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
485- reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32,598+ reflect.Uintptr, reflect.Pointer, reflect.UnsafePointer, reflect.Float64, reflect.Float32,
486 reflect.Bool:599 reflect.Bool:
487 default:600 default:
488 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))601 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
@@ -490,12 +603,12 @@ func checkStructFieldsSupported(ty reflect.Type) {
490 }603 }
491 }604 }
492 605
493-func ensureStructSupportedForRegisterFunc() {606+func ensureStructSupported() {
494 if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {607 if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
495- panic("purego: struct arguments are only supported on amd64 and arm64")608+ panic("purego: struct arguments/returns are only supported on amd64 and arm64")
496 }609 }
497 if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {610 if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
498- panic("purego: struct arguments are only supported on darwin and linux")611+ panic("purego: struct arguments/returns are only supported on darwin and linux")
499 }612 }
500 }613 }
501 614
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 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