v0.10.0-alpha.5 585a4c5

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	"unsafe"
10)
11
12func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
13	outSize := outType.Size()
14	switch {
15	case outSize == 0:
16		return reflect.New(outType).Elem()
17	case outSize <= 8:
18		if isAllFloats(outType) {
19			// 2 float32s or 1 float64s are return in the float register
20			return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem()
21		}
22		// up to 8 bytes is returned in RAX
23		return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
24	case outSize <= 16:
25		r1, r2 := syscall.a1, syscall.a2
26		if isAllFloats(outType) {
27			r1 = syscall.f1
28			r2 = syscall.f2
29		} else {
30			// check first 8 bytes if it's floats
31			hasFirstFloat := false
32			f1 := outType.Field(0).Type
33			if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 {
34				r1 = syscall.f1
35				hasFirstFloat = true
36			}
37
38			// find index of the field that starts the second 8 bytes
39			var i int
40			for i = 0; i < outType.NumField(); i++ {
41				if outType.Field(i).Offset == 8 {
42					break
43				}
44			}
45
46			// check last 8 bytes if they are floats
47			f1 = outType.Field(i).Type
48			if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() {
49				r2 = syscall.f1
50			} else if hasFirstFloat {
51				// if the first field was a float then that means the second integer field
52				// comes from the first integer register
53				r2 = syscall.a1
54			}
55		}
56		return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
57	default:
58		// create struct from the Go pointer created above
59		// weird pointer dereference to circumvent go vet
60		return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
61	}
62}
63
64func isAllFloats(ty reflect.Type) bool {
65	for i := 0; i < ty.NumField(); i++ {
66		f := ty.Field(i)
67		switch f.Type.Kind() {
68		case reflect.Float64, reflect.Float32:
69		default:
70			return false
71		}
72	}
73	return true
74}
75
76// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
77// https://gitlab.com/x86-psABIs/x86-64-ABI
78// Class determines where the 8 byte value goes.
79// Higher value classes win over lower value classes
80const (
81	_NO_CLASS = 0b0000
82	_SSE      = 0b0001
83	_X87      = 0b0011 // long double not used in Go
84	_INTEGER  = 0b0111
85	_MEMORY   = 0b1111
86)
87
88func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
89	if v.Type().Size() == 0 {
90		return keepAlive
91	}
92
93	// if greater than 64 bytes place on stack
94	if v.Type().Size() > 8*8 {
95		placeStack(v, addStack)
96		return keepAlive
97	}
98	var (
99		savedNumFloats = *numFloats
100		savedNumInts   = *numInts
101		savedNumStack  = *numStack
102	)
103	placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
104	if placeOnStack {
105		// reset any values placed in registers
106		*numFloats = savedNumFloats
107		*numInts = savedNumInts
108		*numStack = savedNumStack
109		placeStack(v, addStack)
110	}
111	return keepAlive
112}
113
114func postMerger(t reflect.Type) (passInMemory bool) {
115	// (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
116	// eightbyte isn’t SSEUP, the whole argument is passed in memory.
117	if t.Kind() != reflect.Struct {
118		return false
119	}
120	if t.Size() <= 2*8 {
121		return false
122	}
123	return true // Go does not have an SSE/SSEUP type so this is always true
124}
125
126func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
127	ok = true
128	var val uint64
129	var shift byte // # of bits to shift
130	var flushed bool
131	class := _NO_CLASS
132	flushIfNeeded := func() {
133		if flushed {
134			return
135		}
136		flushed = true
137		if class == _SSE {
138			addFloat(uintptr(val))
139		} else {
140			addInt(uintptr(val))
141		}
142		val = 0
143		shift = 0
144		class = _NO_CLASS
145	}
146	var place func(v reflect.Value)
147	place = func(v reflect.Value) {
148		var numFields int
149		if v.Kind() == reflect.Struct {
150			numFields = v.Type().NumField()
151		} else {
152			numFields = v.Type().Len()
153		}
154
155		for i := 0; i < numFields; i++ {
156			flushed = false
157			var f reflect.Value
158			if v.Kind() == reflect.Struct {
159				f = v.Field(i)
160			} else {
161				f = v.Index(i)
162			}
163			switch f.Kind() {
164			case reflect.Struct:
165				place(f)
166			case reflect.Bool:
167				if f.Bool() {
168					val |= 1 << shift
169				}
170				shift += 8
171				class |= _INTEGER
172			case reflect.Pointer, reflect.UnsafePointer:
173				val = uint64(f.Pointer())
174				shift = 64
175				class = _INTEGER
176			case reflect.Int8:
177				val |= uint64(f.Int()&0xFF) << shift
178				shift += 8
179				class |= _INTEGER
180			case reflect.Int16:
181				val |= uint64(f.Int()&0xFFFF) << shift
182				shift += 16
183				class |= _INTEGER
184			case reflect.Int32:
185				val |= uint64(f.Int()&0xFFFF_FFFF) << shift
186				shift += 32
187				class |= _INTEGER
188			case reflect.Int64, reflect.Int:
189				val = uint64(f.Int())
190				shift = 64
191				class = _INTEGER
192			case reflect.Uint8:
193				val |= f.Uint() << shift
194				shift += 8
195				class |= _INTEGER
196			case reflect.Uint16:
197				val |= f.Uint() << shift
198				shift += 16
199				class |= _INTEGER
200			case reflect.Uint32:
201				val |= f.Uint() << shift
202				shift += 32
203				class |= _INTEGER
204			case reflect.Uint64, reflect.Uint, reflect.Uintptr:
205				val = f.Uint()
206				shift = 64
207				class = _INTEGER
208			case reflect.Float32:
209				val |= uint64(math.Float32bits(float32(f.Float()))) << shift
210				shift += 32
211				class |= _SSE
212			case reflect.Float64:
213				if v.Type().Size() > 16 {
214					ok = false
215					return
216				}
217				val = uint64(math.Float64bits(f.Float()))
218				shift = 64
219				class = _SSE
220			case reflect.Array:
221				place(f)
222			default:
223				panic("purego: unsupported kind " + f.Kind().String())
224			}
225
226			if shift == 64 {
227				flushIfNeeded()
228			} else if shift > 64 {
229				// Should never happen, but may if we forget to reset shift after flush (or forget to flush),
230				// better fall apart here, than corrupt arguments.
231				panic("purego: tryPlaceRegisters shift > 64")
232			}
233		}
234	}
235
236	place(v)
237	flushIfNeeded()
238	return ok
239}
240
241func placeStack(v reflect.Value, addStack func(uintptr)) {
242	// Copy the struct as a contiguous block of memory in eightbyte (8-byte)
243	// chunks. The x86-64 ABI requires structs passed on the stack to be
244	// laid out exactly as in memory, including padding and field packing
245	// within eightbytes. Decomposing field-by-field would place each field
246	// as a separate stack slot, breaking structs with mixed-type fields
247	// that share an eightbyte (e.g. int32 + float32).
248	if !v.CanAddr() {
249		tmp := reflect.New(v.Type()).Elem()
250		tmp.Set(v)
251		v = tmp
252	}
253	ptr := v.Addr().UnsafePointer()
254	size := v.Type().Size()
255	for off := uintptr(0); off < size; off += 8 {
256		chunk := *(*uintptr)(unsafe.Add(ptr, off))
257		addStack(chunk)
258	}
259}
260
261func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
262	panic("purego: placeRegisters not implemented on amd64")
263}
264
265// shouldBundleStackArgs always returns false on non-Darwin platforms
266// since C-style stack argument bundling is only needed on Darwin ARM64.
267func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
268	return false
269}
270
271// structFitsInRegisters is not used on amd64.
272func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
273	panic("purego: structFitsInRegisters should not be called on amd64")
274}
275
276// collectStackArgs is not used on amd64.
277func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
278	keepAlive []any, addInt, addFloat, addStack func(uintptr),
279	pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
280	panic("purego: collectStackArgs should not be called on amd64")
281}
282
283// bundleStackArgs is not used on amd64.
284func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
285	panic("purego: bundleStackArgs should not be called on amd64")
286}

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