github.com/ebitengine/purego@v0.8.0-alpha.2.0.20240512170805-6cd12240d332/struct_arm64.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 r1 := syscall.a1 19 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { 20 r1 = syscall.f1 21 if numFields == 2 { 22 r1 = syscall.f2<<32 | syscall.f1 23 } 24 } 25 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() 26 case outSize <= 16: 27 r1, r2 := syscall.a1, syscall.a2 28 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { 29 switch numFields { 30 case 4: 31 r1 = syscall.f2<<32 | syscall.f1 32 r2 = syscall.f4<<32 | syscall.f3 33 case 3: 34 r1 = syscall.f2<<32 | syscall.f1 35 r2 = syscall.f3 36 case 2: 37 r1 = syscall.f1 38 r2 = syscall.f2 39 default: 40 panic("unreachable") 41 } 42 } 43 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() 44 default: 45 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 { 46 switch numFields { 47 case 4: 48 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem() 49 case 3: 50 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem() 51 default: 52 panic("unreachable") 53 } 54 } 55 // create struct from the Go pointer created in arm64_r8 56 // weird pointer dereference to circumvent go vet 57 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem() 58 } 59 } 60 61 // https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst 62 const ( 63 _NO_CLASS = 0b00 64 _FLOAT = 0b01 65 _INT = 0b11 66 ) 67 68 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { 69 if v.Type().Size() == 0 { 70 return keepAlive 71 } 72 73 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 { 74 // if this doesn't fit entirely in registers then 75 // each element goes onto the stack 76 if hfa && *numFloats+v.NumField() > numOfFloats { 77 *numFloats = numOfFloats 78 } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() { 79 *numInts = numOfIntegerRegisters() 80 } 81 82 placeRegisters(v, addFloat, addInt) 83 } else { 84 keepAlive = placeStack(v, keepAlive, addInt) 85 } 86 return keepAlive // the struct was allocated so don't panic 87 } 88 89 func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { 90 var val uint64 91 var shift byte 92 var flushed bool 93 class := _NO_CLASS 94 var place func(v reflect.Value) 95 place = func(v reflect.Value) { 96 var numFields int 97 if v.Kind() == reflect.Struct { 98 numFields = v.Type().NumField() 99 } else { 100 numFields = v.Type().Len() 101 } 102 for k := 0; k < numFields; k++ { 103 flushed = false 104 var f reflect.Value 105 if v.Kind() == reflect.Struct { 106 f = v.Field(k) 107 } else { 108 f = v.Index(k) 109 } 110 if shift >= 64 { 111 shift = 0 112 flushed = true 113 if class == _FLOAT { 114 addFloat(uintptr(val)) 115 } else { 116 addInt(uintptr(val)) 117 } 118 } 119 switch f.Type().Kind() { 120 case reflect.Struct: 121 place(f) 122 case reflect.Bool: 123 if f.Bool() { 124 val |= 1 125 } 126 shift += 8 127 class |= _INT 128 case reflect.Uint8: 129 val |= f.Uint() << shift 130 shift += 8 131 class |= _INT 132 case reflect.Uint16: 133 val |= f.Uint() << shift 134 shift += 16 135 class |= _INT 136 case reflect.Uint32: 137 val |= f.Uint() << shift 138 shift += 32 139 class |= _INT 140 case reflect.Uint64: 141 addInt(uintptr(f.Uint())) 142 shift = 0 143 flushed = true 144 case reflect.Int8: 145 val |= uint64(f.Int()&0xFF) << shift 146 shift += 8 147 class |= _INT 148 case reflect.Int16: 149 val |= uint64(f.Int()&0xFFFF) << shift 150 shift += 16 151 class |= _INT 152 case reflect.Int32: 153 val |= uint64(f.Int()&0xFFFF_FFFF) << shift 154 shift += 32 155 class |= _INT 156 case reflect.Int64: 157 addInt(uintptr(f.Int())) 158 shift = 0 159 flushed = true 160 case reflect.Float32: 161 if class == _FLOAT { 162 addFloat(uintptr(val)) 163 val = 0 164 shift = 0 165 } 166 val |= uint64(math.Float32bits(float32(f.Float()))) << shift 167 shift += 32 168 class |= _FLOAT 169 case reflect.Float64: 170 addFloat(uintptr(math.Float64bits(float64(f.Float())))) 171 shift = 0 172 flushed = true 173 case reflect.Array: 174 place(f) 175 default: 176 panic("purego: unsupported kind " + f.Kind().String()) 177 } 178 } 179 } 180 place(v) 181 if !flushed { 182 if class == _FLOAT { 183 addFloat(uintptr(val)) 184 } else { 185 addInt(uintptr(val)) 186 } 187 } 188 } 189 190 func placeStack(v reflect.Value, keepAlive []interface{}, addInt func(uintptr)) []interface{} { 191 // Struct is too big to be placed in registers. 192 // Copy to heap and place the pointer in register 193 ptrStruct := reflect.New(v.Type()) 194 ptrStruct.Elem().Set(v) 195 ptr := ptrStruct.Elem().Addr().UnsafePointer() 196 keepAlive = append(keepAlive, ptr) 197 addInt(uintptr(ptr)) 198 return keepAlive 199 } 200 201 // isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a 202 // Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]). 203 // This type of struct will be placed more compactly than the individual fields. 204 // 205 // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst 206 func isHFA(t reflect.Type) bool { 207 // round up struct size to nearest 8 see section B.4 208 structSize := roundUpTo8(t.Size()) 209 if structSize == 0 || t.NumField() > 4 { 210 return false 211 } 212 first := t.Field(0) 213 switch first.Type.Kind() { 214 case reflect.Float32, reflect.Float64: 215 firstKind := first.Type.Kind() 216 for i := 0; i < t.NumField(); i++ { 217 if t.Field(i).Type.Kind() != firstKind { 218 return false 219 } 220 } 221 return true 222 case reflect.Array: 223 switch first.Type.Elem().Kind() { 224 case reflect.Float32, reflect.Float64: 225 return true 226 default: 227 return false 228 } 229 case reflect.Struct: 230 for i := 0; i < first.Type.NumField(); i++ { 231 if !isHFA(first.Type) { 232 return false 233 } 234 } 235 return true 236 default: 237 return false 238 } 239 } 240 241 // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type 242 // and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]). 243 // A short vector is a machine type that is composed of repeated instances of one fundamental integral or 244 // floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]). 245 // This type of struct will be placed more compactly than the individual fields. 246 // 247 // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst 248 func isHVA(t reflect.Type) bool { 249 // round up struct size to nearest 8 see section B.4 250 structSize := roundUpTo8(t.Size()) 251 if structSize == 0 || (structSize != 8 && structSize != 16) { 252 return false 253 } 254 first := t.Field(0) 255 switch first.Type.Kind() { 256 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: 257 firstKind := first.Type.Kind() 258 for i := 0; i < t.NumField(); i++ { 259 if t.Field(i).Type.Kind() != firstKind { 260 return false 261 } 262 } 263 return true 264 case reflect.Array: 265 switch first.Type.Elem().Kind() { 266 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: 267 return true 268 default: 269 return false 270 } 271 default: 272 return false 273 } 274 }