detached 9059adf

Fix alignment of stack args on macOS (#310)

Fixes #309
commit
9059adfae616e486aa4145d6f4d5fefaa1b47a61
author
TotallyGamerJet · · 2025-04-11 14:46 UTC
committer
GitHub · · 2025-04-11 14:46 UTC
parent
d99d2be
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
refs
tag: v0.9.0-alpha.3
12 files · +189 -58
M .github/workflows/test.yml +5 -2
@@ -34,10 +34,13 @@ jobs:
3434 sudo apt-get update
3535 sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user
3636
37+ - name: Set up the prerequisites
38+ if: runner.os == 'Windows'
39+ uses: msys2/setup-msys2@v2
40+
3741 - name: go vet
38- if: runner.os != 'Windows' && runner.os != 'macOS'
3942 run: |
40- go vet -v ./...
43+ env CGO_ENABLED=0 go vet -v ./...
4144
4245 - name: go build
4346 run: |
@@ -34,10 +34,13 @@ jobs:
34 sudo apt-get update34 sudo apt-get update
35 sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user35 sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user
36 36
37+ - name: Set up the prerequisites
38+ if: runner.os == 'Windows'
39+ uses: msys2/setup-msys2@v2
40+
37 - name: go vet41 - name: go vet
38- if: runner.os != 'Windows' && runner.os != 'macOS'
39 run: |42 run: |
40- go vet -v ./...43+ env CGO_ENABLED=0 go vet -v ./...
41 44
42 - name: go build45 - name: go build
43 run: |46 run: |
M dlfcn_test.go +0 -43
@@ -6,13 +6,8 @@
66 package purego_test
77
88 import (
9- "errors"
10- "fmt"
119 "os"
12- "os/exec"
1310 "path/filepath"
14- "runtime"
15- "strings"
1611 "testing"
1712 "unsafe"
1813
@@ -42,44 +37,6 @@ func TestNestedDlopenCall(t *testing.T) {
4237 purego.Dlclose(lib)
4338 }
4439
45-func buildSharedLib(compilerEnv, libFile string, sources ...string) error {
46- out, err := exec.Command("go", "env", compilerEnv).Output()
47- if err != nil {
48- return fmt.Errorf("go env %s error: %w", compilerEnv, err)
49- }
50-
51- compiler := strings.TrimSpace(string(out))
52- if compiler == "" {
53- return errors.New("compiler not found")
54- }
55-
56- args := []string{"-shared", "-Wall", "-Werror", "-fPIC", "-o", libFile}
57- if runtime.GOARCH == "386" {
58- args = append(args, "-m32")
59- }
60- // macOS arm64 can run amd64 tests through Rossetta.
61- // Build the shared library based on the GOARCH and not
62- // the default behavior of the compiler.
63- if runtime.GOOS == "darwin" {
64- var arch string
65- switch runtime.GOARCH {
66- case "arm64":
67- arch = "arm64"
68- case "amd64":
69- arch = "x86_64"
70- default:
71- return fmt.Errorf("unknown macOS architecture %s", runtime.GOARCH)
72- }
73- args = append(args, "-arch", arch)
74- }
75- cmd := exec.Command(compiler, append(args, sources...)...)
76- if out, err := cmd.CombinedOutput(); err != nil {
77- return fmt.Errorf("compile lib: %w\n%q\n%s", err, cmd, string(out))
78- }
79-
80- return nil
81-}
82-
8340 func TestSyscallN(t *testing.T) {
8441 var dlsym uintptr
8542 var err error
@@ -6,13 +6,8 @@
6 package purego_test6 package purego_test
7 7
8 import (8 import (
9- "errors"
10- "fmt"
11 "os"9 "os"
12- "os/exec"
13 "path/filepath"10 "path/filepath"
14- "runtime"
15- "strings"
16 "testing"11 "testing"
17 "unsafe"12 "unsafe"
18 13
@@ -42,44 +37,6 @@ func TestNestedDlopenCall(t *testing.T) {
42 purego.Dlclose(lib)37 purego.Dlclose(lib)
43 }38 }
44 39
45-func buildSharedLib(compilerEnv, libFile string, sources ...string) error {
46- out, err := exec.Command("go", "env", compilerEnv).Output()
47- if err != nil {
48- return fmt.Errorf("go env %s error: %w", compilerEnv, err)
49- }
50-
51- compiler := strings.TrimSpace(string(out))
52- if compiler == "" {
53- return errors.New("compiler not found")
54- }
55-
56- args := []string{"-shared", "-Wall", "-Werror", "-fPIC", "-o", libFile}
57- if runtime.GOARCH == "386" {
58- args = append(args, "-m32")
59- }
60- // macOS arm64 can run amd64 tests through Rossetta.
61- // Build the shared library based on the GOARCH and not
62- // the default behavior of the compiler.
63- if runtime.GOOS == "darwin" {
64- var arch string
65- switch runtime.GOARCH {
66- case "arm64":
67- arch = "arm64"
68- case "amd64":
69- arch = "x86_64"
70- default:
71- return fmt.Errorf("unknown macOS architecture %s", runtime.GOARCH)
72- }
73- args = append(args, "-arch", arch)
74- }
75- cmd := exec.Command(compiler, append(args, sources...)...)
76- if out, err := cmd.CombinedOutput(); err != nil {
77- return fmt.Errorf("compile lib: %w\n%q\n%s", err, cmd, string(out))
78- }
79-
80- return nil
81-}
82-
83 func TestSyscallN(t *testing.T) {40 func TestSyscallN(t *testing.T) {
84 var dlsym uintptr41 var dlsym uintptr
85 var err error42 var err error
M func.go +28 -4
@@ -10,6 +10,7 @@ import (
1010 "math"
1111 "reflect"
1212 "runtime"
13+ "strconv"
1314 "sync"
1415 "unsafe"
1516
@@ -161,7 +162,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
161162 if is32bit {
162163 panic("purego: floats only supported on 64bit platforms")
163164 }
164- if floats < numOfFloats {
165+ if floats < numOfFloatRegisters {
165166 floats++
166167 } else {
167168 stack++
@@ -206,8 +207,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
206207 }
207208 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
208209 var sysargs [maxArgs]uintptr
209- stack := sysargs[numOfIntegerRegisters():]
210- var floats [numOfFloats]uintptr
210+ var floats [numOfFloatRegisters]uintptr
211211 var numInts int
212212 var numFloats int
213213 var numStack int
@@ -215,7 +215,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
215215 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
216216 // Windows arm64 uses the same calling convention as macOS and Linux
217217 addStack = func(x uintptr) {
218- stack[numStack] = x
218+ sysargs[numOfIntegerRegisters()+numStack] = x
219219 numStack++
220220 }
221221 addInt = func(x uintptr) {
@@ -280,6 +280,30 @@ func RegisterFunc(fptr any, cfn uintptr) {
280280 }
281281 continue
282282 }
283+ if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" &&
284+ (numInts >= numOfIntegerRegisters() || numFloats >= numOfFloatRegisters) && v.Kind() != reflect.Struct { // hit the stack
285+ fields := make([]reflect.StructField, len(args[i:]))
286+
287+ for j, val := range args[i:] {
288+ if val.Kind() == reflect.String {
289+ ptr := strings.CString(v.String())
290+ keepAlive = append(keepAlive, ptr)
291+ val = reflect.ValueOf(ptr)
292+ args[i+j] = val
293+ }
294+ fields[j] = reflect.StructField{
295+ Name: "X" + strconv.Itoa(j),
296+ Type: val.Type(),
297+ }
298+ }
299+ structType := reflect.StructOf(fields)
300+ structInstance := reflect.New(structType).Elem()
301+ for j, val := range args[i:] {
302+ structInstance.Field(j).Set(val)
303+ }
304+ placeRegisters(structInstance, addFloat, addInt)
305+ break
306+ }
283307 keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
284308 }
285309
@@ -10,6 +10,7 @@ import (
10 "math"10 "math"
11 "reflect"11 "reflect"
12 "runtime"12 "runtime"
13+ "strconv"
13 "sync"14 "sync"
14 "unsafe"15 "unsafe"
15 16
@@ -161,7 +162,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
161 if is32bit {162 if is32bit {
162 panic("purego: floats only supported on 64bit platforms")163 panic("purego: floats only supported on 64bit platforms")
163 }164 }
164- if floats < numOfFloats {165+ if floats < numOfFloatRegisters {
165 floats++166 floats++
166 } else {167 } else {
167 stack++168 stack++
@@ -206,8 +207,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
206 }207 }
207 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {208 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
208 var sysargs [maxArgs]uintptr209 var sysargs [maxArgs]uintptr
209- stack := sysargs[numOfIntegerRegisters():]210+ var floats [numOfFloatRegisters]uintptr
210- var floats [numOfFloats]uintptr
211 var numInts int211 var numInts int
212 var numFloats int212 var numFloats int
213 var numStack int213 var numStack int
@@ -215,7 +215,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
215 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {215 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
216 // Windows arm64 uses the same calling convention as macOS and Linux216 // Windows arm64 uses the same calling convention as macOS and Linux
217 addStack = func(x uintptr) {217 addStack = func(x uintptr) {
218- stack[numStack] = x218+ sysargs[numOfIntegerRegisters()+numStack] = x
219 numStack++219 numStack++
220 }220 }
221 addInt = func(x uintptr) {221 addInt = func(x uintptr) {
@@ -280,6 +280,30 @@ func RegisterFunc(fptr any, cfn uintptr) {
280 }280 }
281 continue281 continue
282 }282 }
283+ if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" &&
284+ (numInts >= numOfIntegerRegisters() || numFloats >= numOfFloatRegisters) && v.Kind() != reflect.Struct { // hit the stack
285+ fields := make([]reflect.StructField, len(args[i:]))
286+
287+ for j, val := range args[i:] {
288+ if val.Kind() == reflect.String {
289+ ptr := strings.CString(v.String())
290+ keepAlive = append(keepAlive, ptr)
291+ val = reflect.ValueOf(ptr)
292+ args[i+j] = val
293+ }
294+ fields[j] = reflect.StructField{
295+ Name: "X" + strconv.Itoa(j),
296+ Type: val.Type(),
297+ }
298+ }
299+ structType := reflect.StructOf(fields)
300+ structInstance := reflect.New(structType).Elem()
301+ for j, val := range args[i:] {
302+ structInstance.Field(j).Set(val)
303+ }
304+ placeRegisters(structInstance, addFloat, addInt)
305+ break
306+ }
283 keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)307 keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
284 }308 }
285 309
M func_test.go +94 -0
@@ -4,8 +4,12 @@
44 package purego_test
55
66 import (
7+ "errors"
78 "fmt"
9+ "os/exec"
10+ "path/filepath"
811 "runtime"
12+ "strings"
913 "testing"
1014 "unsafe"
1115
@@ -130,3 +134,93 @@ func TestRegisterLibFunc_Bool(t *testing.T) {
130134 t.Errorf("runFalse failed. got %t but wanted %t", got, expected)
131135 }
132136 }
137+
138+func TestABI(t *testing.T) {
139+ if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
140+ t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test
141+ }
142+ libFileName := filepath.Join(t.TempDir(), "abitest.so")
143+ t.Logf("Build %v", libFileName)
144+
145+ if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil {
146+ t.Fatal(err)
147+ }
148+
149+ lib, err := load.OpenLibrary(libFileName)
150+ if err != nil {
151+ t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
152+ }
153+ defer func() {
154+ if err := load.CloseLibrary(lib); err != nil {
155+ t.Fatalf("failed to close library: %s", err)
156+ }
157+ }()
158+ {
159+ const cName = "stack_uint8_t"
160+ const expect = 2047
161+ var fn func(a, b, c, d, e, f, g, h uint32, i, j uint8, k uint32) uint32
162+ purego.RegisterLibFunc(&fn, lib, cName)
163+ res := fn(256, 512, 4, 8, 16, 32, 64, 128, 1, 2, 1024)
164+ if res != expect {
165+ t.Fatalf("%s: got %d, want %d", cName, res, expect)
166+ }
167+ }
168+ {
169+ const cName = "reg_uint8_t"
170+ const expect = 1027
171+ var fn func(a, b uint8, c uint32) uint32
172+ purego.RegisterLibFunc(&fn, lib, cName)
173+ res := fn(1, 2, 1024)
174+ if res != expect {
175+ t.Fatalf("%s: got %d, want %d", cName, res, expect)
176+ }
177+ }
178+ {
179+ const cName = "stack_string"
180+ const expect = 255
181+ var fn func(a, b, c, d, e, f, g, h uint32, i string) uint32
182+ purego.RegisterLibFunc(&fn, lib, cName)
183+ res := fn(1, 2, 4, 8, 16, 32, 64, 128, "test")
184+ if res != expect {
185+ t.Fatalf("%s: got %d, want %d", cName, res, expect)
186+ }
187+ }
188+}
189+
190+func buildSharedLib(compilerEnv, libFile string, sources ...string) error {
191+ out, err := exec.Command("go", "env", compilerEnv).Output()
192+ if err != nil {
193+ return fmt.Errorf("go env %s error: %w", compilerEnv, err)
194+ }
195+
196+ compiler := strings.TrimSpace(string(out))
197+ if compiler == "" {
198+ return errors.New("compiler not found")
199+ }
200+
201+ args := []string{"-shared", "-Wall", "-Werror", "-fPIC", "-o", libFile}
202+ if runtime.GOARCH == "386" {
203+ args = append(args, "-m32")
204+ }
205+ // macOS arm64 can run amd64 tests through Rossetta.
206+ // Build the shared library based on the GOARCH and not
207+ // the default behavior of the compiler.
208+ if runtime.GOOS == "darwin" {
209+ var arch string
210+ switch runtime.GOARCH {
211+ case "arm64":
212+ arch = "arm64"
213+ case "amd64":
214+ arch = "x86_64"
215+ default:
216+ return fmt.Errorf("unknown macOS architecture %s", runtime.GOARCH)
217+ }
218+ args = append(args, "-arch", arch)
219+ }
220+ cmd := exec.Command(compiler, append(args, sources...)...)
221+ if out, err := cmd.CombinedOutput(); err != nil {
222+ return fmt.Errorf("compile lib: %w\n%q\n%s", err, cmd, string(out))
223+ }
224+
225+ return nil
226+}
@@ -4,8 +4,12 @@
4 package purego_test4 package purego_test
5 5
6 import (6 import (
7+ "errors"
7 "fmt"8 "fmt"
9+ "os/exec"
10+ "path/filepath"
8 "runtime"11 "runtime"
12+ "strings"
9 "testing"13 "testing"
10 "unsafe"14 "unsafe"
11 15
@@ -130,3 +134,93 @@ func TestRegisterLibFunc_Bool(t *testing.T) {
130 t.Errorf("runFalse failed. got %t but wanted %t", got, expected)134 t.Errorf("runFalse failed. got %t but wanted %t", got, expected)
131 }135 }
132 }136 }
137+
138+func TestABI(t *testing.T) {
139+ if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
140+ t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test
141+ }
142+ libFileName := filepath.Join(t.TempDir(), "abitest.so")
143+ t.Logf("Build %v", libFileName)
144+
145+ if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil {
146+ t.Fatal(err)
147+ }
148+
149+ lib, err := load.OpenLibrary(libFileName)
150+ if err != nil {
151+ t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
152+ }
153+ defer func() {
154+ if err := load.CloseLibrary(lib); err != nil {
155+ t.Fatalf("failed to close library: %s", err)
156+ }
157+ }()
158+ {
159+ const cName = "stack_uint8_t"
160+ const expect = 2047
161+ var fn func(a, b, c, d, e, f, g, h uint32, i, j uint8, k uint32) uint32
162+ purego.RegisterLibFunc(&fn, lib, cName)
163+ res := fn(256, 512, 4, 8, 16, 32, 64, 128, 1, 2, 1024)
164+ if res != expect {
165+ t.Fatalf("%s: got %d, want %d", cName, res, expect)
166+ }
167+ }
168+ {
169+ const cName = "reg_uint8_t"
170+ const expect = 1027
171+ var fn func(a, b uint8, c uint32) uint32
172+ purego.RegisterLibFunc(&fn, lib, cName)
173+ res := fn(1, 2, 1024)
174+ if res != expect {
175+ t.Fatalf("%s: got %d, want %d", cName, res, expect)
176+ }
177+ }
178+ {
179+ const cName = "stack_string"
180+ const expect = 255
181+ var fn func(a, b, c, d, e, f, g, h uint32, i string) uint32
182+ purego.RegisterLibFunc(&fn, lib, cName)
183+ res := fn(1, 2, 4, 8, 16, 32, 64, 128, "test")
184+ if res != expect {
185+ t.Fatalf("%s: got %d, want %d", cName, res, expect)
186+ }
187+ }
188+}
189+
190+func buildSharedLib(compilerEnv, libFile string, sources ...string) error {
191+ out, err := exec.Command("go", "env", compilerEnv).Output()
192+ if err != nil {
193+ return fmt.Errorf("go env %s error: %w", compilerEnv, err)
194+ }
195+
196+ compiler := strings.TrimSpace(string(out))
197+ if compiler == "" {
198+ return errors.New("compiler not found")
199+ }
200+
201+ args := []string{"-shared", "-Wall", "-Werror", "-fPIC", "-o", libFile}
202+ if runtime.GOARCH == "386" {
203+ args = append(args, "-m32")
204+ }
205+ // macOS arm64 can run amd64 tests through Rossetta.
206+ // Build the shared library based on the GOARCH and not
207+ // the default behavior of the compiler.
208+ if runtime.GOOS == "darwin" {
209+ var arch string
210+ switch runtime.GOARCH {
211+ case "arm64":
212+ arch = "arm64"
213+ case "amd64":
214+ arch = "x86_64"
215+ default:
216+ return fmt.Errorf("unknown macOS architecture %s", runtime.GOARCH)
217+ }
218+ args = append(args, "-arch", arch)
219+ }
220+ cmd := exec.Command(compiler, append(args, sources...)...)
221+ if out, err := cmd.CombinedOutput(); err != nil {
222+ return fmt.Errorf("compile lib: %w\n%q\n%s", err, cmd, string(out))
223+ }
224+
225+ return nil
226+}
M internal/load/load_unix.go +4 -0
@@ -11,6 +11,10 @@ func OpenLibrary(name string) (uintptr, error) {
1111 return purego.Dlopen(name, purego.RTLD_NOW|purego.RTLD_GLOBAL)
1212 }
1313
14+func CloseLibrary(handle uintptr) error {
15+ return purego.Dlclose(handle)
16+}
17+
1418 func OpenSymbol(lib uintptr, name string) (uintptr, error) {
1519 return purego.Dlsym(lib, name)
1620 }
@@ -11,6 +11,10 @@ func OpenLibrary(name string) (uintptr, error) {
11 return purego.Dlopen(name, purego.RTLD_NOW|purego.RTLD_GLOBAL)11 return purego.Dlopen(name, purego.RTLD_NOW|purego.RTLD_GLOBAL)
12 }12 }
13 13
14+func CloseLibrary(handle uintptr) error {
15+ return purego.Dlclose(handle)
16+}
17+
14 func OpenSymbol(lib uintptr, name string) (uintptr, error) {18 func OpenSymbol(lib uintptr, name string) (uintptr, error) {
15 return purego.Dlsym(lib, name)19 return purego.Dlsym(lib, name)
16 }20 }
M internal/load/load_windows.go +4 -0
@@ -12,6 +12,10 @@ func OpenLibrary(name string) (uintptr, error) {
1212 return uintptr(handle), err
1313 }
1414
15+func CloseLibrary(handle uintptr) error {
16+ return syscall.FreeLibrary(syscall.Handle(handle))
17+}
18+
1519 func OpenSymbol(lib uintptr, name string) (uintptr, error) {
1620 return syscall.GetProcAddress(syscall.Handle(lib), name)
1721 }
@@ -12,6 +12,10 @@ func OpenLibrary(name string) (uintptr, error) {
12 return uintptr(handle), err12 return uintptr(handle), err
13 }13 }
14 14
15+func CloseLibrary(handle uintptr) error {
16+ return syscall.FreeLibrary(syscall.Handle(handle))
17+}
18+
15 func OpenSymbol(lib uintptr, name string) (uintptr, error) {19 func OpenSymbol(lib uintptr, name string) (uintptr, error) {
16 return syscall.GetProcAddress(syscall.Handle(lib), name)20 return syscall.GetProcAddress(syscall.Handle(lib), name)
17 }21 }
M struct_amd64.go +4 -0
@@ -258,3 +258,7 @@ func placeStack(v reflect.Value, addStack func(uintptr)) {
258258 }
259259 }
260260 }
261+
262+func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
263+ panic("purego: not needed on amd64")
264+}
@@ -258,3 +258,7 @@ func placeStack(v reflect.Value, addStack func(uintptr)) {
258 }258 }
259 }259 }
260 }260 }
261+
262+func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
263+ panic("purego: not needed on amd64")
264+}
M struct_arm64.go +14 -4
@@ -73,8 +73,8 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
7373 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
7474 // if this doesn't fit entirely in registers then
7575 // each element goes onto the stack
76- if hfa && *numFloats+v.NumField() > numOfFloats {
77- *numFloats = numOfFloats
76+ if hfa && *numFloats+v.NumField() > numOfFloatRegisters {
77+ *numFloats = numOfFloatRegisters
7878 } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
7979 *numInts = numOfIntegerRegisters()
8080 }
@@ -107,6 +107,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
107107 } else {
108108 f = v.Index(k)
109109 }
110+ align := byte(f.Type().Align()*8 - 1)
111+ shift = (shift + align) &^ align
110112 if shift >= 64 {
111113 shift = 0
112114 flushed = true
@@ -137,10 +139,11 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
137139 val |= f.Uint() << shift
138140 shift += 32
139141 class |= _INT
140- case reflect.Uint64:
142+ case reflect.Uint64, reflect.Uint, reflect.Uintptr:
141143 addInt(uintptr(f.Uint()))
142144 shift = 0
143145 flushed = true
146+ class = _NO_CLASS
144147 case reflect.Int8:
145148 val |= uint64(f.Int()&0xFF) << shift
146149 shift += 8
@@ -153,10 +156,11 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
153156 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
154157 shift += 32
155158 class |= _INT
156- case reflect.Int64:
159+ case reflect.Int64, reflect.Int:
157160 addInt(uintptr(f.Int()))
158161 shift = 0
159162 flushed = true
163+ class = _NO_CLASS
160164 case reflect.Float32:
161165 if class == _FLOAT {
162166 addFloat(uintptr(val))
@@ -170,6 +174,12 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
170174 addFloat(uintptr(math.Float64bits(float64(f.Float()))))
171175 shift = 0
172176 flushed = true
177+ class = _NO_CLASS
178+ case reflect.Ptr:
179+ addInt(f.Pointer())
180+ shift = 0
181+ flushed = true
182+ class = _NO_CLASS
173183 case reflect.Array:
174184 place(f)
175185 default:
@@ -73,8 +73,8 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
73 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {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 then74 // if this doesn't fit entirely in registers then
75 // each element goes onto the stack75 // each element goes onto the stack
76- if hfa && *numFloats+v.NumField() > numOfFloats {76+ if hfa && *numFloats+v.NumField() > numOfFloatRegisters {
77- *numFloats = numOfFloats77+ *numFloats = numOfFloatRegisters
78 } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {78 } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
79 *numInts = numOfIntegerRegisters()79 *numInts = numOfIntegerRegisters()
80 }80 }
@@ -107,6 +107,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
107 } else {107 } else {
108 f = v.Index(k)108 f = v.Index(k)
109 }109 }
110+ align := byte(f.Type().Align()*8 - 1)
111+ shift = (shift + align) &^ align
110 if shift >= 64 {112 if shift >= 64 {
111 shift = 0113 shift = 0
112 flushed = true114 flushed = true
@@ -137,10 +139,11 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
137 val |= f.Uint() << shift139 val |= f.Uint() << shift
138 shift += 32140 shift += 32
139 class |= _INT141 class |= _INT
140- case reflect.Uint64:142+ case reflect.Uint64, reflect.Uint, reflect.Uintptr:
141 addInt(uintptr(f.Uint()))143 addInt(uintptr(f.Uint()))
142 shift = 0144 shift = 0
143 flushed = true145 flushed = true
146+ class = _NO_CLASS
144 case reflect.Int8:147 case reflect.Int8:
145 val |= uint64(f.Int()&0xFF) << shift148 val |= uint64(f.Int()&0xFF) << shift
146 shift += 8149 shift += 8
@@ -153,10 +156,11 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
153 val |= uint64(f.Int()&0xFFFF_FFFF) << shift156 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
154 shift += 32157 shift += 32
155 class |= _INT158 class |= _INT
156- case reflect.Int64:159+ case reflect.Int64, reflect.Int:
157 addInt(uintptr(f.Int()))160 addInt(uintptr(f.Int()))
158 shift = 0161 shift = 0
159 flushed = true162 flushed = true
163+ class = _NO_CLASS
160 case reflect.Float32:164 case reflect.Float32:
161 if class == _FLOAT {165 if class == _FLOAT {
162 addFloat(uintptr(val))166 addFloat(uintptr(val))
@@ -170,6 +174,12 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
170 addFloat(uintptr(math.Float64bits(float64(f.Float()))))174 addFloat(uintptr(math.Float64bits(float64(f.Float()))))
171 shift = 0175 shift = 0
172 flushed = true176 flushed = true
177+ class = _NO_CLASS
178+ case reflect.Ptr:
179+ addInt(f.Pointer())
180+ shift = 0
181+ flushed = true
182+ class = _NO_CLASS
173 case reflect.Array:183 case reflect.Array:
174 place(f)184 place(f)
175 default:185 default:
M struct_other.go +4 -0
@@ -14,3 +14,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
1414 func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
1515 panic("purego: struct returns are not supported")
1616 }
17+
18+func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
19+ panic("purego: not needed on other platforms")
20+}
@@ -14,3 +14,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
14 func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {14 func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
15 panic("purego: struct returns are not supported")15 panic("purego: struct returns are not supported")
16 }16 }
17+
18+func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
19+ panic("purego: not needed on other platforms")
20+}
M syscall.go +2 -2
@@ -13,8 +13,8 @@ package purego
1313 type CDecl struct{}
1414
1515 const (
16- maxArgs = 15
17- numOfFloats = 8 // arm64 and amd64 both have 8 float registers
16+ maxArgs = 15
17+ numOfFloatRegisters = 8 // arm64 and amd64 both have 8 float registers
1818 )
1919
2020 type syscall15Args struct {
@@ -13,8 +13,8 @@ package purego
13 type CDecl struct{}13 type CDecl struct{}
14 14
15 const (15 const (
16- maxArgs = 1516+ maxArgs = 15
17- numOfFloats = 8 // arm64 and amd64 both have 8 float registers17+ numOfFloatRegisters = 8 // arm64 and amd64 both have 8 float registers
18 )18 )
19 19
20 type syscall15Args struct {20 type syscall15Args struct {
M syscall_sysv.go +3 -3
@@ -146,12 +146,12 @@ func callbackWrap(a *callbackArgs) {
146146 var intsN int // intsN represents the number of integer arguments processed
147147 // stack points to the index into frame of the current stack element.
148148 // The stack begins after the float and integer registers.
149- stack := numOfIntegerRegisters() + numOfFloats
149+ stack := numOfIntegerRegisters() + numOfFloatRegisters
150150 for i := range args {
151151 var pos int
152152 switch fnType.In(i).Kind() {
153153 case reflect.Float32, reflect.Float64:
154- if floatsN >= numOfFloats {
154+ if floatsN >= numOfFloatRegisters {
155155 pos = stack
156156 stack++
157157 } else {
@@ -169,7 +169,7 @@ func callbackWrap(a *callbackArgs) {
169169 stack++
170170 } else {
171171 // the integers begin after the floats in frame
172- pos = intsN + numOfFloats
172+ pos = intsN + numOfFloatRegisters
173173 }
174174 intsN++
175175 }
@@ -146,12 +146,12 @@ func callbackWrap(a *callbackArgs) {
146 var intsN int // intsN represents the number of integer arguments processed146 var intsN int // intsN represents the number of integer arguments processed
147 // stack points to the index into frame of the current stack element.147 // stack points to the index into frame of the current stack element.
148 // The stack begins after the float and integer registers.148 // The stack begins after the float and integer registers.
149- stack := numOfIntegerRegisters() + numOfFloats149+ stack := numOfIntegerRegisters() + numOfFloatRegisters
150 for i := range args {150 for i := range args {
151 var pos int151 var pos int
152 switch fnType.In(i).Kind() {152 switch fnType.In(i).Kind() {
153 case reflect.Float32, reflect.Float64:153 case reflect.Float32, reflect.Float64:
154- if floatsN >= numOfFloats {154+ if floatsN >= numOfFloatRegisters {
155 pos = stack155 pos = stack
156 stack++156 stack++
157 } else {157 } else {
@@ -169,7 +169,7 @@ func callbackWrap(a *callbackArgs) {
169 stack++169 stack++
170 } else {170 } else {
171 // the integers begin after the floats in frame171 // the integers begin after the floats in frame
172- pos = intsN + numOfFloats172+ pos = intsN + numOfFloatRegisters
173 }173 }
174 intsN++174 intsN++
175 }175 }
A testdata/abitest/abi_test.c +27 -0
@@ -0,0 +1,27 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
3+
4+#include <stdint.h>
5+#include <assert.h>
6+#include <stdio.h>
7+#include <string.h>
8+
9+uint32_t stack_uint8_t(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f, uint32_t g, uint32_t h, uint8_t i, uint8_t j, uint32_t k ) {
10+ assert(i == 1);
11+ assert(j == 2);
12+ assert(k == 1024);
13+ return a | b | c | d | e | f | g | h | (uint32_t) i | (uint32_t) j | k;
14+}
15+
16+uint32_t reg_uint8_t(uint8_t a, uint8_t b, uint32_t c) {
17+ assert(a == 1);
18+ assert(b == 2);
19+ assert(c == 1024);
20+ return a | b | c;
21+}
22+
23+uint32_t stack_string(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f, uint32_t g, uint32_t h, const char * i) {
24+ assert(i != 0);
25+ assert(strcmp(i, "test") == 0);
26+ return a | b | c | d | e | f | g | h;
27+}
@@ -0,0 +1,27 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
3+
4+#include <stdint.h>
5+#include <assert.h>
6+#include <stdio.h>
7+#include <string.h>
8+
9+uint32_t stack_uint8_t(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f, uint32_t g, uint32_t h, uint8_t i, uint8_t j, uint32_t k ) {
10+ assert(i == 1);
11+ assert(j == 2);
12+ assert(k == 1024);
13+ return a | b | c | d | e | f | g | h | (uint32_t) i | (uint32_t) j | k;
14+}
15+
16+uint32_t reg_uint8_t(uint8_t a, uint8_t b, uint32_t c) {
17+ assert(a == 1);
18+ assert(b == 2);
19+ assert(c == 1024);
20+ return a | b | c;
21+}
22+
23+uint32_t stack_string(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f, uint32_t g, uint32_t h, const char * i) {
24+ assert(i != 0);
25+ assert(strcmp(i, "test") == 0);
26+ return a | b | c | d | e | f | g | h;
27+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close