struct_arm64.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. // SPDX-License-Identifier: Apache-2.0
  2. // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
  3. package purego
  4. import (
  5. "math"
  6. "reflect"
  7. "runtime"
  8. "strconv"
  9. stdstrings "strings"
  10. "unsafe"
  11. "github.com/ebitengine/purego/internal/strings"
  12. )
  13. func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
  14. outSize := outType.Size()
  15. switch {
  16. case outSize == 0:
  17. return reflect.New(outType).Elem()
  18. case outSize <= 8:
  19. r1 := syscall.a1
  20. if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
  21. r1 = syscall.f1
  22. if numFields == 2 {
  23. r1 = syscall.f2<<32 | syscall.f1
  24. }
  25. }
  26. return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
  27. case outSize <= 16:
  28. r1, r2 := syscall.a1, syscall.a2
  29. if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
  30. switch numFields {
  31. case 4:
  32. r1 = syscall.f2<<32 | syscall.f1
  33. r2 = syscall.f4<<32 | syscall.f3
  34. case 3:
  35. r1 = syscall.f2<<32 | syscall.f1
  36. r2 = syscall.f3
  37. case 2:
  38. r1 = syscall.f1
  39. r2 = syscall.f2
  40. default:
  41. panic("unreachable")
  42. }
  43. }
  44. return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
  45. default:
  46. if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 {
  47. switch numFields {
  48. case 4:
  49. return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
  50. case 3:
  51. return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem()
  52. default:
  53. panic("unreachable")
  54. }
  55. }
  56. // create struct from the Go pointer created in arm64_r8
  57. // weird pointer dereference to circumvent go vet
  58. return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem()
  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. func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
  68. if v.Type().Size() == 0 {
  69. return keepAlive
  70. }
  71. if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
  72. // if this doesn't fit entirely in registers then
  73. // each element goes onto the stack
  74. if hfa && *numFloats+v.NumField() > numOfFloatRegisters() {
  75. *numFloats = numOfFloatRegisters()
  76. } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
  77. *numInts = numOfIntegerRegisters()
  78. }
  79. placeRegisters(v, addFloat, addInt)
  80. } else {
  81. keepAlive = placeStack(v, keepAlive, addInt)
  82. }
  83. return keepAlive // the struct was allocated so don't panic
  84. }
  85. func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
  86. if runtime.GOOS == "darwin" {
  87. placeRegistersDarwin(v, addFloat, addInt)
  88. return
  89. }
  90. placeRegistersArm64(v, addFloat, addInt)
  91. }
  92. func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
  93. var val uint64
  94. var shift byte
  95. var flushed bool
  96. class := _NO_CLASS
  97. var place func(v reflect.Value)
  98. place = func(v reflect.Value) {
  99. var numFields int
  100. if v.Kind() == reflect.Struct {
  101. numFields = v.Type().NumField()
  102. } else {
  103. numFields = v.Type().Len()
  104. }
  105. for k := 0; k < numFields; k++ {
  106. flushed = false
  107. var f reflect.Value
  108. if v.Kind() == reflect.Struct {
  109. f = v.Field(k)
  110. } else {
  111. f = v.Index(k)
  112. }
  113. align := byte(f.Type().Align()*8 - 1)
  114. shift = (shift + align) &^ align
  115. if shift >= 64 {
  116. shift = 0
  117. flushed = true
  118. if class == _FLOAT {
  119. addFloat(uintptr(val))
  120. } else {
  121. addInt(uintptr(val))
  122. }
  123. val = 0
  124. class = _NO_CLASS
  125. }
  126. switch f.Type().Kind() {
  127. case reflect.Struct:
  128. place(f)
  129. case reflect.Bool:
  130. if f.Bool() {
  131. val |= 1 << shift
  132. }
  133. shift += 8
  134. class |= _INT
  135. case reflect.Uint8:
  136. val |= f.Uint() << shift
  137. shift += 8
  138. class |= _INT
  139. case reflect.Uint16:
  140. val |= f.Uint() << shift
  141. shift += 16
  142. class |= _INT
  143. case reflect.Uint32:
  144. val |= f.Uint() << shift
  145. shift += 32
  146. class |= _INT
  147. case reflect.Uint64, reflect.Uint, reflect.Uintptr:
  148. addInt(uintptr(f.Uint()))
  149. shift = 0
  150. flushed = true
  151. class = _NO_CLASS
  152. case reflect.Int8:
  153. val |= uint64(f.Int()&0xFF) << shift
  154. shift += 8
  155. class |= _INT
  156. case reflect.Int16:
  157. val |= uint64(f.Int()&0xFFFF) << shift
  158. shift += 16
  159. class |= _INT
  160. case reflect.Int32:
  161. val |= uint64(f.Int()&0xFFFF_FFFF) << shift
  162. shift += 32
  163. class |= _INT
  164. case reflect.Int64, reflect.Int:
  165. addInt(uintptr(f.Int()))
  166. shift = 0
  167. flushed = true
  168. class = _NO_CLASS
  169. case reflect.Float32:
  170. if class == _FLOAT {
  171. addFloat(uintptr(val))
  172. val = 0
  173. shift = 0
  174. }
  175. val |= uint64(math.Float32bits(float32(f.Float()))) << shift
  176. shift += 32
  177. class |= _FLOAT
  178. case reflect.Float64:
  179. addFloat(uintptr(math.Float64bits(float64(f.Float()))))
  180. shift = 0
  181. flushed = true
  182. class = _NO_CLASS
  183. case reflect.Ptr, reflect.UnsafePointer:
  184. addInt(f.Pointer())
  185. shift = 0
  186. flushed = true
  187. class = _NO_CLASS
  188. case reflect.Array:
  189. place(f)
  190. default:
  191. panic("purego: unsupported kind " + f.Kind().String())
  192. }
  193. }
  194. }
  195. place(v)
  196. if !flushed {
  197. if class == _FLOAT {
  198. addFloat(uintptr(val))
  199. } else {
  200. addInt(uintptr(val))
  201. }
  202. }
  203. }
  204. func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any {
  205. // Struct is too big to be placed in registers.
  206. // Copy to heap and place the pointer in register
  207. ptrStruct := reflect.New(v.Type())
  208. ptrStruct.Elem().Set(v)
  209. ptr := ptrStruct.Elem().Addr().UnsafePointer()
  210. keepAlive = append(keepAlive, ptr)
  211. addInt(uintptr(ptr))
  212. return keepAlive
  213. }
  214. // isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a
  215. // Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]).
  216. // This type of struct will be placed more compactly than the individual fields.
  217. //
  218. // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
  219. func isHFA(t reflect.Type) bool {
  220. // round up struct size to nearest 8 see section B.4
  221. structSize := roundUpTo8(t.Size())
  222. if structSize == 0 || t.NumField() > 4 {
  223. return false
  224. }
  225. first := t.Field(0)
  226. switch first.Type.Kind() {
  227. case reflect.Float32, reflect.Float64:
  228. firstKind := first.Type.Kind()
  229. for i := 0; i < t.NumField(); i++ {
  230. if t.Field(i).Type.Kind() != firstKind {
  231. return false
  232. }
  233. }
  234. return true
  235. case reflect.Array:
  236. switch first.Type.Elem().Kind() {
  237. case reflect.Float32, reflect.Float64:
  238. return true
  239. default:
  240. return false
  241. }
  242. case reflect.Struct:
  243. for i := 0; i < first.Type.NumField(); i++ {
  244. if !isHFA(first.Type) {
  245. return false
  246. }
  247. }
  248. return true
  249. default:
  250. return false
  251. }
  252. }
  253. // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
  254. // and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]).
  255. // A short vector is a machine type that is composed of repeated instances of one fundamental integral or
  256. // floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]).
  257. // This type of struct will be placed more compactly than the individual fields.
  258. //
  259. // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
  260. func isHVA(t reflect.Type) bool {
  261. // round up struct size to nearest 8 see section B.4
  262. structSize := roundUpTo8(t.Size())
  263. if structSize == 0 || (structSize != 8 && structSize != 16) {
  264. return false
  265. }
  266. first := t.Field(0)
  267. switch first.Type.Kind() {
  268. case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
  269. firstKind := first.Type.Kind()
  270. for i := 0; i < t.NumField(); i++ {
  271. if t.Field(i).Type.Kind() != firstKind {
  272. return false
  273. }
  274. }
  275. return true
  276. case reflect.Array:
  277. switch first.Type.Elem().Kind() {
  278. case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
  279. return true
  280. default:
  281. return false
  282. }
  283. default:
  284. return false
  285. }
  286. }
  287. // copyStruct8ByteChunks copies struct memory in 8-byte chunks to the provided callback.
  288. // This is used for Darwin ARM64's byte-level packing of non-HFA/HVA structs.
  289. func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintptr)) {
  290. if runtime.GOOS != "darwin" {
  291. panic("purego: should only be called on darwin")
  292. }
  293. for offset := uintptr(0); offset < size; offset += 8 {
  294. var chunk uintptr
  295. remaining := size - offset
  296. if remaining >= 8 {
  297. chunk = *(*uintptr)(unsafe.Add(ptr, offset))
  298. } else {
  299. // Read byte-by-byte to avoid reading beyond allocation
  300. for i := uintptr(0); i < remaining; i++ {
  301. b := *(*byte)(unsafe.Add(ptr, offset+i))
  302. chunk |= uintptr(b) << (i * 8)
  303. }
  304. }
  305. addChunk(chunk)
  306. }
  307. }
  308. // placeRegisters implements Darwin ARM64 calling convention for struct arguments.
  309. //
  310. // For HFA/HVA structs, each element must go in a separate register (or stack slot for elements
  311. // that don't fit in registers). We use placeRegistersArm64 for this.
  312. //
  313. // For non-HFA/HVA structs, Darwin uses byte-level packing. We copy the struct memory in
  314. // 8-byte chunks, which works correctly for both register and stack placement.
  315. func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
  316. if runtime.GOOS != "darwin" {
  317. panic("purego: placeRegistersDarwin should only be called on darwin")
  318. }
  319. // Check if this is an HFA/HVA
  320. hfa := isHFA(v.Type())
  321. hva := isHVA(v.Type())
  322. // For HFA/HVA structs, use the standard ARM64 logic which places each element separately
  323. if hfa || hva {
  324. placeRegistersArm64(v, addFloat, addInt)
  325. return
  326. }
  327. // For non-HFA/HVA structs, use byte-level copying
  328. // If the value is not addressable, create an addressable copy
  329. if !v.CanAddr() {
  330. addressable := reflect.New(v.Type()).Elem()
  331. addressable.Set(v)
  332. v = addressable
  333. }
  334. ptr := unsafe.Pointer(v.Addr().Pointer())
  335. size := v.Type().Size()
  336. copyStruct8ByteChunks(ptr, size, addInt)
  337. }
  338. // shouldBundleStackArgs determines if we need to start C-style packing for
  339. // Darwin ARM64 stack arguments. This happens when registers are exhausted.
  340. func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
  341. if runtime.GOOS != "darwin" {
  342. return false
  343. }
  344. kind := v.Kind()
  345. isFloat := kind == reflect.Float32 || kind == reflect.Float64
  346. isInt := !isFloat && kind != reflect.Struct
  347. primitiveOnStack :=
  348. (isInt && numInts >= numOfIntegerRegisters()) ||
  349. (isFloat && numFloats >= numOfFloatRegisters())
  350. if primitiveOnStack {
  351. return true
  352. }
  353. if kind != reflect.Struct {
  354. return false
  355. }
  356. hfa := isHFA(v.Type())
  357. hva := isHVA(v.Type())
  358. size := v.Type().Size()
  359. eligible := hfa || hva || size <= 16
  360. if !eligible {
  361. return false
  362. }
  363. if hfa {
  364. need := v.NumField()
  365. return numFloats+need > numOfFloatRegisters()
  366. }
  367. if hva {
  368. need := v.NumField()
  369. return numInts+need > numOfIntegerRegisters()
  370. }
  371. slotsNeeded := int((size + align8ByteMask) / align8ByteSize)
  372. return numInts+slotsNeeded > numOfIntegerRegisters()
  373. }
  374. // structFitsInRegisters determines if a struct can still fit in remaining
  375. // registers, used during stack argument bundling to decide if a struct
  376. // should go through normal register allocation or be bundled with stack args.
  377. func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
  378. if runtime.GOOS != "darwin" {
  379. panic("purego: structFitsInRegisters should only be called on darwin")
  380. }
  381. hfa := isHFA(val.Type())
  382. hva := isHVA(val.Type())
  383. size := val.Type().Size()
  384. if hfa {
  385. // HFA: check if elements fit in float registers
  386. if tempNumFloats+val.NumField() <= numOfFloatRegisters() {
  387. return true, tempNumInts, tempNumFloats + val.NumField()
  388. }
  389. } else if hva {
  390. // HVA: check if elements fit in int registers
  391. if tempNumInts+val.NumField() <= numOfIntegerRegisters() {
  392. return true, tempNumInts + val.NumField(), tempNumFloats
  393. }
  394. } else if size <= 16 {
  395. // Non-HFA/HVA small structs use int registers for byte-packing
  396. slotsNeeded := int((size + align8ByteMask) / align8ByteSize)
  397. if tempNumInts+slotsNeeded <= numOfIntegerRegisters() {
  398. return true, tempNumInts + slotsNeeded, tempNumFloats
  399. }
  400. }
  401. return false, tempNumInts, tempNumFloats
  402. }
  403. // collectStackArgs separates remaining arguments into those that fit in registers vs those that go on stack.
  404. // It returns the stack arguments and processes register arguments through addValue.
  405. func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
  406. keepAlive []any, addInt, addFloat, addStack func(uintptr),
  407. pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
  408. if runtime.GOOS != "darwin" {
  409. panic("purego: collectStackArgs should only be called on darwin")
  410. }
  411. var stackArgs []reflect.Value
  412. tempNumInts := numInts
  413. tempNumFloats := numFloats
  414. for j, val := range args[startIdx:] {
  415. // Determine if this argument goes to register or stack
  416. var fitsInRegister bool
  417. var newNumInts, newNumFloats int
  418. if val.Kind() == reflect.Struct {
  419. // Check if struct still fits in remaining registers
  420. fitsInRegister, newNumInts, newNumFloats = structFitsInRegisters(val, tempNumInts, tempNumFloats)
  421. } else {
  422. // Primitive argument
  423. isFloat := val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64
  424. if isFloat {
  425. fitsInRegister = tempNumFloats < numOfFloatRegisters()
  426. newNumFloats = tempNumFloats + 1
  427. newNumInts = tempNumInts
  428. } else {
  429. fitsInRegister = tempNumInts < numOfIntegerRegisters()
  430. newNumInts = tempNumInts + 1
  431. newNumFloats = tempNumFloats
  432. }
  433. }
  434. if fitsInRegister {
  435. // Process through normal register allocation
  436. tempNumInts = newNumInts
  437. tempNumFloats = newNumFloats
  438. keepAlive = addValue(val, keepAlive, addInt, addFloat, addStack, pNumInts, pNumFloats, pNumStack)
  439. } else {
  440. // Convert strings to C strings before bundling
  441. if val.Kind() == reflect.String {
  442. ptr := strings.CString(val.String())
  443. keepAlive = append(keepAlive, ptr)
  444. val = reflect.ValueOf(ptr)
  445. args[startIdx+j] = val
  446. }
  447. stackArgs = append(stackArgs, val)
  448. }
  449. }
  450. return stackArgs, keepAlive
  451. }
  452. const (
  453. paddingFieldPrefix = "Pad"
  454. )
  455. // bundleStackArgs bundles remaining arguments for Darwin ARM64 C-style stack packing.
  456. // It creates a packed struct with proper alignment and copies it to the stack in 8-byte chunks.
  457. func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
  458. if runtime.GOOS != "darwin" {
  459. panic("purego: bundleStackArgs should only be called on darwin")
  460. }
  461. if len(stackArgs) == 0 {
  462. return
  463. }
  464. // Build struct fields with proper C alignment and padding
  465. var fields []reflect.StructField
  466. currentOffset := uintptr(0)
  467. fieldIndex := 0
  468. for j, val := range stackArgs {
  469. valSize := val.Type().Size()
  470. valAlign := val.Type().Align()
  471. // ARM64 requires 8-byte alignment for 8-byte or larger structs
  472. if val.Kind() == reflect.Struct && valSize >= 8 {
  473. valAlign = 8
  474. }
  475. // Add padding field if needed for alignment
  476. if currentOffset%uintptr(valAlign) != 0 {
  477. paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign))
  478. fields = append(fields, reflect.StructField{
  479. Name: paddingFieldPrefix + strconv.Itoa(fieldIndex),
  480. Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeOf(byte(0))),
  481. })
  482. currentOffset += paddingNeeded
  483. fieldIndex++
  484. }
  485. fields = append(fields, reflect.StructField{
  486. Name: "X" + strconv.Itoa(j),
  487. Type: val.Type(),
  488. })
  489. currentOffset += valSize
  490. fieldIndex++
  491. }
  492. // Create and populate the packed struct
  493. structType := reflect.StructOf(fields)
  494. structInstance := reflect.New(structType).Elem()
  495. // Set values (skip padding fields)
  496. argIndex := 0
  497. for j := 0; j < structInstance.NumField(); j++ {
  498. fieldName := structType.Field(j).Name
  499. if stdstrings.HasPrefix(fieldName, paddingFieldPrefix) {
  500. continue
  501. }
  502. structInstance.Field(j).Set(stackArgs[argIndex])
  503. argIndex++
  504. }
  505. ptr := unsafe.Pointer(structInstance.Addr().Pointer())
  506. size := structType.Size()
  507. copyStruct8ByteChunks(ptr, size, addStack)
  508. }