v0.11.0-bnema.4 cf1b83e

syscall_unix.go RawHistoryFolder

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4// TODO: remove s390x cgo dependency once golang/go#77449 is resolved
5//go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (cgo && s390x))) || netbsd
6
7package purego
8
9import (
10	"encoding/json"
11	"errors"
12	"fmt"
13	"os"
14	"reflect"
15	"runtime"
16	"runtime/debug"
17	"strings"
18	"sync"
19	"time"
20	"unsafe"
21
22	internalstrings "github.com/bnema/purego/internal/strings"
23)
24
25var syscall15XABI0 uintptr
26
27func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
28	args := &syscall15Args{
29		fn: fn,
30		a1: a1, a2: a2, a3: a3, a4: a4, a5: a5, a6: a6, a7: a7, a8: a8,
31		a9: a9, a10: a10, a11: a11, a12: a12, a13: a13, a14: a14, a15: a15,
32		f1: a1, f2: a2, f3: a3, f4: a4, f5: a5, f6: a6, f7: a7, f8: a8,
33	}
34
35	runtime_cgocall(syscall15XABI0, unsafe.Pointer(args))
36	return args.a1, args.a2, args.a3
37}
38
39// NewCallback converts a Go function to a function pointer conforming to the C calling convention.
40// This is useful when interoperating with C code requiring callbacks. The argument is expected to be a
41// function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size
42// of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated
43// for these callbacks is never released. At least 2000 callbacks can always be created. Although this function
44// provides similar functionality to windows.NewCallback it is distinct.
45func NewCallback(fn any) uintptr {
46	ty := reflect.TypeOf(fn)
47	for i := 0; i < ty.NumIn(); i++ {
48		in := ty.In(i)
49		if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
50			continue
51		}
52		if i != 0 {
53			panic("purego: CDecl must be the first argument")
54		}
55	}
56	return compileCallback(fn)
57}
58
59// NewCallbackFnPtr converts a Go function pointer to a function pointer conforming to the C calling convention.
60// Calling this function multiple times with the same function pointer returns the original callback address.
61func NewCallbackFnPtr(fnPtr any) uintptr {
62	val := reflect.ValueOf(fnPtr)
63	if val.IsNil() {
64		panic("purego: function must not be nil")
65	}
66	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Func {
67		panic("purego: the type must be a function pointer but was not")
68	}
69	if addr, idx, ok := getCallbackByFnPtr(val); ok {
70		cbs.lock.Lock()
71		recordCallbackLedger("dedupe", idx, addr, val.Pointer(), val.Elem(), false, "")
72		cbs.lock.Unlock()
73		return addr
74	}
75	addr := compileCallback(val.Elem().Interface())
76	cbs.lock.Lock()
77	cbs.knownFnPtr[val.Pointer()] = addr
78	if idx, ok := cbs.knownIdx[addr]; ok {
79		cbs.fnPtrKeys[idx] = val.Pointer()
80		recordCallbackLedger("fnptr-bind", idx, addr, val.Pointer(), val.Elem(), false, "")
81	}
82	cbs.lock.Unlock()
83	return addr
84}
85
86// UnrefCallback unreferences a callback created by NewCallback or NewCallbackFnPtr and frees its slot.
87func UnrefCallback(cb uintptr) error {
88	cbs.lock.Lock()
89	defer cbs.lock.Unlock()
90	idx, ok := cbs.knownIdx[cb]
91	if !ok {
92		recordCallbackLedger("release-miss", -1, cb, 0, reflect.Value{}, false, "callback not found")
93		return errors.New("callback not found")
94	}
95	val := cbs.funcs[idx]
96	key := cbs.fnPtrKeys[idx]
97	if key != 0 {
98		delete(cbs.knownFnPtr, key)
99		cbs.fnPtrKeys[idx] = 0
100	}
101	delete(cbs.knownIdx, cb)
102	cbs.holes[idx] = struct{}{}
103	cbs.funcs[idx] = reflect.Value{}
104	cbs.argPools[idx] = nil
105	recordCallbackLedger("release", idx, cb, key, val, false, "")
106	return nil
107}
108
109// UnrefCallbackFnPtr unreferences a callback previously created via NewCallbackFnPtr.
110func UnrefCallbackFnPtr(fnPtr any) error {
111	val := reflect.ValueOf(fnPtr)
112	if val.IsNil() {
113		panic("purego: function must not be nil")
114	}
115	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Func {
116		panic("purego: the type must be a function pointer but was not")
117	}
118	cbs.lock.Lock()
119	defer cbs.lock.Unlock()
120	addr, ok := cbs.knownFnPtr[val.Pointer()]
121	if !ok {
122		recordCallbackLedger("release-fnptr-miss", -1, 0, val.Pointer(), val.Elem(), false, "callback not found")
123		return errors.New("callback not found")
124	}
125	idx, ok := cbs.knownIdx[addr]
126	if !ok {
127		delete(cbs.knownFnPtr, val.Pointer())
128		recordCallbackLedger("release-fnptr-miss", -1, addr, val.Pointer(), val.Elem(), false, "callback not found")
129		return errors.New("callback not found")
130	}
131	callbackVal := cbs.funcs[idx]
132	delete(cbs.knownFnPtr, val.Pointer())
133	delete(cbs.knownIdx, addr)
134	cbs.fnPtrKeys[idx] = 0
135	cbs.holes[idx] = struct{}{}
136	cbs.funcs[idx] = reflect.Value{}
137	cbs.argPools[idx] = nil
138	recordCallbackLedger("release-fnptr", idx, addr, val.Pointer(), callbackVal, false, "")
139	return nil
140}
141
142// maxCb is the maximum number of callbacks
143// only increase this if you have added more to the callbackasm function
144const maxCB = 2000
145
146var cbs = struct {
147	lock       sync.RWMutex
148	numFn      int                  // the highest allocated callback index + 1
149	holes      map[int]struct{}     // reusable callback slots
150	funcs      [maxCB]reflect.Value // the saved callbacks
151	argPools   [maxCB]*sync.Pool    // pre-allocated argument buffers per callback
152	knownIdx   map[uintptr]int      // callback address -> slot index
153	knownFnPtr map[uintptr]uintptr  // function pointer variable address -> callback address
154	fnPtrKeys  [maxCB]uintptr       // slot index -> function pointer variable address
155	ledgerSeq  uint64               // monotonic callback ledger event sequence
156}{
157	holes:      make(map[int]struct{}),
158	knownIdx:   make(map[uintptr]int, maxCB),
159	knownFnPtr: make(map[uintptr]uintptr, maxCB),
160}
161
162func getCallbackByFnPtr(val reflect.Value) (uintptr, int, bool) {
163	cbs.lock.RLock()
164	defer cbs.lock.RUnlock()
165	addr, ok := cbs.knownFnPtr[val.Pointer()]
166	if !ok {
167		return 0, -1, false
168	}
169	idx, ok := cbs.knownIdx[addr]
170	if !ok {
171		return addr, -1, true
172	}
173	return addr, idx, true
174}
175
176type callbackLedgerEvent struct {
177	Marker     string `json:"marker"`
178	PID        int    `json:"pid"`
179	Seq        uint64 `json:"seq"`
180	Time       string `json:"time"`
181	Event      string `json:"event"`
182	Index      int    `json:"index"`
183	Addr       string `json:"addr,omitempty"`
184	FnPtrKey   string `json:"fn_ptr_key,omitempty"`
185	Type       string `json:"type,omitempty"`
186	Family     string `json:"family"`
187	StackKey   string `json:"stack_key"`
188	NumFn      int    `json:"num_fn"`
189	Occupied   int    `json:"occupied"`
190	Reusable   int    `json:"reusable"`
191	KnownFnPtr int    `json:"known_fn_ptr"`
192	Remaining  int    `json:"remaining"`
193	Max        int    `json:"max"`
194	ReusedSlot bool   `json:"reused_slot,omitempty"`
195	Stack      string `json:"stack,omitempty"`
196	Error      string `json:"error,omitempty"`
197}
198
199func callbackEnvEnabled(name string) bool {
200	switch strings.ToLower(os.Getenv(name)) {
201	case "1", "true", "yes", "on":
202		return true
203	default:
204		return false
205	}
206}
207
208func callbackLedgerEnabled() bool {
209	return callbackEnvEnabled("PUREGO_CALLBACK_LEDGER")
210}
211
212func callbackLedgerStackEnabled() bool {
213	return callbackEnvEnabled("PUREGO_CALLBACK_LEDGER_STACK")
214}
215
216func callbackTraceEnabled() bool {
217	return callbackEnvEnabled("PUREGO_CALLBACK_TRACE")
218}
219
220func callbackLedgerPath() string {
221	if path := os.Getenv("PUREGO_CALLBACK_LEDGER_FILE"); path != "" {
222		return path
223	}
224	return fmt.Sprintf("/tmp/purego-callback-ledger-%d.jsonl", os.Getpid())
225}
226
227func typeName(val reflect.Value) string {
228	if !val.IsValid() {
229		return ""
230	}
231	return val.Type().String()
232}
233
234func hexUintptr(v uintptr) string {
235	if v == 0 {
236		return ""
237	}
238	return fmt.Sprintf("0x%x", v)
239}
240
241func classifyCallbackStack(stack string) (family, stackKey string) {
242	family = "unknown"
243	stackKey = "unknown"
244	lines := strings.Split(stack, "\n")
245	for i, line := range lines {
246		if line == "" || strings.HasPrefix(line, "goroutine ") || strings.Contains(line, "runtime/debug.Stack") || strings.Contains(line, "recordCallbackLedger") || strings.Contains(line, "traceCallbackAllocation") {
247			continue
248		}
249		if strings.Contains(line, "github.com/bnema/purego.") || strings.Contains(line, "github.com/ebitengine/purego.") {
250			continue
251		}
252		if strings.HasPrefix(strings.TrimSpace(line), "/") || strings.HasPrefix(strings.TrimSpace(line), "../") {
253			continue
254		}
255		stackKey = strings.TrimSpace(line)
256		for j := i + 1; j < len(lines); j++ {
257			fileLine := strings.TrimSpace(lines[j])
258			if fileLine == "" {
259				continue
260			}
261			family = classifyCallbackFamily(fileLine + " " + stackKey)
262			return family, stackKey
263		}
264		family = classifyCallbackFamily(stackKey)
265		return family, stackKey
266	}
267	return family, stackKey
268}
269
270func classifyCallbackFamily(s string) string {
271	switch {
272	case strings.Contains(s, "purego-cef2gtk"):
273		return "purego-cef2gtk"
274	case strings.Contains(s, "purego-cef"):
275		return "purego-cef"
276	case strings.Contains(s, "puregotk"):
277		return "puregotk"
278	case strings.Contains(s, "dumber"):
279		return "dumber"
280	case strings.Contains(s, "github.com/bnema/purego") || strings.Contains(s, "/purego/"):
281		return "purego"
282	default:
283		return "unknown"
284	}
285}
286
287func recordCallbackLedger(event string, idx int, addr uintptr, fnPtrKey uintptr, val reflect.Value, reused bool, errText string) {
288	if !callbackLedgerEnabled() {
289		return
290	}
291	stack := ""
292	family := "unknown"
293	stackKey := "unknown"
294	if callbackLedgerStackEnabled() {
295		stack = string(debug.Stack())
296		family, stackKey = classifyCallbackStack(stack)
297	}
298	cbs.ledgerSeq++
299	occupied := cbs.numFn - len(cbs.holes)
300	entry := callbackLedgerEvent{
301		Marker:     "PUREGO-CALLBACK-LEDGER",
302		PID:        os.Getpid(),
303		Seq:        cbs.ledgerSeq,
304		Time:       time.Now().Format(time.RFC3339Nano),
305		Event:      event,
306		Index:      idx,
307		Addr:       hexUintptr(addr),
308		FnPtrKey:   hexUintptr(fnPtrKey),
309		Type:       typeName(val),
310		Family:     family,
311		StackKey:   stackKey,
312		NumFn:      cbs.numFn,
313		Occupied:   occupied,
314		Reusable:   len(cbs.holes),
315		KnownFnPtr: len(cbs.knownFnPtr),
316		Remaining:  maxCB - occupied,
317		Max:        maxCB,
318		ReusedSlot: reused,
319		Error:      errText,
320	}
321	if callbackLedgerStackEnabled() {
322		entry.Stack = stack
323	}
324	payload, err := json.Marshal(entry)
325	if err != nil {
326		return
327	}
328	payload = append(payload, '\n')
329	if f, err := os.OpenFile(callbackLedgerPath(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); err == nil {
330		_, _ = f.Write(payload)
331		_ = f.Close()
332	}
333}
334
335func traceCallbackAllocation(event string, val reflect.Value, remaining int) {
336	if !callbackTraceEnabled() {
337		return
338	}
339	message := fmt.Sprintf("PUREGO-CALLBACK-TRACE event=%s remaining=%d live=%d type=%s\n%s\n", event, remaining, maxCB-remaining, val.Type(), debug.Stack())
340	fmt.Fprint(os.Stderr, message)
341	traceFile := os.Getenv("PUREGO_CALLBACK_TRACE_FILE")
342	if traceFile == "" {
343		traceFile = fmt.Sprintf("/tmp/purego-callback-trace-%d.log", os.Getpid())
344	}
345	// traceCallbackAllocation intentionally accepts the opt-in
346	// PUREGO_CALLBACK_TRACE_FILE path. Users must set PUREGO_CALLBACK_TRACE=1 to
347	// enable tracing, and callers who override the path control their debugging
348	// environment. This documents the deliberate gosec G304 tradeoff for audits.
349	if f, err := os.OpenFile(traceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); err == nil {
350		_, _ = f.WriteString(message)
351		_ = f.Close()
352	}
353}
354
355func compileCallback(fn any) uintptr {
356	val := reflect.ValueOf(fn)
357	if val.Kind() != reflect.Func {
358		panic("purego: the type must be a function but was not")
359	}
360	if val.IsNil() {
361		panic("purego: function must not be nil")
362	}
363	ty := val.Type()
364	for i := 0; i < ty.NumIn(); i++ {
365		in := ty.In(i)
366		switch in.Kind() {
367		case reflect.Struct:
368			if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) {
369				continue
370			}
371			ensureStructSupported()
372			checkStructFieldsSupported(in)
373			continue
374		case reflect.Interface, reflect.Func, reflect.Slice,
375			reflect.Chan, reflect.Complex64, reflect.Complex128,
376			reflect.Map, reflect.Invalid:
377			panic("purego: unsupported argument type: " + in.Kind().String())
378		}
379	}
380output:
381	switch {
382	case ty.NumOut() == 1:
383		switch ty.Out(0).Kind() {
384		case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
385			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
386			reflect.Bool, reflect.UnsafePointer, reflect.Struct:
387			break output
388		}
389		panic("purego: unsupported return type: " + ty.String())
390	case ty.NumOut() > 1:
391		panic("purego: callbacks can only have one return")
392	}
393	cbs.lock.Lock()
394	defer cbs.lock.Unlock()
395	index := -1
396	reused := false
397	for i := range cbs.holes {
398		index = i
399		delete(cbs.holes, i)
400		reused = true
401		break
402	}
403	if index < 0 {
404		remaining := maxCB - cbs.numFn
405		if remaining <= 0 {
406			recordCallbackLedger("exhausted", -1, 0, 0, val, false, "maximum callbacks reached")
407			traceCallbackAllocation("exhausted", val, 0)
408			panic("purego: the maximum number of callbacks has been reached")
409		}
410		if remaining <= 100 || remaining%250 == 0 {
411			traceCallbackAllocation("allocated", val, remaining)
412		}
413		index = cbs.numFn
414		cbs.numFn++
415	}
416	cbs.funcs[index] = val
417	numIn := ty.NumIn()
418	cbs.argPools[index] = &sync.Pool{
419		New: func() any {
420			return make([]reflect.Value, numIn)
421		},
422	}
423	addr := callbackasmAddr(index)
424	cbs.knownIdx[addr] = index
425	if reused {
426		recordCallbackLedger("reuse", index, addr, 0, val, true, "")
427	} else {
428		recordCallbackLedger("alloc", index, addr, 0, val, false, "")
429	}
430	return addr
431}
432
433const ptrSize = unsafe.Sizeof((*int)(nil))
434
435const callbackMaxFrame = 64 * ptrSize
436
437// callbackasm is implemented in zcallback_GOOS_GOARCH.s
438//
439//go:linkname __callbackasm callbackasm
440var __callbackasm byte
441var callbackasmABI0 = uintptr(unsafe.Pointer(&__callbackasm))
442
443// callbackWrap_call allows the calling of the ABIInternal wrapper
444// which is required for runtime.cgocallback without the
445// <ABIInternal> tag which is only allowed in the runtime.
446// This closure is used inside sys_darwin_GOARCH.s
447var callbackWrap_call = callbackWrap
448
449// callbackWrap is called by assembly code which determines which Go function to call.
450// This function takes the arguments and passes them to the Go function and returns the result.
451func callbackWrap(a *callbackArgs) {
452	cbs.lock.RLock()
453	fn := cbs.funcs[a.index]
454	pool := cbs.argPools[a.index]
455	cbs.lock.RUnlock()
456	fnType := fn.Type()
457	args := pool.Get().([]reflect.Value)
458	defer func() {
459		for i := range args {
460			args[i] = reflect.Value{}
461		}
462		pool.Put(args)
463	}()
464	frame := (*[callbackMaxFrame]uintptr)(a.args)
465	// stackFrame points to stack-passed arguments. On most architectures this is
466	// contiguous with frame (after register args), but on ppc64le it's separate.
467	var stackFrame *[callbackMaxFrame]uintptr
468	if sf := a.stackFrame(); sf != nil {
469		// Only ppc64le uses separate stackArgs pointer due to NOSPLIT constraints
470		stackFrame = (*[callbackMaxFrame]uintptr)(sf)
471	}
472	// floatsN and intsN track the number of register slots used, not argument count.
473	// This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers).
474	var floatsN int
475	var intsN int
476	// On amd64/loong64/ppc64le/riscv64/s390x, when returning a struct larger than
477	// maxRegAllocStructSize, the caller passes a hidden pointer in the first integer
478	// register. Skip it to avoid misreading it as the first function argument.
479	if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") &&
480		fnType.NumOut() == 1 && fnType.Out(0).Kind() == reflect.Struct &&
481		fnType.Out(0).Size() > maxRegAllocStructSize {
482		intsN = 1
483	}
484	// stackSlot points to the index into frame (or stackFrame) of the current stack element.
485	// When stackFrame is nil, stack begins after float and integer registers in frame.
486	// When stackFrame is not nil (ppc64le), stackSlot indexes into stackFrame starting at 0.
487	stackSlot := numOfIntegerRegisters() + numOfFloatRegisters()
488	if stackFrame != nil {
489		// ppc64le: stackArgs is a separate pointer, indices start at 0
490		stackSlot = 0
491	}
492	// stackByteOffset tracks the byte offset within the stack area for Darwin ARM64
493	// tight packing. On Darwin ARM64, C passes small types packed on the stack.
494	stackByteOffset := uintptr(0)
495	for i := range args {
496		// slots is the number of pointer-sized slots the argument takes
497		var slots int
498		inType := fnType.In(i)
499		switch inType.Kind() {
500		case reflect.Float32, reflect.Float64:
501			slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize)
502			if floatsN+slots > numOfFloatRegisters() {
503				if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
504					// Darwin ARM64: read from packed stack with proper alignment
505					args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)
506				} else if stackFrame != nil {
507					// ppc64le/s390x: stack args are in separate stackFrame
508					if runtime.GOARCH == "s390x" {
509						// s390x big-endian: sub-8-byte values are right-justified
510						args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType)
511					} else {
512						args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
513					}
514					stackSlot += slots
515				} else {
516					args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
517					stackSlot += slots
518				}
519			} else {
520				if runtime.GOARCH == "s390x" {
521					// s390x big-endian: float32 is right-justified in 8-byte FPR slot
522					args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType)
523				} else {
524					args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem()
525				}
526			}
527			floatsN += slots
528		case reflect.String:
529			slots = 1
530			var ptr uintptr
531			if intsN+slots > numOfIntegerRegisters() {
532				if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
533					ptr = uintptr(callbackArgFromStack(a.args, stackSlot, &stackByteOffset, reflect.TypeOf(uintptr(0))).Uint())
534				} else if stackFrame != nil {
535					if runtime.GOARCH == "s390x" {
536						ptr = uintptr(callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), reflect.TypeOf(uintptr(0))).Uint())
537					} else {
538						ptr = *(*uintptr)(unsafe.Pointer(&stackFrame[stackSlot]))
539					}
540					stackSlot += slots
541				} else {
542					ptr = frame[stackSlot]
543					stackSlot += slots
544				}
545			} else {
546				pos := intsN + numOfFloatRegisters()
547				ptr = frame[pos]
548			}
549			intsN += slots
550			args[i] = reflect.ValueOf(internalstrings.GoString(ptr))
551			continue
552		case reflect.Struct:
553			if i == 0 && inType.AssignableTo(reflect.TypeOf(CDecl{})) {
554				args[i] = reflect.Zero(inType)
555				continue
556			}
557			if inType.Size() == 0 {
558				args[i] = reflect.New(inType).Elem()
559				continue
560			}
561			args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset)
562			continue
563		default:
564			slots = int((inType.Size() + ptrSize - 1) / ptrSize)
565			if intsN+slots > numOfIntegerRegisters() {
566				if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
567					// Darwin ARM64: read from packed stack with proper alignment
568					args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)
569				} else if stackFrame != nil {
570					// ppc64le/s390x: stack args are in separate stackFrame
571					if runtime.GOARCH == "s390x" {
572						// s390x big-endian: sub-8-byte values are right-justified
573						args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType)
574					} else {
575						args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
576					}
577					stackSlot += slots
578				} else {
579					args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
580					stackSlot += slots
581				}
582			} else {
583				// the integers begin after the floats in frame
584				pos := intsN + numOfFloatRegisters()
585				if runtime.GOARCH == "s390x" {
586					// s390x big-endian: sub-8-byte values are right-justified in GPR slot
587					args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[pos]), inType)
588				} else {
589					args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem()
590				}
591			}
592			intsN += slots
593		}
594	}
595	ret := fn.Call(args)
596	if len(ret) > 0 {
597		switch k := ret[0].Kind(); k {
598		case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
599			a.result[0] = uintptr(ret[0].Uint())
600		case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
601			a.result[0] = uintptr(ret[0].Int())
602		case reflect.Bool:
603			if ret[0].Bool() {
604				a.result[0] = 1
605			} else {
606				a.result[0] = 0
607			}
608		case reflect.Pointer:
609			a.result[0] = ret[0].Pointer()
610		case reflect.UnsafePointer:
611			a.result[0] = ret[0].Pointer()
612		case reflect.Struct:
613			setStruct(a, ret[0])
614		default:
615			panic("purego: unsupported kind: " + k.String())
616		}
617	}
618}
619
620// callbackArgFromStack reads an argument from the tightly-packed stack area on Darwin ARM64.
621// The C ABI on Darwin ARM64 packs small types on the stack without padding to 8 bytes.
622// This function handles proper alignment and advances stackByteOffset accordingly.
623func callbackArgFromStack(argsBase unsafe.Pointer, stackSlot int, stackByteOffset *uintptr, inType reflect.Type) reflect.Value {
624	// Calculate base address of stack area (after float and int registers)
625	stackBase := unsafe.Add(argsBase, stackSlot*int(ptrSize))
626
627	// Get type's natural alignment
628	align := uintptr(inType.Align())
629	size := inType.Size()
630
631	// Align the offset
632	if *stackByteOffset%align != 0 {
633		*stackByteOffset = (*stackByteOffset + align - 1) &^ (align - 1)
634	}
635
636	// Read value at aligned offset
637	ptr := unsafe.Add(stackBase, *stackByteOffset)
638	*stackByteOffset += size
639
640	return reflect.NewAt(inType, ptr).Elem()
641}
642
643// callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures.
644// On s390x:
645// - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size)
646// - Float32 in FPRs is left-justified: stored in upper 32 bits, so at offset 0
647// - Float64 occupies the full 8-byte slot
648func callbackArgFromSlotBigEndian(slotPtr unsafe.Pointer, inType reflect.Type) reflect.Value {
649	size := inType.Size()
650	if size >= 8 {
651		// 8-byte values occupy the entire slot
652		return reflect.NewAt(inType, slotPtr).Elem()
653	}
654	// Float32 is left-justified in FPRs (upper 32 bits), so offset is 0
655	if inType.Kind() == reflect.Float32 {
656		return reflect.NewAt(inType, slotPtr).Elem()
657	}
658	// Integer types are right-justified: offset = 8 - size
659	offset := 8 - size
660	ptr := unsafe.Add(slotPtr, offset)
661	return reflect.NewAt(inType, ptr).Elem()
662}
663
664// callbackasmAddr returns address of runtime.callbackasm
665// function adjusted by i.
666// On x86 and amd64, runtime.callbackasm is a series of CALL instructions,
667// and we want callback to arrive at
668// correspondent call instruction instead of start of
669// runtime.callbackasm.
670// On ARM, runtime.callbackasm is a series of mov and branch instructions.
671// R12 is loaded with the callback index. Each entry is two instructions,
672// hence 8 bytes.
673func callbackasmAddr(i int) uintptr {
674	var entrySize int
675	switch runtime.GOARCH {
676	default:
677		panic("purego: unsupported architecture")
678	case "amd64":
679		// On amd64, each callback entry is just a CALL instruction (5 bytes)
680		entrySize = 5
681	case "386":
682		// On 386, each callback entry is MOVL $imm, CX (5 bytes) + JMP (5 bytes)
683		entrySize = 10
684	case "arm", "arm64", "loong64", "ppc64le", "riscv64":
685		// On ARM, ARM64, Loong64, PPC64LE and RISCV64, each entry is a MOV instruction
686		// followed by a branch instruction
687		entrySize = 8
688	case "s390x":
689		// On S390X, each entry is LGHI (4 bytes) + JG (6 bytes)
690		entrySize = 10
691	}
692	return callbackasmABI0 + uintptr(i*entrySize)
693}

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