v0.11.0-bnema.4 cf1b83e

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

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