struct_amd64.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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. if isAllFloats(outType) {
  16. // 2 float32s or 1 float64s are return in the float register
  17. return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem()
  18. }
  19. // up to 8 bytes is returned in RAX
  20. return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
  21. case outSize <= 16:
  22. r1, r2 := syscall.a1, syscall.a2
  23. if isAllFloats(outType) {
  24. r1 = syscall.f1
  25. r2 = syscall.f2
  26. } else {
  27. // check first 8 bytes if it's floats
  28. hasFirstFloat := false
  29. f1 := outType.Field(0).Type
  30. if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 {
  31. r1 = syscall.f1
  32. hasFirstFloat = true
  33. }
  34. // find index of the field that starts the second 8 bytes
  35. var i int
  36. for i = 0; i < outType.NumField(); i++ {
  37. if outType.Field(i).Offset == 8 {
  38. break
  39. }
  40. }
  41. // check last 8 bytes if they are floats
  42. f1 = outType.Field(i).Type
  43. if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() {
  44. r2 = syscall.f1
  45. } else if hasFirstFloat {
  46. // if the first field was a float then that means the second integer field
  47. // comes from the first integer register
  48. r2 = syscall.a1
  49. }
  50. }
  51. return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
  52. default:
  53. // create struct from the Go pointer created above
  54. // weird pointer dereference to circumvent go vet
  55. return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
  56. }
  57. }
  58. func isAllFloats(ty reflect.Type) bool {
  59. for i := 0; i < ty.NumField(); i++ {
  60. f := ty.Field(i)
  61. switch f.Type.Kind() {
  62. case reflect.Float64, reflect.Float32:
  63. default:
  64. return false
  65. }
  66. }
  67. return true
  68. }
  69. // https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
  70. // https://gitlab.com/x86-psABIs/x86-64-ABI
  71. // Class determines where the 8 byte value goes.
  72. // Higher value classes win over lower value classes
  73. const (
  74. _NO_CLASS = 0b0000
  75. _SSE = 0b0001
  76. _X87 = 0b0011 // long double not used in Go
  77. _INTEGER = 0b0111
  78. _MEMORY = 0b1111
  79. )
  80. func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
  81. if v.Type().Size() == 0 {
  82. return keepAlive
  83. }
  84. // if greater than 64 bytes place on stack
  85. if v.Type().Size() > 8*8 {
  86. placeStack(v, addStack)
  87. return keepAlive
  88. }
  89. var (
  90. savedNumFloats = *numFloats
  91. savedNumInts = *numInts
  92. savedNumStack = *numStack
  93. )
  94. placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
  95. if placeOnStack {
  96. // reset any values placed in registers
  97. *numFloats = savedNumFloats
  98. *numInts = savedNumInts
  99. *numStack = savedNumStack
  100. placeStack(v, addStack)
  101. }
  102. return keepAlive
  103. }
  104. func postMerger(t reflect.Type) (passInMemory bool) {
  105. // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
  106. // eightbyte isn’t SSEUP, the whole argument is passed in memory.
  107. if t.Kind() != reflect.Struct {
  108. return false
  109. }
  110. if t.Size() <= 2*8 {
  111. return false
  112. }
  113. return true // Go does not have an SSE/SSEUP type so this is always true
  114. }
  115. func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
  116. ok = true
  117. var val uint64
  118. var shift byte // # of bits to shift
  119. var flushed bool
  120. class := _NO_CLASS
  121. flushIfNeeded := func() {
  122. if flushed {
  123. return
  124. }
  125. flushed = true
  126. if class == _SSE {
  127. addFloat(uintptr(val))
  128. } else {
  129. addInt(uintptr(val))
  130. }
  131. val = 0
  132. shift = 0
  133. class = _NO_CLASS
  134. }
  135. var place func(v reflect.Value)
  136. place = func(v reflect.Value) {
  137. var numFields int
  138. if v.Kind() == reflect.Struct {
  139. numFields = v.Type().NumField()
  140. } else {
  141. numFields = v.Type().Len()
  142. }
  143. for i := 0; i < numFields; i++ {
  144. flushed = false
  145. var f reflect.Value
  146. if v.Kind() == reflect.Struct {
  147. f = v.Field(i)
  148. } else {
  149. f = v.Index(i)
  150. }
  151. switch f.Kind() {
  152. case reflect.Struct:
  153. place(f)
  154. case reflect.Bool:
  155. if f.Bool() {
  156. val |= 1 << shift
  157. }
  158. shift += 8
  159. class |= _INTEGER
  160. case reflect.Pointer, reflect.UnsafePointer:
  161. val = uint64(f.Pointer())
  162. shift = 64
  163. class = _INTEGER
  164. case reflect.Int8:
  165. val |= uint64(f.Int()&0xFF) << shift
  166. shift += 8
  167. class |= _INTEGER
  168. case reflect.Int16:
  169. val |= uint64(f.Int()&0xFFFF) << shift
  170. shift += 16
  171. class |= _INTEGER
  172. case reflect.Int32:
  173. val |= uint64(f.Int()&0xFFFF_FFFF) << shift
  174. shift += 32
  175. class |= _INTEGER
  176. case reflect.Int64, reflect.Int:
  177. val = uint64(f.Int())
  178. shift = 64
  179. class = _INTEGER
  180. case reflect.Uint8:
  181. val |= f.Uint() << shift
  182. shift += 8
  183. class |= _INTEGER
  184. case reflect.Uint16:
  185. val |= f.Uint() << shift
  186. shift += 16
  187. class |= _INTEGER
  188. case reflect.Uint32:
  189. val |= f.Uint() << shift
  190. shift += 32
  191. class |= _INTEGER
  192. case reflect.Uint64, reflect.Uint, reflect.Uintptr:
  193. val = f.Uint()
  194. shift = 64
  195. class = _INTEGER
  196. case reflect.Float32:
  197. val |= uint64(math.Float32bits(float32(f.Float()))) << shift
  198. shift += 32
  199. class |= _SSE
  200. case reflect.Float64:
  201. if v.Type().Size() > 16 {
  202. ok = false
  203. return
  204. }
  205. val = uint64(math.Float64bits(f.Float()))
  206. shift = 64
  207. class = _SSE
  208. case reflect.Array:
  209. place(f)
  210. default:
  211. panic("purego: unsupported kind " + f.Kind().String())
  212. }
  213. if shift == 64 {
  214. flushIfNeeded()
  215. } else if shift > 64 {
  216. // Should never happen, but may if we forget to reset shift after flush (or forget to flush),
  217. // better fall apart here, than corrupt arguments.
  218. panic("purego: tryPlaceRegisters shift > 64")
  219. }
  220. }
  221. }
  222. place(v)
  223. flushIfNeeded()
  224. return ok
  225. }
  226. func placeStack(v reflect.Value, addStack func(uintptr)) {
  227. // Copy the struct as a contiguous block of memory in eightbyte (8-byte)
  228. // chunks. The x86-64 ABI requires structs passed on the stack to be
  229. // laid out exactly as in memory, including padding and field packing
  230. // within eightbytes. Decomposing field-by-field would place each field
  231. // as a separate stack slot, breaking structs with mixed-type fields
  232. // that share an eightbyte (e.g. int32 + float32).
  233. if !v.CanAddr() {
  234. tmp := reflect.New(v.Type()).Elem()
  235. tmp.Set(v)
  236. v = tmp
  237. }
  238. ptr := v.Addr().UnsafePointer()
  239. size := v.Type().Size()
  240. for off := uintptr(0); off < size; off += 8 {
  241. chunk := *(*uintptr)(unsafe.Add(ptr, off))
  242. addStack(chunk)
  243. }
  244. }
  245. func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
  246. panic("purego: placeRegisters not implemented on amd64")
  247. }
  248. // shouldBundleStackArgs always returns false on non-Darwin platforms
  249. // since C-style stack argument bundling is only needed on Darwin ARM64.
  250. func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
  251. return false
  252. }
  253. // structFitsInRegisters is not used on amd64.
  254. func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
  255. panic("purego: structFitsInRegisters should not be called on amd64")
  256. }
  257. // collectStackArgs is not used on amd64.
  258. func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
  259. keepAlive []any, addInt, addFloat, addStack func(uintptr),
  260. pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
  261. panic("purego: collectStackArgs should not be called on amd64")
  262. }
  263. // bundleStackArgs is not used on amd64.
  264. func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
  265. panic("purego: bundleStackArgs should not be called on amd64")
  266. }