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:
173 ok = false
174 return
175 case reflect.Int8:
176 val |= uint64(f.Int()&0xFF) << shift
177 shift += 8
178 class |= _INTEGER
179 case reflect.Int16:
180 val |= uint64(f.Int()&0xFFFF) << shift
181 shift += 16
182 class |= _INTEGER
183 case reflect.Int32:
184 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
185 shift += 32
186 class |= _INTEGER
187 case reflect.Int64, reflect.Int:
188 val = uint64(f.Int())
189 shift = 64
190 class = _INTEGER
191 case reflect.Uint8:
192 val |= f.Uint() << shift
193 shift += 8
194 class |= _INTEGER
195 case reflect.Uint16:
196 val |= f.Uint() << shift
197 shift += 16
198 class |= _INTEGER
199 case reflect.Uint32:
200 val |= f.Uint() << shift
201 shift += 32
202 class |= _INTEGER
203 case reflect.Uint64, reflect.Uint, reflect.Uintptr:
204 val = f.Uint()
205 shift = 64
206 class = _INTEGER
207 case reflect.Float32:
208 val |= uint64(math.Float32bits(float32(f.Float()))) << shift
209 shift += 32
210 class |= _SSE
211 case reflect.Float64:
212 if v.Type().Size() > 16 {
213 ok = false
214 return
215 }
216 val = uint64(math.Float64bits(f.Float()))
217 shift = 64
218 class = _SSE
219 case reflect.Array:
220 place(f)
221 default:
222 panic("purego: unsupported kind " + f.Kind().String())
223 }
224
225 if shift == 64 {
226 flushIfNeeded()
227 } else if shift > 64 {
228 // Should never happen, but may if we forget to reset shift after flush (or forget to flush),
229 // better fall apart here, than corrupt arguments.
230 panic("purego: tryPlaceRegisters shift > 64")
231 }
232 }
233 }
234
235 place(v)
236 flushIfNeeded()
237 return ok
238 }
239
240 func placeStack(v reflect.Value, addStack func(uintptr)) {
241 for i := 0; i < v.Type().NumField(); i++ {
242 f := v.Field(i)
243 switch f.Kind() {
244 case reflect.Pointer:
245 addStack(f.Pointer())
246 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
247 addStack(uintptr(f.Int()))
248 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
249 addStack(uintptr(f.Uint()))
250 case reflect.Float32:
251 addStack(uintptr(math.Float32bits(float32(f.Float()))))
252 case reflect.Float64:
253 addStack(uintptr(math.Float64bits(f.Float())))
254 case reflect.Struct:
255 placeStack(f, addStack)
256 default:
257 panic("purego: unsupported kind " + f.Kind().String())
258 }
259 }
260 }
261
262 func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
263 panic("purego: not needed on amd64")
264 }
265