github.com/ebitengine/purego@v0.8.0-alpha.2.0.20240512170805-6cd12240d332/struct_amd64.go (about) 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 []interface{}) []interface{} { 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) 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 first := getFirst(t).Kind() 124 if first != reflect.Float32 && first != reflect.Float64 { 125 return false 126 } 127 return true 128 } 129 130 func getFirst(t reflect.Type) reflect.Type { 131 first := t.Field(0).Type 132 if first.Kind() == reflect.Struct { 133 return getFirst(first) 134 } 135 return first 136 } 137 138 func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) { 139 ok = true 140 var val uint64 141 var shift byte // # of bits to shift 142 var flushed bool 143 class := _NO_CLASS 144 flushIfNeeded := func() { 145 if flushed { 146 return 147 } 148 flushed = true 149 if class == _SSE { 150 addFloat(uintptr(val)) 151 } else { 152 addInt(uintptr(val)) 153 } 154 val = 0 155 shift = 0 156 class = _NO_CLASS 157 } 158 var place func(v reflect.Value) 159 place = func(v reflect.Value) { 160 var numFields int 161 if v.Kind() == reflect.Struct { 162 numFields = v.Type().NumField() 163 } else { 164 numFields = v.Type().Len() 165 } 166 167 for i := 0; i < numFields; i++ { 168 flushed = false 169 var f reflect.Value 170 if v.Kind() == reflect.Struct { 171 f = v.Field(i) 172 } else { 173 f = v.Index(i) 174 } 175 switch f.Kind() { 176 case reflect.Struct: 177 place(f) 178 case reflect.Bool: 179 if f.Bool() { 180 val |= 1 181 } 182 shift += 8 183 class |= _INTEGER 184 case reflect.Pointer: 185 ok = false 186 return 187 case reflect.Int8: 188 val |= uint64(f.Int()&0xFF) << shift 189 shift += 8 190 class |= _INTEGER 191 case reflect.Int16: 192 val |= uint64(f.Int()&0xFFFF) << shift 193 shift += 16 194 class |= _INTEGER 195 case reflect.Int32: 196 val |= uint64(f.Int()&0xFFFF_FFFF) << shift 197 shift += 32 198 class |= _INTEGER 199 case reflect.Int64: 200 val = uint64(f.Int()) 201 shift = 64 202 class = _INTEGER 203 case reflect.Uint8: 204 val |= f.Uint() << shift 205 shift += 8 206 class |= _INTEGER 207 case reflect.Uint16: 208 val |= f.Uint() << shift 209 shift += 16 210 class |= _INTEGER 211 case reflect.Uint32: 212 val |= f.Uint() << shift 213 shift += 32 214 class |= _INTEGER 215 case reflect.Uint64: 216 val = f.Uint() 217 shift = 64 218 class = _INTEGER 219 case reflect.Float32: 220 val |= uint64(math.Float32bits(float32(f.Float()))) << shift 221 shift += 32 222 class |= _SSE 223 case reflect.Float64: 224 if v.Type().Size() > 16 { 225 ok = false 226 return 227 } 228 val = uint64(math.Float64bits(f.Float())) 229 shift = 64 230 class = _SSE 231 case reflect.Array: 232 place(f) 233 default: 234 panic("purego: unsupported kind " + f.Kind().String()) 235 } 236 237 if shift == 64 { 238 flushIfNeeded() 239 } else if shift > 64 { 240 // Should never happen, but may if we forget to reset shift after flush (or forget to flush), 241 // better fall apart here, than corrupt arguments. 242 panic("purego: tryPlaceRegisters shift > 64") 243 } 244 } 245 } 246 247 place(v) 248 flushIfNeeded() 249 return ok 250 } 251 252 func placeStack(v reflect.Value, addStack func(uintptr)) { 253 for i := 0; i < v.Type().NumField(); i++ { 254 f := v.Field(i) 255 switch f.Kind() { 256 case reflect.Pointer: 257 addStack(f.Pointer()) 258 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 259 addStack(uintptr(f.Int())) 260 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 261 addStack(uintptr(f.Uint())) 262 case reflect.Float32: 263 addStack(uintptr(math.Float32bits(float32(f.Float())))) 264 case reflect.Float64: 265 addStack(uintptr(math.Float64bits(f.Float()))) 266 case reflect.Struct: 267 placeStack(f, addStack) 268 default: 269 panic("purego: unsupported kind " + f.Kind().String()) 270 } 271 } 272 }