struct_arm64.go 7.4 KB

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