struct_amd64.go raw
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3
4 package purego
5
6 import (
7 "math"
8 "reflect"
9 "unsafe"
10 )
11
12 func 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
64 func 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
80 const (
81 _NO_CLASS = 0b0000
82 _SSE = 0b0001
83 _X87 = 0b0011 // long double not used in Go
84 _INTEGER = 0b0111
85 _MEMORY = 0b1111
86 )
87
88 func 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
114 func 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
126 func 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
241 func 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
261 func 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.
267 func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
268 return false
269 }
270
271 // structFitsInRegisters is not used on amd64.
272 func 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.
277 func 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.
284 func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
285 panic("purego: bundleStackArgs should not be called on amd64")
286 }
287