func.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. // SPDX-License-Identifier: Apache-2.0
  2. // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
  3. //go:build darwin || freebsd || linux || netbsd || windows
  4. package purego
  5. import (
  6. "fmt"
  7. "math"
  8. "reflect"
  9. "runtime"
  10. "sync"
  11. "unsafe"
  12. "github.com/ebitengine/purego/internal/strings"
  13. "github.com/ebitengine/purego/internal/xreflect"
  14. )
  15. const (
  16. align8ByteMask = 7 // Mask for 8-byte alignment: (val + 7) &^ 7
  17. align8ByteSize = 8 // 8-byte alignment boundary
  18. )
  19. var thePool = sync.Pool{New: func() any {
  20. return new(syscall15Args)
  21. }}
  22. // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
  23. // It panics if it can't find the name symbol.
  24. func RegisterLibFunc(fptr any, handle uintptr, name string) {
  25. sym, err := loadSymbol(handle, name)
  26. if err != nil {
  27. panic(err)
  28. }
  29. RegisterFunc(fptr, sym)
  30. }
  31. // RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
  32. // fptr will be set to a function that when called will call the C function given by cfn with the
  33. // parameters passed in the correct registers and stack.
  34. //
  35. // A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
  36. //
  37. // These conversions describe how a Go type in the fptr will be used to call
  38. // the C function. It is important to note that there is no way to verify that fptr
  39. // matches the C function. This also holds true for struct types where the padding
  40. // needs to be ensured to match that of C; RegisterFunc does not verify this.
  41. //
  42. // # Type Conversions (Go <=> C)
  43. //
  44. // string <=> char*
  45. // bool <=> _Bool
  46. // uintptr <=> uintptr_t
  47. // uint <=> uint32_t or uint64_t
  48. // uint8 <=> uint8_t
  49. // uint16 <=> uint16_t
  50. // uint32 <=> uint32_t
  51. // uint64 <=> uint64_t
  52. // int <=> int32_t or int64_t
  53. // int8 <=> int8_t
  54. // int16 <=> int16_t
  55. // int32 <=> int32_t
  56. // int64 <=> int64_t
  57. // float32 <=> float
  58. // float64 <=> double
  59. // struct <=> struct (darwin amd64/arm64, linux amd64/arm64)
  60. // func <=> C function
  61. // unsafe.Pointer, *T <=> void*
  62. // []T => void*
  63. //
  64. // There is a special case when the last argument of fptr is a variadic interface (or []interface}
  65. // it will be expanded into a call to the C function as if it had the arguments in that slice.
  66. // This means that using arg ...any is like a cast to the function with the arguments inside arg.
  67. // This is not the same as C variadic.
  68. //
  69. // # Memory
  70. //
  71. // In general it is not possible for purego to guarantee the lifetimes of objects returned or received from
  72. // calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't
  73. // hold onto a reference to Go memory. This is the same as the [Cgo rules].
  74. //
  75. // However, there are some special cases. When passing a string as an argument if the string does not end in a null
  76. // terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for
  77. // that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some
  78. // undefined time. However, if the string does already contain a null-terminated byte then no copy is done.
  79. // It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory.
  80. // This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function
  81. // returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory
  82. // and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced.
  83. // This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue
  84. // to point to C memory (because it's a buffer for example) then use a pointer to byte and then convert that to a slice
  85. // using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
  86. // of the pointer
  87. //
  88. // # Structs
  89. //
  90. // Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
  91. // it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
  92. // that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
  93. //
  94. // On Darwin ARM64, purego handles proper alignment of struct arguments when passing them on the stack,
  95. // following the C ABI's byte-level packing rules.
  96. //
  97. // # Example
  98. //
  99. // All functions below call this C function:
  100. //
  101. // char *foo(char *str);
  102. //
  103. // // Let purego convert types
  104. // var foo func(s string) string
  105. // goString := foo("copied")
  106. // // Go will garbage collect this string
  107. //
  108. // // Manually, handle allocations
  109. // var foo2 func(b string) *byte
  110. // mustFree := foo2("not copied\x00")
  111. // defer free(mustFree)
  112. //
  113. // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
  114. func RegisterFunc(fptr any, cfn uintptr) {
  115. const is32bit = unsafe.Sizeof(uintptr(0)) == 4
  116. fn := reflect.ValueOf(fptr).Elem()
  117. ty := fn.Type()
  118. if ty.Kind() != reflect.Func {
  119. panic("purego: fptr must be a function pointer")
  120. }
  121. if ty.NumOut() > 1 {
  122. panic("purego: function can only return zero or one values")
  123. }
  124. if cfn == 0 {
  125. panic("purego: cfn is nil")
  126. }
  127. if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) &&
  128. runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" {
  129. panic("purego: float returns are not supported")
  130. }
  131. {
  132. // this code checks how many registers and stack this function will use
  133. // to avoid crashing with too many arguments
  134. var ints int
  135. var floats int
  136. var stack int
  137. for i := 0; i < ty.NumIn(); i++ {
  138. arg := ty.In(i)
  139. switch arg.Kind() {
  140. case reflect.Func:
  141. // This only does preliminary testing to ensure the CDecl argument
  142. // is the first argument. Full testing is done when the callback is actually
  143. // created in NewCallback.
  144. for j := 0; j < arg.NumIn(); j++ {
  145. in := arg.In(j)
  146. if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
  147. continue
  148. }
  149. if j != 0 {
  150. panic("purego: CDecl must be the first argument")
  151. }
  152. }
  153. case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
  154. reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer,
  155. reflect.Slice, reflect.Bool:
  156. if ints < numOfIntegerRegisters() {
  157. ints++
  158. } else {
  159. stack++
  160. }
  161. case reflect.Float32, reflect.Float64:
  162. if floats < numOfFloatRegisters() {
  163. floats++
  164. } else {
  165. stack++
  166. }
  167. case reflect.Struct:
  168. ensureStructSupportedForRegisterFunc()
  169. if arg.Size() == 0 {
  170. continue
  171. }
  172. addInt := func(u uintptr) {
  173. ints++
  174. }
  175. addFloat := func(u uintptr) {
  176. floats++
  177. }
  178. addStack := func(u uintptr) {
  179. stack++
  180. }
  181. _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
  182. default:
  183. panic("purego: unsupported kind " + arg.Kind().String())
  184. }
  185. }
  186. if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
  187. ensureStructSupportedForRegisterFunc()
  188. outType := ty.Out(0)
  189. checkStructFieldsSupported(outType)
  190. if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
  191. // on amd64 if struct is bigger than 16 bytes allocate the return struct
  192. // and pass it in as a hidden first argument.
  193. ints++
  194. }
  195. }
  196. sizeOfStack := maxArgs - numOfIntegerRegisters()
  197. // On Darwin ARM64, use byte-based validation since arguments pack efficiently.
  198. // See https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
  199. if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
  200. stackBytes := estimateStackBytes(ty)
  201. maxStackBytes := sizeOfStack * 8
  202. if stackBytes > maxStackBytes {
  203. panic("purego: too many stack arguments")
  204. }
  205. } else {
  206. if stack > sizeOfStack {
  207. panic("purego: too many stack arguments")
  208. }
  209. }
  210. }
  211. v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
  212. var sysargs [maxArgs]uintptr
  213. // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,
  214. // since numOfFloatRegisters() is a function call, not a constant.
  215. // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe.
  216. var floats [maxArgs]uintptr
  217. var numInts int
  218. var numFloats int
  219. var numStack int
  220. var addStack, addInt, addFloat func(x uintptr)
  221. if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
  222. // Windows arm64 uses the same calling convention as macOS and Linux
  223. addStack = func(x uintptr) {
  224. sysargs[numOfIntegerRegisters()+numStack] = x
  225. numStack++
  226. }
  227. addInt = func(x uintptr) {
  228. if numInts >= numOfIntegerRegisters() {
  229. addStack(x)
  230. } else {
  231. sysargs[numInts] = x
  232. numInts++
  233. }
  234. }
  235. addFloat = func(x uintptr) {
  236. if numFloats < numOfFloatRegisters() {
  237. floats[numFloats] = x
  238. numFloats++
  239. } else {
  240. addStack(x)
  241. }
  242. }
  243. } else {
  244. // On Windows amd64 the arguments are passed in the numbered registered.
  245. // So the first int is in the first integer register and the first float
  246. // is in the second floating register if there is already a first int.
  247. // This is in contrast to how macOS and Linux pass arguments which
  248. // tries to use as many registers as possible in the calling convention.
  249. addStack = func(x uintptr) {
  250. sysargs[numStack] = x
  251. numStack++
  252. }
  253. addInt = addStack
  254. addFloat = addStack
  255. }
  256. var keepAlive []any
  257. defer func() {
  258. runtime.KeepAlive(keepAlive)
  259. runtime.KeepAlive(args)
  260. }()
  261. var arm64_r8 uintptr
  262. if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
  263. outType := ty.Out(0)
  264. if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize {
  265. val := reflect.New(outType)
  266. keepAlive = append(keepAlive, val)
  267. addInt(val.Pointer())
  268. } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
  269. isAllFloats, numFields := isAllSameFloat(outType)
  270. if !isAllFloats || numFields > 4 {
  271. val := reflect.New(outType)
  272. keepAlive = append(keepAlive, val)
  273. arm64_r8 = val.Pointer()
  274. }
  275. }
  276. }
  277. for i, v := range args {
  278. if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok {
  279. if i != len(args)-1 {
  280. panic("purego: can only expand last parameter")
  281. }
  282. for _, x := range variadic {
  283. keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
  284. }
  285. continue
  286. }
  287. // Check if we need to start Darwin ARM64 C-style stack packing
  288. if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {
  289. // Collect and separate remaining args into register vs stack
  290. stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats,
  291. keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
  292. keepAlive = newKeepAlive
  293. // Bundle stack arguments with C-style packing
  294. bundleStackArgs(stackArgs, addStack)
  295. break
  296. }
  297. keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
  298. }
  299. syscall := thePool.Get().(*syscall15Args)
  300. defer thePool.Put(syscall)
  301. if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {
  302. syscall.Set(cfn, sysargs[:], floats[:], 0)
  303. runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
  304. } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
  305. // Use the normal arm64 calling convention even on Windows
  306. syscall.Set(cfn, sysargs[:], floats[:], arm64_r8)
  307. runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
  308. } else {
  309. *syscall = syscall15Args{}
  310. // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
  311. syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
  312. sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
  313. sysargs[12], sysargs[13], sysargs[14])
  314. syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
  315. }
  316. if ty.NumOut() == 0 {
  317. return nil
  318. }
  319. outType := ty.Out(0)
  320. v := reflect.New(outType).Elem()
  321. switch outType.Kind() {
  322. case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  323. v.SetUint(uint64(syscall.a1))
  324. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  325. v.SetInt(int64(syscall.a1))
  326. case reflect.Bool:
  327. v.SetBool(byte(syscall.a1) != 0)
  328. case reflect.UnsafePointer:
  329. // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
  330. v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
  331. case reflect.Ptr:
  332. v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
  333. case reflect.Func:
  334. // wrap this C function in a nicely typed Go function
  335. v = reflect.New(outType)
  336. RegisterFunc(v.Interface(), syscall.a1)
  337. case reflect.String:
  338. v.SetString(strings.GoString(syscall.a1))
  339. case reflect.Float32:
  340. // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
  341. // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
  342. // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
  343. // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
  344. // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register.
  345. switch runtime.GOARCH {
  346. case "386":
  347. v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
  348. case "ppc64le":
  349. v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
  350. case "s390x":
  351. // S390X is big-endian: float32 in upper 32 bits of 64-bit register
  352. v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32))))
  353. default:
  354. v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
  355. }
  356. case reflect.Float64:
  357. // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
  358. // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
  359. if is32bit {
  360. v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
  361. } else {
  362. v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
  363. }
  364. case reflect.Struct:
  365. v = getStruct(outType, *syscall)
  366. default:
  367. panic("purego: unsupported return kind: " + outType.Kind().String())
  368. }
  369. if len(args) > 0 {
  370. // reuse args slice instead of allocating one when possible
  371. args[0] = v
  372. return args[:1]
  373. } else {
  374. return []reflect.Value{v}
  375. }
  376. })
  377. fn.Set(v)
  378. }
  379. func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any {
  380. const is32bit = unsafe.Sizeof(uintptr(0)) == 4
  381. switch v.Kind() {
  382. case reflect.String:
  383. ptr := strings.CString(v.String())
  384. keepAlive = append(keepAlive, ptr)
  385. addInt(uintptr(unsafe.Pointer(ptr)))
  386. case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  387. addInt(uintptr(v.Uint()))
  388. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  389. addInt(uintptr(v.Int()))
  390. case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
  391. // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
  392. addInt(v.Pointer())
  393. case reflect.Func:
  394. addInt(NewCallback(v.Interface()))
  395. case reflect.Bool:
  396. if v.Bool() {
  397. addInt(1)
  398. } else {
  399. addInt(0)
  400. }
  401. case reflect.Float32:
  402. // On S390X big-endian, float32 goes in upper 32 bits of 64-bit FP register
  403. if runtime.GOARCH == "s390x" {
  404. addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32)
  405. } else {
  406. addFloat(uintptr(math.Float32bits(float32(v.Float()))))
  407. }
  408. case reflect.Float64:
  409. if is32bit {
  410. bits := math.Float64bits(v.Float())
  411. addFloat(uintptr(bits))
  412. addFloat(uintptr(bits >> 32))
  413. } else {
  414. addFloat(uintptr(math.Float64bits(v.Float())))
  415. }
  416. case reflect.Struct:
  417. keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
  418. default:
  419. panic("purego: unsupported kind: " + v.Kind().String())
  420. }
  421. return keepAlive
  422. }
  423. // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
  424. // if it is bigger than this than enough space must be allocated on the heap and then passed into
  425. // the function as the first parameter on amd64 or in R8 on arm64.
  426. //
  427. // If you change this make sure to update it in objc_runtime_darwin.go
  428. const maxRegAllocStructSize = 16
  429. func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
  430. allFloats = true
  431. root := ty.Field(0).Type
  432. for root.Kind() == reflect.Struct {
  433. root = root.Field(0).Type
  434. }
  435. first := root.Kind()
  436. if first != reflect.Float32 && first != reflect.Float64 {
  437. allFloats = false
  438. }
  439. for i := 0; i < ty.NumField(); i++ {
  440. f := ty.Field(i).Type
  441. if f.Kind() == reflect.Struct {
  442. var structNumFields int
  443. allFloats, structNumFields = isAllSameFloat(f)
  444. numFields += structNumFields
  445. continue
  446. }
  447. numFields++
  448. if f.Kind() != first {
  449. allFloats = false
  450. }
  451. }
  452. return allFloats, numFields
  453. }
  454. func checkStructFieldsSupported(ty reflect.Type) {
  455. for i := 0; i < ty.NumField(); i++ {
  456. f := ty.Field(i).Type
  457. if f.Kind() == reflect.Array {
  458. f = f.Elem()
  459. } else if f.Kind() == reflect.Struct {
  460. checkStructFieldsSupported(f)
  461. continue
  462. }
  463. switch f.Kind() {
  464. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
  465. reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
  466. reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32,
  467. reflect.Bool:
  468. default:
  469. panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
  470. }
  471. }
  472. }
  473. func ensureStructSupportedForRegisterFunc() {
  474. if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
  475. panic("purego: struct arguments are only supported on amd64 and arm64")
  476. }
  477. if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
  478. panic("purego: struct arguments are only supported on darwin and linux")
  479. }
  480. }
  481. func roundUpTo8(val uintptr) uintptr {
  482. return (val + align8ByteMask) &^ align8ByteMask
  483. }
  484. func numOfFloatRegisters() int {
  485. switch runtime.GOARCH {
  486. case "amd64", "arm64", "loong64", "ppc64le", "riscv64":
  487. return 8
  488. case "s390x":
  489. return 4
  490. case "arm":
  491. return 16
  492. case "386":
  493. // i386 SysV ABI passes all arguments on the stack, including floats
  494. return 0
  495. default:
  496. // since this platform isn't supported and can therefore only access
  497. // integer registers it is safest to return 8
  498. return 8
  499. }
  500. }
  501. func numOfIntegerRegisters() int {
  502. switch runtime.GOARCH {
  503. case "arm64", "loong64", "ppc64le", "riscv64":
  504. return 8
  505. case "amd64":
  506. return 6
  507. case "s390x":
  508. // S390X uses R2-R6 for integer arguments
  509. return 5
  510. case "arm":
  511. return 4
  512. case "386":
  513. // i386 SysV ABI passes all arguments on the stack
  514. return 0
  515. default:
  516. // since this platform isn't supported and can therefore only access
  517. // integer registers it is fine to return the maxArgs
  518. return maxArgs
  519. }
  520. }
  521. // estimateStackBytes estimates stack bytes needed for Darwin ARM64 validation.
  522. // This is a conservative estimate used only for early error detection.
  523. func estimateStackBytes(ty reflect.Type) int {
  524. var numInts, numFloats int
  525. var stackBytes int
  526. for i := 0; i < ty.NumIn(); i++ {
  527. arg := ty.In(i)
  528. size := int(arg.Size())
  529. // Check if this goes to register or stack
  530. usesInt := arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64
  531. if usesInt && numInts < numOfIntegerRegisters() {
  532. numInts++
  533. } else if !usesInt && numFloats < numOfFloatRegisters() {
  534. numFloats++
  535. } else {
  536. // Goes to stack - accumulate total bytes
  537. stackBytes += size
  538. }
  539. }
  540. // Round total to 8-byte boundary
  541. if stackBytes > 0 && stackBytes%align8ByteSize != 0 {
  542. stackBytes = int(roundUpTo8(uintptr(stackBytes)))
  543. }
  544. return stackBytes
  545. }