v0.11.0-alpha.5 5faf54b

struct_amd64.go RawHistoryFolder

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3
4package purego
5
6import (
7	"math"
8	"reflect"
9	"runtime"
10	"unsafe"
11)
12
13func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) {
14	outSize := outType.Size()
15	switch {
16	case outSize == 0:
17		return reflect.New(outType).Elem()
18	case outSize <= 8:
19		if isAllFloats(outType) {
20			// 2 float32s or 1 float64s are return in the float register
21			return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem()
22		}
23		// up to 8 bytes is returned in RAX
24		return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
25	case outSize <= 16:
26		r1, r2 := syscall.a1, syscall.a2
27		if isAllFloats(outType) {
28			r1 = syscall.f1
29			r2 = syscall.f2
30		} else {
31			// check first 8 bytes if it's floats
32			hasFirstFloat := false
33			f1 := outType.Field(0).Type
34			if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 {
35				r1 = syscall.f1
36				hasFirstFloat = true
37			}
38
39			// find index of the field that starts the second 8 bytes
40			var i int
41			for i = 0; i < outType.NumField(); i++ {
42				if outType.Field(i).Offset == 8 {
43					break
44				}
45			}
46
47			// check last 8 bytes if they are floats
48			f1 = outType.Field(i).Type
49			if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() {
50				r2 = syscall.f1
51			} else if hasFirstFloat {
52				// if the first field was a float then that means the second integer field
53				// comes from the first integer register
54				r2 = syscall.a1
55			}
56		}
57		return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
58	default:
59		// create struct from the Go pointer created above
60		// weird pointer dereference to circumvent go vet
61		return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
62	}
63}
64
65func isAllFloats(ty reflect.Type) bool {
66	for i := 0; i < ty.NumField(); i++ {
67		f := ty.Field(i)
68		switch f.Type.Kind() {
69		case reflect.Float64, reflect.Float32:
70		default:
71			return false
72		}
73	}
74	return true
75}
76
77// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
78// https://gitlab.com/x86-psABIs/x86-64-ABI
79// Class determines where the 8 byte value goes.
80// Higher value classes win over lower value classes
81const (
82	_NO_CLASS = 0b0000
83	_SSE      = 0b0001
84	_X87      = 0b0011 // long double not used in Go
85	_INTEGER  = 0b0111
86	_MEMORY   = 0b1111
87)
88
89func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
90	if v.Type().Size() == 0 {
91		return keepAlive
92	}
93
94	// if greater than 64 bytes place on stack
95	if v.Type().Size() > 8*8 {
96		placeStack(v, addStack)
97		return keepAlive
98	}
99	var (
100		savedNumFloats = *numFloats
101		savedNumInts   = *numInts
102		savedNumStack  = *numStack
103	)
104	placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
105	if placeOnStack {
106		// reset any values placed in registers
107		*numFloats = savedNumFloats
108		*numInts = savedNumInts
109		*numStack = savedNumStack
110		placeStack(v, addStack)
111	}
112	return keepAlive
113}
114
115func postMerger(t reflect.Type) (passInMemory bool) {
116	// (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
117	// eightbyte isn’t SSEUP, the whole argument is passed in memory.
118	if t.Kind() != reflect.Struct {
119		return false
120	}
121	if t.Size() <= 2*8 {
122		return false
123	}
124	return true // Go does not have an SSE/SSEUP type so this is always true
125}
126
127func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
128	ok = true
129	var val uint64
130	var shift byte // # of bits to shift
131	var flushed bool
132	class := _NO_CLASS
133	flushIfNeeded := func() {
134		if flushed {
135			return
136		}
137		flushed = true
138		if class == _SSE {
139			addFloat(uintptr(val))
140		} else {
141			addInt(uintptr(val))
142		}
143		val = 0
144		shift = 0
145		class = _NO_CLASS
146	}
147	var place func(v reflect.Value)
148	place = func(v reflect.Value) {
149		var numFields int
150		if v.Kind() == reflect.Struct {
151			numFields = v.Type().NumField()
152		} else {
153			numFields = v.Type().Len()
154		}
155
156		for i := 0; i < numFields; i++ {
157			flushed = false
158			var f reflect.Value
159			if v.Kind() == reflect.Struct {
160				f = v.Field(i)
161			} else {
162				f = v.Index(i)
163			}
164			switch f.Kind() {
165			case reflect.Struct:
166				place(f)
167			case reflect.Bool:
168				if f.Bool() {
169					val |= 1 << shift
170				}
171				shift += 8
172				class |= _INTEGER
173			case reflect.Pointer, reflect.UnsafePointer:
174				val = uint64(f.Pointer())
175				shift = 64
176				class = _INTEGER
177			case reflect.Int8:
178				val |= uint64(f.Int()&0xFF) << shift
179				shift += 8
180				class |= _INTEGER
181			case reflect.Int16:
182				val |= uint64(f.Int()&0xFFFF) << shift
183				shift += 16
184				class |= _INTEGER
185			case reflect.Int32:
186				val |= uint64(f.Int()&0xFFFF_FFFF) << shift
187				shift += 32
188				class |= _INTEGER
189			case reflect.Int64, reflect.Int:
190				val = uint64(f.Int())
191				shift = 64
192				class = _INTEGER
193			case reflect.Uint8:
194				val |= f.Uint() << shift
195				shift += 8
196				class |= _INTEGER
197			case reflect.Uint16:
198				val |= f.Uint() << shift
199				shift += 16
200				class |= _INTEGER
201			case reflect.Uint32:
202				val |= f.Uint() << shift
203				shift += 32
204				class |= _INTEGER
205			case reflect.Uint64, reflect.Uint, reflect.Uintptr:
206				val = f.Uint()
207				shift = 64
208				class = _INTEGER
209			case reflect.Float32:
210				val |= uint64(math.Float32bits(float32(f.Float()))) << shift
211				shift += 32
212				class |= _SSE
213			case reflect.Float64:
214				if v.Type().Size() > 16 {
215					ok = false
216					return
217				}
218				val = uint64(math.Float64bits(f.Float()))
219				shift = 64
220				class = _SSE
221			case reflect.Array:
222				place(f)
223			default:
224				panic("purego: unsupported kind " + f.Kind().String())
225			}
226
227			if shift == 64 {
228				flushIfNeeded()
229			} else if shift > 64 {
230				// Should never happen, but may if we forget to reset shift after flush (or forget to flush),
231				// better fall apart here, than corrupt arguments.
232				panic("purego: tryPlaceRegisters shift > 64")
233			}
234		}
235	}
236
237	place(v)
238	flushIfNeeded()
239	return ok
240}
241
242func placeStack(v reflect.Value, addStack func(uintptr)) {
243	// Copy the struct as a contiguous block of memory in eightbyte (8-byte)
244	// chunks. The x86-64 ABI requires structs passed on the stack to be
245	// laid out exactly as in memory, including padding and field packing
246	// within eightbytes. Decomposing field-by-field would place each field
247	// as a separate stack slot, breaking structs with mixed-type fields
248	// that share an eightbyte (e.g. int32 + float32).
249	if !v.CanAddr() {
250		tmp := reflect.New(v.Type()).Elem()
251		tmp.Set(v)
252		v = tmp
253	}
254	ptr := v.Addr().UnsafePointer()
255	size := v.Type().Size()
256	for off := uintptr(0); off < size; off += 8 {
257		chunk := *(*uintptr)(unsafe.Add(ptr, off))
258		addStack(chunk)
259	}
260}
261
262func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
263	panic("purego: placeRegisters not implemented on amd64")
264}
265
266// shouldBundleStackArgs always returns false on non-Darwin platforms
267// since C-style stack argument bundling is only needed on Darwin ARM64.
268func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
269	return false
270}
271
272// structFitsInRegisters is not used on amd64.
273func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
274	panic("purego: structFitsInRegisters should not be called on amd64")
275}
276
277// collectStackArgs is not used on amd64.
278func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
279	keepAlive []any, addInt, addFloat, addStack func(uintptr),
280	pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
281	panic("purego: collectStackArgs should not be called on amd64")
282}
283
284// bundleStackArgs is not used on amd64.
285func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
286	panic("purego: bundleStackArgs should not be called on amd64")
287}
288
289// getCallbackStruct reads a struct argument from the callback frame on amd64.
290// It mirrors the SysV AMD64 ABI rules used by addStruct for the Go→C path.
291//
292// getCallbackStruct is only used on Unix. On Windows, callbacks are handled by
293// the runtime's own callback mechanism, so this function is compiled but unused.
294//
295// SysV AMD64 struct argument passing rules:
296//   - Struct > 16 bytes (postMerger): passed on the stack as raw bytes
297//   - Struct ≤ 16 bytes: classify each eightbyte (INTEGER or SSE),
298//     read from the appropriate register class
299//   - If not enough registers for all eightbytes: entire struct goes on the stack
300func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
301	switch runtime.GOOS {
302	case "darwin", "freebsd", "linux", "netbsd":
303	default:
304		panic("purego: getCallbackStruct is not supported on " + runtime.GOOS)
305	}
306
307	f := (*[callbackMaxFrame]uintptr)(frame)
308	size := inType.Size()
309
310	// Structs > 16 bytes are passed on the stack as raw bytes (SysV ABI MEMORY class).
311	if postMerger(inType) {
312		numSlots := int((size + 7) / 8)
313		v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem()
314		*stackSlot += numSlots
315		return v
316	}
317
318	// Struct ≤ 16 bytes: classify each eightbyte and read from the appropriate register.
319	numEightbytes := int((size + 7) / 8)
320
321	// Count how many integer and SSE registers this struct needs.
322	var needInts, needFloats int
323	for i := 0; i < numEightbytes; i++ {
324		class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
325		if class == _SSE {
326			needFloats++
327		} else {
328			needInts++
329		}
330	}
331
332	// If not enough registers for all eightbytes, the entire struct goes on the stack.
333	if *intsN+needInts > numOfIntegerRegisters() || *floatsN+needFloats > numOfFloatRegisters() {
334		v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem()
335		*stackSlot += numEightbytes
336		return v
337	}
338
339	// Read each eightbyte from its appropriate register class.
340	var r1, r2 uintptr
341	for i := 0; i < numEightbytes; i++ {
342		class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
343		if class == _SSE {
344			if i == 0 {
345				r1 = f[*floatsN]
346			} else {
347				r2 = f[*floatsN]
348			}
349			*floatsN++
350		} else {
351			if i == 0 {
352				r1 = f[numOfFloatRegisters()+*intsN]
353			} else {
354				r2 = f[numOfFloatRegisters()+*intsN]
355			}
356			*intsN++
357		}
358	}
359
360	if numEightbytes == 1 {
361		return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
362	}
363	return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
364}
365
366func setStruct(a *callbackArgs, ret reflect.Value) {
367	outSize := ret.Type().Size()
368	switch {
369	case outSize == 0:
370		return
371	case outSize <= 16:
372		// Copy the struct's raw bytes (including padding) into a buffer.
373		var buf [2]uintptr
374		reflect.NewAt(ret.Type(), unsafe.Pointer(&buf[0])).Elem().Set(ret)
375		// Classify each eightbyte by the SysV ABI rules (§3.2.3, rule 4d
376		// of https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf).
377		// INTEGER wins over SSE for mixed eightbytes. Place INTEGER eightbytes in
378		// result[0]/result[1] (AX/DX) and SSE eightbytes in result[2]/result[3]
379		// (XMM0/XMM1).
380		// Assign each eightbyte to the next available register of the
381		// appropriate class. The ABI counts integer (AX, DX) and SSE
382		// (XMM0, XMM1) return registers independently.
383		var numInts int
384		var numFloats int
385		for i := 0; i < 2 && uintptr(i)*8 < outSize; i++ {
386			class := classifyEightbyte(ret.Type(), uintptr(i)*8, uintptr(i)*8+8)
387			if class == _SSE {
388				switch numFloats {
389				case 0:
390					a.result[2] = buf[i]
391				case 1:
392					a.result[3] = buf[i]
393				}
394				numFloats++
395			} else {
396				switch numInts {
397				case 0:
398					a.result[0] = buf[i]
399				case 1:
400					a.result[1] = buf[i]
401				}
402				numInts++
403			}
404		}
405	default:
406		// Structs > 16 bytes are returned by hidden pointer.
407		// a.result[0] contains the pointer passed by the caller in RDI.
408		// Write the struct through this pointer.
409		reflect.NewAt(ret.Type(), *(*unsafe.Pointer)(unsafe.Pointer(&a.result[0]))).Elem().Set(ret)
410	}
411}
412
413// classifyEightbyte returns the SysV ABI class for the byte range [start, end)
414// within a type, by examining all scalar fields that overlap that range.
415func classifyEightbyte(t reflect.Type, start, end uintptr) int {
416	return doClassifyEightbyte(t, 0, start, end)
417}
418
419func doClassifyEightbyte(t reflect.Type, base, start, end uintptr) int {
420	switch t.Kind() {
421	case reflect.Struct:
422		class := _NO_CLASS
423		for i := 0; i < t.NumField(); i++ {
424			f := t.Field(i)
425			class |= doClassifyEightbyte(f.Type, base+f.Offset, start, end)
426		}
427		return class
428	case reflect.Array:
429		class := _NO_CLASS
430		elemSize := t.Elem().Size()
431		for i := 0; i < t.Len(); i++ {
432			class |= doClassifyEightbyte(t.Elem(), base+uintptr(i)*elemSize, start, end)
433		}
434		return class
435	default:
436		fStart := base
437		fEnd := base + t.Size()
438		if fStart >= end || fEnd <= start {
439			return _NO_CLASS
440		}
441		switch t.Kind() {
442		case reflect.Float32, reflect.Float64:
443			return _SSE
444		default:
445			return _INTEGER
446		}
447	}
448}

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