detached 06b205c

Merge pull request #3 from bnema/realign-upstream

chore: realign fork with upstream ebitengine/purego
commit
06b205cdb626057ac7a9b636b7afc6338f7c3b04
author
bnema · · 2026-09-27 09:00 UTC
committer
GitHub · · 2026-09-27 09:00 UTC
merge of
cf1b83e + 61051dd · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
104 files · +4501 -2327
M .github/ISSUE_TEMPLATE/config.yml +1 -1
@@ -2,4 +2,4 @@ blank_issues_enabled: false
2 contact_links:2 contact_links:
3 - name: Questions3 - name: Questions
4 about: Please use one of the forums for questions or general discussions4 about: Please use one of the forums for questions or general discussions
5- url: https://github.com/bnema/purego/discussions5+ url: https://github.com/ebitengine/purego/discussions
M .github/workflows/test.yml +168 -37
@@ -3,15 +3,15 @@ name: Test
3 on: [push, pull_request]3 on: [push, pull_request]
4 4
5 env:5 env:
6- QEMU_VERSION: "11.0.1+ds-1"6+ QEMU_MAJOR_VERSION: "10"
7 7
8 jobs:8 jobs:
9 test:9 test:
10 strategy:10 strategy:
11 fail-fast: false11 fail-fast: false
12 matrix:12 matrix:
13- os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]13+ os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest, windows-11-arm]
14- go: ['1.20.x', '1.21.x', '1.22.x', '1.23.x', '1.24.x', '1.25.x', '1.26.x']14+ go: ['1.25.x', '1.26.x', '1.27.x']
15 name: Test with Go ${{ matrix.go }} on ${{ matrix.os }}15 name: Test with Go ${{ matrix.go }} on ${{ matrix.os }}
16 runs-on: ${{ matrix.os }}16 runs-on: ${{ matrix.os }}
17 defaults:17 defaults:
@@ -32,8 +32,8 @@ jobs:
32 with:32 with:
33 go-version: ${{ matrix.go }}33 go-version: ${{ matrix.go }}
34 34
35- - name: Set up the prerequisites35+ - name: Set up Msys2
36- if: runner.os == 'Windows'36+ if: runner.os == 'Windows' && runner.arch != 'ARM64'
37 uses: msys2/setup-msys2@v237 uses: msys2/setup-msys2@v2
38 38
39 - name: go vet39 - name: go vet
@@ -76,13 +76,21 @@ jobs:
76 76
77 - name: go build (Linux minor architectures)77 - name: go build (Linux minor architectures)
78 # Test them only on the latest Go to reduce CI time.78 # Test them only on the latest Go to reduce CI time.
79- # s390x cannot be tested here as it requires Cgo.79+ if: startsWith(matrix.go, '1.27.')
80- if: startsWith(matrix.go, '1.26.')
81 run: |80 run: |
82 # Check cross-compiling Linux binaries for minor architectures.81 # Check cross-compiling Linux binaries for minor architectures.
83 env GOOS=linux GOARCH=loong64 go build -v ./...82 env GOOS=linux GOARCH=loong64 go build -v ./...
84 env GOOS=linux GOARCH=ppc64le go build -v ./...83 env GOOS=linux GOARCH=ppc64le go build -v ./...
85 env GOOS=linux GOARCH=riscv64 go build -v ./...84 env GOOS=linux GOARCH=riscv64 go build -v ./...
85+ env GOOS=linux GOARCH=s390x go build -v ./...
86+
87+ - name: go build (Linux mips, Cgo)
88+ # mips builds only via the Cgo fallback (CGO_ENABLED=1). See #460.
89+ if: runner.os == 'Linux'
90+ run: |
91+ sudo apt-get update
92+ sudo apt-get install -y gcc-mips-linux-gnu
93+ env GOOS=linux GOARCH=mips CGO_ENABLED=1 CC=mips-linux-gnu-gcc go build -v ./...
86 94
87 - name: go build (plugin)95 - name: go build (plugin)
88 if: runner.os == 'Linux' || runner.os == 'macOS'96 if: runner.os == 'Linux' || runner.os == 'macOS'
@@ -120,32 +128,68 @@ jobs:
120 if: runner.os == 'Windows'128 if: runner.os == 'Windows'
121 run: |129 run: |
122 env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./...130 env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./...
131+
132+ - name: go test (Windows 386, Cgo)
133+ if: runner.os == 'Windows' && runner.arch != 'ARM64'
134+ run: |
123 env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./...135 env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./...
124 136
137+ - name: go test (Linux 386)
138+ if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' }}
139+ run: |
140+ sudo apt-get update
141+ sudo apt-get install gcc-multilib
142+ sudo apt-get install g++-multilib
143+ env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./...
144+ env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./...
145+
125 - name: go test (Linux arm)146 - name: go test (Linux arm)
126 if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }}147 if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }}
127 run: |148 run: |
128- sudo apt-get update149+ sudo apt-get update
129- sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross150+ sudo apt-get install -y \
130-151+ gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross \
131- # Install QEMU 10.x from Debian trixie. Ubuntu 24.04 ships QEMU 8.2152+ gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libc6-armel-cross
132- # which has a bug where inter-thread signal delivery (tgkill) hangs,153+
133- # causing TestSetuidEtc to deadlock.154+ # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2
134- wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user_${QEMU_VERSION}_arm64.deb -O /tmp/qemu-user.deb155+ # which has a bug where inter-thread signal delivery (tgkill) hangs,
135- wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_${QEMU_VERSION}_arm64.deb -O /tmp/qemu-user-binfmt.deb156+ # causing TestSetuidEtc to deadlock.
136- sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.deb157+ arch=$(dpkg --print-architecture)
137- sudo apt-get -f install -y158+ qemu_user_deb=$(
138-159+ curl -fsSL https://ftp.debian.org/debian/pool/main/q/qemu/ |
139- # Create symlink for the dynamic linker that QEMU expects160+ grep -Eo "qemu-user_${QEMU_MAJOR_VERSION}\.[0-9.]+\+ds-[0-9]+([+~][A-Za-z0-9.+:~-]+)*_${arch}\.deb" |
140- sudo ln -sf /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3161+ sort -V |
141- go env -w CC=arm-linux-gnueabihf-gcc162+ tail -n 1
142- go env -w CXX=arm-linux-gnueabihf-g++163+ )
143- env GOOS=linux GOARCH=arm CGO_ENABLED=0 go test -c -o=purego-test-nocgo .164+ if [ -z "${qemu_user_deb}" ]; then
144- env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10165+ echo "missing qemu-user ${QEMU_MAJOR_VERSION}.x package for ${arch}" >&2
145- env GOOS=linux GOARCH=arm CGO_ENABLED=1 go test -c -o=purego-test-cgo .166+ exit 1
146- env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10167+ fi
147- go env -u CC168+ wget -q "https://ftp.debian.org/debian/pool/main/q/qemu/${qemu_user_deb}" -O /tmp/qemu-user.deb
148- go env -u CXX169+ sudo dpkg --force-depends -i /tmp/qemu-user.deb
170+ sudo apt-get -f install -y
171+
172+ # Create symlink for the dynamic linker that QEMU expects
173+ sudo ln -sf /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
174+ go env -w CC=arm-linux-gnueabihf-gcc
175+ go env -w CXX=arm-linux-gnueabihf-g++
176+ env GOOS=linux GOARCH=arm CGO_ENABLED=0 go test -c -o=purego-test-nocgo .
177+ env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10
178+ env GOOS=linux GOARCH=arm CGO_ENABLED=1 go test -c -o=purego-test-cgo .
179+ env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10
180+ go env -u CC
181+ go env -u CXX
182+
183+ # Repeat the same for ARMv5 target (soft-float)
184+ sudo ln -sf /usr/arm-linux-gnueabi/lib/ld-linux.so.3 /lib/ld-linux.so.3
185+ go env -w CC=arm-linux-gnueabi-gcc
186+ go env -w CXX=arm-linux-gnueabi-g++
187+ env GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=0 go test -c -o=purego-test-nocgo .
188+ env QEMU_LD_PREFIX=/usr/arm-linux-gnueabi qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10
189+ env GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 go test -c -o=purego-test-cgo .
190+ env QEMU_LD_PREFIX=/usr/arm-linux-gnueabi qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10
191+ go env -u CC
192+ go env -u CXX
149 193
150 - name: go test race (no Cgo)194 - name: go test race (no Cgo)
151 if: runner.os == 'macOS'195 if: runner.os == 'macOS'
@@ -154,6 +198,7 @@ jobs:
154 env CGO_ENABLED=0 go test -race -shuffle=on -v -count=10 ./...198 env CGO_ENABLED=0 go test -race -shuffle=on -v -count=10 ./...
155 199
156 - name: go test race (Cgo)200 - name: go test race (Cgo)
201+ if: ${{ !(runner.os == 'Windows' && runner.arch == 'ARM64') }}
157 run: |202 run: |
158 env CGO_ENABLED=1 go test -race -shuffle=on -v -count=10 ./...203 env CGO_ENABLED=1 go test -race -shuffle=on -v -count=10 ./...
159 204
@@ -161,9 +206,7 @@ jobs:
161 strategy:206 strategy:
162 fail-fast: false207 fail-fast: false
163 matrix:208 matrix:
164- # Test only the latest two Go versions to save CI time.209+ go: ['1.25.x', '1.26.x', '1.27.x']
165- # See https://go.dev/doc/devel/release#policy
166- go: ['1.25.x', '1.26.x']
167 name: Test with Go ${{ matrix.go }} on Linux minor architectures210 name: Test with Go ${{ matrix.go }} on Linux minor architectures
168 runs-on: ubuntu-latest211 runs-on: ubuntu-latest
169 defaults:212 defaults:
@@ -187,12 +230,22 @@ jobs:
187 gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \230 gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \
188 gcc-s390x-linux-gnu g++-s390x-linux-gnu231 gcc-s390x-linux-gnu g++-s390x-linux-gnu
189 232
190- # Install QEMU 10.x from Debian trixie. Ubuntu 24.04 ships QEMU 8.2233+ # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2
191 # which has a bug where inter-thread signal delivery (tgkill) hangs,234 # which has a bug where inter-thread signal delivery (tgkill) hangs,
192 # causing TestSetuidEtc to deadlock.235 # causing TestSetuidEtc to deadlock.
193- wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user_${QEMU_VERSION}_amd64.deb -O /tmp/qemu-user.deb236+ arch=$(dpkg --print-architecture)
194- wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_${QEMU_VERSION}_amd64.deb -O /tmp/qemu-user-binfmt.deb237+ qemu_user_deb=$(
195- sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.deb238+ curl -fsSL https://ftp.debian.org/debian/pool/main/q/qemu/ |
239+ grep -Eo "qemu-user_${QEMU_MAJOR_VERSION}\.[0-9.]+\+ds-[0-9]+([+~][A-Za-z0-9.+:~-]+)*_${arch}\.deb" |
240+ sort -V |
241+ tail -n 1
242+ )
243+ if [ -z "${qemu_user_deb}" ]; then
244+ echo "missing qemu-user ${QEMU_MAJOR_VERSION}.x package for ${arch}" >&2
245+ exit 1
246+ fi
247+ wget -q "https://ftp.debian.org/debian/pool/main/q/qemu/${qemu_user_deb}" -O /tmp/qemu-user.deb
248+ sudo dpkg --force-depends -i /tmp/qemu-user.deb
196 sudo apt-get -f install -y249 sudo apt-get -f install -y
197 250
198 # Verify that the expected QEMU binaries are installed and usable.251 # Verify that the expected QEMU binaries are installed and usable.
@@ -240,6 +293,11 @@ jobs:
240 run: |293 run: |
241 go env -w CC=s390x-linux-gnu-gcc294 go env -w CC=s390x-linux-gnu-gcc
242 go env -w CXX=s390x-linux-gnu-g++295 go env -w CXX=s390x-linux-gnu-g++
296+ # CGO_ENABLED=0 is only supported on Go 1.27+ for s390x.
297+ if [[ "${{ matrix.go }}" != 1.25.* && "${{ matrix.go }}" != 1.26.* ]]; then
298+ env GOOS=linux GOARCH=s390x CGO_ENABLED=0 go test -c -o=purego-test-nocgo .
299+ env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10
300+ fi
243 env GOOS=linux GOARCH=s390x CGO_ENABLED=1 go test -c -o=purego-test-cgo .301 env GOOS=linux GOARCH=s390x CGO_ENABLED=1 go test -c -o=purego-test-cgo .
244 env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-cgo -test.shuffle=on -test.v -test.count=10302 env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-cgo -test.shuffle=on -test.v -test.count=10
245 go env -u CC303 go env -u CC
@@ -250,8 +308,6 @@ jobs:
250 fail-fast: false308 fail-fast: false
251 matrix:309 matrix:
252 os: ['FreeBSD', 'NetBSD']310 os: ['FreeBSD', 'NetBSD']
253- # Test only the latest two Go versions to save CI time.
254- # See https://go.dev/doc/devel/release#policy
255 go: ['1.25.8', '1.26.1']311 go: ['1.25.8', '1.26.1']
256 name: Test with Go ${{ matrix.go }} on ${{ matrix.os }}312 name: Test with Go ${{ matrix.go }} on ${{ matrix.os }}
257 runs-on: ubuntu-22.04313 runs-on: ubuntu-22.04
@@ -281,3 +337,78 @@ jobs:
281 rm -fr /usr/local/go && tar -C /usr/local -xf go${{matrix.go}}.netbsd-amd64.tar.gz337 rm -fr /usr/local/go && tar -C /usr/local -xf go${{matrix.go}}.netbsd-amd64.tar.gz
282 chmod +x $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh338 chmod +x $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh
283 run: $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh339 run: $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh
340+
341+ # Android is tested on amd64 only. The emulator requires the system image
342+ # architecture to match the host, and GitHub's arm64 hosted runners do not
343+ # provide a usable KVM device, so there is no hosted hardware-accelerated path
344+ # to an Android arm64 emulator. The arm64 struct ABI is already covered by the
345+ # linux/arm64 `test` job, and Android's Bionic/cgo integration by this job.
346+ android:
347+ strategy:
348+ fail-fast: false
349+ matrix:
350+ go: ['1.25.x', '1.26.x', '1.27.x']
351+ name: Test with Go ${{ matrix.go }} on Android amd64
352+ runs-on: ubuntu-latest
353+ defaults:
354+ run:
355+ shell: bash
356+ steps:
357+ - uses: actions/checkout@v5
358+
359+ - name: Setup Go
360+ uses: actions/setup-go@v6
361+ with:
362+ go-version: ${{ matrix.go }}
363+
364+ # The tests compile their C libraries at run time via `go env CC`, but the
365+ # emulator has no C toolchain. Every library the tests load must therefore be
366+ # cross-compiled here and loaded as a prebuilt library at run time instead.
367+ - name: Cross-compile the test binary and C libraries
368+ run: |
369+ set -eux
370+ NDK="$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
371+ CC="$NDK/x86_64-linux-android21-clang"
372+ mkdir -p prebuilt
373+ # Build every library the tests load. Keep this in sync with the
374+ # buildSharedLib callers in the tests; a missing entry surfaces as a
375+ # "prebuilt lib ... no such file" failure on the emulator.
376+ "$CC" -shared -Wall -Werror -fPIC -o prebuilt/structtest.so testdata/structtest/struct_test.c
377+ "$CC" -shared -Wall -Werror -fPIC -o prebuilt/structreturntest.so testdata/structtest/structreturn_test.c
378+ "$CC" -shared -Wall -Werror -fPIC -o prebuilt/libcbtest.so testdata/libcbtest/callback_test.c
379+ "$CC" -shared -Wall -Werror -fPIC -o prebuilt/abitest.so testdata/abitest/abi_test.c
380+ "$CC" -shared -Wall -Werror -fPIC -o prebuilt/slicetest.so testdata/slicetest/slice_test.c
381+ env GOOS=android GOARCH=amd64 CGO_ENABLED=1 CC="$CC" go test -c -o purego.test .
382+
383+ - name: Enable KVM
384+ run: |
385+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
386+ sudo udevadm control --reload-rules
387+ sudo udevadm trigger --name-match=kvm
388+
389+ - name: Run the test suite on the Android emulator
390+ uses: reactivecircus/android-emulator-runner@v2
391+ with:
392+ api-level: 34
393+ arch: x86_64
394+ target: google_apis
395+ emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim
396+ script: |
397+ set -eux
398+ adb push purego.test /data/local/tmp/purego.test
399+ adb push prebuilt /data/local/tmp/prebuilt
400+ adb shell "chmod 755 /data/local/tmp/purego.test"
401+ # TestSetuidEtc needs root and glibc (not Bionic) semantics; TestNestedDlopenCall
402+ # needs the C++ runtime present on the device. Both are covered by the Linux
403+ # runners, so skip them here rather than special-casing the emulator.
404+ adb shell "cd /data/local/tmp && TMPDIR=/data/local/tmp PUREGO_TEST_PREBUILT_LIBDIR=/data/local/tmp/prebuilt ./purego.test -test.v -test.skip 'TestSetuidEtc|TestNestedDlopenCall'" | tee result.txt
405+ grep -q '^PASS' result.txt
406+ ! grep -q '^FAIL' result.txt
407+
408+ # TODO: Add an iOS job that runs the test suite on the iOS Simulator.
409+ # iOS has no emulator runner equivalent: on a macOS runner the test binary must
410+ # be wrapped into a .app and launched via `xcrun simctl` (or x/mobile's
411+ # go_ios_exec), with the C libraries cross-compiled against the iphonesimulator
412+ # SDK and loaded through PUREGO_TEST_PREBUILT_LIBDIR (as the android job does).
413+ # Deprioritized because the iOS arm64 ABI matches macOS arm64, which the `test`
414+ # job already runs.
M README.md +15 -27
@@ -3,8 +3,6 @@
3 3
4 A library for calling C functions from Go without Cgo.4 A library for calling C functions from Go without Cgo.
5 5
6-> Fork notice: this repository is a maintained fork of `github.com/ebitengine/purego` with additional ABI, callback, and generator-facing fixes needed by the PureGoTK stack.
7-
8 > This is beta software so expect bugs and potentially API breaking changes6 > This is beta software so expect bugs and potentially API breaking changes
9 > but each release will be tagged to avoid breaking people's code.7 > but each release will be tagged to avoid breaking people's code.
10 > Bug reports are encouraged.8 > Bug reports are encouraged.
@@ -36,24 +34,27 @@ Tier 1 platforms are the primary targets officially supported by PureGo. When a
36 - **iOS**: amd64<sup>1</sup>, arm64<sup>1</sup>34 - **iOS**: amd64<sup>1</sup>, arm64<sup>1</sup>
37 - **Linux**: amd64, arm6435 - **Linux**: amd64, arm64
38 - **macOS**: amd64, arm6436 - **macOS**: amd64, arm64
39-- **Windows**: amd64, arm6437+- **Windows**: amd64<sup>2</sup>, arm64<sup>2</sup>
40 38
41 ### Tier 239 ### Tier 2
42 40
43 Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs on Tier 2 platforms do not block new PureGo releases. However, fixes contributed by external contributors are very welcome and encouraged.41 Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs on Tier 2 platforms do not block new PureGo releases. However, fixes contributed by external contributors are very welcome and encouraged.
44 42
45-- **Android**: 386<sup>1</sup>, arm<sup>1</sup>43+- **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup>
46-- **FreeBSD**: amd64<sup>2</sup>, arm64<sup>2</sup>44+- **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>
47-- **Linux**: 386, arm, loong64, ppc64le, riscv64, s390x<sup>1</sup>45+- **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>2</sup>, ppc64le<sup>2</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup>
48-- **NetBSD**: amd64<sup>2</sup>, arm64<sup>2</sup>46+- **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>
49-- **Windows**: 386<sup>3</sup>, arm<sup>3,4</sup>47+- **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup>
50 48
51 #### Support Notes49 #### Support Notes
52 50
53 1. These architectures require CGO_ENABLED=1 to compile51 1. These architectures require CGO_ENABLED=1 to compile
54-2. These architectures require the special flag `-gcflags="github.com/bnema/purego/internal/fakecgo=-std"` to compile with CGO_ENABLED=052+2. These architectures support passing structs by value as arguments and return values when calling C functions, but not in callbacks created with `NewCallback`
55-3. These architectures only support `SyscallN` and `NewCallback`53+3. These architectures do not support passing structs by value as arguments or return values
56-4. These architectures are no longer supported as of Go 1.2654+4. These architectures require the special flag `-gcflags="github.com/bnema/purego/internal/fakecgo=-std"` to compile with CGO_ENABLED=0
55+5. These architectures require CGO_ENABLED=1 to compile in versions before Go 1.27, but will be supported without Cgo in Go 1.27 and later
56+6. These architectures only support `SyscallN` and `NewCallback`
57+7. These architectures are no longer supported as of Go 1.26
57 58
58 ## Example59 ## Example
59 60
@@ -101,21 +102,9 @@ exhaustion on Unix-like platforms, set `PUREGO_CALLBACK_LEDGER=1` to append JSON
101 callback lifecycle events to `/tmp/purego-callback-ledger-<pid>.jsonl`, or set102 callback lifecycle events to `/tmp/purego-callback-ledger-<pid>.jsonl`, or set
102 `PUREGO_CALLBACK_LEDGER_FILE=/path/to/file.jsonl` to choose the output file. Stack103 `PUREGO_CALLBACK_LEDGER_FILE=/path/to/file.jsonl` to choose the output file. Stack
103 capture is also disabled by default; set `PUREGO_CALLBACK_LEDGER_STACK=1` when call104 capture is also disabled by default; set `PUREGO_CALLBACK_LEDGER_STACK=1` when call
104-site attribution is needed. Stack capture adds per-callback overhead and should be105+site attribution is needed. For ad-hoc stderr/file traces near callback exhaustion,
105-reserved for active debugging sessions. For ad-hoc stderr/file traces near callback106+set `PUREGO_CALLBACK_TRACE=1`; `PUREGO_CALLBACK_TRACE_FILE` overrides the default
106-exhaustion, set `PUREGO_CALLBACK_TRACE=1`; `PUREGO_CALLBACK_TRACE_FILE` overrides107+`/tmp/purego-callback-trace-<pid>.log` path.
107-the default `/tmp/purego-callback-trace-<pid>.log` path.
108-
109-Diagnostic files are created with `0o600` permissions. They are append-only and
110-unbounded, so long-running processes can exhaust disk space if diagnostics stay
111-enabled. A ledger line is a JSON object such as:
112-
113-```json
114-{"time":"2026-06-09T14:03:00Z","pid":1234,"seq":42,"event":"alloc","index":7,"addr":"0x1234","stack":"..."}
115-```
116-
117-Use `time` as the timestamp, `index` as the callback ID, and the `pid`, `seq`,
118-`event`, `addr`, and optional `stack` fields when parsing the ledger output.
119 108
120 ## Questions109 ## Questions
121 110
@@ -130,7 +119,6 @@ This is a list of the copied files:
130 119
131 * `abi_*.h` from package `runtime/cgo`120 * `abi_*.h` from package `runtime/cgo`
132 * `wincallback.go` from package `runtime`121 * `wincallback.go` from package `runtime`
133-* `zcallback_darwin_*.s` from package `runtime`
134 * `internal/fakecgo/abi_*.h` from package `runtime/cgo`122 * `internal/fakecgo/abi_*.h` from package `runtime/cgo`
135 * `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo`123 * `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo`
136 * `internal/fakecgo/callbacks.go` from package `runtime/cgo`124 * `internal/fakecgo/callbacks.go` from package `runtime/cgo`
M callback_test.go +102 -9
@@ -22,7 +22,7 @@ func TestCallGoFromSharedLib(t *testing.T) {
22 libFileName := filepath.Join(t.TempDir(), "libcbtest.so")22 libFileName := filepath.Join(t.TempDir(), "libcbtest.so")
23 t.Logf("Build %v", libFileName)23 t.Logf("Build %v", libFileName)
24 24
25- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {25+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {
26 t.Fatal(err)26 t.Fatal(err)
27 }27 }
28 defer os.Remove(libFileName)28 defer os.Remove(libFileName)
@@ -43,7 +43,7 @@ func TestCallGoFromSharedLib(t *testing.T) {
43 43
44 const want = 1010144 const want = 10101
45 cb := purego.NewCallback(goFunc)45 cb := purego.NewCallback(goFunc)
46- for i := 0; i < 10; i++ {46+ for i := range 10 {
47 got := callCallback(cb, "a test string")47 got := callCallback(cb, "a test string")
48 if got != want {48 if got != want {
49 t.Fatalf("%d: callCallback() got %v want %v", i, got, want)49 t.Fatalf("%d: callCallback() got %v want %v", i, got, want)
@@ -57,7 +57,7 @@ func TestCallbackStringConversion(t *testing.T) {
57 libFileName := filepath.Join(t.TempDir(), "libcbtest.so")57 libFileName := filepath.Join(t.TempDir(), "libcbtest.so")
58 t.Logf("Build %v", libFileName)58 t.Logf("Build %v", libFileName)
59 59
60- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {60+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {
61 t.Fatal(err)61 t.Fatal(err)
62 }62 }
63 defer os.Remove(libFileName)63 defer os.Remove(libFileName)
@@ -191,6 +191,53 @@ func TestNewCallbackFloat32AndFloat64(t *testing.T) {
191 }191 }
192 }192 }
193 193
194+func TestNewCallbackInt64Result(t *testing.T) {
195+ // A 64-bit result needs two words: EDX:EAX on 386 and R1:R0 on arm.
196+ // The trampoline must forward both halves of the value.
197+ const mask = int64(0x0123456789abcdef)
198+ imp := purego.NewCallback(func(v int64) int64 { return v ^ mask })
199+ var fn func(v int64) int64
200+ purego.RegisterFunc(&fn, imp)
201+ for _, v := range []int64{
202+ 0,
203+ -1,
204+ 1 << 32,
205+ -0x7edcba9876543210,
206+ } {
207+ if got, want := fn(v), v^mask; got != want {
208+ t.Errorf("callback(%#x) = %#x, want %#x", uint64(v), uint64(got), uint64(want))
209+ }
210+ }
211+}
212+
213+func TestNewCallbackUint64Result(t *testing.T) {
214+ // The upper half of a uint64 result must not be dropped by the trampoline.
215+ imp := purego.NewCallback(func(lo, hi uint32) uint64 { return uint64(hi)<<32 | uint64(lo) })
216+ var fn func(lo, hi uint32) uint64
217+ purego.RegisterFunc(&fn, imp)
218+ const want = uint64(0x0123456789abcdef)
219+ if got := fn(0x89abcdef, 0x01234567); got != want {
220+ t.Errorf("callback() = %#x, want %#x", got, want)
221+ }
222+}
223+
224+func TestNewCallbackInt64ResultWithStackArgs(t *testing.T) {
225+ // On 386 all the arguments are passed on the stack, so this also checks
226+ // that the copied arguments do not overlap the result of the callback.
227+ // The number of arguments is kept small enough for ppc64le, which only
228+ // has maxArgs(15) slots in total, 8 of them in integer registers.
229+ const wantSum = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12
230+ imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 int) int64 {
231+ return int64(a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12)<<32 | 0x0badf00d
232+ })
233+ var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 int) int64
234+ purego.RegisterFunc(&fn, imp)
235+ const want = int64(wantSum)<<32 | 0x0badf00d
236+ if got := fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); got != want {
237+ t.Errorf("callback() = %#x, want %#x", uint64(got), uint64(want))
238+ }
239+}
240+
194 func ExampleNewCallback() {241 func ExampleNewCallback() {
195 cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int) int {242 cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int) int {
196 fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)243 fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)
@@ -223,7 +270,7 @@ func TestCallbackInt32Packing(t *testing.T) {
223 }270 }
224 271
225 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")272 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
226- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {273+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
227 t.Fatal(err)274 t.Fatal(err)
228 }275 }
229 defer os.Remove(libFileName)276 defer os.Remove(libFileName)
@@ -255,7 +302,7 @@ func TestCallbackMixedStackPacking(t *testing.T) {
255 }302 }
256 303
257 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")304 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
258- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {305+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
259 t.Fatal(err)306 t.Fatal(err)
260 }307 }
261 defer os.Remove(libFileName)308 defer os.Remove(libFileName)
@@ -288,7 +335,7 @@ func TestCallbackSmallTypesPacking(t *testing.T) {
288 }335 }
289 336
290 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")337 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
291- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {338+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
292 t.Fatal(err)339 t.Fatal(err)
293 }340 }
294 defer os.Remove(libFileName)341 defer os.Remove(libFileName)
@@ -356,7 +403,7 @@ func TestCallback10Int32Packing(t *testing.T) {
356 }403 }
357 404
358 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")405 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
359- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {406+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
360 t.Fatal(err)407 t.Fatal(err)
361 }408 }
362 defer os.Remove(libFileName)409 defer os.Remove(libFileName)
@@ -387,7 +434,7 @@ func TestCallbackFloat64StackPacking(t *testing.T) {
387 }434 }
388 435
389 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")436 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
390- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {437+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
391 t.Fatal(err)438 t.Fatal(err)
392 }439 }
393 defer os.Remove(libFileName)440 defer os.Remove(libFileName)
@@ -421,7 +468,7 @@ func TestCallbackFloat32StackPacking(t *testing.T) {
421 }468 }
422 469
423 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")470 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
424- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {471+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
425 t.Fatal(err)472 t.Fatal(err)
426 }473 }
427 defer os.Remove(libFileName)474 defer os.Remove(libFileName)
@@ -448,3 +495,49 @@ func TestCallbackFloat32StackPacking(t *testing.T) {
448 t.Errorf("callCallback12Float32() = %d, want %d", got, want)495 t.Errorf("callCallback12Float32() = %d, want %d", got, want)
449 }496 }
450 }497 }
498+
499+func TestRegisterFuncExpandFinalAnyArgument(t *testing.T) {
500+ // The last argument is expanded into the C arguments held in it,
501+ // whether it is declared as ...any or as []any (#506).
502+ cb := purego.NewCallback(func(a, b, c uintptr) uintptr {
503+ return a*100 + b*10 + c
504+ })
505+ const want = uintptr(123)
506+ t.Run("Variadic", func(t *testing.T) {
507+ var fn func(a uintptr, args ...any) uintptr
508+ purego.RegisterFunc(&fn, cb)
509+ if got := fn(1, uintptr(2), uintptr(3)); got != want {
510+ t.Errorf("func(uintptr, ...any) = %d, want %d", got, want)
511+ }
512+ })
513+ t.Run("Slice", func(t *testing.T) {
514+ var fn func(a uintptr, args []any) uintptr
515+ purego.RegisterFunc(&fn, cb)
516+ if got := fn(1, []any{uintptr(2), uintptr(3)}); got != want {
517+ t.Errorf("func(uintptr, []any) = %d, want %d", got, want)
518+ }
519+ })
520+}
521+
522+func TestRegisterFuncKeepNonFinalAnyArgument(t *testing.T) {
523+ // A []any that isn't the last argument is a regular argument:
524+ // it is passed as one value and must not panic (#506).
525+ var first any
526+ var second uintptr
527+ cb := purego.NewCallback(func(args *any, b uintptr) uintptr {
528+ first = *args
529+ second = b
530+ return 0
531+ })
532+ var fn func(args []any, b uintptr) uintptr
533+ purego.RegisterFunc(&fn, cb)
534+ s := []any{uintptr(2), uintptr(3)}
535+ fn(s, 7)
536+ runtime.KeepAlive(s)
537+ if want := any(uintptr(2)); first != want {
538+ t.Errorf("the first C argument = %v, want %v", first, want)
539+ }
540+ if second != 7 {
541+ t.Errorf("the second C argument = %d, want 7", second)
542+ }
543+}
A cdecl.go +11 -0
@@ -0,0 +1,11 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+package purego
5+
6+// CDecl marks a function as being called using the __cdecl calling convention as defined in
7+// the [MSDocs] when passed to NewCallback. It must be the first argument to the function.
8+// This is only useful on 386 Windows, but it is safe to use on other platforms.
9+//
10+// [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170
11+type CDecl struct{}
A cstrings/nsstrings_darwin.go +44 -0
@@ -0,0 +1,44 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+package cstrings
5+
6+import (
7+ "fmt"
8+
9+ "github.com/bnema/purego"
10+ "github.com/bnema/purego/internal/strings"
11+ "github.com/bnema/purego/objc"
12+)
13+
14+var (
15+ sel_isKindOf objc.SEL
16+ sel_UTF8String objc.SEL
17+
18+ class_NSString objc.Class
19+)
20+
21+func init() {
22+ // Must pull in Foundation to get the NSString class.
23+ _, err := purego.Dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", purego.RTLD_GLOBAL|purego.RTLD_NOW)
24+ if err != nil {
25+ panic(fmt.Errorf("cstrings: %w", err))
26+ }
27+ sel_isKindOf = objc.RegisterName("isKindOfClass:")
28+ sel_UTF8String = objc.RegisterName("UTF8String")
29+ class_NSString = objc.GetClass("NSString")
30+}
31+
32+// NSStringToString returns a copy of the NSString contents as a Go string.
33+// If the ID is 0 then an empty string is returned.
34+// If the ID is not an NSString class then the function panics.
35+// This function is only available on darwin.
36+func NSStringToString(str objc.ID) string {
37+ if str == 0 {
38+ return ""
39+ }
40+ if str.Send(sel_isKindOf, class_NSString) == 0 {
41+ panic("cstrings: provided ID is not an NSString")
42+ }
43+ return strings.GoString(uintptr(str.Send(sel_UTF8String)))
44+}
A cstrings/nsstrings_darwin_test.go +54 -0
@@ -0,0 +1,54 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+package cstrings_test
5+
6+import (
7+ "testing"
8+
9+ "github.com/bnema/purego/cstrings"
10+ "github.com/bnema/purego/objc"
11+)
12+
13+func TestNSStringToString(t *testing.T) {
14+ t.Run("nil ID returns empty string", func(t *testing.T) {
15+ result := cstrings.NSStringToString(0)
16+ if result != "" {
17+ t.Errorf("expected empty string for nil ID, got %q", result)
18+ }
19+ })
20+
21+ t.Run("valid NSString returns correct string", func(t *testing.T) {
22+ sel := objc.RegisterName("stringWithUTF8String:")
23+ nsString := objc.ID(objc.GetClass("NSString")).Send(sel, "Hello, World!\x00")
24+
25+ result := cstrings.NSStringToString(nsString)
26+ if result != "Hello, World!" {
27+ t.Errorf("expected %q, got %q", "Hello, World!", result)
28+ }
29+ })
30+
31+ t.Run("empty NSString returns empty string", func(t *testing.T) {
32+ sel := objc.RegisterName("stringWithUTF8String:")
33+ nsString := objc.ID(objc.GetClass("NSString")).Send(sel, "\x00")
34+
35+ result := cstrings.NSStringToString(nsString)
36+ if result != "" {
37+ t.Errorf("expected empty string, got %q", result)
38+ }
39+ })
40+
41+ t.Run("non-NSString panics", func(t *testing.T) {
42+ defer func() {
43+ if r := recover(); r == nil {
44+ t.Error("expected panic for non-NSString ID")
45+ }
46+ }()
47+
48+ classNSNumber := objc.GetClass("NSNumber")
49+ sel := objc.RegisterName("numberWithInt:")
50+ nsNumber := objc.ID(classNSNumber).Send(sel, 42)
51+
52+ cstrings.NSStringToString(nsNumber)
53+ })
54+}
M dlfcn_test.go +31 -1
@@ -8,6 +8,7 @@ package purego_test
8 import (8 import (
9 "os"9 "os"
10 "path/filepath"10 "path/filepath"
11+ "syscall"
11 "testing"12 "testing"
12 "unsafe"13 "unsafe"
13 14
@@ -24,7 +25,7 @@ func TestNestedDlopenCall(t *testing.T) {
24 libFileName := filepath.Join(t.TempDir(), "libdlnested.so")25 libFileName := filepath.Join(t.TempDir(), "libdlnested.so")
25 t.Logf("Build %v", libFileName)26 t.Logf("Build %v", libFileName)
26 27
27- if err := buildSharedLib("CXX", libFileName, filepath.Join("testdata", "libdlnested", "nested_test.cpp")); err != nil {28+ if err := buildSharedLib(t, "CXX", libFileName, filepath.Join("testdata", "libdlnested", "nested_test.cpp")); err != nil {
28 t.Fatal(err)29 t.Fatal(err)
29 }30 }
30 defer os.Remove(libFileName)31 defer os.Remove(libFileName)
@@ -48,3 +49,32 @@ func TestSyscallN(t *testing.T) {
48 t.Fatalf("SyscallN didn't return the same result as purego.Dlsym: %d", err2)49 t.Fatalf("SyscallN didn't return the same result as purego.Dlsym: %d", err2)
49 }50 }
50 }51 }
52+
53+func TestSyscallNErrnoIsNotAnInputArgument(t *testing.T) {
54+ if errnoIsCaptured() {
55+ t.Skip("this platform saves errno; see TestErrno")
56+ }
57+
58+ openSym, err := purego.Dlsym(purego.RTLD_DEFAULT, "open")
59+ if err != nil {
60+ t.Fatal(err)
61+ }
62+
63+ path, err := syscall.BytePtrFromString("_file_that_does_not_exist_")
64+ if err != nil {
65+ t.Fatal(err)
66+ }
67+
68+ // The third argument is non-zero so that echoing it back as errno is visible.
69+ const third = uintptr(0o600)
70+ r1, _, errno := purego.SyscallN(openSym,
71+ uintptr(unsafe.Pointer(path)),
72+ uintptr(os.O_RDWR),
73+ third)
74+ if int32(r1) != -1 {
75+ t.Fatalf("open returned %d, wanted -1", r1)
76+ }
77+ if errno != 0 {
78+ t.Errorf("SyscallN returned %d as errno where errno is not captured, wanted 0", errno)
79+ }
80+}
M examples/objc/main_darwin.go +2 -2
@@ -18,7 +18,7 @@ var (
18 )18 )
19 19
20 func BarInit(id objc.ID, cmd objc.SEL) objc.ID {20 func BarInit(id objc.ID, cmd objc.SEL) objc.ID {
21- return id.SendSuper(cmd)21+ return id.SendSuper2(objc.GetClass("BarObject"), cmd)
22 }22 }
23 23
24 func main() {24 func main() {
@@ -36,7 +36,7 @@ func main() {
36 []objc.FieldDef{36 []objc.FieldDef{
37 {37 {
38 Name: "bar",38 Name: "bar",
39- Type: reflect.TypeOf(int(0)),39+ Type: reflect.TypeFor[int](),
40 Attribute: objc.ReadWrite,40 Attribute: objc.ReadWrite,
41 },41 },
42 },42 },
M examples/window/main_darwin.go +5 -1
@@ -5,6 +5,7 @@ package main
5 5
6 import (6 import (
7 "runtime"7 "runtime"
8+ "structs"
8 9
9 "github.com/bnema/purego"10 "github.com/bnema/purego"
10 "github.com/bnema/purego/objc"11 "github.com/bnema/purego/objc"
@@ -17,14 +18,17 @@ const (
17 )18 )
18 19
19 type NSPoint struct {20 type NSPoint struct {
21+ _ structs.HostLayout
20 X, Y float6422 X, Y float64
21 }23 }
22 24
23 type NSSize struct {25 type NSSize struct {
26+ _ structs.HostLayout
24 Width, Height float6427 Width, Height float64
25 }28 }
26 29
27 type NSRect struct {30 type NSRect struct {
31+ _ structs.HostLayout
28 Origin NSPoint32 Origin NSPoint
29 Size NSSize33 Size NSSize
30 }34 }
@@ -56,5 +60,5 @@ func main() {
56 }60 }
57 61
58 func NSMakeRect(x, y, width, height float64) NSRect {62 func NSMakeRect(x, y, width, height float64) NSRect {
59- return NSRect{Origin: NSPoint{x, y}, Size: NSSize{width, height}}63+ return NSRect{Origin: NSPoint{X: x, Y: y}, Size: NSSize{Width: width, Height: height}}
60 }64 }
M examples/window/main_windows.go +5 -0
@@ -5,6 +5,7 @@ package main
5 5
6 import (6 import (
7 "runtime"7 "runtime"
8+ "structs"
8 "syscall"9 "syscall"
9 "unsafe"10 "unsafe"
10 11
@@ -30,6 +31,7 @@ type (
30 )31 )
31 32
32 type WNDCLASSEX struct {33 type WNDCLASSEX struct {
34+ _ structs.HostLayout
33 Size uint3235 Size uint32
34 Style uint3236 Style uint32
35 WndProc uintptr37 WndProc uintptr
@@ -45,14 +47,17 @@ type WNDCLASSEX struct {
45 }47 }
46 48
47 type RECT struct {49 type RECT struct {
50+ _ structs.HostLayout
48 Left, Top, Right, Bottom int3251 Left, Top, Right, Bottom int32
49 }52 }
50 53
51 type POINT struct {54 type POINT struct {
55+ _ structs.HostLayout
52 X, Y int3256 X, Y int32
53 }57 }
54 58
55 type MSG struct {59 type MSG struct {
60+ _ structs.HostLayout
56 Hwnd HWND61 Hwnd HWND
57 Message uint3262 Message uint32
58 WParam uintptr63 WParam uintptr
A export_test.go +16 -0
@@ -0,0 +1,16 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build darwin || freebsd || linux || netbsd || windows
5+
6+package purego
7+
8+import "reflect"
9+
10+// MaxArgs re-exports maxArgs for external tests.
11+const MaxArgs = maxArgs
12+
13+// StructReturnInMemory re-exports structReturnInMemory for external tests.
14+func StructReturnInMemory(outType reflect.Type) bool {
15+ return structReturnInMemory(outType)
16+}
M func.go +399 -251
@@ -10,10 +10,11 @@ import (
10 "math"10 "math"
11 "reflect"11 "reflect"
12 "runtime"12 "runtime"
13+ "structs"
14+ "sync"
13 "unsafe"15 "unsafe"
14 16
15 "github.com/bnema/purego/internal/strings"17 "github.com/bnema/purego/internal/strings"
16- "github.com/bnema/purego/internal/xreflect"
17 )18 )
18 19
19 const (20 const (
@@ -21,6 +22,14 @@ const (
21 align8ByteSize = 8 // 8-byte alignment boundary22 align8ByteSize = 8 // 8-byte alignment boundary
22 )23 )
23 24
25+func isARMSoftFloat() bool {
26+ return runtime.GOARCH == "arm" && *(*uint8)(unsafe.Pointer(&runtime_goarmsoftfp)) != 0
27+}
28+
29+var thePool = sync.Pool{New: func() any {
30+ return new(syscallArgs)
31+}}
32+
24 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).33 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
25 // It panics if it can't find the name symbol.34 // It panics if it can't find the name symbol.
26 func RegisterLibFunc(fptr any, handle uintptr, name string) {35 func RegisterLibFunc(fptr any, handle uintptr, name string) {
@@ -59,15 +68,17 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) {
59 // int64 <=> int64_t68 // int64 <=> int64_t
60 // float32 <=> float69 // float32 <=> float
61 // float64 <=> double70 // float64 <=> double
62-// struct <=> struct (darwin amd64/arm64, linux amd64/arm64)71+// struct <=> struct (android, darwin, ios, linux, and windows on amd64/arm64)
63 // func <=> C function72 // func <=> C function
64 // unsafe.Pointer, *T <=> void*73 // unsafe.Pointer, *T <=> void*
65 // []T => void*74 // []T => void*
66 //75 //
67-// There is a special case when the last argument of fptr is a variadic interface (or []interface}76+// There is a special case when the last argument of fptr is a variadic interface (or []any):
68 // it will be expanded into a call to the C function as if it had the arguments in that slice.77 // it will be expanded into a call to the C function as if it had the arguments in that slice.
69 // This means that using arg ...any is like a cast to the function with the arguments inside arg.78 // This means that using arg ...any is like a cast to the function with the arguments inside arg.
70 // This is not the same as C variadic.79 // This is not the same as C variadic.
80+// Only the last argument is expanded this way. A []any in any other position is an ordinary
81+// argument and is passed as a single value like any other slice.
71 //82 //
72 // # Memory83 // # Memory
73 //84 //
@@ -94,8 +105,11 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) {
94 // it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure105 // it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
95 // that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.106 // that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
96 //107 //
97-// On Darwin ARM64, purego handles proper alignment of struct arguments when passing them on the stack,108+// On Apple ARM64 platforms (macOS and iOS), purego handles proper alignment of struct arguments
98-// following the C ABI's byte-level packing rules.109+// when passing them on the stack, following the C ABI's byte-level packing rules.
110+//
111+// On Windows, struct arguments and returns are supported on amd64 and arm64 when calling C functions.
112+// Passing or returning structs in callbacks created with [NewCallback] is not supported on Windows.
99 //113 //
100 // # Example114 // # Example
101 //115 //
@@ -116,7 +130,11 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) {
116 // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C130 // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
117 func RegisterFunc(fptr any, cfn uintptr) {131 func RegisterFunc(fptr any, cfn uintptr) {
118 const is32bit = unsafe.Sizeof(uintptr(0)) == 4132 const is32bit = unsafe.Sizeof(uintptr(0)) == 4
119- fn := reflect.ValueOf(fptr).Elem()133+ rv := reflect.ValueOf(fptr)
134+ if rv.Kind() != reflect.Ptr || rv.IsNil() {
135+ panic("purego: fptr must be a non-nil function pointer")
136+ }
137+ fn := rv.Elem()
120 ty := fn.Type()138 ty := fn.Type()
121 if ty.Kind() != reflect.Func {139 if ty.Kind() != reflect.Func {
122 panic("purego: fptr must be a function pointer")140 panic("purego: fptr must be a function pointer")
@@ -136,40 +154,71 @@ func RegisterFunc(fptr any, cfn uintptr) {
136 // to avoid crashing with too many arguments154 // to avoid crashing with too many arguments
137 var ints int155 var ints int
138 var floats int156 var floats int
157+ floatArgRegs := numOfFloatRegisters()
158+ ptrSize := unsafe.Sizeof(uintptr(0))
139 var stack int159 var stack int
140- for i := 0; i < ty.NumIn(); i++ {160+ for i := range ty.NumIn() {
141 arg := ty.In(i)161 arg := ty.In(i)
142 switch arg.Kind() {162 switch arg.Kind() {
143 case reflect.Func:163 case reflect.Func:
144 // This only does preliminary testing to ensure the CDecl argument164 // This only does preliminary testing to ensure the CDecl argument
145 // is the first argument. Full testing is done when the callback is actually165 // is the first argument. Full testing is done when the callback is actually
146 // created in NewCallback.166 // created in NewCallback.
147- for j := 0; j < arg.NumIn(); j++ {167+ for j := range arg.NumIn() {
148 in := arg.In(j)168 in := arg.In(j)
149- if !in.AssignableTo(reflect.TypeOf(CDecl{})) {169+ if !in.AssignableTo(reflect.TypeFor[CDecl]()) {
150 continue170 continue
151 }171 }
152 if j != 0 {172 if j != 0 {
153 panic("purego: CDecl must be the first argument")173 panic("purego: CDecl must be the first argument")
154 }174 }
155 }175 }
156- case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,176+ case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
157- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer,177+ reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Pointer, reflect.UnsafePointer,
158 reflect.Slice, reflect.Array, reflect.Bool:178 reflect.Slice, reflect.Array, reflect.Bool:
159 if ints < numOfIntegerRegisters() {179 if ints < numOfIntegerRegisters() {
160 ints++180 ints++
161 } else {181 } else {
162 stack++182 stack++
163 }183 }
184+ case reflect.Uint64, reflect.Int64:
185+ usesSlots := max(1, int(arg.Size()/ptrSize))
186+ if isARMPaddingNeeded(arg, ints, stack) {
187+ usesSlots++
188+ }
189+
190+ if ints < numOfIntegerRegisters() {
191+ ints += usesSlots
192+ } else {
193+ stack += usesSlots
194+ }
164 case reflect.Float32, reflect.Float64:195 case reflect.Float32, reflect.Float64:
165- if floats < numOfFloatRegisters() {196+ usesSlots := max(1, int(arg.Size()/ptrSize))
197+ if isARMSoftFloat() {
198+ // float64 for arm with softfloat uses same rules as int64
199+ if isARMPaddingNeeded(arg, ints, stack) {
200+ usesSlots++
201+ }
202+ if ints < numOfIntegerRegisters() {
203+ ints += usesSlots
204+ } else {
205+ stack += usesSlots
206+ }
207+ continue
208+ }
209+
210+ if isARMFloatPaddingNeeded(arg, floats, stack) {
211+ usesSlots++
212+ }
213+ if floats < floatArgRegs {
166 floats++214 floats++
167 } else {215 } else {
168- stack++216+ stack += usesSlots
169 }217 }
170 case reflect.Struct:218 case reflect.Struct:
171 ensureStructSupported()219 ensureStructSupported()
172- if arg.Size() == 0 {220+ if arg.Size() == 0 && runtime.GOOS != "windows" {
221+ // On Windows an empty struct still consumes one argument slot.
173 continue222 continue
174 }223 }
175 addInt := func(u uintptr) {224 addInt := func(u uintptr) {
@@ -190,17 +239,28 @@ func RegisterFunc(fptr any, cfn uintptr) {
190 ensureStructSupported()239 ensureStructSupported()
191 outType := ty.Out(0)240 outType := ty.Out(0)
192 checkStructFieldsSupported(outType)241 checkStructFieldsSupported(outType)
193- if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {242+ if structReturnInMemory(outType) {
194- // on amd64 if struct is bigger than 16 bytes allocate the return struct243+ // A struct returned in memory is allocated by the caller and its
195- // and pass it in as a hidden first argument.244+ // pointer is passed as a hidden first integer argument. When the
196- ints++245+ // integer registers are already full, prepending it spills a
246+ // regular argument onto the stack.
247+ if ints < numOfIntegerRegisters() {
248+ ints++
249+ } else {
250+ stack++
251+ }
197 }252 }
198 }253 }
199 254
200- sizeOfStack := maxArgs - numOfIntegerRegisters()255+ argsLimit := maxArgs
201- // On Darwin ARM64, use byte-based validation since arguments pack efficiently.256+ sizeOfStack := argsLimit - numOfIntegerRegisters()
202- // See https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms257+ if runtime.GOOS == "windows" {
203- if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {258+ if ints+floats+stack > argsLimit {
259+ panic("purego: too many stack arguments")
260+ }
261+ } else if isDarwin && runtime.GOARCH == "arm64" {
262+ // On Darwin ARM64, use byte-based validation since arguments pack efficiently.
263+ // See https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
204 stackBytes := estimateStackBytes(ty)264 stackBytes := estimateStackBytes(ty)
205 maxStackBytes := sizeOfStack * 8265 maxStackBytes := sizeOfStack * 8
206 if stackBytes > maxStackBytes {266 if stackBytes > maxStackBytes {
@@ -213,35 +273,13 @@ func RegisterFunc(fptr any, cfn uintptr) {
213 }273 }
214 }274 }
215 275
216- // Task 3: Analyze types at RegisterFunc time to determine if keepAlive is needed.
217- // keepAlive is only used for: string args (CString alloc), struct args (addStruct),
218- // struct returns > maxRegAllocStructSize (hidden pointer), array args (addressable copy),
219- // and variadic []any args (unknown types at registration time).
220- needsKeepAlive := false
221- for i := 0; i < ty.NumIn(); i++ {
222- inKind := ty.In(i).Kind()
223- switch inKind {
224- case reflect.String, reflect.Struct, reflect.Array, reflect.Slice:
225- // Slice covers []string (CString allocs) and variadic ...any ([]any)
226- needsKeepAlive = true
227- case reflect.Interface:
228- needsKeepAlive = true
229- }
230- }
231- if ty.NumOut() == 1 {
232- switch ty.Out(0).Kind() {
233- case reflect.Struct:
234- // struct returns may need hidden pointer allocation stored in keepAlive
235- needsKeepAlive = true
236- }
237- }
238-
239 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {276 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
240 var sysargs [maxArgs]uintptr277 var sysargs [maxArgs]uintptr
241 // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,278 // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,
242 // since numOfFloatRegisters() is a function call, not a constant.279 // since numOfFloatRegisters() is a function call, not a constant.
243 // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe.280 // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe.
244 var floats [maxArgs]uintptr281 var floats [maxArgs]uintptr
282+ floatArgRegs := numOfFloatRegisters()
245 var numInts int283 var numInts int
246 var numFloats int284 var numFloats int
247 var numStack int285 var numStack int
@@ -261,7 +299,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
261 }299 }
262 }300 }
263 addFloat = func(x uintptr) {301 addFloat = func(x uintptr) {
264- if numFloats < numOfFloatRegisters() {302+ if numFloats < floatArgRegs {
265 floats[numFloats] = x303 floats[numFloats] = x
266 numFloats++304 numFloats++
267 } else {305 } else {
@@ -275,6 +313,9 @@ func RegisterFunc(fptr any, cfn uintptr) {
275 // This is in contrast to how macOS and Linux pass arguments which313 // This is in contrast to how macOS and Linux pass arguments which
276 // tries to use as many registers as possible in the calling convention.314 // tries to use as many registers as possible in the calling convention.
277 addStack = func(x uintptr) {315 addStack = func(x uintptr) {
316+ if numStack >= maxArgs {
317+ panic("purego: too many stack arguments")
318+ }
278 sysargs[numStack] = x319 sysargs[numStack] = x
279 numStack++320 numStack++
280 }321 }
@@ -282,189 +323,169 @@ func RegisterFunc(fptr any, cfn uintptr) {
282 addFloat = addStack323 addFloat = addStack
283 }324 }
284 325
285- if needsKeepAlive {326+ var keepAlive []any
286- // Slow path: allocate keepAlive slice and defer cleanup.327+ defer func() {
287- // Used when args include strings, structs, arrays, or variadic []any.328+ runtime.KeepAlive(keepAlive)
288- var keepAlive []any329+ runtime.KeepAlive(args)
289- defer func() {330+ }()
290- runtime.KeepAlive(keepAlive)
291- runtime.KeepAlive(args)
292- }()
293 331
294- var arm64_r8 uintptr332+ var arm64_r8 uintptr
295- if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {333+ if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
296- outType := ty.Out(0)334+ outType := ty.Out(0)
297- if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize {335+ if structReturnInMemory(outType) {
336+ // The caller allocates the return value and passes its pointer
337+ // as a hidden first integer argument.
338+ val := reflect.New(outType)
339+ keepAlive = append(keepAlive, val)
340+ addInt(val.Pointer())
341+ } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
342+ isAllFloats, numFields := isAllSameFloat(outType)
343+ if !isAllFloats || numFields > 4 {
298 val := reflect.New(outType)344 val := reflect.New(outType)
299 keepAlive = append(keepAlive, val)345 keepAlive = append(keepAlive, val)
300- addInt(val.Pointer())346+ arm64_r8 = val.Pointer()
301- } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
302- isAllFloats, numFields := isAllSameFloat(outType)
303- if !isAllFloats || numFields > 4 {
304- val := reflect.New(outType)
305- keepAlive = append(keepAlive, val)
306- arm64_r8 = val.Pointer()
307- }
308 }347 }
309 }348 }
310- for i, v := range args {349+ }
311- if variadic, ok := xreflect.TypeAssert[[]any](v); ok {350+ for i, v := range args {
312- if i != len(args)-1 {351+ // Only the last argument is expanded into several C arguments.
313- panic("purego: can only expand last parameter")352+ // Both a declared ...any parameter and a final []any parameter
314- }353+ // arrive here as a []any holding those arguments. A []any in any
354+ // other position is an ordinary argument that is passed as one value.
355+ if i == len(args)-1 {
356+ if variadic, ok := reflect.TypeAssert[[]any](args[i]); ok {
315 for _, x := range variadic {357 for _, x := range variadic {
316 keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)358 keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
317 }359 }
318 continue360 continue
319 }361 }
320- // Check if we need to start Darwin ARM64 C-style stack packing
321- if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {
322- // Collect and separate remaining args into register vs stack
323- stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats,
324- keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
325- keepAlive = newKeepAlive
326-
327- // Bundle stack arguments with C-style packing
328- bundleStackArgs(stackArgs, addStack)
329- break
330- }
331- keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
332- }
333- return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, arm64_r8, args)
334- }
335-
336- // Fast path: no keepAlive needed (pointer/integer/bool/float args only).
337- // Skip keepAlive slice allocation and defer overhead.
338- defer runtime.KeepAlive(args)
339-
340- for i, v := range args {
341- if variadic, ok := xreflect.TypeAssert[[]any](v); ok {
342- if i != len(args)-1 {
343- panic("purego: can only expand last parameter")
344- }
345- for _, x := range variadic {
346- addValue(reflect.ValueOf(x), nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
347- }
348- continue
349 }362 }
350 // Check if we need to start Darwin ARM64 C-style stack packing363 // Check if we need to start Darwin ARM64 C-style stack packing
351- if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) {364+ if runtime.GOARCH == "arm64" && isDarwin && shouldBundleStackArgs(v, numInts, numFloats) {
352- stackArgs, _ := collectStackArgs(args, i, numInts, numFloats,365+ // Collect and separate remaining args into register vs stack
353- nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)366+ stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats,
367+ keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
368+ keepAlive = newKeepAlive
369+
370+ // Bundle stack arguments with C-style packing
354 bundleStackArgs(stackArgs, addStack)371 bundleStackArgs(stackArgs, addStack)
355 break372 break
356 }373 }
357- addValue(v, nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)374+ keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
358 }375 }
359 376
360- return callAndReturn(ty, is32bit, cfn, &sysargs, &floats, 0, args)377+ var syscall *syscallArgs
361- })378+ if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" {
362- fn.Set(v)379+ // Windows amd64, 386, and arm use syscall.SyscallN.
363-}380+ syscall = thePool.Get().(*syscallArgs)
364-381+ syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...)
365-// callAndReturn performs the actual C function call and constructs the return value.382+ syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
366-// It is shared between the fast path (no keepAlive) and slow path (with keepAlive).
367-func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs]uintptr, floats *[maxArgs]uintptr, arm64_r8 uintptr, args []reflect.Value) []reflect.Value {
368- syscall := &syscall15Args{}
369-
370- if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {
371- syscall.Set(cfn, sysargs[:], floats[:], 0)
372- runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
373- } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
374- // Use the normal arm64 calling convention even on Windows
375- syscall.Set(cfn, sysargs[:], floats[:], arm64_r8)
376- runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
377- } else {
378- // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
379- syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
380- sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
381- sysargs[12], sysargs[13], sysargs[14])
382- syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
383- }
384- if ty.NumOut() == 0 {
385- return nil
386- }
387- outType := ty.Out(0)
388- v := reflect.New(outType).Elem()
389- switch outType.Kind() {
390- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
391- v.SetUint(uint64(syscall.a1))
392- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
393- v.SetInt(int64(syscall.a1))
394- case reflect.Bool:
395- v.SetBool(byte(syscall.a1) != 0)
396- case reflect.UnsafePointer:
397- // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
398- v = reflect.New(outType).Elem()
399- v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
400- case reflect.Pointer:
401- v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
402- case reflect.Func:
403- // wrap this C function in a nicely typed Go function
404- v = reflect.New(outType)
405- RegisterFunc(v.Interface(), syscall.a1)
406- case reflect.String:
407- v = reflect.ValueOf(strings.GoString(syscall.a1))
408- case reflect.Float32:
409- // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
410- // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
411- // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
412- // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
413- // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register.
414- v = reflect.New(outType).Elem()
415- switch runtime.GOARCH {
416- case "386":
417- v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
418- case "ppc64le":
419- v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
420- case "s390x":
421- // S390X is big-endian: float32 in upper 32 bits of 64-bit register
422- v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32))))
423- default:
424- v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
425- }
426- case reflect.Float64:
427- // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
428- // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
429- v = reflect.New(outType).Elem()
430- if is32bit {
431- v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
432 } else {383 } else {
433- v.SetFloat(math.Float64frombits(uint64(syscall.f1)))384+ syscall = syscall_SyscallN(cfn, sysargs[:], floats[:], arm64_r8)
434 }385 }
435- case reflect.Struct:386+ defer thePool.Put(syscall)
436- v = getStruct(outType, *syscall)387+ if ty.NumOut() == 0 {
437- case reflect.Slice:388+ return nil
438- if outType.Elem().Kind() == reflect.String {389+ }
439- // C returns char** (NULL-terminated array of strings)390+ outType := ty.Out(0)
440- if syscall.a1 == 0 {391+ v := reflect.New(outType).Elem()
441- v = reflect.Zero(outType)392+ switch outType.Kind() {
393+ case reflect.Uint64:
394+ if is32bit {
395+ // high-word is recorded at a2 for 32-bit platforms and 64-bit returns
396+ v.SetUint(uint64(syscall.a1) | (uint64(syscall.a2) << 32))
397+ } else {
398+ v.SetUint(uint64(syscall.a1))
399+ }
400+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
401+ v.SetUint(uint64(syscall.a1))
402+ case reflect.Int64:
403+ if is32bit {
404+ v.SetInt(int64(syscall.a1) | (int64(syscall.a2) << 32))
405+ } else {
406+ v.SetInt(int64(syscall.a1))
407+ }
408+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
409+ v.SetInt(int64(syscall.a1))
410+ case reflect.Bool:
411+ v.SetBool(byte(syscall.a1) != 0)
412+ case reflect.UnsafePointer:
413+ // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
414+ v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
415+ case reflect.Pointer:
416+ // Copy syscall.a1 into a local variable to prevent v
417+ // from holding a pointer to the pooled syscallArgs field.
418+ a1 := syscall.a1
419+ v = reflect.NewAt(outType, unsafe.Pointer(&a1)).Elem()
420+ case reflect.Func:
421+ // wrap this C function in a nicely typed Go function
422+ if syscall.a1 != 0 {
423+ RegisterFunc(v.Addr().Interface(), syscall.a1)
424+ }
425+ case reflect.String:
426+ v.SetString(strings.GoString(syscall.a1))
427+ case reflect.Float32:
428+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
429+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
430+ // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
431+ // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
432+ // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register.
433+ // On 32bit ARM with softfloat float32 returned as integer
434+ switch runtime.GOARCH {
435+ case "386":
436+ v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
437+ case "ppc64le":
438+ v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
439+ case "s390x":
440+ // S390X is big-endian: float32 in upper 32 bits of 64-bit register
441+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32))))
442+ case "arm":
443+ if isARMSoftFloat() {
444+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.a1))))
445+ } else {
446+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
447+ }
448+ default:
449+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
450+ }
451+ case reflect.Float64:
452+ // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
453+ // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
454+ if isARMSoftFloat() {
455+ // a1,a2 are populated in this case
456+ v.SetFloat(math.Float64frombits(uint64(syscall.a1) | (uint64(syscall.a2) << 32)))
457+ } else if is32bit {
458+ v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
442 } else {459 } else {
460+ v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
461+ }
462+ case reflect.Struct:
463+ v = getStruct(outType, *syscall)
464+ case reflect.Slice:
465+ if outType.Elem().Kind() != reflect.String {
466+ panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String())
467+ }
468+ // C returns a NULL-terminated char** array.
469+ if syscall.a1 != 0 {
443 var result []string470 var result []string
444 p := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))471 p := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))
445- for {472+ for ; *(*uintptr)(p) != 0; p = unsafe.Add(p, unsafe.Sizeof(uintptr(0))) {
446- strPtr := *(*uintptr)(p)473+ result = append(result, strings.GoString(*(*uintptr)(p)))
447- if strPtr == 0 {
448- break
449- }
450- result = append(result, strings.GoString(strPtr))
451- p = unsafe.Add(p, unsafe.Sizeof(uintptr(0)))
452 }474 }
453- v = reflect.ValueOf(result)475+ v = reflect.ValueOf(result).Convert(outType)
454 }476 }
477+ default:
478+ panic("purego: unsupported return kind: " + outType.Kind().String())
479+ }
480+ if len(args) > 0 {
481+ // reuse args slice instead of allocating one when possible
482+ args[0] = v
483+ return args[:1]
455 } else {484 } else {
456- panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String())485+ return []reflect.Value{v}
457 }486 }
458- default:487+ })
459- panic("purego: unsupported return kind: " + outType.Kind().String())488+ fn.Set(v)
460- }
461- if len(args) > 0 {
462- // reuse args slice instead of allocating one when possible
463- args[0] = v
464- return args[:1]
465- } else {
466- return []reflect.Value{v}
467- }
468 }489 }
469 490
470 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 {491 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 {
@@ -474,33 +495,52 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
474 ptr := strings.CString(v.String())495 ptr := strings.CString(v.String())
475 keepAlive = append(keepAlive, ptr)496 keepAlive = append(keepAlive, ptr)
476 addInt(uintptr(unsafe.Pointer(ptr)))497 addInt(uintptr(unsafe.Pointer(ptr)))
477- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:498+ case reflect.Uint64:
499+ if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
500+ addInt(0)
501+ }
478 addInt(uintptr(v.Uint()))502 addInt(uintptr(v.Uint()))
479- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:503+ if is32bit {
504+ addInt(uintptr(v.Uint() >> 32)) // on 32bit we must add high word too
505+ }
506+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
507+ addInt(uintptr(v.Uint()))
508+ case reflect.Int64:
509+ if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
510+ addInt(0)
511+ }
512+ addInt(uintptr(v.Int()))
513+ if is32bit {
514+ addInt(uintptr(v.Int() >> 32))
515+ }
516+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
480 addInt(uintptr(v.Int()))517 addInt(uintptr(v.Int()))
481- case reflect.Pointer, reflect.UnsafePointer:
482- // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
483- addInt(v.Pointer())
484 case reflect.Slice:518 case reflect.Slice:
485- if v.Type().Elem().Kind() == reflect.String {519+ if v.Type().Elem().Kind() != reflect.String {
486- ss := v.Interface().([]string)520+ // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
487- if ss == nil {
488- addInt(0)
489- } else {
490- res := make([]*byte, len(ss)+1)
491- for i, s := range ss {
492- ptr := strings.CString(s)
493- keepAlive = append(keepAlive, ptr)
494- res[i] = ptr
495- }
496- // NULL-terminated for C
497- res[len(ss)] = nil
498- keepAlive = append(keepAlive, res)
499- addInt(uintptr(unsafe.Pointer(&res[0])))
500- }
501- } else {
502 addInt(v.Pointer())521 addInt(v.Pointer())
522+ break
523+ }
524+ if v.IsNil() {
525+ addInt(0)
526+ break
503 }527 }
528+ // Pass []string as a NULL-terminated char** array.
529+ res := make([]*byte, v.Len()+1)
530+ for i := range v.Len() {
531+ res[i] = strings.CString(v.Index(i).String())
532+ }
533+ keepAlive = append(keepAlive, res)
534+ addInt(uintptr(unsafe.Pointer(&res[0])))
535+ case reflect.Array:
536+ // C array parameters decay to pointers, so pass a pointer to a copy.
537+ tmp := reflect.New(v.Type())
538+ tmp.Elem().Set(v)
539+ keepAlive = append(keepAlive, tmp)
540+ addInt(tmp.Pointer())
541+ case reflect.Pointer, reflect.UnsafePointer:
542+ // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
543+ addInt(v.Pointer())
504 case reflect.Func:544 case reflect.Func:
505 addInt(NewCallback(v.Interface()))545 addInt(NewCallback(v.Interface()))
506 case reflect.Bool:546 case reflect.Bool:
@@ -510,30 +550,41 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
510 addInt(0)550 addInt(0)
511 }551 }
512 case reflect.Float32:552 case reflect.Float32:
513- // On S390X big-endian, float32 goes in upper 32 bits of 64-bit FP register553+ switch runtime.GOARCH {
514- if runtime.GOARCH == "s390x" {554+ case "ppc64le":
555+ // A single-precision argument occupies a floating-point register in double format on Power.
556+ addFloat(uintptr(math.Float64bits(v.Float())))
557+ case "s390x":
558+ // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register.
515 addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32)559 addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32)
516- } else {560+ case "arm":
561+ if isARMSoftFloat() {
562+ // 32-bit ARM with softfloat: float32 goes as integer
563+ addInt(uintptr(math.Float32bits(float32(v.Float()))))
564+ } else {
565+ addFloat(uintptr(math.Float32bits(float32(v.Float()))))
566+ }
567+ default:
517 addFloat(uintptr(math.Float32bits(float32(v.Float()))))568 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
518 }569 }
519 case reflect.Float64:570 case reflect.Float64:
520- if is32bit {571+ bits := math.Float64bits(v.Float())
521- bits := math.Float64bits(v.Float())572+ if isARMFloatPaddingNeeded(v.Type(), *numFloats, *numStack) {
573+ // if floats are spilled onto stack on ARM than we must follow AAPCS C.7
574+ addFloat(0)
575+ }
576+ if isARMSoftFloat() {
577+ // add as uint64
578+ if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
579+ addInt(0)
580+ }
581+ addInt(uintptr(bits))
582+ addInt(uintptr(bits >> 32))
583+ } else if is32bit {
522 addFloat(uintptr(bits))584 addFloat(uintptr(bits))
523 addFloat(uintptr(bits >> 32))585 addFloat(uintptr(bits >> 32))
524 } else {586 } else {
525- addFloat(uintptr(math.Float64bits(v.Float())))587+ addFloat(uintptr(bits))
526- }
527- case reflect.Array:
528- // Task 7: Support fixed-size array arguments by passing a pointer to the array data.
529- if v.CanAddr() {
530- addInt(v.Addr().Pointer())
531- } else {
532- // Array is not addressable (e.g. passed by value); make an addressable copy.
533- tmp := reflect.New(v.Type()).Elem()
534- tmp.Set(v)
535- keepAlive = append(keepAlive, tmp)
536- addInt(tmp.Addr().Pointer())
537 }588 }
538 case reflect.Struct:589 case reflect.Struct:
539 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)590 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
@@ -550,17 +601,61 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
550 // If you change this make sure to update it in objc_runtime_darwin.go601 // If you change this make sure to update it in objc_runtime_darwin.go
551 const maxRegAllocStructSize = 16602 const maxRegAllocStructSize = 16
552 603
604+var hostLayoutType = reflect.TypeFor[structs.HostLayout]()
605+
606+// isABIField reports whether f takes part in the C ABI of the struct that
607+// contains it. Only the structs.HostLayout marker does not.
608+func isABIField(f reflect.StructField) bool {
609+ return !f.Type.ConvertibleTo(hostLayoutType)
610+}
611+
612+// numABIFields returns how many of ty's fields take part in the C ABI.
613+func numABIFields(ty reflect.Type) int {
614+ var n int
615+ for i := range ty.NumField() {
616+ if isABIField(ty.Field(i)) {
617+ n++
618+ }
619+ }
620+ return n
621+}
622+
623+// abiField returns the i'th field of ty that takes part in the C ABI. It panics
624+// if ty has fewer than i+1 such fields.
625+func abiField(ty reflect.Type, i int) reflect.StructField {
626+ for j := range ty.NumField() {
627+ f := ty.Field(j)
628+ if !isABIField(f) {
629+ continue
630+ }
631+ if i == 0 {
632+ return f
633+ }
634+ i--
635+ }
636+ panic("purego: struct field index out of range")
637+}
638+
553 func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {639 func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
554 allFloats = true640 allFloats = true
555- root := ty.Field(0).Type641+ if numABIFields(ty) == 0 {
642+ return false, 0
643+ }
644+ root := abiField(ty, 0).Type
556 for root.Kind() == reflect.Struct {645 for root.Kind() == reflect.Struct {
557- root = root.Field(0).Type646+ if numABIFields(root) == 0 {
647+ return false, 0
648+ }
649+ root = abiField(root, 0).Type
558 }650 }
559 first := root.Kind()651 first := root.Kind()
560 if first != reflect.Float32 && first != reflect.Float64 {652 if first != reflect.Float32 && first != reflect.Float64 {
561 allFloats = false653 allFloats = false
562 }654 }
563- for i := 0; i < ty.NumField(); i++ {655+ for i := range ty.NumField() {
656+ if !isABIField(ty.Field(i)) {
657+ continue
658+ }
564 f := ty.Field(i).Type659 f := ty.Field(i).Type
565 if f.Kind() == reflect.Struct {660 if f.Kind() == reflect.Struct {
566 var structNumFields int661 var structNumFields int
@@ -577,7 +672,10 @@ func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
577 }672 }
578 673
579 func checkStructFieldsSupported(ty reflect.Type) {674 func checkStructFieldsSupported(ty reflect.Type) {
580- for i := 0; i < ty.NumField(); i++ {675+ for i := range ty.NumField() {
676+ if !isABIField(ty.Field(i)) {
677+ continue
678+ }
581 f := ty.Field(i).Type679 f := ty.Field(i).Type
582 if f.Kind() == reflect.Array {680 if f.Kind() == reflect.Array {
583 f = f.Elem()681 f = f.Elem()
@@ -596,15 +694,39 @@ func checkStructFieldsSupported(ty reflect.Type) {
596 }694 }
597 }695 }
598 696
697+// ensureStructSupported panics if passing or returning structs through a call to
698+// a C function is unsupported on the current platform.
599 func ensureStructSupported() {699 func ensureStructSupported() {
700+ switch runtime.GOARCH {
701+ case "amd64", "arm64", "loong64", "ppc64le":
702+ default:
703+ panic("purego: struct arguments/returns are only supported on amd64, arm64, loong64, and ppc64le")
704+ }
705+ switch runtime.GOOS {
706+ case "android", "darwin", "ios", "linux", "windows":
707+ default:
708+ panic("purego: struct arguments/returns are only supported on android, darwin, ios, linux, and windows")
709+ }
710+}
711+
712+// ensureCallbackStructSupported panics if passing or returning structs through a
713+// callback is unsupported on the current platform. Callbacks support structs on
714+// fewer architectures than a direct call to a C function.
715+func ensureCallbackStructSupported() {
600 if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {716 if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
601- panic("purego: struct arguments/returns are only supported on amd64 and arm64")717+ panic("purego: struct arguments/returns in callbacks are only supported on amd64 and arm64")
602 }718 }
603- if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {719+ switch runtime.GOOS {
604- panic("purego: struct arguments/returns are only supported on darwin and linux")720+ case "android", "darwin", "ios", "linux", "windows":
721+ default:
722+ panic("purego: struct arguments/returns in callbacks are only supported on android, darwin, ios, linux, and windows")
605 }723 }
606 }724 }
607 725
726+// isDarwin is true on platforms that use Apple's calling convention.
727+// iOS (GOOS=ios) shares it with macOS (GOOS=darwin).
728+const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios"
729+
608 func roundUpTo8(val uintptr) uintptr {730 func roundUpTo8(val uintptr) uintptr {
609 return (val + align8ByteMask) &^ align8ByteMask731 return (val + align8ByteMask) &^ align8ByteMask
610 }732 }
@@ -616,6 +738,7 @@ func numOfFloatRegisters() int {
616 case "s390x":738 case "s390x":
617 return 4739 return 4
618 case "arm":740 case "arm":
741+ // 8 doubles (16 words) are always reserved by asm trampolines, even if softfloat is used
619 return 16742 return 16
620 case "386":743 case "386":
621 // i386 SysV ABI passes all arguments on the stack, including floats744 // i386 SysV ABI passes all arguments on the stack, including floats
@@ -654,7 +777,7 @@ func estimateStackBytes(ty reflect.Type) int {
654 var numInts, numFloats int777 var numInts, numFloats int
655 var stackBytes int778 var stackBytes int
656 779
657- for i := 0; i < ty.NumIn(); i++ {780+ for i := range ty.NumIn() {
658 arg := ty.In(i)781 arg := ty.In(i)
659 size := int(arg.Size())782 size := int(arg.Size())
660 783
@@ -665,7 +788,6 @@ func estimateStackBytes(ty reflect.Type) int {
665 } else if !usesInt && numFloats < numOfFloatRegisters() {788 } else if !usesInt && numFloats < numOfFloatRegisters() {
666 numFloats++789 numFloats++
667 } else {790 } else {
668- // Goes to stack - accumulate total bytes
669 stackBytes += size791 stackBytes += size
670 }792 }
671 }793 }
@@ -675,3 +797,29 @@ func estimateStackBytes(ty reflect.Type) int {
675 }797 }
676 return stackBytes798 return stackBytes
677 }799 }
800+
801+func isARMPaddingNeeded(ty reflect.Type, numInts, numStack int) bool {
802+ // ARM EABI (AAPCS): 8-byte-aligned types (int64/uint64) start on an
803+ // even core register (C.3); if they then spill, the stack slot is
804+ // 8-byte aligned too (C.7).
805+ // https://github.com/ARM-software/abi-aa/blob/main/aapcs32/aapcs32.rst#6111handling-values-larger-than-32-bits
806+ if runtime.GOARCH != "arm" || ty.Size() != 8 {
807+ return false
808+ }
809+ if numInts >= 0 && numInts < numOfIntegerRegisters() {
810+ return numInts%2 != 0
811+ }
812+ return numStack%2 != 0
813+}
814+
815+func isARMFloatPaddingNeeded(ty reflect.Type, numFloats, numStack int) bool {
816+ if runtime.GOARCH != "arm" || ty.Size() != 8 {
817+ return false
818+ }
819+ if numFloats >= 0 && numFloats < numOfFloatRegisters() {
820+ // float registers are 64bit so alignment never needed for args in registers
821+ return false
822+ }
823+ // Only check if AAPCS C.7 is applicable here
824+ return numStack%2 != 0
825+}
A func_slice_test.go +61 -0
@@ -0,0 +1,61 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build darwin || freebsd || linux || netbsd
5+
6+package purego_test
7+
8+import (
9+ "path/filepath"
10+ "slices"
11+ "testing"
12+
13+ "github.com/bnema/purego"
14+ "github.com/bnema/purego/internal/load"
15+)
16+
17+func TestRegisterFuncStringSliceAndArray(t *testing.T) {
18+ libFileName := filepath.Join(t.TempDir(), "slicetest.so")
19+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "slicetest", "slice_test.c")); err != nil {
20+ t.Fatal(err)
21+ }
22+ lib, err := load.OpenLibrary(libFileName)
23+ if err != nil {
24+ t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
25+ }
26+ defer load.CloseLibrary(lib)
27+
28+ var join func([]string) string
29+ purego.RegisterLibFunc(&join, lib, "join_strings")
30+ var get func(int32) []string
31+ purego.RegisterLibFunc(&get, lib, "get_strings")
32+ var sum func([4]float32) float32
33+ purego.RegisterLibFunc(&sum, lib, "sum_floats4")
34+
35+ t.Run("[]string argument", func(t *testing.T) {
36+ if got := join([]string{"a", "bc", "def"}); got != "a,bc,def" {
37+ t.Errorf("join = %q, want %q", got, "a,bc,def")
38+ }
39+ if got := join([]string{}); got != "" {
40+ t.Errorf("join(empty) = %q, want empty", got)
41+ }
42+ if got := join(nil); got != "(null)" {
43+ t.Errorf("join(nil) = %q, want %q", got, "(null)")
44+ }
45+ })
46+
47+ t.Run("[]string return", func(t *testing.T) {
48+ if got, want := get(0), []string{"alpha", "beta", "gamma"}; !slices.Equal(got, want) {
49+ t.Errorf("get = %q, want %q", got, want)
50+ }
51+ if got := get(1); got != nil {
52+ t.Errorf("get(NULL) = %q, want nil", got)
53+ }
54+ })
55+
56+ t.Run("array argument", func(t *testing.T) {
57+ if got := sum([4]float32{1, 2, 3, 4.5}); got != 10.5 {
58+ t.Errorf("sum = %v, want 10.5", got)
59+ }
60+ })
61+}
M func_test.go +412 -45
@@ -7,10 +7,15 @@ import (
7 "bytes"7 "bytes"
8 "errors"8 "errors"
9 "fmt"9 "fmt"
10+ "math"
11+ "os"
10 "os/exec"12 "os/exec"
11 "path/filepath"13 "path/filepath"
14+ "reflect"
12 "runtime"15 "runtime"
16+ "strconv"
13 "strings"17 "strings"
18+ "structs"
14 "sync"19 "sync"
15 "testing"20 "testing"
16 "unsafe"21 "unsafe"
@@ -21,7 +26,9 @@ import (
21 26
22 func getSystemLibrary() (string, error) {27 func getSystemLibrary() (string, error) {
23 switch runtime.GOOS {28 switch runtime.GOOS {
24- case "darwin":29+ case "android":
30+ return "libc.so", nil
31+ case "darwin", "ios":
25 return "/usr/lib/libSystem.B.dylib", nil32 return "/usr/lib/libSystem.B.dylib", nil
26 case "freebsd":33 case "freebsd":
27 return "libc.so.7", nil34 return "libc.so.7", nil
@@ -52,19 +59,21 @@ func TestRegisterFunc_ConcurrentPointerReturn(t *testing.T) {
52 purego.RegisterLibFunc(&free, libc, "free")59 purego.RegisterLibFunc(&free, libc, "free")
53 60
54 var wg sync.WaitGroup61 var wg sync.WaitGroup
55- for i := 0; i < runtime.NumCPU(); i++ {62+
63+ for i := range runtime.NumCPU() {
56 wg.Add(1)64 wg.Add(1)
57- go func() {65+ go func(id int) {
58 defer wg.Done()66 defer wg.Done()
59- for j := 0; j < 400_000; j++ {67+ for range 400_000 {
60 ptr := alloc(5)68 ptr := alloc(5)
61 if ptr == nil {69 if ptr == nil {
62 continue70 continue
63 }71 }
64 free(ptr)72 free(ptr)
65 }73 }
66- }()74+ }(i)
67 }75 }
76+
68 wg.Wait()77 wg.Wait()
69 }78 }
70 79
@@ -172,14 +181,65 @@ func TestRegisterLibFunc_Bool(t *testing.T) {
172 }181 }
173 }182 }
174 183
175-func TestABI(t *testing.T) {184+func TestNewCallback_NotAFunction(t *testing.T) {
176- if runtime.GOOS == "windows" && runtime.GOARCH == "386" {185+ for _, tc := range []struct {
177- t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test186+ name string
187+ fn any
188+ }{
189+ {"nil", nil},
190+ {"int", 42},
191+ {"string", "not a function"},
192+ {"pointer", new(int)},
193+ } {
194+ t.Run(tc.name, func(t *testing.T) {
195+ defer func() {
196+ r := recover()
197+ if r == nil {
198+ t.Fatal("NewCallback did not panic")
199+ }
200+ const want = "purego: the type must be a function but was not"
201+ if got := fmt.Sprint(r); got != want {
202+ t.Fatalf("panic mismatch:\n got: %q\n want: %q", got, want)
203+ }
204+ }()
205+ purego.NewCallback(tc.fn)
206+ })
207+ }
208+}
209+
210+func TestRegisterFunc_InvalidFunctionPointer(t *testing.T) {
211+ for _, tc := range []struct {
212+ name string
213+ fptr any
214+ want string
215+ }{
216+ {"nil", nil, "purego: fptr must be a non-nil function pointer"},
217+ {"non_pointer", 42, "purego: fptr must be a non-nil function pointer"},
218+ {"function_value", func() {}, "purego: fptr must be a non-nil function pointer"},
219+ {"nil_function_pointer", (*func())(nil), "purego: fptr must be a non-nil function pointer"},
220+ {"pointer_to_non_function", new(int), "purego: fptr must be a function pointer"},
221+ {"pointer_to_function_pointer", new(*func()), "purego: fptr must be a function pointer"},
222+ } {
223+ t.Run(tc.name, func(t *testing.T) {
224+ defer func() {
225+ r := recover()
226+ if r == nil {
227+ t.Fatal("RegisterFunc did not panic")
228+ }
229+ if got := fmt.Sprint(r); got != tc.want {
230+ t.Fatalf("panic mismatch:\n got: %q\n want: %q", got, tc.want)
231+ }
232+ }()
233+ purego.RegisterFunc(tc.fptr, 1)
234+ })
178 }235 }
236+}
237+
238+func TestABI(t *testing.T) {
179 libFileName := filepath.Join(t.TempDir(), "abitest.so")239 libFileName := filepath.Join(t.TempDir(), "abitest.so")
180 t.Logf("Build %v", libFileName)240 t.Logf("Build %v", libFileName)
181 241
182- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil {242+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil {
183 t.Fatal(err)243 t.Fatal(err)
184 }244 }
185 245
@@ -234,14 +294,29 @@ func TestABI(t *testing.T) {
234 t.Fatalf("%s: got %q, want %q", cName, res, want)294 t.Fatalf("%s: got %q, want %q", cName, res, want)
235 }295 }
236 }296 }
297+ {
298+ const cName = "return_func_ptr"
299+ var fn func() func(a, b int32) int32
300+ purego.RegisterLibFunc(&fn, lib, cName)
301+ add := fn()
302+ const expect = 5
303+ if res := add(2, 3); res != expect {
304+ t.Fatalf("%s: got %d, want %d", cName, res, expect)
305+ }
306+ }
307+ {
308+ const cName = "return_null_func_ptr"
309+ var fn func() func(a, b int32) int32
310+ purego.RegisterLibFunc(&fn, lib, cName)
311+ if fn() != nil {
312+ t.Fatalf("%s: got a non-nil func, want nil", cName)
313+ }
314+ }
237 }315 }
238 316
239 func TestABI_ArgumentPassing(t *testing.T) {317 func TestABI_ArgumentPassing(t *testing.T) {
240- if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
241- t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test
242- }
243 libFileName := filepath.Join(t.TempDir(), "abitest.so")318 libFileName := filepath.Join(t.TempDir(), "abitest.so")
244- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil {319+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil {
245 t.Fatal(err)320 t.Fatal(err)
246 }321 }
247 lib, err := load.OpenLibrary(libFileName)322 lib, err := load.OpenLibrary(libFileName)
@@ -373,11 +448,20 @@ func TestABI_ArgumentPassing(t *testing.T) {
373 },448 },
374 {449 {
375 name: "8int_hfa4_stack",450 name: "8int_hfa4_stack",
376- fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y, z, w float32 })),451+ fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct {
377- cFn: "stack_8int_hfa4_stack",452+ _ structs.HostLayout
453+ x, y, z, w float32
454+ })),
455+ cFn: "stack_8int_hfa4_stack",
378 call: func(f any) string {456 call: func(f any) string {
379 buf := make([]byte, 256)457 buf := make([]byte, 256)
380- (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y, z, w float32 })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct{ x, y, z, w float32 }{10.0, 20.0, 30.0, 40.0})458+ (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct {
459+ _ structs.HostLayout
460+ x, y, z, w float32
461+ })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct {
462+ _ structs.HostLayout
463+ x, y, z, w float32
464+ }{x: 10.0, y: 20.0, z: 30.0, w: 40.0})
381 return string(buf[:bytes.IndexByte(buf, 0)])465 return string(buf[:bytes.IndexByte(buf, 0)])
382 },466 },
383 want: "1:2:3:4:5:6:7:8:10.0:20.0:30.0:40.0",467 want: "1:2:3:4:5:6:7:8:10.0:20.0:30.0:40.0",
@@ -385,6 +469,7 @@ func TestABI_ArgumentPassing(t *testing.T) {
385 {469 {
386 name: "8int_mixed_struct",470 name: "8int_mixed_struct",
387 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct {471 fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct {
472+ _ structs.HostLayout
388 a int32473 a int32
389 b float32474 b float32
390 })),475 })),
@@ -392,24 +477,108 @@ func TestABI_ArgumentPassing(t *testing.T) {
392 call: func(f any) string {477 call: func(f any) string {
393 buf := make([]byte, 256)478 buf := make([]byte, 256)
394 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct {479 (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct {
480+ _ structs.HostLayout
395 a int32481 a int32
396 b float32482 b float32
397 })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct {483 })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct {
484+ _ structs.HostLayout
398 a int32485 a int32
399 b float32486 b float32
400- }{9, 10.0})487+ }{a: 9, b: 10.0})
401 return string(buf[:bytes.IndexByte(buf, 0)])488 return string(buf[:bytes.IndexByte(buf, 0)])
402 },489 },
403 want: "1:2:3:4:5:6:7:8:9:10.0",490 want: "1:2:3:4:5:6:7:8:9:10.0",
404 },491 },
492+ {
493+ // check if unaligned 64bit argument and 64bit returned value is properly passed via registers
494+ // arm-specific but must work everywhere
495+ name: "arm_int64_unaligned_in_registers",
496+ fn: new(func(uintptr, int64) int64),
497+ cFn: "arm_int64_unaligned_in_registers",
498+ call: func(f any) string {
499+ fn := *(f).(*func(x uintptr, y int64) int64)
500+ return strconv.FormatInt(fn(456, math.MaxInt32+1500), 10)
501+ },
502+ want: strconv.FormatInt(456*123+math.MaxInt32+1500, 10),
503+ },
504+ {
505+ // check if unaligned 64bit argument and 64bit returned value is properly passed via stack
506+ // arm-specific but must work everywhere
507+ name: "arm_int64_unaligned_on_stack",
508+ fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, int64) int64),
509+ cFn: "arm_int64_unaligned_on_stack",
510+ call: func(f any) string {
511+ fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 int64) int64)
512+ return strconv.FormatInt(fn(12, 34, 56, 78, 90, math.MaxInt32+1500), 10)
513+ },
514+ want: strconv.FormatInt(12*1+34*2+56*3+78*4+90*5+math.MaxInt32+1500, 10),
515+ },
516+ {
517+ // check if unaligned 64bit argument and 64bit returned value is properly passed via stack when it's occupied by floats
518+ // arm-specific but must work everywhere
519+ name: "arm_int64_unaligned_on_stack_after_floats",
520+ fn: new(func(
521+ uintptr, uintptr, uintptr, uintptr,
522+ float32, float32, float32, float32,
523+ float32, float32, float32, float32,
524+ float32, float32, float32, float32,
525+ float32, float32, float32, float32,
526+ float32, int64,
527+ ) int64),
528+ cFn: "arm_int64_unaligned_on_stack_after_floats",
529+ call: func(f any) string {
530+ fn := *(f).(*func(
531+ a1, a2, a3, a4 uintptr,
532+ f1, f2, f3, f4 float32,
533+ f5, f6, f7, f8 float32,
534+ f9, f10, f11, f12 float32,
535+ f13, f14, f15, f16 float32,
536+ f17 float32, a5 int64,
537+ ) int64)
538+ return strconv.FormatInt(fn(
539+ 12, 34, 56, 78,
540+ 0, 0, 0, 0,
541+ 0, 0, 0, 0,
542+ 0, 0, 0, 0,
543+ 0, 0, 0, 0,
544+ 0, math.MaxInt32+1500,
545+ ), 10)
546+ },
547+ want: strconv.FormatInt(12*1+34*2+56*3+78*4+math.MaxInt32+1500, 10),
548+ },
549+ {
550+ // check if unaligned float 64bit argument and float 64bit returned value is properly passed via registers
551+ // arm-softfloat-specific but must work everywhere
552+ name: "arm_float64_unaligned_in_registers",
553+ fn: new(func(uintptr, float64) float64),
554+ cFn: "arm_float64_unaligned_in_registers",
555+ call: func(f any) string {
556+ fn := *(f).(*func(x uintptr, y float64) float64)
557+ return strconv.FormatFloat(fn(456, math.MaxFloat32+1500), 'b', 10, 64)
558+ },
559+ want: strconv.FormatFloat(456*123.5+math.MaxFloat32+1500, 'b', 10, 64),
560+ },
561+ {
562+ // check if unaligned float 64bit argument and float 64bit returned value is properly passed via stack
563+ // arm-softfloat-specific but must work everywhere
564+ name: "arm_float64_unaligned_on_stack",
565+ fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, float64) float64),
566+ cFn: "arm_float64_unaligned_on_stack",
567+ call: func(f any) string {
568+ fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 float64) float64)
569+ return strconv.FormatFloat(fn(12, 34, 56, 78, 90, math.MaxFloat32+1500), 'b', 10, 64)
570+ },
571+ want: strconv.FormatFloat(12*1+34*2+56*3+78*4+90*5+math.MaxFloat32+1500, 'b', 10, 64),
572+ },
405 }573 }
406 574
407 for _, tt := range tests {575 for _, tt := range tests {
408 t.Run(tt.name, func(t *testing.T) {576 t.Run(tt.name, func(t *testing.T) {
409- if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") {577+ if tt.name == "20_int32" && runtime.GOARCH == "ppc64le" {
410- t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking")578+ t.Skip("ppc64le retains the 15-argument limit")
411 }579 }
412- if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") {580+ if (tt.name == "10_float32" || tt.name == "arm_int64_unaligned_on_stack_after_floats") &&
581+ (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") {
413 t.Skip("float32 stack arguments not yet supported on this platform")582 t.Skip("float32 stack arguments not yet supported on this platform")
414 }583 }
415 // Struct tests require Darwin ARM64 or AMD64584 // Struct tests require Darwin ARM64 or AMD64
@@ -427,43 +596,241 @@ func TestABI_ArgumentPassing(t *testing.T) {
427 }596 }
428 })597 })
429 }598 }
430-}
431 599
432-func TestABI_TooManyArguments(t *testing.T) {600+ t.Run("20_uintptr", func(t *testing.T) {
433- if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {601+ if runtime.GOARCH == "ppc64le" {
434- t.Skip("This test is specific to Darwin ARM64")602+ t.Skip("ppc64le retains the 15-argument limit")
435- }603+ }
604+ var fn func(uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr) uintptr
605+ purego.RegisterLibFunc(&fn, lib, "stack_20_uintptr")
606+ got := fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
607+ const want = uintptr(210)
608+ if got != want {
609+ t.Fatalf("stack_20_uintptr: got %d, want %d", got, want)
610+ }
611+ })
436 612
437- libFileName := filepath.Join(t.TempDir(), "abitest.so")613+ t.Run("32_uintptr", func(t *testing.T) {
438- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil {614+ if runtime.GOARCH == "ppc64le" {
439- t.Fatal(err)615+ t.Skip("ppc64le retains the 15-argument limit")
440- }616+ }
441- lib, err := load.OpenLibrary(libFileName)617+ var fn func(
442- if err != nil {618+ uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
443- t.Fatalf("Failed to open library %q: %v", libFileName, err)619+ uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
444- }620+ uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
445- t.Cleanup(func() {621+ uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
446- if err := load.CloseLibrary(lib); err != nil {622+ ) uintptr
447- t.Errorf("Failed to close library: %v", err)623+ purego.RegisterLibFunc(&fn, lib, "stack_32_uintptr")
624+ got := fn(
625+ 1, 2, 3, 4, 5, 6, 7, 8,
626+ 9, 10, 11, 12, 13, 14, 15, 16,
627+ 17, 18, 19, 20, 21, 22, 23, 24,
628+ 25, 26, 27, 28, 29, 30, 31, 32,
629+ )
630+ const want = uintptr(528)
631+ if got != want {
632+ t.Fatalf("stack_32_uintptr: got %d, want %d", got, want)
633+ }
634+ })
635+
636+ t.Run("syscalln_20_uintptr", func(t *testing.T) {
637+ if runtime.GOARCH == "ppc64le" {
638+ t.Skip("ppc64le retains the 15-argument limit")
639+ }
640+ fn, err := load.OpenSymbol(lib, "stack_20_uintptr")
641+ if err != nil {
642+ t.Fatalf("OpenSymbol(stack_20_uintptr) failed: %v", err)
643+ }
644+ got, _, _ := purego.SyscallN(fn,
645+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
646+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
647+ )
648+ const want = uintptr(210)
649+ if got != want {
650+ t.Fatalf("stack_20_uintptr SyscallN: got %d, want %d", got, want)
448 }651 }
449 })652 })
450 653
451- // Test that 35 int64 arguments (27 slots needed) exceeds the limit654+ t.Run("syscalln_32_uintptr", func(t *testing.T) {
452- t.Run("35_int64_exceeds_limit", func(t *testing.T) {655+ if runtime.GOARCH == "ppc64le" {
656+ t.Skip("ppc64le retains the 15-argument limit")
657+ }
658+ fn, err := load.OpenSymbol(lib, "stack_32_uintptr")
659+ if err != nil {
660+ t.Fatalf("OpenSymbol(stack_32_uintptr) failed: %v", err)
661+ }
662+ got, _, _ := purego.SyscallN(fn,
663+ 1, 2, 3, 4, 5, 6, 7, 8,
664+ 9, 10, 11, 12, 13, 14, 15, 16,
665+ 17, 18, 19, 20, 21, 22, 23, 24,
666+ 25, 26, 27, 28, 29, 30, 31, 32,
667+ )
668+ const want = uintptr(528)
669+ if got != want {
670+ t.Fatalf("stack_32_uintptr SyscallN: got %d, want %d", got, want)
671+ }
672+ })
673+
674+ t.Run("32_mixed_int_float", func(t *testing.T) {
675+ if unsafe.Sizeof(uintptr(0)) == 4 {
676+ t.Skip("requires 64-bit uintptr slots")
677+ }
678+ if runtime.GOARCH == "ppc64le" {
679+ t.Skip("mixed int/float stack arguments are not yet supported on ppc64le")
680+ }
681+
682+ var fn func(
683+ uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
684+ uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr,
685+ float64, float64, float64, float64, float64, float64, float64, float64,
686+ float64, float64, float64, float64, float64, float64, float64, float64,
687+ ) float64
688+ purego.RegisterLibFunc(&fn, lib, "stack_32_mixed_int_float")
689+ got := fn(
690+ 1, 2, 3, 4, 5, 6, 7, 8,
691+ 9, 10, 11, 12, 13, 14, 15, 16,
692+ 1, 2, 3, 4, 5, 6, 7, 8,
693+ 9, 10, 11, 12, 13, 14, 15, 16,
694+ )
695+ const want = 5168.0
696+ if got != want {
697+ t.Fatalf("stack_32_mixed_int_float: got %f, want %f", got, want)
698+ }
699+ })
700+}
701+
702+func TestABI_TooManyArguments(t *testing.T) {
703+ mustPanic := func(t *testing.T, want string, f func()) {
704+ t.Helper()
453 defer func() {705 defer func() {
454- if r := recover(); r != nil {706+ r := recover()
455- t.Logf("Got expected panic: %v", r)707+ if r == nil {
456- } else {708+ t.Fatalf("expected panic %q, got none", want)
457- t.Errorf("Expected panic but didn't get one")709+ }
710+ got := fmt.Sprint(r)
711+ if got != want {
712+ t.Fatalf("panic mismatch:\n got: %q\n want: %q", got, want)
458 }713 }
459 }()714 }()
715+ f()
716+ }
460 717
461- var fn func(*byte, uintptr, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64)718+ // 33 int64 parameters exceeds maxArgs=32.
462- purego.RegisterLibFunc(&fn, lib, "stack_35_int64_exceeds")719+ t.Run("registerfunc_33_int64_exceeds_limit", func(t *testing.T) {
720+ mustPanic(t, "purego: too many stack arguments", func() {
721+ var fn func(
722+ int64, int64, int64, int64, int64, int64, int64, int64,
723+ int64, int64, int64, int64, int64, int64, int64, int64,
724+ int64, int64, int64, int64, int64, int64, int64, int64,
725+ int64, int64, int64, int64, int64, int64, int64, int64,
726+ int64,
727+ )
728+ purego.RegisterFunc(&fn, 1)
729+ })
730+ })
731+
732+ t.Run("registerfunc_16_int64_exceeds_ppc64le_limit", func(t *testing.T) {
733+ if runtime.GOARCH != "ppc64le" {
734+ t.Skip("ppc64le retains the 15-argument limit")
735+ }
736+ mustPanic(t, "purego: too many stack arguments", func() {
737+ var fn func(
738+ int64, int64, int64, int64, int64, int64, int64, int64,
739+ int64, int64, int64, int64, int64, int64, int64, int64,
740+ )
741+ purego.RegisterFunc(&fn, 1)
742+ })
743+ })
744+
745+ t.Run("syscalln_33_uintptr_exceeds_limit", func(t *testing.T) {
746+ mustPanic(t, "purego: too many arguments to SyscallN", func() {
747+ purego.SyscallN(1,
748+ 1, 2, 3, 4, 5, 6, 7, 8,
749+ 9, 10, 11, 12, 13, 14, 15, 16,
750+ 17, 18, 19, 20, 21, 22, 23, 24,
751+ 25, 26, 27, 28, 29, 30, 31, 32,
752+ 33,
753+ )
754+ })
755+ })
756+
757+ t.Run("syscalln_16_uintptr_exceeds_ppc64le_limit", func(t *testing.T) {
758+ if runtime.GOARCH != "ppc64le" {
759+ t.Skip("ppc64le retains the 15-argument limit")
760+ }
761+ mustPanic(t, "purego: too many arguments to SyscallN", func() {
762+ purego.SyscallN(1,
763+ 1, 2, 3, 4, 5, 6, 7, 8,
764+ 9, 10, 11, 12, 13, 14, 15, 16,
765+ )
766+ })
463 })767 })
464 }768 }
465 769
466-func buildSharedLib(compilerEnv, libFile string, sources ...string) error {770+func TestABI_StructReturnHiddenPointer(t *testing.T) {
771+ // A struct returned in memory is passed a hidden pointer as the first
772+ // integer argument, so maxArgs integer arguments plus it need one more slot
773+ // than sysargs holds and RegisterFunc must reject the registration.
774+ type bigStruct struct{ A, B, C uint64 } // larger than two eightbytes
775+
776+ if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]()) {
777+ t.Skipf("GOARCH=%s does not return large structs via a hidden integer argument", runtime.GOARCH)
778+ }
779+
780+ in := make([]reflect.Type, purego.MaxArgs)
781+ for i := range in {
782+ in[i] = reflect.TypeFor[uintptr]()
783+ }
784+ fnType := reflect.FuncOf(in, []reflect.Type{reflect.TypeFor[bigStruct]()}, false)
785+ fptr := reflect.New(fnType)
786+
787+ defer func() {
788+ switch r := recover(); {
789+ case r == nil:
790+ t.Fatal("RegisterFunc accepted the call; the hidden struct-return pointer was not counted against the stack limit")
791+ case strings.Contains(fmt.Sprint(r), "too many stack arguments"):
792+ // Expected: the guard fired.
793+ case strings.Contains(fmt.Sprint(r), "only supported on"):
794+ t.Skipf("struct returns are unsupported on this platform: %v", r)
795+ default:
796+ t.Fatalf("unexpected panic: %v", r)
797+ }
798+ }()
799+
800+ // A non-zero cfn passes the nil check; the panic fires during the preflight
801+ // argument count, before the function is ever called.
802+ purego.RegisterFunc(fptr.Interface(), uintptr(1))
803+}
804+
805+func buildSharedLib(tb testing.TB, compilerEnv, libFile string, sources ...string) error {
806+ tb.Helper()
807+ // When PUREGO_TEST_PREBUILT_LIBDIR is set, the shared library has been
808+ // cross-compiled ahead of time and placed in that directory under the
809+ // base name of libFile. This allows running the tests on a target that
810+ // has no C toolchain, such as an Android emulator.
811+ if dir := os.Getenv("PUREGO_TEST_PREBUILT_LIBDIR"); dir != "" {
812+ data, err := os.ReadFile(filepath.Join(dir, filepath.Base(libFile)))
813+ if err != nil {
814+ return fmt.Errorf("prebuilt lib: %w", err)
815+ }
816+ if err := os.WriteFile(libFile, data, 0o755); err != nil {
817+ return fmt.Errorf("prebuilt lib: %w", err)
818+ }
819+ return nil
820+ }
821+
822+ // Compiling the library needs a C toolchain targeting GOARCH. CI has none
823+ // for Windows on 386 or arm64, so skip those (the prebuilt path above
824+ // avoids the toolchain).
825+ if runtime.GOOS == "windows" {
826+ switch runtime.GOARCH {
827+ case "386":
828+ tb.Skip("need a 386 C toolchain to run this test") // TODO: find a 386 C toolchain for test
829+ case "arm64":
830+ tb.Skip("need an arm64 C toolchain to run this test")
831+ }
832+ }
833+
467 out, err := exec.Command("go", "env", compilerEnv).Output()834 out, err := exec.Command("go", "env", compilerEnv).Output()
468 if err != nil {835 if err != nil {
469 return fmt.Errorf("go env %s error: %w", compilerEnv, err)836 return fmt.Errorf("go env %s error: %w", compilerEnv, err)
M go.mod +1 -1
@@ -1,3 +1,3 @@
1 module github.com/bnema/purego1 module github.com/bnema/purego
2 2
3-go 1.203+go 1.25.0
M go_runtime.go +6 -0
@@ -11,3 +11,9 @@ import (
11 11
12 //go:linkname runtime_cgocall runtime.cgocall12 //go:linkname runtime_cgocall runtime.cgocall
13 func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go13 func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go
14+
15+// from runtime/runtime2.go, exported via go:linkname for usage with cgo assembly
16+// pulled in as struct{} for proper linking, see https://github.com/golang/go/issues/72032
17+//
18+//go:linkname runtime_goarmsoftfp runtime.goarmsoftfp
19+var runtime_goarmsoftfp struct{}
D hostlayout_go123.go +0 -12
@@ -1,12 +0,0 @@
1-// SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3-
4-//go:build go1.23
5-
6-package purego
7-
8-import "structs"
9-
10-// hostLayout is structs.HostLayout on Go 1.23+, ensuring the compiler
11-// uses host (C-compatible) memory layout for structs that embed it.
12-type hostLayout = structs.HostLayout
D hostlayout_nogo123.go +0 -11
@@ -1,11 +0,0 @@
1-// SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3-
4-//go:build !go1.23
5-
6-package purego
7-
8-// hostLayout is a zero-size placeholder for Go versions before 1.23
9-// that lack the structs package. On Go 1.23+ this is replaced by
10-// structs.HostLayout which tells the compiler to use host memory layout.
11-type hostLayout struct{}
M internal/cgo/syscall_cgo_unix.go +16 -22
@@ -16,40 +16,34 @@ package cgo
16 #include <errno.h>16 #include <errno.h>
17 #include <assert.h>17 #include <assert.h>
18 18
19-typedef struct syscall15Args {19+typedef struct syscallArgs {
20 uintptr_t fn;20 uintptr_t fn;
21 uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15;21 uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15;
22+ uintptr_t a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32;
22 uintptr_t f1, f2, f3, f4, f5, f6, f7, f8;23 uintptr_t f1, f2, f3, f4, f5, f6, f7, f8;
23- uintptr_t err;24+ uintptr_t arm64_r8;
24-} syscall15Args;25+} syscallArgs;
25 26
26-void syscall15(struct syscall15Args *args) {27+void syscall15(struct syscallArgs *args) {
27 assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0);28 assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0);
28 uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6,29 uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6,
29 uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12,30 uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12,
30- uintptr_t a13, uintptr_t a14, uintptr_t a15);31+ uintptr_t a13, uintptr_t a14, uintptr_t a15, uintptr_t a16, uintptr_t a17, uintptr_t a18,
32+ uintptr_t a19, uintptr_t a20, uintptr_t a21, uintptr_t a22, uintptr_t a23, uintptr_t a24,
33+ uintptr_t a25, uintptr_t a26, uintptr_t a27, uintptr_t a28, uintptr_t a29, uintptr_t a30,
34+ uintptr_t a31, uintptr_t a32);
31 *(void**)(&func_name) = (void*)(args->fn);35 *(void**)(&func_name) = (void*)(args->fn);
32- uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9,36+ uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9,
33- args->a10,args->a11,args->a12,args->a13,args->a14,args->a15);37+ args->a10,args->a11,args->a12,args->a13,args->a14,args->a15,args->a16,args->a17,args->a18,
38+ args->a19,args->a20,args->a21,args->a22,args->a23,args->a24,args->a25,args->a26,args->a27,
39+ args->a28,args->a29,args->a30,args->a31,args->a32);
34 args->a1 = r1;40 args->a1 = r1;
35- args->err = errno;41+ args->a3 = errno;
36 }42 }
37 43
38 */44 */
39 import "C"45 import "C"
40 import "unsafe"46 import "unsafe"
41 47
42-// assign purego.syscall15XABI0 to the C version of this function.48+// assign purego.syscallXABI0 to the C version of this function.
43-var Syscall15XABI0 = unsafe.Pointer(C.syscall15)49+var SyscallXABI0 = unsafe.Pointer(C.syscall15)
44-
45-//go:nosplit
46-func Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
47- args := C.syscall15Args{
48- C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3),
49- C.uintptr_t(a4), C.uintptr_t(a5), C.uintptr_t(a6),
50- C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), C.uintptr_t(a10), C.uintptr_t(a11), C.uintptr_t(a12),
51- C.uintptr_t(a13), C.uintptr_t(a14), C.uintptr_t(a15), 0, 0, 0, 0, 0, 0, 0, 0, 0,
52- }
53- C.syscall15(&args)
54- return uintptr(args.a1), 0, uintptr(args.err)
55-}
M internal/fakecgo/asm_arm.s +9 -3
@@ -21,8 +21,11 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
21 // starting at 4(R13).21 // starting at 4(R13).
22 MOVW.W R14, -4(R13)22 MOVW.W R14, -4(R13)
23 23
24+ // Skip floating point registers if runtime.goarmsoftfp!=0.
25+ MOVB runtime·goarmsoftfp(SB), R11
26+ CMP $0, R11
27+ BNE skipfpsave
24 // Save VFP callee-saved registers D8-D15 (same as S16-S31).28 // Save VFP callee-saved registers D8-D15 (same as S16-S31).
25- // Note: We always save these since we target hard-float ABI.
26 MOVD F8, (13*4+8*1)(R13)29 MOVD F8, (13*4+8*1)(R13)
27 MOVD F9, (13*4+8*2)(R13)30 MOVD F9, (13*4+8*2)(R13)
28 MOVD F10, (13*4+8*3)(R13)31 MOVD F10, (13*4+8*3)(R13)
@@ -32,11 +35,13 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
32 MOVD F14, (13*4+8*7)(R13)35 MOVD F14, (13*4+8*7)(R13)
33 MOVD F15, (13*4+8*8)(R13)36 MOVD F15, (13*4+8*8)(R13)
34 37
35- BL runtime·load_g(SB)38+skipfpsave:
36-
37 // We set up the arguments to cgocallback when saving registers above.39 // We set up the arguments to cgocallback when saving registers above.
38 BL runtime·cgocallback(SB)40 BL runtime·cgocallback(SB)
39 41
42+ MOVB runtime·goarmsoftfp(SB), R11
43+ CMP $0, R11
44+ BNE skipfprest
40 MOVD (13*4+8*1)(R13), F845 MOVD (13*4+8*1)(R13), F8
41 MOVD (13*4+8*2)(R13), F946 MOVD (13*4+8*2)(R13), F9
42 MOVD (13*4+8*3)(R13), F1047 MOVD (13*4+8*3)(R13), F10
@@ -46,6 +51,7 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
46 MOVD (13*4+8*7)(R13), F1451 MOVD (13*4+8*7)(R13), F14
47 MOVD (13*4+8*8)(R13), F1552 MOVD (13*4+8*8)(R13), F15
48 53
54+skipfprest:
49 MOVW.P 4(R13), R1455 MOVW.P 4(R13), R14
50 MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12]56 MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12]
51 ADD $(8*9), R1357 ADD $(8*9), R13
M internal/fakecgo/asm_arm64.s +0 -1
@@ -25,7 +25,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
25 STP (R29, R30), (8*22)(RSP)25 STP (R29, R30), (8*22)(RSP)
26 26
27 // Initialize Go ABI environment27 // Initialize Go ABI environment
28- BL runtime·load_g(SB)
29 BL runtime·cgocallback(SB)28 BL runtime·cgocallback(SB)
30 29
31 RESTORE_R19_TO_R28(8*4)30 RESTORE_R19_TO_R28(8*4)
M internal/fakecgo/asm_loong64.s +0 -2
@@ -27,8 +27,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
27 MOVV R1, (22*8)(R3)27 MOVV R1, (22*8)(R3)
28 28
29 // Initialize Go ABI environment29 // Initialize Go ABI environment
30- JAL runtime·load_g(SB)
31-
32 JAL runtime·cgocallback(SB)30 JAL runtime·cgocallback(SB)
33 31
34 RESTORE_R22_TO_R31((4*8))32 RESTORE_R22_TO_R31((4*8))
M internal/fakecgo/asm_ppc64le.s +0 -3
@@ -50,9 +50,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
50 // Initialize R0 to 0 as expected by Go50 // Initialize R0 to 0 as expected by Go
51 MOVD $0, R051 MOVD $0, R0
52 52
53- // Load the current g.
54- BL runtime·load_g(SB)
55-
56 // Set up arguments for cgocallback53 // Set up arguments for cgocallback
57 MOVD R3, FIXED_FRAME+0(R1) // fn unsafe.Pointer54 MOVD R3, FIXED_FRAME+0(R1) // fn unsafe.Pointer
58 MOVD R4, FIXED_FRAME+8(R1) // a unsafe.Pointer55 MOVD R4, FIXED_FRAME+8(R1) // a unsafe.Pointer
M internal/fakecgo/asm_riscv64.s +0 -1
@@ -25,7 +25,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
25 SAVE_FPR((8*17))25 SAVE_FPR((8*17))
26 26
27 // Initialize Go ABI environment27 // Initialize Go ABI environment
28- CALL runtime·load_g(SB)
29 CALL runtime·cgocallback(SB)28 CALL runtime·cgocallback(SB)
30 29
31 RESTORE_GPR((8*4))30 RESTORE_GPR((8*4))
M internal/fakecgo/asm_s390x.s +0 -3
@@ -27,9 +27,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
27 FMOVD F14, 80(R15)27 FMOVD F14, 80(R15)
28 FMOVD F15, 88(R15)28 FMOVD F15, 88(R15)
29 29
30- // Initialize Go ABI environment.
31- BL runtime·load_g(SB)
32-
33 MOVD R2, 8(R15) // fn unsafe.Pointer30 MOVD R2, 8(R15) // fn unsafe.Pointer
34 MOVD R3, 16(R15) // a unsafe.Pointer31 MOVD R3, 16(R15) // a unsafe.Pointer
35 32
M internal/fakecgo/gen.go +1 -1
@@ -268,7 +268,7 @@ func run() error {
268 pthreadSO = "/usr/lib/libSystem.B.dylib"268 pthreadSO = "/usr/lib/libSystem.B.dylib"
269 case "freebsd":269 case "freebsd":
270 libcSO = "libc.so.7"270 libcSO = "libc.so.7"
271- pthreadSO = "libpthread.so"271+ pthreadSO = "libthr.so.3"
272 case "linux":272 case "linux":
273 libcSO = "libc.so.6"273 libcSO = "libc.so.6"
274 pthreadSO = "libpthread.so.0"274 pthreadSO = "libpthread.so.0"
M internal/fakecgo/go_darwin.go +1 -3
@@ -55,9 +55,7 @@ func threadentry(v unsafe.Pointer) unsafe.Pointer {
55 //#endif55 //#endif
56 setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))56 setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))
57 57
58- // faking funcs in go is a bit a... involved - but the following works :)58+ callThreadEntryFn(ts.fn)
59- fn := uintptr(unsafe.Pointer(&ts.fn))
60- (*(*func())(unsafe.Pointer(&fn)))()
61 59
62 return nil60 return nil
63 }61 }
M internal/fakecgo/go_freebsd.go +2 -4
@@ -2,7 +2,7 @@
2 // Use of this source code is governed by a BSD-style2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.3 // license that can be found in the LICENSE file.
4 4
5-//go:build !cgo5+//go:build !cgo && (amd64 || arm64)
6 6
7 package fakecgo7 package fakecgo
8 8
@@ -49,9 +49,7 @@ func threadentry(v unsafe.Pointer) unsafe.Pointer {
49 49
50 setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))50 setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))
51 51
52- // faking funcs in go is a bit a... involved - but the following works :)52+ callThreadEntryFn(ts.fn)
53- fn := uintptr(unsafe.Pointer(&ts.fn))
54- (*(*func())(unsafe.Pointer(&fn)))()
55 53
56 return nil54 return nil
57 }55 }
M internal/fakecgo/go_libinit.go +1 -1
@@ -52,7 +52,7 @@ func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe
52 tries := ts.Nsec52 tries := ts.Nsec
53 var err int53 var err int
54 54
55- for tries = 0; tries < 20; tries++ {55+ for tries = range 20 {
56 // inlined this call because it ran out of stack when inlining was disabled56 // inlined this call because it ran out of stack when inlining was disabled
57 err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0))57 err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0))
58 if err == 0 {58 if err == 0 {
M internal/fakecgo/go_linux.go +1 -3
@@ -49,9 +49,7 @@ func threadentry(v unsafe.Pointer) unsafe.Pointer {
49 49
50 setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))50 setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))
51 51
52- // faking funcs in go is a bit a... involved - but the following works :)52+ callThreadEntryFn(ts.fn)
53- fn := uintptr(unsafe.Pointer(&ts.fn))
54- (*(*func())(unsafe.Pointer(&fn)))()
55 53
56 return nil54 return nil
57 }55 }
M internal/fakecgo/go_netbsd.go +1 -3
@@ -60,9 +60,7 @@ func threadentry(v unsafe.Pointer) unsafe.Pointer {
60 60
61 setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))61 setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))
62 62
63- // faking funcs in go is a bit a... involved - but the following works :)63+ callThreadEntryFn(ts.fn)
64- fn := uintptr(unsafe.Pointer(&ts.fn))
65- (*(*func())(unsafe.Pointer(&fn)))()
66 64
67 return nil65 return nil
68 }66 }
M internal/fakecgo/libcgo.go +4 -0
@@ -5,6 +5,8 @@
5 5
6 package fakecgo6 package fakecgo
7 7
8+import "structs"
9+
8 type (10 type (
9 size_t uintptr11 size_t uintptr
10 // Sources:12 // Sources:
@@ -28,11 +30,13 @@ const (
28 )30 )
29 31
30 type G struct {32 type G struct {
33+ _ structs.HostLayout
31 stacklo uintptr34 stacklo uintptr
32 stackhi uintptr35 stackhi uintptr
33 }36 }
34 37
35 type ThreadStart struct {38 type ThreadStart struct {
39+ _ structs.HostLayout
36 g *G40 g *G
37 tls *uintptr41 tls *uintptr
38 fn uintptr42 fn uintptr
M internal/fakecgo/libcgo_darwin.go +4 -0
@@ -5,12 +5,16 @@
5 5
6 package fakecgo6 package fakecgo
7 7
8+import "structs"
9+
8 type (10 type (
9 pthread_mutex_t struct {11 pthread_mutex_t struct {
12+ _ structs.HostLayout
10 sig int6413 sig int64
11 opaque [56]byte14 opaque [56]byte
12 }15 }
13 pthread_cond_t struct {16 pthread_cond_t struct {
17+ _ structs.HostLayout
14 sig int6418 sig int64
15 opaque [40]byte19 opaque [40]byte
16 }20 }
M internal/fakecgo/libcgo_netbsd.go +3 -0
@@ -5,6 +5,8 @@
5 5
6 package fakecgo6 package fakecgo
7 7
8+import "structs"
9+
8 type (10 type (
9 pthread_cond_t uintptr11 pthread_cond_t uintptr
10 pthread_mutex_t uintptr12 pthread_mutex_t uintptr
@@ -17,6 +19,7 @@ var (
17 19
18 // Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/sys/signal.h#L22520 // Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/sys/signal.h#L225
19 type stack_t struct {21 type stack_t struct {
22+ _ structs.HostLayout
20 ss_sp uintptr23 ss_sp uintptr
21 ss_size uintptr24 ss_size uintptr
22 ss_flags int3225 ss_flags int32
M internal/fakecgo/linux.go +7 -2
@@ -6,10 +6,15 @@
6 6
7 package fakecgo7 package fakecgo
8 8
9-import "unsafe"9+import (
10+ "structs"
11+ "unsafe"
12+)
10 13
11-// argset matches runtime/cgocall.go:argset.14+// argset matches runtime/cgocall.go:argset, which in turn matches
15+// runtime/cgo/linux_syscall.c:argset_t.
12 type argset struct {16 type argset struct {
17+ _ structs.HostLayout
13 args *uintptr18 args *uintptr
14 retval uintptr19 retval uintptr
15 }20 }
A internal/fakecgo/threadentry_amd64.go +15 -0
@@ -0,0 +1,15 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && amd64 && (darwin || freebsd || linux || netbsd)
5+
6+package fakecgo
7+
8+// callThreadEntryFn calls fn (runtime.mstart) while saving and restoring the
9+// frame pointer and other callee-saved registers around the call. mstart
10+// returns with BP clobbered, so without this shim the caller's frame-pointer
11+// epilogue would fault. Implemented in trampolines_amd64.s.
12+//
13+//go:nosplit
14+//go:norace
15+func callThreadEntryFn(fn uintptr)
A internal/fakecgo/threadentry_noasm.go +24 -0
@@ -0,0 +1,24 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && !amd64 && (darwin || freebsd || linux || netbsd)
5+
6+package fakecgo
7+
8+import "unsafe"
9+
10+// callThreadEntryFn calls fn (runtime.mstart). On architectures without the
11+// amd64 frame-pointer epilogue issue this is a plain indirect call. It must be
12+// nosplit and norace like the threadentry callers so it neither inserts a
13+// morestack preamble nor runs race instrumentation during the fragile
14+// thread-bootstrap window.
15+//
16+//go:nosplit
17+//go:norace
18+func callThreadEntryFn(fn uintptr) {
19+ // fn is the code pointer. Build a func value whose first word is fn by
20+ // pointing the closure at &fn, then call it (same trick fakecgo has always
21+ // used to call a raw PC from Go).
22+ fnPtr := uintptr(unsafe.Pointer(&fn))
23+ (*(*func())(unsafe.Pointer(&fnPtr)))()
24+}
A internal/fakecgo/threadentry_test.go +50 -0
@@ -0,0 +1,50 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+// 386/arm are excluded: the standalone fakecgo test binary resolves pthread
5+// from libpthread.so.0, which fails the dynamic symbol lookup on 32-bit linux.
6+//go:build !cgo && (linux || darwin || freebsd || netbsd) && !386 && !arm
7+
8+package fakecgo
9+
10+import (
11+ "runtime"
12+ "sync"
13+ "testing"
14+)
15+
16+// TestThreadEntryReturn exercises the fakecgo threadentry -> runtime.mstart
17+// return path, i.e. that an M created by fakecgo can exit cleanly.
18+//
19+// When iscgo is forced true by fakecgo, the runtime creates every new OS
20+// thread through _cgo_thread_start -> threadentry_trampoline -> threadentry,
21+// which calls runtime.mstart. For a fakecgo thread the M's stack is
22+// system-allocated, so when the M exits, mexit(osStack=true) returns from
23+// mstart back into threadentry. This return path has been the source of
24+// crashes (a frame-pointer LEAVE fault on amd64, a bad indirect call and race
25+// instrumentation on other platforms), so it is worth exercising everywhere.
26+//
27+// Locking an OS thread and then letting the goroutine exit (without calling
28+// UnlockOSThread) forces the locked fakecgo M to exit, driving mstart to return
29+// into threadentry. A busy loop that never lets the goroutine exit does NOT
30+// reproduce the crash, because the M never exits and mstart never returns.
31+func TestThreadEntryReturn(t *testing.T) {
32+ // The M exits on the first goroutine return, so a modest amount of churn is
33+ // enough to catch a broken teardown while staying quick under emulation.
34+ const rounds = 10
35+ const workers = 16
36+ for range rounds {
37+ var wg sync.WaitGroup
38+ wg.Add(workers)
39+ for range workers {
40+ go func() {
41+ defer wg.Done()
42+ runtime.LockOSThread()
43+ // Intentionally do NOT call UnlockOSThread: returning here exits
44+ // the goroutine while its OS thread is locked, forcing the M to
45+ // exit and mstart to return into fakecgo.threadentry.
46+ }()
47+ }
48+ wg.Wait()
49+ }
50+}
M internal/fakecgo/trampolines_amd64.s +17 -0
@@ -83,6 +83,23 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $0
83 POP_REGS_HOST_TO_ABI0()83 POP_REGS_HOST_TO_ABI0()
84 RET84 RET
85 85
86+// func callThreadEntryFn(fn uintptr)
87+// Calls fn (runtime.mstart) with the frame pointer and other callee-saved
88+// registers saved on the stack across the call. mstart returns with BP
89+// clobbered (BP==0); saving/restoring BP here (as real cgo's crosscall does)
90+// keeps the caller's frame-pointer LEAVE epilogue valid on return.
91+TEXT ·callThreadEntryFn(SB), NOSPLIT, $0-8
92+ MOVQ fn+0(FP), R11
93+ PUSH_REGS_HOST_TO_ABI0()
94+
95+ // X15 is designated by Go as a fixed zero register.
96+ PXOR X15, X15
97+
98+ CALL R11
99+
100+ POP_REGS_HOST_TO_ABI0()
101+ RET
102+
86 TEXT ·call5(SB), NOSPLIT, $0-56103 TEXT ·call5(SB), NOSPLIT, $0-56
87 MOVQ fn+0(FP), R11104 MOVQ fn+0(FP), R11
88 MOVQ a1+8(FP), DI105 MOVQ a1+8(FP), DI
M internal/fakecgo/trampolines_arm.s +9 -0
@@ -70,6 +70,10 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
70 MOVW g, 32(R13) // R1070 MOVW g, 32(R13) // R10
71 MOVW R11, 36(R13)71 MOVW R11, 36(R13)
72 72
73+ // Skip floating point registers if runtime.goarmsoftfp!=0.
74+ MOVB runtime·goarmsoftfp(SB), R11
75+ CMP $0, R11
76+ BNE skipfpsave
73 MOVD F8, 40(R13)77 MOVD F8, 40(R13)
74 MOVD F9, 48(R13)78 MOVD F9, 48(R13)
75 MOVD F10, 56(R13)79 MOVD F10, 56(R13)
@@ -79,10 +83,14 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
79 MOVD F14, 88(R13)83 MOVD F14, 88(R13)
80 MOVD F15, 96(R13)84 MOVD F15, 96(R13)
81 85
86+skipfpsave:
82 MOVW ·threadentry_call(SB), R1287 MOVW ·threadentry_call(SB), R12
83 MOVW (R12), R1288 MOVW (R12), R12
84 CALL (R12)89 CALL (R12)
85 90
91+ MOVB runtime·goarmsoftfp(SB), R11
92+ CMP $0, R11
93+ BNE skipfprest
86 MOVD 40(R13), F894 MOVD 40(R13), F8
87 MOVD 48(R13), F995 MOVD 48(R13), F9
88 MOVD 56(R13), F1096 MOVD 56(R13), F10
@@ -92,6 +100,7 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
92 MOVD 88(R13), F14100 MOVD 88(R13), F14
93 MOVD 96(R13), F15101 MOVD 96(R13), F15
94 102
103+skipfprest:
95 MOVW 8(R13), R4104 MOVW 8(R13), R4
96 MOVW 12(R13), R5105 MOVW 12(R13), R5
97 MOVW 16(R13), R6106 MOVW 16(R13), R6
A internal/fakecgo/trampolines_linux_s390x.s +51 -0
@@ -0,0 +1,51 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+//go:build go1.27 && !cgo
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT|NOFRAME, $0
9+ MOVD ·x_cgo_purego_setegid_call(SB), R1
10+ MOVD (R1), R1
11+ BR R1
12+
13+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT|NOFRAME, $0
14+ MOVD ·x_cgo_purego_seteuid_call(SB), R1
15+ MOVD (R1), R1
16+ BR R1
17+
18+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT|NOFRAME, $0
19+ MOVD ·x_cgo_purego_setgid_call(SB), R1
20+ MOVD (R1), R1
21+ BR R1
22+
23+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT|NOFRAME, $0
24+ MOVD ·x_cgo_purego_setregid_call(SB), R1
25+ MOVD (R1), R1
26+ BR R1
27+
28+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT|NOFRAME, $0
29+ MOVD ·x_cgo_purego_setresgid_call(SB), R1
30+ MOVD (R1), R1
31+ BR R1
32+
33+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT|NOFRAME, $0
34+ MOVD ·x_cgo_purego_setresuid_call(SB), R1
35+ MOVD (R1), R1
36+ BR R1
37+
38+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT|NOFRAME, $0
39+ MOVD ·x_cgo_purego_setreuid_call(SB), R1
40+ MOVD (R1), R1
41+ BR R1
42+
43+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT|NOFRAME, $0
44+ MOVD ·x_cgo_purego_setuid_call(SB), R1
45+ MOVD (R1), R1
46+ BR R1
47+
48+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT|NOFRAME, $0
49+ MOVD ·x_cgo_purego_setgroups_call(SB), R1
50+ MOVD (R1), R1
51+ BR R1
M internal/fakecgo/trampolines_ppc64le.s +6 -2
@@ -47,15 +47,19 @@ TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0-0
47 47
48 // func setg_trampoline(setg uintptr, g uintptr)48 // func setg_trampoline(setg uintptr, g uintptr)
49 TEXT ·setg_trampoline(SB), NOSPLIT, $16-1649 TEXT ·setg_trampoline(SB), NOSPLIT, $16-16
50- MOVD R31, 8(R1) // save R31 (load_g clobbers it)50+ MOVD R31, 8(R1) // save R31
51 51
52 MOVD setg+0(FP), R1252 MOVD setg+0(FP), R12
53 MOVD newg+8(FP), R353 MOVD newg+8(FP), R3
54 54
55+ MOVD R3, 16(R1) // save newg before call
56+
55 MOVD R12, CTR57 MOVD R12, CTR
56 CALL CTR58 CALL CTR
57 59
58- CALL runtime·load_g(SB)60+ // Assign g directly instead of calling runtime·load_g
61+ // setg_gcc has already stored newg into TLS; put it in the g register too.
62+ MOVD 16(R1), g
59 63
60 MOVD 8(R1), R3164 MOVD 8(R1), R31
61 XOR R0, R0, R065 XOR R0, R0, R0
A internal/fakecgo/trampolines_s390x.s +158 -0
@@ -0,0 +1,158 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build go1.27 && !cgo && linux
5+
6+#include "textflag.h"
7+#include "go_asm.h"
8+
9+// these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions.
10+// Note that C arguments are passed in R2-R6, which matches Go ABIInternal for the first five arguments.
11+// R1 is used as a temporary register.
12+
13+TEXT x_cgo_init_trampoline(SB), NOSPLIT|NOFRAME, $0-0
14+ MOVD R15, R1
15+ SUB $192, R15
16+ MOVD R1, 0(R15) // backchain
17+ MOVD R14, 160(R15) // save R14
18+ MOVD R9, 168(R15) // save R9 (Go runtime needs this preserved)
19+
20+ MOVD ·x_cgo_init_call(SB), R1
21+ MOVD (R1), R1
22+ BL R1
23+
24+ MOVD 168(R15), R9
25+ MOVD 160(R15), R14
26+ ADD $192, R15
27+ BR R14
28+
29+TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT|NOFRAME, $0-0
30+ MOVD R15, R1
31+ SUB $176, R15
32+ MOVD R1, 0(R15) // backchain
33+ MOVD R14, 152(R15) // save R14
34+
35+ MOVD ·x_cgo_thread_start_call(SB), R1
36+ MOVD (R1), R1
37+ BL R1
38+
39+ MOVD 152(R15), R14
40+ ADD $176, R15
41+ BR R14
42+
43+TEXT x_cgo_setenv_trampoline(SB), NOSPLIT|NOFRAME, $0-0
44+ MOVD R15, R1
45+ SUB $176, R15
46+ MOVD R1, 0(R15) // backchain
47+ MOVD R14, 152(R15) // save R14
48+
49+ MOVD ·x_cgo_setenv_call(SB), R1
50+ MOVD (R1), R1
51+ BL R1
52+
53+ MOVD 152(R15), R14
54+ ADD $176, R15
55+ BR R14
56+
57+TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT|NOFRAME, $0-0
58+ MOVD R15, R1
59+ SUB $176, R15
60+ MOVD R1, 0(R15) // backchain
61+ MOVD R14, 152(R15) // save R14
62+
63+ MOVD ·x_cgo_unsetenv_call(SB), R1
64+ MOVD (R1), R1
65+ BL R1
66+
67+ MOVD 152(R15), R14
68+ ADD $176, R15
69+ BR R14
70+
71+// These just tail-call into Go functions
72+TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT|NOFRAME, $0-0
73+ BR ·x_cgo_notify_runtime_init_done(SB)
74+
75+TEXT x_cgo_bindm_trampoline(SB), NOSPLIT|NOFRAME, $0-0
76+ BR ·x_cgo_bindm(SB)
77+
78+// setg_trampoline(setg uintptr, g uintptr) - called from Go
79+TEXT ·setg_trampoline(SB), NOSPLIT|NOFRAME, $0-16
80+ MOVD 8(R15), R1 // setg function pointer
81+ MOVD 16(R15), R2 // g pointer -> C arg
82+
83+ MOVD R14, R0
84+ MOVD R15, R3
85+ SUB $160, R15
86+ MOVD R3, 0(R15)
87+ MOVD R0, 112(R15)
88+ MOVD R2, 120(R15) // save newg before call
89+
90+ BL R1 // call setg_gcc
91+
92+ // Assign g directly instead of calling runtime·load_g
93+ // setg_gcc has already stored newg into TLS; put it in the g register too.
94+ MOVD 120(R15), g
95+
96+ MOVD 112(R15), R14
97+ ADD $160, R15
98+ BR R14
99+
100+TEXT threadentry_trampoline(SB), NOSPLIT|NOFRAME, $0-0
101+ STMG R6, R15, 48(R15) // C save area
102+ MOVD R15, R1
103+ SUB $176, R15
104+ MOVD R1, 0(R15) // backchain
105+
106+ MOVD ·threadentry_call(SB), R1
107+ MOVD (R1), R1
108+ BL R1
109+
110+ ADD $176, R15
111+ LMG 48(R15), R6, R15
112+ RET
113+
114+TEXT ·call5(SB), NOSPLIT|NOFRAME, $0-56
115+ // Load Go args before modifying R15
116+ MOVD 8(R15), R1 // fn
117+ MOVD 16(R15), R7 // a1
118+ MOVD 24(R15), R8 // a2
119+ MOVD 32(R15), R9 // a3
120+ MOVD 40(R15), R10 // a4
121+ MOVD 48(R15), R11 // a5
122+
123+ // Save state
124+ MOVD R15, R0 // original R15
125+ MOVD R12, R6 // Go's R12
126+ ADD $-128, R15
127+
128+ // Set up C frame with backchain
129+ MOVD R0, 0(R15) // backchain -> original R15
130+ MOVD R0, R3 // R3 = original R15 (can't use R0 as base!)
131+ MOVD 0(R3), R7 // save 0(original R15)
132+ MOVD $0, 0(R3) // terminate backchain
133+
134+ // Save context
135+ MOVD R14, 8(R15)
136+ MOVD R6, 16(R15) // R12
137+ MOVD R0, 24(R15) // original R15
138+ MOVD R7, 32(R15) // saved backchain
139+
140+ // Set up C args (reload a1 since R7 was clobbered)
141+ MOVD 16(R3), R2 // a1 (use R3 as base, not R0!)
142+ MOVD R8, R3 // a2
143+ MOVD R9, R4 // a3
144+ MOVD R10, R5 // a4
145+ MOVD R11, R6 // a5
146+
147+ BL R1
148+
149+ // Store result and restore
150+ MOVD 24(R15), R3 // original R15
151+ MOVD R2, 56(R3) // return value
152+ MOVD 32(R15), R7
153+ MOVD R7, 0(R3) // restore backchain
154+
155+ MOVD 8(R15), R14
156+ MOVD 16(R15), R12
157+ MOVD 24(R15), R15
158+ BR R14
M internal/fakecgo/zsymbols_freebsd.go +10 -10
@@ -16,16 +16,16 @@ import "unsafe"
16 //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7"16 //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7"
17 //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7"17 //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7"
18 //go:cgo_import_dynamic purego_abort abort "libc.so.7"18 //go:cgo_import_dynamic purego_abort abort "libc.so.7"
19-//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so"19+//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libthr.so.3"
20-//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so"20+//go:cgo_import_dynamic purego_pthread_create pthread_create "libthr.so.3"
21-//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so"21+//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libthr.so.3"
22-//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so"22+//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libthr.so.3"
23-//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so"23+//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libthr.so.3"
24-//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so"24+//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libthr.so.3"
25-//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so"25+//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libthr.so.3"
26-//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so"26+//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libthr.so.3"
27-//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so"27+//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libthr.so.3"
28-//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so"28+//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libthr.so.3"
29 29
30 //go:nosplit30 //go:nosplit
31 //go:norace31 //go:norace
D internal/xreflect/reflect_go124.go +0 -15
@@ -1,15 +0,0 @@
1-// SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
3-
4-//go:build !go1.25
5-
6-package xreflect
7-
8-import "reflect"
9-
10-// TODO: remove this and use Go 1.25's reflect.TypeAssert when minimum go.mod version is 1.25
11-
12-func TypeAssert[T any](v reflect.Value) (T, bool) {
13- v2, ok := v.Interface().(T)
14- return v2, ok
15-}
D internal/xreflect/reflect_go125.go +0 -12
@@ -1,12 +0,0 @@
1-// SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
3-
4-//go:build go1.25
5-
6-package xreflect
7-
8-import "reflect"
9-
10-func TypeAssert[T any](v reflect.Value) (T, bool) {
11- return reflect.TypeAssert[T](v)
12-}
A objc/encoding_darwin_test.go +186 -0
@@ -0,0 +1,186 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+package objc
5+
6+import (
7+ "fmt"
8+ "os"
9+ "os/exec"
10+ "path/filepath"
11+ "reflect"
12+ "strings"
13+ "structs"
14+ "testing"
15+ "unsafe"
16+)
17+
18+type encodeTypeTestStruct struct {
19+ _ structs.HostLayout
20+ A int32
21+ B float64
22+}
23+
24+var encodeTypeTests = []struct {
25+ typ reflect.Type
26+ cType string
27+ want string
28+}{
29+ {reflect.TypeFor[bool](), "_Bool", "B"},
30+ {reflect.TypeFor[int8](), "signed char", "c"},
31+ {reflect.TypeFor[uint8](), "unsigned char", "C"},
32+ {reflect.TypeFor[int16](), "short", "s"},
33+ {reflect.TypeFor[uint16](), "unsigned short", "S"},
34+ {reflect.TypeFor[int32](), "int", "i"},
35+ {reflect.TypeFor[uint32](), "unsigned int", "I"},
36+ {reflect.TypeFor[int64](), "long long", "q"},
37+ {reflect.TypeFor[uint64](), "unsigned long long", "Q"},
38+ {reflect.TypeFor[int](), "long", "q"},
39+ {reflect.TypeFor[uint](), "unsigned long", "Q"},
40+ {reflect.TypeFor[float32](), "float", "f"},
41+ {reflect.TypeFor[float64](), "double", "d"},
42+ {reflect.TypeFor[string](), "char *", "*"},
43+ {reflect.TypeFor[unsafe.Pointer](), "void *", "^v"},
44+ {reflect.TypeFor[*int32](), "int *", "^i"},
45+ {reflect.TypeFor[**int32](), "int **", "^^i"},
46+ {reflect.TypeFor[ID](), "id", "@"},
47+ {reflect.TypeFor[Class](), "Class", "#"},
48+ {reflect.TypeFor[SEL](), "SEL", ":"},
49+ {reflect.TypeFor[encodeTypeTestStruct](), "struct encodeTypeTestStruct", "{encodeTypeTestStruct=id}"},
50+}
51+
52+func TestEncodeType(t *testing.T) {
53+ for _, tt := range encodeTypeTests {
54+ got, err := encodeType(tt.typ, false)
55+ if err != nil {
56+ t.Errorf("encodeType(%v) returned error: %v", tt.typ, err)
57+ continue
58+ }
59+ if got != tt.want {
60+ t.Errorf("encodeType(%v) = %q; want %q (@encode(%s))", tt.typ, got, tt.want, tt.cType)
61+ }
62+ }
63+}
64+
65+// TestEncodeTypeMatchesClang uses the C compiler as an oracle: it compiles a
66+// program that prints @encode for each C type in encodeTypeTests and checks
67+// that the expected encodings are the ones the compiler actually produces.
68+func TestEncodeTypeMatchesClang(t *testing.T) {
69+ out, err := exec.Command("go", "env", "CC").Output()
70+ if err != nil {
71+ t.Fatalf("go env CC: %v", err)
72+ }
73+ compiler := strings.TrimSpace(string(out))
74+ if compiler == "" {
75+ t.Skip("no C compiler to use as an @encode oracle")
76+ }
77+ if _, err := exec.LookPath(compiler); err != nil {
78+ t.Skipf("no C compiler to use as an @encode oracle: %v", err)
79+ }
80+
81+ var src strings.Builder
82+ src.WriteString("#include <stdio.h>\n#include <objc/objc.h>\n")
83+ src.WriteString("struct encodeTypeTestStruct { int a; double b; };\n")
84+ src.WriteString("int main(void) {\n")
85+ for _, tt := range encodeTypeTests {
86+ fmt.Fprintf(&src, "\tprintf(\"%%s\\n\", @encode(%s));\n", tt.cType)
87+ }
88+ src.WriteString("\treturn 0;\n}\n")
89+
90+ dir := t.TempDir()
91+ srcFile := filepath.Join(dir, "encode.m")
92+ if err := os.WriteFile(srcFile, []byte(src.String()), 0o644); err != nil {
93+ t.Fatal(err)
94+ }
95+
96+ // Built for the host architecture rather than GOARCH, unlike the shared
97+ // libraries in the root package's tests: every darwin target Go supports
98+ // is LP64, so these encodings do not vary by architecture, and building
99+ // for the host avoids needing Rosetta to run an amd64 binary on arm64.
100+ exeFile := filepath.Join(dir, "encode")
101+ cmd := exec.Command(compiler, "-Wall", "-Werror", "-o", exeFile, srcFile)
102+ if out, err := cmd.CombinedOutput(); err != nil {
103+ t.Fatalf("compile oracle: %v\n%q\n%s", err, cmd, out)
104+ }
105+
106+ out, err = exec.Command(exeFile).Output()
107+ if err != nil {
108+ t.Fatalf("run oracle: %v", err)
109+ }
110+ lines := strings.Split(strings.TrimSuffix(string(out), "\n"), "\n")
111+ if len(lines) != len(encodeTypeTests) {
112+ t.Fatalf("oracle printed %d encodings; want %d", len(lines), len(encodeTypeTests))
113+ }
114+ for i, tt := range encodeTypeTests {
115+ if lines[i] != tt.want {
116+ t.Errorf("@encode(%s) = %q; want %q", tt.cType, lines[i], tt.want)
117+ }
118+ }
119+}
120+
121+// TestEncodeFunc checks how encodeFunc assembles a method signature: the
122+// return type, the implicit self and _cmd parameters, and the argument order.
123+// encodeType is checked per type by TestEncodeType, so the cases here vary the
124+// shape of the signature rather than the types in it. The first case is the
125+// signature from issue #493, which was recorded as v@:LLLLLLLLS - declaring
126+// 64-bit arguments as 32-bit made NSInvocation and other consumers of the type
127+// encoding truncate values above 2^32.
128+func TestEncodeFunc(t *testing.T) {
129+ tests := []struct {
130+ name string
131+ fn any
132+ want string
133+ }{
134+ {
135+ name: "void return, 64-bit args",
136+ fn: func(_ ID, _ SEL, a1, a2, a3, a4, a5, a6, a7, a8 uint64, a9 uint16) {},
137+ want: "v@:QQQQQQQQS",
138+ },
139+ {
140+ name: "value return, mixed integer kinds",
141+ fn: func(_ ID, _ SEL, a int, b int64, c uint, d uint64) int { return 0 },
142+ want: "q@:qqQQ",
143+ },
144+ {
145+ name: "no arguments",
146+ fn: func(_ ID, _ SEL) {},
147+ want: "v@:",
148+ },
149+ {
150+ name: "argument order is preserved",
151+ fn: func(_ ID, _ SEL, a int8, b float64, c ID) Class { return 0 },
152+ want: "#@:cd@",
153+ },
154+ }
155+ for _, tt := range tests {
156+ t.Run(tt.name, func(t *testing.T) {
157+ got, err := encodeFunc(tt.fn)
158+ if err != nil {
159+ t.Fatal(err)
160+ }
161+ if got != tt.want {
162+ t.Errorf("encodeFunc = %q; want %q", got, tt.want)
163+ }
164+ })
165+ }
166+}
167+
168+func TestEncodeFuncErrors(t *testing.T) {
169+ tests := []struct {
170+ name string
171+ fn any
172+ }{
173+ {"not a func", 0},
174+ {"too many return values", func(_ ID, _ SEL) (int, int) { return 0, 0 }},
175+ {"missing self and _cmd", func() {}},
176+ {"missing _cmd", func(_ ID) {}},
177+ {"unencodable argument", func(_ ID, _ SEL, c chan int) {}},
178+ }
179+ for _, tt := range tests {
180+ t.Run(tt.name, func(t *testing.T) {
181+ if got, err := encodeFunc(tt.fn); err == nil {
182+ t.Errorf("encodeFunc = %q; want an error", got)
183+ }
184+ })
185+ }
186+}
M objc/objc_block_darwin.go +10 -5
@@ -6,11 +6,11 @@ package objc
6 import (6 import (
7 "fmt"7 "fmt"
8 "reflect"8 "reflect"
9+ "structs"
9 "sync"10 "sync"
10 "unsafe"11 "unsafe"
11 12
12 "github.com/bnema/purego"13 "github.com/bnema/purego"
13- "github.com/bnema/purego/internal/xreflect"
14 )14 )
15 15
16 const (16 const (
@@ -19,7 +19,10 @@ const (
19 // when the reference count drops to zero, so the associated function is also unreferenced.19 // when the reference count drops to zero, so the associated function is also unreferenced.
20 20
21 // blockBaseClass is the name of the class that block objects will be initialized with.21 // blockBaseClass is the name of the class that block objects will be initialized with.
22- blockBaseClass = "__NSMallocBlock__"22+ // A new block is a template that Block_copy always relocates to the
23+ // Objective-C heap, so the stack-block class is the accurate name here,
24+ // as the isa of the copied block becomes __NSMallocBlock__ anyway.
25+ blockBaseClass = "__NSStackBlock__"
23 // blockFlags is the set of flags that block objects will be initialized with.26 // blockFlags is the set of flags that block objects will be initialized with.
24 blockFlags = blockHasCopyDispose | blockHasSignature27 blockFlags = blockHasCopyDispose | blockHasSignature
25 28
@@ -35,6 +38,7 @@ const (
35 //38 //
36 // The layout of this struct matches Block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level39 // The layout of this struct matches Block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level
37 type blockDescriptor struct {40 type blockDescriptor struct {
41+ _ structs.HostLayout
38 _ uintptr42 _ uintptr
39 size uintptr43 size uintptr
40 _ uintptr44 _ uintptr
@@ -48,6 +52,7 @@ type blockDescriptor struct {
48 //52 //
49 // The layout of this struct matches __block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level53 // The layout of this struct matches __block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level
50 type blockLayout struct {54 type blockLayout struct {
55+ _ structs.HostLayout
51 isa Class56 isa Class
52 flags uint3257 flags uint32
53 _ uint3258 _ uint32
@@ -125,7 +130,7 @@ func (*blockCache) encode(typ reflect.Type) *uint8 {
125 encoding = returnType130 encoding = returnType
126 }131 }
127 132
128- if typ.NumIn() == 0 || typ.In(0) != reflect.TypeOf(Block(0)) {133+ if typ.NumIn() == 0 || typ.In(0) != reflect.TypeFor[Block]() {
129 panic(fmt.Sprintf("objc: A Block implementation must take a Block as its first argument; got %v", typ.String()))134 panic(fmt.Sprintf("objc: A Block implementation must take a Block as its first argument; got %v", typ.String()))
130 }135 }
131 136
@@ -168,7 +173,7 @@ func (b *blockCache) getLayout(typ reflect.Type) blockLayout {
168 reflect.MakeFunc(173 reflect.MakeFunc(
169 typ,174 typ,
170 func(args []reflect.Value) (results []reflect.Value) {175 func(args []reflect.Value) (results []reflect.Value) {
171- block, ok := xreflect.TypeAssert[Block](args[0])176+ block, ok := reflect.TypeAssert[Block](args[0])
172 if !ok {177 if !ok {
173 panic(fmt.Sprintf("objc: block argument is not a block but %s", args[0].Type().String()))178 panic(fmt.Sprintf("objc: block argument is not a block but %s", args[0].Type().String()))
174 }179 }
@@ -263,7 +268,7 @@ func InvokeBlock[T any](block Block, args ...any) (result T, err error) {
263 callResult := fn.Call(reflectedArgs)268 callResult := fn.Call(reflectedArgs)
264 269
265 var ok bool270 var ok bool
266- result, ok = xreflect.TypeAssert[T](callResult[0])271+ result, ok = reflect.TypeAssert[T](callResult[0])
267 if !ok {272 if !ok {
268 return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result)273 return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result)
269 }274 }
M objc/objc_block_darwin_test.go +2 -0
@@ -5,6 +5,7 @@ package objc_test
5 5
6 import (6 import (
7 "fmt"7 "fmt"
8+ "structs"
8 "testing"9 "testing"
9 10
10 "github.com/bnema/purego"11 "github.com/bnema/purego"
@@ -39,6 +40,7 @@ func ExampleNewBlock() {
39 40
40 func ExampleInvokeBlock() {41 func ExampleInvokeBlock() {
41 type vector struct {42 type vector struct {
43+ _ structs.HostLayout
42 X, Y, Z float6444 X, Y, Z float64
43 }45 }
44 46
M objc/objc_runtime_darwin.go +58 -43
@@ -12,15 +12,18 @@ import (
12 "reflect"12 "reflect"
13 "regexp"13 "regexp"
14 "runtime"14 "runtime"
15+ "slices"
15 stdstrings "strings"16 stdstrings "strings"
17+ "structs"
16 "unicode"18 "unicode"
17 "unsafe"19 "unsafe"
18 20
19 "github.com/bnema/purego"21 "github.com/bnema/purego"
20 "github.com/bnema/purego/internal/strings"22 "github.com/bnema/purego/internal/strings"
21- "github.com/bnema/purego/internal/xreflect"
22 )23 )
23 24
25+var hostLayoutType = reflect.TypeFor[structs.HostLayout]()
26+
24 // TODO: support try/catch?27 // TODO: support try/catch?
25 // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime28 // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
26 var (29 var (
@@ -168,32 +171,44 @@ func Send[T any](id ID, sel SEL, args ...any) T {
168 return fn(id, sel, args...)171 return fn(id, sel, args...)
169 }172 }
170 173
171-// objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword174+// objc_super supplies the receiver and defining class to objc_msgSendSuper2,
172-// as the receiver of a message. It specifies the class definition of the particular superclass that should175+// which begins method lookup in that class's superclass.
173-// be messaged.
174 type objc_super struct {176 type objc_super struct {
177+ _ structs.HostLayout
175 receiver ID178 receiver ID
176 superClass Class179 superClass Class
177 }180 }
178 181
179-// SendSuper is a convenience method for sending message to object's super. This function takes a SEL182+// SendSuper sends a message starting lookup in the receiver's runtime superclass.
180-// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result183+//
181-// of RegisterName.184+// Deprecated: Use [ID.SendSuper2] with the class defining the executing method.
182 func (id ID) SendSuper(sel SEL, args ...any) ID {185 func (id ID) SendSuper(sel SEL, args ...any) ID {
186+ return id.SendSuper2(id.Class(), sel, args...)
187+}
188+
189+// SendSuper2 sends a message starting lookup in the superclass of class.
190+// For a super call, class must be the class defining the executing method.
191+func (id ID) SendSuper2(class Class, sel SEL, args ...any) ID {
183 super := &objc_super{192 super := &objc_super{
184 receiver: id,193 receiver: id,
185- superClass: id.Class(),194+ superClass: class,
186 }195 }
187 return objc_msgSendSuper2(super, sel, args...)196 return objc_msgSendSuper2(super, sel, args...)
188 }197 }
189 198
190-// SendSuper is a convenience method for sending message to object's super that can return any type.199+// SendSuper sends a message starting lookup in the receiver's runtime superclass.
191-// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.200+//
192-// It is best to cache the result of RegisterName.201+// Deprecated: Use [SendSuper2] with the class defining the executing method.
193 func SendSuper[T any](id ID, sel SEL, args ...any) T {202 func SendSuper[T any](id ID, sel SEL, args ...any) T {
203+ return SendSuper2[T](id, id.Class(), sel, args...)
204+}
205+
206+// SendSuper2 sends a message starting lookup in the superclass of class and returns T.
207+// For a super call, class must be the class defining the executing method.
208+func SendSuper2[T any](id ID, class Class, sel SEL, args ...any) T {
194 super := &objc_super{209 super := &objc_super{
195 receiver: id,210 receiver: id,
196- superClass: id.Class(),211+ superClass: class,
197 }212 }
198 var fn func(objcSuper *objc_super, sel SEL, args ...any) T213 var fn func(objcSuper *objc_super, sel SEL, args ...any) T
199 var zero T214 var zero T
@@ -265,7 +280,7 @@ type FieldDef struct {
265 }280 }
266 281
267 // ivarRegex checks to make sure the Ivar is correctly formatted282 // ivarRegex checks to make sure the Ivar is correctly formatted
268-var ivarRegex = regexp.MustCompile("[a-z_][a-zA-Z0-9_]*")283+var ivarRegex = regexp.MustCompile(`^[a-z_][a-zA-Z0-9_]*$`)
269 284
270 // RegisterClass takes the name of the class to create, the superclass, a list of protocols this class285 // RegisterClass takes the name of the class to create, the superclass, a list of protocols this class
271 // implements, a list of fields this class has and a list of methods. It returns the created class or an error286 // implements, a list of fields this class has and a list of methods. It returns the created class or an error
@@ -322,7 +337,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [
322 case ReadWrite:337 case ReadWrite:
323 ty := reflect.FuncOf(338 ty := reflect.FuncOf(
324 []reflect.Type{339 []reflect.Type{
325- reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), ivar.Type,340+ reflect.TypeFor[ID](), reflect.TypeFor[SEL](), ivar.Type,
326 },341 },
327 nil, false,342 nil, false,
328 )343 )
@@ -343,7 +358,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [
343 // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123358 // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123
344 //359 //
345 // However, since the type of the variable is unknown reflection is used to actually assign the value360 // However, since the type of the variable is unknown reflection is used to actually assign the value
346- id, ok := xreflect.TypeAssert[ID](args[0])361+ id, ok := reflect.TypeAssert[ID](args[0])
347 if !ok {362 if !ok {
348 panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String()))363 panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String()))
349 }364 }
@@ -358,7 +373,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [
358 case ReadOnly:373 case ReadOnly:
359 ty := reflect.FuncOf(374 ty := reflect.FuncOf(
360 []reflect.Type{375 []reflect.Type{
361- reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)),376+ reflect.TypeFor[ID](), reflect.TypeFor[SEL](),
362 },377 },
363 []reflect.Type{ivar.Type}, false,378 []reflect.Type{ivar.Type}, false,
364 )379 )
@@ -371,7 +386,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [
371 if len(args) != 2 {386 if len(args) != 2 {
372 panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args)))387 panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args)))
373 }388 }
374- id, ok := xreflect.TypeAssert[ID](args[0])389+ id, ok := reflect.TypeAssert[ID](args[0])
375 if !ok {390 if !ok {
376 panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String()))391 panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String()))
377 }392 }
@@ -402,8 +417,8 @@ const (
402 encUShort = "S"417 encUShort = "S"
403 encInt = "i"418 encInt = "i"
404 encUInt = "I"419 encUInt = "I"
405- encLong = "l"420+ encLongLong = "q"
406- encULong = "L"421+ encULongLong = "Q"
407 encFloat = "f"422 encFloat = "f"
408 encDouble = "d"423 encDouble = "d"
409 encBool = "B"424 encBool = "B"
@@ -419,11 +434,11 @@ const (
419 // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100434 // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
420 func encodeType(typ reflect.Type, insidePtr bool) (string, error) {435 func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
421 switch typ {436 switch typ {
422- case reflect.TypeOf(Class(0)):437+ case reflect.TypeFor[Class]():
423 return encClass, nil438 return encClass, nil
424- case reflect.TypeOf(ID(0)), reflect.TypeOf(Block(0)):439+ case reflect.TypeFor[ID](), reflect.TypeFor[Block]():
425 return encId, nil440 return encId, nil
426- case reflect.TypeOf(SEL(0)):441+ case reflect.TypeFor[SEL]():
427 return encSelector, nil442 return encSelector, nil
428 }443 }
429 444
@@ -432,7 +447,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
432 case reflect.Bool:447 case reflect.Bool:
433 return encBool, nil448 return encBool, nil
434 case reflect.Int:449 case reflect.Int:
435- return encLong, nil450+ return encLongLong, nil
436 case reflect.Int8:451 case reflect.Int8:
437 return encChar, nil452 return encChar, nil
438 case reflect.Int16:453 case reflect.Int16:
@@ -440,9 +455,9 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
440 case reflect.Int32:455 case reflect.Int32:
441 return encInt, nil456 return encInt, nil
442 case reflect.Int64:457 case reflect.Int64:
443- return encULong, nil458+ return encLongLong, nil
444 case reflect.Uint:459 case reflect.Uint:
445- return encULong, nil460+ return encULongLong, nil
446 case reflect.Uint8:461 case reflect.Uint8:
447 return encUChar, nil462 return encUChar, nil
448 case reflect.Uint16:463 case reflect.Uint16:
@@ -450,7 +465,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
450 case reflect.Uint32:465 case reflect.Uint32:
451 return encUInt, nil466 return encUInt, nil
452 case reflect.Uint64:467 case reflect.Uint64:
453- return encULong, nil468+ return encULongLong, nil
454 case reflect.Uintptr:469 case reflect.Uintptr:
455 return encPtr, nil470 return encPtr, nil
456 case reflect.Float32:471 case reflect.Float32:
@@ -468,8 +483,15 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
468 encoding.WriteString(encStructBegin)483 encoding.WriteString(encStructBegin)
469 encoding.WriteString(typ.Name())484 encoding.WriteString(typ.Name())
470 encoding.WriteString("=")485 encoding.WriteString("=")
471- for i := 0; i < typ.NumField(); i++ {486+ for i := range typ.NumField() {
472 f := typ.Field(i)487 f := typ.Field(i)
488+ if f.Type.ConvertibleTo(hostLayoutType) {
489+ // The structs.HostLayout marker is a signal to the Go compiler
490+ // with no counterpart in C, so it is not a member as far as
491+ // @encode is concerned. Other zero-sized fields are: clang
492+ // encodes a zero-length array member as [0c].
493+ continue
494+ }
473 tmp, err := encodeType(f.Type, false)495 tmp, err := encodeType(f.Type, false)
474 if err != nil {496 if err != nil {
475 return "", err497 return "", err
@@ -571,7 +593,9 @@ func (i Ivar) Name() string {
571 }593 }
572 594
573 // MethodDescription holds the name and type definition of a method.595 // MethodDescription holds the name and type definition of a method.
596+// It matches the Objective-C runtime's struct objc_method_description.
574 type MethodDescription struct {597 type MethodDescription struct {
598+ _ structs.HostLayout
575 name, types uintptr599 name, types uintptr
576 }600 }
577 601
@@ -586,7 +610,9 @@ func (m MethodDescription) Types() string {
586 }610 }
587 611
588 // PropertyAttribute contains the null-terminated Name and Value pair of a Properties internal description.612 // PropertyAttribute contains the null-terminated Name and Value pair of a Properties internal description.
613+// It matches the Objective-C runtime's objc_property_attribute_t.
589 type PropertyAttribute struct {614 type PropertyAttribute struct {
615+ _ structs.HostLayout
590 Name, Value *byte616 Name, Value *byte
591 }617 }
592 618
@@ -626,7 +652,7 @@ func (p *Protocol) Register() {
626 func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription {652 func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription {
627 count := uint32(0)653 count := uint32(0)
628 desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count)654 desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count)
629- methods := clone(unsafe.Slice(desc, count))655+ methods := slices.Clone(unsafe.Slice(desc, count))
630 free(unsafe.Pointer(desc))656 free(unsafe.Pointer(desc))
631 return methods657 return methods
632 }658 }
@@ -635,7 +661,7 @@ func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod
635 func (p *Protocol) CopyProtocolList() []*Protocol {661 func (p *Protocol) CopyProtocolList() []*Protocol {
636 count := uint32(0)662 count := uint32(0)
637 desc := protocol_copyProtocolList(p, &count)663 desc := protocol_copyProtocolList(p, &count)
638- protocols := clone(unsafe.Slice(desc, count))664+ protocols := slices.Clone(unsafe.Slice(desc, count))
639 free(unsafe.Pointer(desc))665 free(unsafe.Pointer(desc))
640 return protocols666 return protocols
641 }667 }
@@ -644,7 +670,7 @@ func (p *Protocol) CopyProtocolList() []*Protocol {
644 func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property {670 func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property {
645 count := uint32(0)671 count := uint32(0)
646 desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty)672 desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty)
647- protocols := clone(unsafe.Slice(desc, count))673+ protocols := slices.Clone(unsafe.Slice(desc, count))
648 free(unsafe.Pointer(desc))674 free(unsafe.Pointer(desc))
649 return protocols675 return protocols
650 }676 }
@@ -691,21 +717,10 @@ func NewIMP(fn any) IMP {
691 switch {717 switch {
692 case ty.NumIn() < 2:718 case ty.NumIn() < 2:
693 fallthrough719 fallthrough
694- case ty.In(0) != reflect.TypeOf(ID(0)):720+ case ty.In(0) != reflect.TypeFor[ID]():
695 fallthrough721 fallthrough
696- case ty.In(1) != reflect.TypeOf(SEL(0)):722+ case ty.In(1) != reflect.TypeFor[SEL]():
697 panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String())723 panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String())
698 }724 }
699 return IMP(purego.NewCallback(fn))725 return IMP(purego.NewCallback(fn))
700 }726 }
701-
702-// TODO: remove and use slices.Clone when minimum version for purego is 1.21
703-func clone[S ~[]E, E any](s S) S {
704- // Preserve nilness in case it matters.
705- if s == nil {
706- return nil
707- }
708- // Avoid s[:0:0] as it leads to unwanted liveness when cloning a
709- // zero-length slice of a large array; see https://go.dev/issue/68488.
710- return append(S{}, s...)
711-}
M objc/objc_runtime_darwin_test.go +56 -24
@@ -7,6 +7,7 @@ import (
7 "fmt"7 "fmt"
8 "log"8 "log"
9 "reflect"9 "reflect"
10+ "structs"
10 "testing"11 "testing"
11 12
12 "github.com/bnema/purego"13 "github.com/bnema/purego"
@@ -38,6 +39,7 @@ func ExampleRegisterClass_helloworld() {
38 }39 }
39 40
40 func ExampleRegisterClass() {41 func ExampleRegisterClass() {
42+ var class objc.Class
41 var (43 var (
42 sel_new = objc.RegisterName("new")44 sel_new = objc.RegisterName("new")
43 sel_init = objc.RegisterName("init")45 sel_init = objc.RegisterName("init")
@@ -45,11 +47,12 @@ func ExampleRegisterClass() {
45 sel_bar = objc.RegisterName("bar")47 sel_bar = objc.RegisterName("bar")
46 48
47 BarInit = func(id objc.ID, cmd objc.SEL) objc.ID {49 BarInit = func(id objc.ID, cmd objc.SEL) objc.ID {
48- return id.SendSuper(cmd)50+ return id.SendSuper2(class, cmd)
49 }51 }
50 )52 )
51 53
52- class, err := objc.RegisterClass(54+ var err error
55+ class, err = objc.RegisterClass(
53 "BarObject",56 "BarObject",
54 objc.GetClass("NSObject"),57 objc.GetClass("NSObject"),
55 []*objc.Protocol{58 []*objc.Protocol{
@@ -58,12 +61,12 @@ func ExampleRegisterClass() {
58 []objc.FieldDef{61 []objc.FieldDef{
59 {62 {
60 Name: "bar",63 Name: "bar",
61- Type: reflect.TypeOf(int(0)),64+ Type: reflect.TypeFor[int](),
62 Attribute: objc.ReadWrite,65 Attribute: objc.ReadWrite,
63 },66 },
64 {67 {
65 Name: "foo",68 Name: "foo",
66- Type: reflect.TypeOf(false),69+ Type: reflect.TypeFor[bool](),
67 Attribute: objc.ReadWrite,70 Attribute: objc.ReadWrite,
68 },71 },
69 },72 },
@@ -94,17 +97,25 @@ func ExampleIMP() {
94 // Output: IMP: 105 567 9 2 3 -5 4 8 997 // Output: IMP: 105 567 9 2 3 -5 4 8 9
95 }98 }
96 99
97-func ExampleID_SendSuper() {100+// This example shows three-level inheritance with Base <- Child1 <- Child2.
98- super, err := objc.RegisterClass(101+// SendSuper2 takes Child1, the class defining the method.
99- "SuperObject",102+func ExampleID_SendSuper2() {
103+ _, err := purego.Dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", purego.RTLD_GLOBAL|purego.RTLD_NOW)
104+ if err != nil {
105+ panic(err)
106+ }
107+
108+ sel := objc.RegisterName("run")
109+ base, err := objc.RegisterClass(
110+ "SuperExampleBase",
100 objc.GetClass("NSObject"),111 objc.GetClass("NSObject"),
101 nil,112 nil,
102 nil,113 nil,
103 []objc.MethodDef{114 []objc.MethodDef{
104 {115 {
105- Cmd: objc.RegisterName("doSomething"),116+ Cmd: sel,
106- Fn: func(self objc.ID, _cmd objc.SEL) {117+ Fn: func(self objc.ID, cmd objc.SEL) {
107- fmt.Println("In Super!")118+ fmt.Println("Base")
108 },119 },
109 },120 },
110 },121 },
@@ -113,17 +124,19 @@ func ExampleID_SendSuper() {
113 panic(err)124 panic(err)
114 }125 }
115 126
116- child, err := objc.RegisterClass(127+ var child1 objc.Class
117- "ChildObject",128+ child1, err = objc.RegisterClass(
118- super,129+ "SuperExampleChild1",
130+ base,
119 nil,131 nil,
120 nil,132 nil,
121 []objc.MethodDef{133 []objc.MethodDef{
122 {134 {
123- Cmd: objc.RegisterName("doSomething"),135+ Cmd: sel,
124- Fn: func(self objc.ID, _cmd objc.SEL) {136+ Fn: func(self objc.ID, cmd objc.SEL) {
125- fmt.Println("In Child")137+ // Start lookup in the superclass of Child1.
126- self.SendSuper(_cmd)138+ fmt.Print("SendSuper2: ")
139+ self.SendSuper2(child1, cmd)
127 },140 },
128 },141 },
129 },142 },
@@ -132,9 +145,27 @@ func ExampleID_SendSuper() {
132 panic(err)145 panic(err)
133 }146 }
134 147
135- objc.ID(child).Send(objc.RegisterName("new")).Send(objc.RegisterName("doSomething"))148+ // Child2 inherits Child1's implementation of run.
136- // Output: In Child149+ child2, err := objc.RegisterClass("SuperExampleChild2", child1, nil, nil, nil)
137- // In Super!150+ if err != nil {
151+ panic(err)
152+ }
153+
154+ for _, class := range []objc.Class{child1, child2} {
155+ object := objc.ID(class).Send(objc.RegisterName("new"))
156+ defer object.Send(objc.RegisterName("release"))
157+ if class == child1 {
158+ fmt.Println("Child1 instance")
159+ } else {
160+ fmt.Println("Child2 instance")
161+ }
162+ object.Send(sel)
163+ }
164+ // Output:
165+ // Child1 instance
166+ // SendSuper2: Base
167+ // Child2 instance
168+ // SendSuper2: Base
138 }169 }
139 170
140 func TestSend(t *testing.T) {171 func TestSend(t *testing.T) {
@@ -155,6 +186,7 @@ func TestSend(t *testing.T) {
155 186
156 func ExampleSend() {187 func ExampleSend() {
157 type NSRange struct {188 type NSRange struct {
189+ _ structs.HostLayout
158 Location, Range uint190 Location, Range uint
159 }191 }
160 class_NSString := objc.GetClass("NSString")192 class_NSString := objc.GetClass("NSString")
@@ -164,11 +196,11 @@ func ExampleSend() {
164 subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00")196 subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00")
165 197
166 r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString)198 r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString)
167- fmt.Println(r)199+ fmt.Println(r.Location, r.Range)
168- // Output: {3 7}200+ // Output: 3 7
169 }201 }
170 202
171-func ExampleSendSuper() {203+func ExampleSendSuper2() {
172 super, err := objc.RegisterClass(204 super, err := objc.RegisterClass(
173 "SuperObject2",205 "SuperObject2",
174 objc.GetClass("NSObject"),206 objc.GetClass("NSObject"),
@@ -205,7 +237,7 @@ func ExampleSendSuper() {
205 panic(err)237 panic(err)
206 }238 }
207 239
208- res := objc.SendSuper[int](objc.ID(child).Send(objc.RegisterName("new")), objc.RegisterName("doSomething"))240+ res := objc.SendSuper2[int](objc.ID(child).Send(objc.RegisterName("new")), child, objc.RegisterName("doSomething"))
209 fmt.Println(res)241 fmt.Println(res)
210 // Output: 16242 // Output: 16
211 }243 }
A objc/super_darwin_test.go +130 -0
@@ -0,0 +1,130 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+package objc_test
5+
6+import (
7+ "fmt"
8+ "os/exec"
9+ "path/filepath"
10+ "runtime"
11+ "structs"
12+ "sync/atomic"
13+ "testing"
14+
15+ "github.com/bnema/purego"
16+ "github.com/bnema/purego/objc"
17+)
18+
19+var superTestClassID atomic.Uint64
20+
21+// TestSendSuperDispatch checks argument and receiver forwarding, return values,
22+// and generic super calls from direct and inherited methods.
23+func TestSendSuperDispatch(t *testing.T) {
24+ if _, err := purego.Dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", purego.RTLD_GLOBAL|purego.RTLD_NOW); err != nil {
25+ t.Fatal(err)
26+ }
27+ prefix := fmt.Sprintf("PuregoSuperTest%d", superTestClassID.Add(1))
28+ sel := objc.RegisterName("probe:")
29+ var baseCalls, child1Calls int
30+ var receiver objc.ID
31+ base, err := objc.RegisterClass(prefix+"Base", objc.GetClass("NSObject"), nil, nil, []objc.MethodDef{
32+ {
33+ Cmd: sel,
34+ Fn: func(self objc.ID, cmd objc.SEL, value int) int {
35+ baseCalls++
36+ receiver = self
37+ return value + 3
38+ },
39+ },
40+ })
41+ if err != nil {
42+ t.Fatal(err)
43+ }
44+ var child1 objc.Class
45+ child1, err = objc.RegisterClass(prefix+"Child1", base, nil, nil, []objc.MethodDef{
46+ {
47+ Cmd: sel,
48+ Fn: func(self objc.ID, cmd objc.SEL, value int) int {
49+ child1Calls++
50+ // Bound recursion so an incorrect class anchor fails without overflowing the stack.
51+ if child1Calls > 1 {
52+ return -1
53+ }
54+
55+ return objc.SendSuper2[int](self, child1, cmd, value) + 5
56+ },
57+ },
58+ })
59+ if err != nil {
60+ t.Fatal(err)
61+ }
62+ child2, err := objc.RegisterClass(prefix+"Child2", child1, nil, nil, nil)
63+ if err != nil {
64+ t.Fatal(err)
65+ }
66+ for _, class := range []objc.Class{child1, child2} {
67+ baseCalls, child1Calls, receiver = 0, 0, 0
68+ object := objc.ID(class).Send(objc.RegisterName("new"))
69+ defer object.Send(objc.RegisterName("release"))
70+ if got := objc.Send[int](object, sel, 34); got != 42 {
71+ t.Errorf("class %v: result = %d, want 42", class, got)
72+ }
73+ if baseCalls != 1 || child1Calls != 1 {
74+ t.Errorf("class %v: base calls = %d, child1 calls = %d; want 1 each", class, baseCalls, child1Calls)
75+ }
76+ if receiver != object {
77+ t.Errorf("base receiver = %v, want %v", receiver, object)
78+ }
79+ // Check forwarding through the ID method too.
80+ receiver = 0
81+ if got := object.SendSuper2(child1, sel, 34); got != 37 {
82+ t.Errorf("class %v: SendSuper2 = %d, want 37", class, got)
83+ }
84+ if receiver != object {
85+ t.Errorf("SendSuper2 receiver = %v, want %v", receiver, object)
86+ }
87+ }
88+ // The legacy helpers retain their direct-instance behavior.
89+ object := objc.ID(child1).Send(objc.RegisterName("new"))
90+ defer object.Send(objc.RegisterName("release"))
91+ if got := object.SendSuper(sel, 34); got != 37 {
92+ t.Errorf("SendSuper = %d, want 37", got)
93+ }
94+ if got := objc.SendSuper[int](object, sel, 34); got != 37 {
95+ t.Errorf("SendSuper[int] = %d, want 37", got)
96+ }
97+}
98+
99+func TestSendSuperStruct(t *testing.T) {
100+ arch := "arm64"
101+ if runtime.GOARCH == "amd64" {
102+ arch = "x86_64"
103+ }
104+ library := filepath.Join(t.TempDir(), "super.dylib")
105+ cmd := exec.Command("clang", "-dynamiclib", "-arch", arch, "-framework", "Foundation", "-o", library, "testdata/super.m")
106+ if out, err := cmd.CombinedOutput(); err != nil {
107+ t.Fatalf("compile super fixture: %v\n%s", err, out)
108+ }
109+ // Objective-C retains the registered classes and their implementations.
110+ if _, err := purego.Dlopen(library, purego.RTLD_GLOBAL|purego.RTLD_NOW); err != nil {
111+ t.Fatal(err)
112+ }
113+ type result struct {
114+ _ structs.HostLayout
115+ A, B, C int64
116+ }
117+ want := result{
118+ A: 12,
119+ B: 34,
120+ C: 56,
121+ }
122+ child1 := objc.GetClass("PuregoSuperStructChild1")
123+ for _, class := range []objc.Class{child1, objc.GetClass("PuregoSuperStructChild2")} {
124+ object := objc.ID(class).Send(objc.RegisterName("new"))
125+ defer object.Send(objc.RegisterName("release"))
126+ if got := objc.SendSuper2[result](object, child1, objc.RegisterName("result")); got != want {
127+ t.Errorf("class %v: result = %+v, want %+v", class, got, want)
128+ }
129+ }
130+}
A objc/testdata/super.m +33 -0
@@ -0,0 +1,33 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+#import <Foundation/Foundation.h>
5+
6+struct Result {
7+ int64_t a, b, c;
8+};
9+
10+@interface PuregoSuperStructBase : NSObject
11+- (struct Result)result;
12+@end
13+
14+@implementation PuregoSuperStructBase
15+- (struct Result)result {
16+ return (struct Result){.a = 12, .b = 34, .c = 56};
17+}
18+@end
19+
20+@interface PuregoSuperStructChild1 : PuregoSuperStructBase
21+@end
22+
23+@implementation PuregoSuperStructChild1
24+- (struct Result)result {
25+ return (struct Result){.a = 78, .b = 90, .c = 12};
26+}
27+@end
28+
29+@interface PuregoSuperStructChild2 : PuregoSuperStructChild1
30+@end
31+
32+@implementation PuregoSuperStructChild2
33+@end
D struct_386.go +0 -52
@@ -1,52 +0,0 @@
1-// SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
3-
4-package purego
5-
6-import (
7- "reflect"
8- "unsafe"
9-)
10-
11-func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
12- panic("purego: struct arguments are not supported")
13-}
14-
15-func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
16- panic("purego: struct returns are not supported")
17-}
18-
19-func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
20- panic("purego: placeRegisters not implemented on 386")
21-}
22-
23-// shouldBundleStackArgs always returns false on 386
24-// since C-style stack argument bundling is only needed on Darwin ARM64.
25-func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
26- return false
27-}
28-
29-// structFitsInRegisters is not used on 386.
30-func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
31- panic("purego: structFitsInRegisters should not be called on 386")
32-}
33-
34-// collectStackArgs is not used on 386.
35-func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
36- keepAlive []any, addInt, addFloat, addStack func(uintptr),
37- pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
38- panic("purego: collectStackArgs should not be called on 386")
39-}
40-
41-// bundleStackArgs is not used on 386.
42-func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
43- panic("purego: bundleStackArgs should not be called on 386")
44-}
45-
46-func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
47- panic("purego: struct callback arguments are not supported on 386")
48-}
49-
50-func setStruct(a *callbackArgs, ret reflect.Value) {
51- panic("purego: struct returns are not supported on 386")
52-}
D struct_abi_helpers.go +0 -38
@@ -1,38 +0,0 @@
1-// SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3-
4-package purego
5-
6-import (
7- "math"
8- "reflect"
9- "unsafe"
10-)
11-
12-func packFloat32Pair(low, high uintptr) uintptr {
13- return uintptr((uint64(high) << 32) | (uint64(low) & math.MaxUint32))
14-}
15-
16-func highFloat32Bits(word uintptr) uint32 {
17- return uint32(uint64(word) >> 32)
18-}
19-
20-func flushPackedWord(val *uint64, shift *byte, flushed *bool, isFloat bool, addFloat, addInt func(uintptr)) {
21- *flushed = true
22- if isFloat {
23- addFloat(uintptr(*val))
24- } else {
25- addInt(uintptr(*val))
26- }
27- *val = 0
28- *shift = 0
29-}
30-
31-func stableValuePointer(v reflect.Value) (unsafe.Pointer, any) {
32- if v.CanAddr() {
33- return v.Addr().UnsafePointer(), nil
34- }
35- tmp := reflect.New(v.Type())
36- tmp.Elem().Set(v)
37- return tmp.UnsafePointer(), tmp.Interface()
38-}
D struct_abi_helpers_test.go +0 -105
@@ -1,105 +0,0 @@
1-// SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3-
4-package purego
5-
6-import (
7- "math"
8- "reflect"
9- "testing"
10-)
11-
12-func TestPackFloat32PairMasksLowWord(t *testing.T) {
13- low := uintptr(0xDEADBEEF00000000 | uint64(math.Float32bits(1.5)))
14- high := uintptr(math.Float32bits(2.5))
15- want := uintptr(uint64(math.Float32bits(2.5))<<32 | uint64(math.Float32bits(1.5)))
16- if got := packFloat32Pair(low, high); got != want {
17- t.Fatalf("packFloat32Pair() = %#x, want %#x", got, want)
18- }
19-}
20-
21-func TestHighFloat32Bits(t *testing.T) {
22- word := uintptr(uint64(math.Float32bits(3.25)) << 32)
23- if got, want := highFloat32Bits(word), math.Float32bits(3.25); got != want {
24- t.Fatalf("highFloat32Bits() = %#x, want %#x", got, want)
25- }
26-}
27-
28-func TestFlushPackedWordResetsState(t *testing.T) {
29- var val uint64 = 0x11223344
30- shift := byte(32)
31- flushed := false
32- var gotFloat uintptr
33- flushPackedWord(&val, &shift, &flushed, true, func(v uintptr) { gotFloat = v }, func(uintptr) {
34- t.Fatal("unexpected integer flush")
35- })
36- if !flushed || val != 0 || shift != 0 {
37- t.Fatalf("flushPackedWord() left state dirty: flushed=%v val=%#x shift=%d", flushed, val, shift)
38- }
39- if gotFloat != 0x11223344 {
40- t.Fatalf("flushPackedWord() flushed %#x, want %#x", gotFloat, uintptr(0x11223344))
41- }
42-}
43-
44-func TestFlushPackedWordResetsStateForIntegerBranch(t *testing.T) {
45- var val uint64 = 0x55667788
46- shift := byte(16)
47- flushed := false
48- var gotInt uintptr
49- flushPackedWord(&val, &shift, &flushed, false, func(uintptr) {
50- t.Fatal("unexpected float flush")
51- }, func(v uintptr) { gotInt = v })
52- if !flushed || val != 0 || shift != 0 {
53- t.Fatalf("flushPackedWord() left state dirty: flushed=%v val=%#x shift=%d", flushed, val, shift)
54- }
55- if gotInt != 0x55667788 {
56- t.Fatalf("flushPackedWord() flushed %#x, want %#x", gotInt, uintptr(0x55667788))
57- }
58-}
59-
60-func TestStableValuePointerCopiesUnaddressableValue(t *testing.T) {
61- type sample struct{ X uint32 }
62- ptr, keepAlive := stableValuePointer(reflect.ValueOf(sample{X: 7}))
63- if keepAlive == nil {
64- t.Fatal("expected keepAlive copy for unaddressable value")
65- }
66- if got := *(*uint32)(ptr); got != 7 {
67- t.Fatalf("stableValuePointer() copied %#x, want %#x", got, uint32(7))
68- }
69-}
70-
71-func TestStableValuePointerUsesAddressableValue(t *testing.T) {
72- type sample struct{ X uint32 }
73- value := sample{X: 9}
74- ptr, keepAlive := stableValuePointer(reflect.ValueOf(&value).Elem())
75- if keepAlive != nil {
76- t.Fatal("expected nil keepAlive for addressable value")
77- }
78- if got := *(*uint32)(ptr); got != 9 {
79- t.Fatalf("stableValuePointer() pointed to %#x, want %#x", got, uint32(9))
80- }
81-}
82-
83-func TestIsAllSameFloatRejectsNestedMixedStruct(t *testing.T) {
84- type inner struct{ X, Y float32 }
85- type mixed struct {
86- Inner inner
87- Tail int32
88- }
89- allFloats, fields := isAllSameFloat(reflect.TypeOf(mixed{}))
90- if allFloats {
91- t.Fatalf("isAllSameFloat() = true for mixed nested struct with %d fields", fields)
92- }
93-}
94-
95-func TestIsAllSameFloatAcceptsNestedFloatStruct(t *testing.T) {
96- type inner struct{ X, Y float32 }
97- type outer struct {
98- Left inner
99- Right inner
100- }
101- allFloats, fields := isAllSameFloat(reflect.TypeOf(outer{}))
102- if !allFloats || fields != 4 {
103- t.Fatalf("isAllSameFloat() = (%v, %d), want (true, 4)", allFloats, fields)
104- }
105-}
M struct_amd64.go +105 -30
@@ -10,8 +10,44 @@ import (
10 "unsafe"10 "unsafe"
11 )11 )
12 12
13-func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {13+// structReturnInMemory reports whether a struct return value of the given size
14+// is returned through a caller-allocated hidden pointer passed as the first
15+// integer argument (true) rather than in registers (false).
16+func structReturnInMemory(outType reflect.Type) bool {
17+ size := outType.Size()
18+ if size == 0 {
19+ return false
20+ }
21+ if runtime.GOOS == "windows" {
22+ // The Win64 ABI returns aggregates of exactly 1, 2, 4, or 8 bytes in
23+ // RAX. Every other size is returned through a caller-allocated hidden
24+ // pointer that the callee also returns in RAX.
25+ switch size {
26+ case 1, 2, 4, 8:
27+ return false
28+ default:
29+ return true
30+ }
31+ }
32+ // The System V ABI returns aggregates of up to two eightbytes in registers.
33+ return size > maxRegAllocStructSize
34+}
35+
36+func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) {
14 outSize := outType.Size()37 outSize := outType.Size()
38+ if runtime.GOOS == "windows" {
39+ switch {
40+ case outSize == 0:
41+ return reflect.New(outType).Elem()
42+ case structReturnInMemory(outType):
43+ // Returned through the caller-allocated hidden pointer, which the
44+ // callee also returns in RAX.
45+ return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
46+ default:
47+ // 1, 2, 4, or 8 byte aggregates are returned in RAX.
48+ return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
49+ }
50+ }
15 switch {51 switch {
16 case outSize == 0:52 case outSize == 0:
17 return reflect.New(outType).Elem()53 return reflect.New(outType).Elem()
@@ -23,30 +59,36 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
23 // up to 8 bytes is returned in RAX59 // up to 8 bytes is returned in RAX
24 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()60 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
25 case outSize <= 16:61 case outSize <= 16:
26- var r1, r2 uintptr62+ r1, r2 := syscall.a1, syscall.a2
27- var intRegs, floatRegs int63+ if isAllFloats(outType) {
28- for i := 0; i < int((outSize+7)/8); i++ {64+ r1 = syscall.f1
29- class := classifyEightbyte(outType, uintptr(i)*8, uintptr(i)*8+8)65+ r2 = syscall.f2
30- var reg uintptr66+ } else {
31- if class == _SSE {67+ // check first 8 bytes if it's floats
32- if floatRegs == 0 {68+ hasFirstFloat := false
33- reg = syscall.f169+ numFields := numABIFields(outType)
34- } else {70+ f1 := abiField(outType, 0).Type
35- reg = syscall.f271+ if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && abiField(outType, 1).Type.Kind() == reflect.Float32 {
36- }72+ r1 = syscall.f1
37- floatRegs++73+ hasFirstFloat = true
38- } else {74+ }
39- if intRegs == 0 {75+
40- reg = syscall.a176+ // find index of the field that starts the second 8 bytes
41- } else {77+ var i int
42- reg = syscall.a278+ for i = 0; i < numFields; i++ {
79+ if abiField(outType, i).Offset == 8 {
80+ break
43 }81 }
44- intRegs++
45 }82 }
46- if i == 0 {83+
47- r1 = reg84+ // check last 8 bytes if they are floats
48- } else {85+ f1 = abiField(outType, i).Type
49- r2 = reg86+ if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == numFields {
87+ r2 = syscall.f1
88+ } else if hasFirstFloat {
89+ // if the first field was a float then that means the second integer field
90+ // comes from the first integer register
91+ r2 = syscall.a1
50 }92 }
51 }93 }
52 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()94 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
@@ -58,8 +100,11 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
58 }100 }
59 101
60 func isAllFloats(ty reflect.Type) bool {102 func isAllFloats(ty reflect.Type) bool {
61- for i := 0; i < ty.NumField(); i++ {103+ for i := range ty.NumField() {
62 f := ty.Field(i)104 f := ty.Field(i)
105+ if !isABIField(f) {
106+ continue
107+ }
63 switch f.Type.Kind() {108 switch f.Type.Kind() {
64 case reflect.Float64, reflect.Float32:109 case reflect.Float64, reflect.Float32:
65 default:110 default:
@@ -82,6 +127,12 @@ const (
82 )127 )
83 128
84 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {129 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
130+ if runtime.GOOS == "windows" {
131+ // Win64 still passes an empty struct as an argument slot, so this must
132+ // run before the zero-size early return used by the System V path.
133+ return addStructWindows(v, addInt, keepAlive)
134+ }
135+
85 if v.Type().Size() == 0 {136 if v.Type().Size() == 0 {
86 return keepAlive137 return keepAlive
87 }138 }
@@ -107,6 +158,27 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
107 return keepAlive158 return keepAlive
108 }159 }
109 160
161+// addStructWindows passes a struct argument under the Win64 ABI. Aggregates of
162+// exactly 1, 2, 4, or 8 bytes are passed by value in a single integer slot; all
163+// other sizes are passed as a pointer to a caller-allocated copy. Empty structs
164+// fall in the latter group: unlike the System V ABI, Win64 still consumes an
165+// argument slot for them.
166+func addStructWindows(v reflect.Value, addInt func(uintptr), keepAlive []any) []any {
167+ switch v.Type().Size() {
168+ case 1, 2, 4, 8:
169+ var val uintptr
170+ reflect.NewAt(v.Type(), unsafe.Pointer(&val)).Elem().Set(v)
171+ addInt(val)
172+ default:
173+ ptrStruct := reflect.New(v.Type())
174+ ptrStruct.Elem().Set(v)
175+ ptr := ptrStruct.Elem().Addr().UnsafePointer()
176+ keepAlive = append(keepAlive, ptr)
177+ addInt(uintptr(ptr))
178+ }
179+ return keepAlive
180+}
181+
110 func postMerger(t reflect.Type) (passInMemory bool) {182 func postMerger(t reflect.Type) (passInMemory bool) {
111 // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other183 // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
112 // eightbyte isn’t SSEUP, the whole argument is passed in memory.184 // eightbyte isn’t SSEUP, the whole argument is passed in memory.
@@ -148,7 +220,10 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
148 numFields = v.Type().Len()220 numFields = v.Type().Len()
149 }221 }
150 222
151- for i := 0; i < numFields; i++ {223+ for i := range numFields {
224+ if v.Kind() == reflect.Struct && !isABIField(v.Type().Field(i)) {
225+ continue
226+ }
152 flushed = false227 flushed = false
153 var f reflect.Value228 var f reflect.Value
154 if v.Kind() == reflect.Struct {229 if v.Kind() == reflect.Struct {
@@ -294,7 +369,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
294 // - If not enough registers for all eightbytes: entire struct goes on the stack369 // - If not enough registers for all eightbytes: entire struct goes on the stack
295 func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {370 func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
296 switch runtime.GOOS {371 switch runtime.GOOS {
297- case "darwin", "freebsd", "linux", "netbsd":372+ case "android", "darwin", "freebsd", "ios", "linux", "netbsd":
298 default:373 default:
299 panic("purego: getCallbackStruct is not supported on " + runtime.GOOS)374 panic("purego: getCallbackStruct is not supported on " + runtime.GOOS)
300 }375 }
@@ -315,7 +390,7 @@ func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int,
315 390
316 // Count how many integer and SSE registers this struct needs.391 // Count how many integer and SSE registers this struct needs.
317 var needInts, needFloats int392 var needInts, needFloats int
318- for i := 0; i < numEightbytes; i++ {393+ for i := range numEightbytes {
319 class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)394 class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
320 if class == _SSE {395 if class == _SSE {
321 needFloats++396 needFloats++
@@ -333,7 +408,7 @@ func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int,
333 408
334 // Read each eightbyte from its appropriate register class.409 // Read each eightbyte from its appropriate register class.
335 var r1, r2 uintptr410 var r1, r2 uintptr
336- for i := 0; i < numEightbytes; i++ {411+ for i := range numEightbytes {
337 class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)412 class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
338 if class == _SSE {413 if class == _SSE {
339 if i == 0 {414 if i == 0 {
@@ -415,7 +490,7 @@ func doClassifyEightbyte(t reflect.Type, base, start, end uintptr) int {
415 switch t.Kind() {490 switch t.Kind() {
416 case reflect.Struct:491 case reflect.Struct:
417 class := _NO_CLASS492 class := _NO_CLASS
418- for i := 0; i < t.NumField(); i++ {493+ for i := range t.NumField() {
419 f := t.Field(i)494 f := t.Field(i)
420 class |= doClassifyEightbyte(f.Type, base+f.Offset, start, end)495 class |= doClassifyEightbyte(f.Type, base+f.Offset, start, end)
421 }496 }
@@ -423,7 +498,7 @@ func doClassifyEightbyte(t reflect.Type, base, start, end uintptr) int {
423 case reflect.Array:498 case reflect.Array:
424 class := _NO_CLASS499 class := _NO_CLASS
425 elemSize := t.Elem().Size()500 elemSize := t.Elem().Size()
426- for i := 0; i < t.Len(); i++ {501+ for i := range t.Len() {
427 class |= doClassifyEightbyte(t.Elem(), base+uintptr(i)*elemSize, start, end)502 class |= doClassifyEightbyte(t.Elem(), base+uintptr(i)*elemSize, start, end)
428 }503 }
429 return class504 return class
D struct_amd64_regression_test.go +0 -21
@@ -1,21 +0,0 @@
1-//go:build amd64 && (darwin || linux)
2-
3-package purego
4-
5-import (
6- "reflect"
7- "testing"
8-)
9-
10-func TestGetStructHandlesSecondEightbyteWithoutOffset8Field(t *testing.T) {
11- type awkward struct {
12- A [3]uint32
13- B uint32
14- }
15- sys := syscall15Args{a1: 0x0000000200000001, a2: 0x0000000400000003}
16- got := getStruct(reflect.TypeOf(awkward{}), sys).Interface().(awkward)
17- want := awkward{A: [3]uint32{1, 2, 3}, B: 4}
18- if got != want {
19- t.Fatalf("got %+v, want %+v", got, want)
20- }
21-}
M struct_arm.go +11 -9
@@ -5,7 +5,6 @@ package purego
5 5
6 import (6 import (
7 "reflect"7 "reflect"
8- "runtime"
9 "unsafe"8 "unsafe"
10 )9 )
11 10
@@ -17,10 +16,8 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
17 16
18 // TODO: ARM EABI: small structs are passed in registers or on stack17 // TODO: ARM EABI: small structs are passed in registers or on stack
19 // For simplicity, pass by pointer for now18 // For simplicity, pass by pointer for now
20- ptr, live := stableValuePointer(v)19+ ptr := v.Addr().UnsafePointer()
21- if live != nil {20+ keepAlive = append(keepAlive, ptr)
22- keepAlive = append(keepAlive, live)
23- }
24 if *numInts < 4 {21 if *numInts < 4 {
25 addInt(uintptr(ptr))22 addInt(uintptr(ptr))
26 *numInts++23 *numInts++
@@ -28,11 +25,17 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
28 addStack(uintptr(ptr))25 addStack(uintptr(ptr))
29 *numStack++26 *numStack++
30 }27 }
31- runtime.KeepAlive(live)
32 return keepAlive28 return keepAlive
33 }29 }
34 30
35-func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {31+// structReturnInMemory always reports false on arm: an indirect struct return
32+// is recovered from the pointer the callee leaves in a1 (see getStruct) rather
33+// than through a caller-allocated hidden first argument.
34+func structReturnInMemory(reflect.Type) bool {
35+ return false
36+}
37+
38+func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) {
36 outSize := outType.Size()39 outSize := outType.Size()
37 if outSize == 0 {40 if outSize == 0 {
38 return reflect.New(outType).Elem()41 return reflect.New(outType).Elem()
@@ -56,14 +59,13 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
56 if size == 0 {59 if size == 0 {
57 return60 return
58 }61 }
59- ptr, live := stableValuePointer(v)62+ ptr := unsafe.Pointer(v.UnsafeAddr())
60 if size <= 4 {63 if size <= 4 {
61 addInt(*(*uintptr)(ptr))64 addInt(*(*uintptr)(ptr))
62 } else if size <= 8 {65 } else if size <= 8 {
63 addInt(*(*uintptr)(ptr))66 addInt(*(*uintptr)(ptr))
64 addInt(*(*uintptr)(unsafe.Add(ptr, 4)))67 addInt(*(*uintptr)(unsafe.Add(ptr, 4)))
65 }68 }
66- runtime.KeepAlive(live)
67 }69 }
68 70
69 // shouldBundleStackArgs always returns false on arm71 // shouldBundleStackArgs always returns false on arm
M struct_arm64.go +65 -67
@@ -14,7 +14,14 @@ import (
14 "github.com/bnema/purego/internal/strings"14 "github.com/bnema/purego/internal/strings"
15 )15 )
16 16
17-func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {17+// structReturnInMemory always reports false on arm64: a struct returned in
18+// memory is passed through the dedicated indirect result register (R8), not as
19+// an ordinary integer argument, so it is handled separately.
20+func structReturnInMemory(reflect.Type) bool {
21+ return false
22+}
23+
24+func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) {
18 outSize := outType.Size()25 outSize := outType.Size()
19 switch {26 switch {
20 case outSize == 0:27 case outSize == 0:
@@ -78,9 +85,9 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
78 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {85 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
79 // if this doesn't fit entirely in registers then86 // if this doesn't fit entirely in registers then
80 // each element goes onto the stack87 // each element goes onto the stack
81- if hfa && *numFloats+v.NumField() > numOfFloatRegisters() {88+ if hfa && *numFloats+numABIFields(v.Type()) > numOfFloatRegisters() {
82 *numFloats = numOfFloatRegisters()89 *numFloats = numOfFloatRegisters()
83- } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {90+ } else if hva && *numInts+numABIFields(v.Type()) > numOfIntegerRegisters() {
84 *numInts = numOfIntegerRegisters()91 *numInts = numOfIntegerRegisters()
85 }92 }
86 93
@@ -92,7 +99,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
92 }99 }
93 100
94 func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {101 func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
95- if runtime.GOOS == "darwin" {102+ if isDarwin {
96 placeRegistersDarwin(v, addFloat, addInt)103 placeRegistersDarwin(v, addFloat, addInt)
97 return104 return
98 }105 }
@@ -112,7 +119,10 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui
112 } else {119 } else {
113 numFields = v.Type().Len()120 numFields = v.Type().Len()
114 }121 }
115- for k := 0; k < numFields; k++ {122+ for k := range numFields {
123+ if v.Kind() == reflect.Struct && !isABIField(v.Type().Field(k)) {
124+ continue
125+ }
116 flushed = false126 flushed = false
117 var f reflect.Value127 var f reflect.Value
118 if v.Kind() == reflect.Struct {128 if v.Kind() == reflect.Struct {
@@ -229,52 +239,39 @@ func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any {
229 //239 //
230 // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst240 // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
231 func isHFA(t reflect.Type) bool {241 func isHFA(t reflect.Type) bool {
242+ // round up struct size to nearest 8 see section B.4
232 structSize := roundUpTo8(t.Size())243 structSize := roundUpTo8(t.Size())
233- if structSize == 0 {244+ numFields := numABIFields(t)
245+ if structSize == 0 || numFields > 4 {
234 return false246 return false
235 }247 }
236- var kind reflect.Kind248+ first := abiField(t, 0)
237- var members int249+ switch first.Type.Kind() {
238- var walk func(reflect.Type) bool250+ case reflect.Float32, reflect.Float64:
239- walk = func(t reflect.Type) bool {251+ firstKind := first.Type.Kind()
240- for i := 0; i < t.NumField(); i++ {252+ for i := range numFields {
241- ft := t.Field(i).Type253+ if abiField(t, i).Type.Kind() != firstKind {
242- switch ft.Kind() {
243- case reflect.Float32, reflect.Float64:
244- if kind == 0 {
245- kind = ft.Kind()
246- } else if ft.Kind() != kind {
247- return false
248- }
249- members++
250- if members > 4 {
251- return false
252- }
253- case reflect.Struct:
254- if !walk(ft) {
255- return false
256- }
257- case reflect.Array:
258- elemKind := ft.Elem().Kind()
259- if elemKind != reflect.Float32 && elemKind != reflect.Float64 {
260- return false
261- }
262- if kind == 0 {
263- kind = elemKind
264- } else if elemKind != kind {
265- return false
266- }
267- members += ft.Len()
268- if members > 4 {
269- return false
270- }
271- default:
272 return false254 return false
273 }255 }
274 }256 }
275 return true257 return true
258+ case reflect.Array:
259+ switch first.Type.Elem().Kind() {
260+ case reflect.Float32, reflect.Float64:
261+ return true
262+ default:
263+ return false
264+ }
265+ case reflect.Struct:
266+ for range numABIFields(first.Type) {
267+ if !isHFA(first.Type) {
268+ return false
269+ }
270+ }
271+ return true
272+ default:
273+ return false
276 }274 }
277- return walk(t) && members > 0
278 }275 }
279 276
280 // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type277 // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
@@ -290,12 +287,13 @@ func isHVA(t reflect.Type) bool {
290 if structSize == 0 || (structSize != 8 && structSize != 16) {287 if structSize == 0 || (structSize != 8 && structSize != 16) {
291 return false288 return false
292 }289 }
293- first := t.Field(0)290+ numFields := numABIFields(t)
291+ first := abiField(t, 0)
294 switch first.Type.Kind() {292 switch first.Type.Kind() {
295 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:293 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
296 firstKind := first.Type.Kind()294 firstKind := first.Type.Kind()
297- for i := 0; i < t.NumField(); i++ {295+ for i := range numFields {
298- if t.Field(i).Type.Kind() != firstKind {296+ if abiField(t, i).Type.Kind() != firstKind {
299 return false297 return false
300 }298 }
301 }299 }
@@ -315,7 +313,7 @@ func isHVA(t reflect.Type) bool {
315 // copyStruct8ByteChunks copies struct memory in 8-byte chunks to the provided callback.313 // copyStruct8ByteChunks copies struct memory in 8-byte chunks to the provided callback.
316 // This is used for Darwin ARM64's byte-level packing of non-HFA/HVA structs.314 // This is used for Darwin ARM64's byte-level packing of non-HFA/HVA structs.
317 func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintptr)) {315 func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintptr)) {
318- if runtime.GOOS != "darwin" {316+ if !isDarwin {
319 panic("purego: should only be called on darwin")317 panic("purego: should only be called on darwin")
320 }318 }
321 for offset := uintptr(0); offset < size; offset += 8 {319 for offset := uintptr(0); offset < size; offset += 8 {
@@ -325,7 +323,7 @@ func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintp
325 chunk = *(*uintptr)(unsafe.Add(ptr, offset))323 chunk = *(*uintptr)(unsafe.Add(ptr, offset))
326 } else {324 } else {
327 // Read byte-by-byte to avoid reading beyond allocation325 // Read byte-by-byte to avoid reading beyond allocation
328- for i := uintptr(0); i < remaining; i++ {326+ for i := range remaining {
329 b := *(*byte)(unsafe.Add(ptr, offset+i))327 b := *(*byte)(unsafe.Add(ptr, offset+i))
330 chunk |= uintptr(b) << (i * 8)328 chunk |= uintptr(b) << (i * 8)
331 }329 }
@@ -342,7 +340,7 @@ func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintp
342 // For non-HFA/HVA structs, Darwin uses byte-level packing. We copy the struct memory in340 // For non-HFA/HVA structs, Darwin uses byte-level packing. We copy the struct memory in
343 // 8-byte chunks, which works correctly for both register and stack placement.341 // 8-byte chunks, which works correctly for both register and stack placement.
344 func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {342 func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
345- if runtime.GOOS != "darwin" {343+ if !isDarwin {
346 panic("purego: placeRegistersDarwin should only be called on darwin")344 panic("purego: placeRegistersDarwin should only be called on darwin")
347 }345 }
348 // Check if this is an HFA/HVA346 // Check if this is an HFA/HVA
@@ -370,7 +368,7 @@ func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(u
370 // shouldBundleStackArgs determines if we need to start C-style packing for368 // shouldBundleStackArgs determines if we need to start C-style packing for
371 // Darwin ARM64 stack arguments. This happens when registers are exhausted.369 // Darwin ARM64 stack arguments. This happens when registers are exhausted.
372 func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {370 func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
373- if runtime.GOOS != "darwin" {371+ if !isDarwin {
374 return false372 return false
375 }373 }
376 374
@@ -395,12 +393,12 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
395 }393 }
396 394
397 if hfa {395 if hfa {
398- need := v.NumField()396+ need := numABIFields(v.Type())
399 return numFloats+need > numOfFloatRegisters()397 return numFloats+need > numOfFloatRegisters()
400 }398 }
401 399
402 if hva {400 if hva {
403- need := v.NumField()401+ need := numABIFields(v.Type())
404 return numInts+need > numOfIntegerRegisters()402 return numInts+need > numOfIntegerRegisters()
405 }403 }
406 404
@@ -412,7 +410,7 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
412 // registers, used during stack argument bundling to decide if a struct410 // registers, used during stack argument bundling to decide if a struct
413 // should go through normal register allocation or be bundled with stack args.411 // should go through normal register allocation or be bundled with stack args.
414 func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {412 func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
415- if runtime.GOOS != "darwin" {413+ if !isDarwin {
416 panic("purego: structFitsInRegisters should only be called on darwin")414 panic("purego: structFitsInRegisters should only be called on darwin")
417 }415 }
418 hfa := isHFA(val.Type())416 hfa := isHFA(val.Type())
@@ -421,13 +419,13 @@ func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (b
421 419
422 if hfa {420 if hfa {
423 // HFA: check if elements fit in float registers421 // HFA: check if elements fit in float registers
424- if tempNumFloats+val.NumField() <= numOfFloatRegisters() {422+ if numFields := numABIFields(val.Type()); tempNumFloats+numFields <= numOfFloatRegisters() {
425- return true, tempNumInts, tempNumFloats + val.NumField()423+ return true, tempNumInts, tempNumFloats + numFields
426 }424 }
427 } else if hva {425 } else if hva {
428 // HVA: check if elements fit in int registers426 // HVA: check if elements fit in int registers
429- if tempNumInts+val.NumField() <= numOfIntegerRegisters() {427+ if numFields := numABIFields(val.Type()); tempNumInts+numFields <= numOfIntegerRegisters() {
430- return true, tempNumInts + val.NumField(), tempNumFloats428+ return true, tempNumInts + numFields, tempNumFloats
431 }429 }
432 } else if size <= 16 {430 } else if size <= 16 {
433 // Non-HFA/HVA small structs use int registers for byte-packing431 // Non-HFA/HVA small structs use int registers for byte-packing
@@ -445,7 +443,7 @@ func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (b
445 func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,443 func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
446 keepAlive []any, addInt, addFloat, addStack func(uintptr),444 keepAlive []any, addInt, addFloat, addStack func(uintptr),
447 pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {445 pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
448- if runtime.GOOS != "darwin" {446+ if !isDarwin {
449 panic("purego: collectStackArgs should only be called on darwin")447 panic("purego: collectStackArgs should only be called on darwin")
450 }448 }
451 449
@@ -502,7 +500,7 @@ const (
502 // bundleStackArgs bundles remaining arguments for Darwin ARM64 C-style stack packing.500 // bundleStackArgs bundles remaining arguments for Darwin ARM64 C-style stack packing.
503 // It creates a packed struct with proper alignment and copies it to the stack in 8-byte chunks.501 // It creates a packed struct with proper alignment and copies it to the stack in 8-byte chunks.
504 func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {502 func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
505- if runtime.GOOS != "darwin" {503+ if !isDarwin {
506 panic("purego: bundleStackArgs should only be called on darwin")504 panic("purego: bundleStackArgs should only be called on darwin")
507 }505 }
508 if len(stackArgs) == 0 {506 if len(stackArgs) == 0 {
@@ -528,7 +526,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
528 paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign))526 paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign))
529 fields = append(fields, reflect.StructField{527 fields = append(fields, reflect.StructField{
530 Name: paddingFieldPrefix + strconv.Itoa(fieldIndex),528 Name: paddingFieldPrefix + strconv.Itoa(fieldIndex),
531- Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeOf(byte(0))),529+ Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeFor[byte]()),
532 })530 })
533 currentOffset += paddingNeeded531 currentOffset += paddingNeeded
534 fieldIndex++532 fieldIndex++
@@ -548,7 +546,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
548 546
549 // Set values (skip padding fields)547 // Set values (skip padding fields)
550 argIndex := 0548 argIndex := 0
551- for j := 0; j < structInstance.NumField(); j++ {549+ for j := range structInstance.NumField() {
552 fieldName := structType.Field(j).Name550 fieldName := structType.Field(j).Name
553 if stdstrings.HasPrefix(fieldName, paddingFieldPrefix) {551 if stdstrings.HasPrefix(fieldName, paddingFieldPrefix) {
554 continue552 continue
@@ -576,7 +574,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
576 // - Darwin ARM64: byte-level packing on the stack574 // - Darwin ARM64: byte-level packing on the stack
577 func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {575 func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
578 switch runtime.GOOS {576 switch runtime.GOOS {
579- case "darwin", "freebsd", "linux", "netbsd":577+ case "android", "darwin", "freebsd", "ios", "linux", "netbsd":
580 default:578 default:
581 panic("purego: getCallbackStruct is not supported on " + runtime.GOOS)579 panic("purego: getCallbackStruct is not supported on " + runtime.GOOS)
582 }580 }
@@ -623,8 +621,8 @@ func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int,
623 }621 }
624 622
625 // Pointer on stack (rare: all integer registers exhausted).623 // Pointer on stack (rare: all integer registers exhausted).
626- if runtime.GOOS == "darwin" {624+ if isDarwin {
627- ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeOf(uintptr(0)))625+ ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeFor[uintptr]())
628 ptr := uintptr(ptrVal.Uint())626 ptr := uintptr(ptrVal.Uint())
629 return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem()627 return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem()
630 }628 }
@@ -640,9 +638,9 @@ func readHFAFromRegisters(inType reflect.Type, f *[callbackMaxFrame]uintptr, flo
640 size := inType.Size()638 size := inType.Size()
641 639
642 // Determine the element type640 // Determine the element type
643- root := inType.Field(0).Type641+ root := abiField(inType, 0).Type
644 for root.Kind() == reflect.Struct {642 for root.Kind() == reflect.Struct {
645- root = root.Field(0).Type643+ root = abiField(root, 0).Type
646 }644 }
647 isFloat32 := root.Kind() == reflect.Float32645 isFloat32 := root.Kind() == reflect.Float32
648 646
@@ -704,7 +702,7 @@ func readHFAFromRegisters(inType reflect.Type, f *[callbackMaxFrame]uintptr, flo
704 // callback frame. On Darwin ARM64, arguments are byte-packed on the stack.702 // callback frame. On Darwin ARM64, arguments are byte-packed on the stack.
705 // On Linux ARM64, arguments are 8-byte aligned.703 // On Linux ARM64, arguments are 8-byte aligned.
706 func readStructFromStackArm64(inType reflect.Type, f *[callbackMaxFrame]uintptr, frame unsafe.Pointer, stackSlot *int, stackByteOffset *uintptr) reflect.Value {704 func readStructFromStackArm64(inType reflect.Type, f *[callbackMaxFrame]uintptr, frame unsafe.Pointer, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
707- if runtime.GOOS == "darwin" {705+ if isDarwin {
708 return callbackArgFromStack(frame, *stackSlot, stackByteOffset, inType)706 return callbackArgFromStack(frame, *stackSlot, stackByteOffset, inType)
709 }707 }
710 // Linux ARM64: 8-byte aligned slots.708 // Linux ARM64: 8-byte aligned slots.
D struct_arm64_regression_test.go +0 -37
@@ -1,37 +0,0 @@
1-//go:build arm64 && (darwin || linux)
2-
3-package purego
4-
5-import (
6- "reflect"
7- "testing"
8-)
9-
10-func TestIsHFARejectsNestedMixedStruct(t *testing.T) {
11- type inner struct{ X, Y float32 }
12- type mixed struct {
13- Inner inner
14- Tail int32
15- }
16- if isHFA(reflect.TypeOf(mixed{})) {
17- t.Fatal("expected mixed nested struct to be non-HFA")
18- }
19-}
20-
21-func TestIsHFAAcceptsNestedFloatStructs(t *testing.T) {
22- type inner struct{ X, Y float32 }
23- type outer struct {
24- Left inner
25- Right inner
26- }
27- if !isHFA(reflect.TypeOf(outer{})) {
28- t.Fatal("expected nested float-only struct to be HFA")
29- }
30-}
31-
32-func TestIsHFAAcceptsFloatArrayStruct(t *testing.T) {
33- type arrayed struct{ A [2]float64 }
34- if !isHFA(reflect.TypeOf(arrayed{})) {
35- t.Fatal("expected float array struct to be HFA")
36- }
37-}
D struct_arm_regression_test.go +0 -41
@@ -1,41 +0,0 @@
1-//go:build arm && (freebsd || linux || netbsd || windows)
2-
3-package purego
4-
5-import (
6- "reflect"
7- "testing"
8- "unsafe"
9-)
10-
11-func TestARMAddStructCopiesUnaddressableValue(t *testing.T) {
12- type sample struct{ X uint32 }
13- var numInts, numFloats, numStack int
14- var ptr uintptr
15- keepAlive := addStruct(reflect.ValueOf(sample{X: 7}), &numInts, &numFloats, &numStack, func(v uintptr) {
16- ptr = v
17- }, func(uintptr) {
18- t.Fatal("unexpected float register")
19- }, func(uintptr) {
20- t.Fatal("unexpected stack slot")
21- }, nil)
22- if len(keepAlive) == 0 {
23- t.Fatal("expected copied keepAlive value")
24- }
25- if got := *(*uint32)(unsafe.Pointer(ptr)); got != 7 {
26- t.Fatalf("copied value = %#x, want %#x", got, uint32(7))
27- }
28-}
29-
30-func TestARMPlaceRegistersReadsUnaddressableValue(t *testing.T) {
31- type sample struct{ X uint32 }
32- var ints []uintptr
33- placeRegisters(reflect.ValueOf(sample{X: 7}), func(uintptr) {
34- t.Fatal("unexpected float register")
35- }, func(v uintptr) {
36- ints = append(ints, v)
37- })
38- if len(ints) != 1 || ints[0] != 7 {
39- t.Fatalf("placeRegisters() = %#v, want [7]", ints)
40- }
41-}
M struct_loong64.go +170 -142
@@ -4,172 +4,200 @@
4 package purego4 package purego
5 5
6 import (6 import (
7- "math"
8 "reflect"7 "reflect"
9 "unsafe"8 "unsafe"
10 )9 )
11 10
12-func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {11+// loong64Leaf is a scalar member of an aggregate after flattening nested structs
13- outSize := outType.Size()12+// and arrays. offset is the byte offset of the member within the aggregate.
14- switch {13+type loong64Leaf struct {
15- case outSize == 0:14+ isFloat bool
16- return reflect.New(outType).Elem()15+ kind reflect.Kind
17- case outSize <= 8:16+ offset uintptr
18- r1 := syscall.a117+ size uintptr
19- if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {18+}
20- r1 = syscall.f119+
21- if numFields == 2 {20+// loong64Flatten appends the scalar leaves of t to leaves, offsetting each member
22- r1 = packFloat32Pair(syscall.f1, syscall.f2)21+// by base. Nested structs and arrays are expanded into their members.
22+func loong64Flatten(t reflect.Type, base uintptr, leaves *[]loong64Leaf) {
23+ switch t.Kind() {
24+ case reflect.Struct:
25+ for i := range t.NumField() {
26+ f := t.Field(i)
27+ if f.Name == "_" {
28+ // Blank fields are explicit padding to match the C layout, not
29+ // members that the calling convention counts.
30+ continue
23 }31 }
32+ loong64Flatten(f.Type, base+f.Offset, leaves)
24 }33 }
25- return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()34+ case reflect.Array:
26- case outSize <= 16:35+ elem := t.Elem()
27- r1, r2 := syscall.a1, syscall.a236+ for i := range t.Len() {
28- if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {37+ loong64Flatten(elem, base+uintptr(i)*elem.Size(), leaves)
29- switch numFields {38+ }
30- case 4:39+ default:
31- r1 = packFloat32Pair(syscall.f1, syscall.f2)40+ k := t.Kind()
32- r2 = packFloat32Pair(syscall.f3, syscall.f4)41+ *leaves = append(*leaves, loong64Leaf{
33- case 3:42+ isFloat: k == reflect.Float32 || k == reflect.Float64,
34- r1 = packFloat32Pair(syscall.f1, syscall.f2)43+ kind: k,
35- r2 = syscall.f344+ offset: base,
36- case 2:45+ size: t.Size(),
37- r1 = syscall.f146+ })
38- r2 = syscall.f247+ }
39- default:48+}
40- panic("not reached")49+
41- }50+// loong64Classify flattens t and reports whether it is passed and returned through
51+// the floating-point calling convention. Under the LoongArch hard-float ABI an
52+// aggregate uses FP registers only when, after flattening, it has one or two
53+// floating-point members and nothing else, or exactly one floating-point member
54+// together with one integer member.
55+func loong64Classify(t reflect.Type) (leaves []loong64Leaf, useFP bool) {
56+ loong64Flatten(t, 0, &leaves)
57+ var floats, ints int
58+ for _, l := range leaves {
59+ if l.isFloat {
60+ floats++
61+ } else {
62+ ints++
42 }63 }
43- return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()64+ }
65+ switch {
66+ case ints == 0 && (floats == 1 || floats == 2):
67+ return leaves, true
68+ case ints == 1 && floats == 1:
69+ return leaves, true
44 default:70 default:
45- // create struct from the Go pointer created above71+ return leaves, false
46- // weird pointer dereference to circumvent go vet72+ }
73+}
74+
75+// structReturnInMemory reports whether a struct return value is returned through
76+// a caller-allocated hidden pointer passed as the first integer argument.
77+// Aggregates larger than two eightbytes are returned in memory.
78+func structReturnInMemory(outType reflect.Type) bool {
79+ return outType.Size() > maxRegAllocStructSize
80+}
81+
82+func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value {
83+ outSize := outType.Size()
84+ if outSize == 0 {
85+ return reflect.New(outType).Elem()
86+ }
87+ if outSize > 16 {
88+ // Returned indirectly through a pointer in the first integer register.
47 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()89 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
48 }90 }
91+
92+ var buf [16]byte
93+ base := unsafe.Pointer(&buf[0])
94+ if leaves, useFP := loong64Classify(outType); useFP {
95+ floatRegs := [2]uintptr{syscall.f1, syscall.f2}
96+ intRegs := [2]uintptr{syscall.a1, syscall.a2}
97+ var fi, ii int
98+ for _, l := range leaves {
99+ dst := unsafe.Add(base, l.offset)
100+ if !l.isFloat {
101+ loong64StoreInt(dst, l.size, intRegs[ii])
102+ ii++
103+ continue
104+ }
105+ r := floatRegs[fi]
106+ fi++
107+ if l.kind == reflect.Float32 {
108+ // A single-precision value is NaN-boxed in the register.
109+ *(*uint32)(dst) = uint32(r)
110+ } else {
111+ *(*uint64)(dst) = uint64(r)
112+ }
113+ }
114+ } else {
115+ *(*uintptr)(base) = syscall.a1
116+ if outSize > 8 {
117+ *(*uintptr)(unsafe.Add(base, 8)) = syscall.a2
118+ }
119+ }
120+ return reflect.NewAt(outType, base).Elem()
49 }121 }
50 122
51-const (123+func loong64StoreInt(dst unsafe.Pointer, size uintptr, r uintptr) {
52- _NO_CLASS = 0b00124+ switch size {
53- _FLOAT = 0b01125+ case 1:
54- _INT = 0b11126+ *(*uint8)(dst) = uint8(r)
55-)127+ case 2:
128+ *(*uint16)(dst) = uint16(r)
129+ case 4:
130+ *(*uint32)(dst) = uint32(r)
131+ default:
132+ *(*uint64)(dst) = uint64(r)
133+ }
134+}
56 135
57 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {136 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
58- if v.Type().Size() == 0 {137+ size := v.Type().Size()
138+ if size == 0 {
59 return keepAlive139 return keepAlive
60 }140 }
141+ if size > 16 {
142+ return placeStack(v, keepAlive, addInt)
143+ }
61 144
62- if size := v.Type().Size(); size <= 16 {145+ var ptr unsafe.Pointer
63- placeRegisters(v, addFloat, addInt)146+ if v.CanAddr() {
147+ ptr = v.Addr().UnsafePointer()
64 } else {148 } else {
65- keepAlive = placeStack(v, keepAlive, addInt)149+ tmp := reflect.New(v.Type())
150+ tmp.Elem().Set(v)
151+ ptr = tmp.UnsafePointer()
152+ keepAlive = append(keepAlive, tmp.Interface())
66 }153 }
67- return keepAlive // the struct was allocated so don't panic
68-}
69 154
70-func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {155+ if leaves, useFP := loong64Classify(v.Type()); useFP {
71- var val uint64156+ for _, l := range leaves {
72- var shift byte157+ src := unsafe.Add(ptr, l.offset)
73- var flushed bool158+ switch {
74- class := _NO_CLASS159+ case l.isFloat && l.kind == reflect.Float32:
75- var place func(v reflect.Value)160+ // NaN-box the single-precision value in the 64-bit FP register.
76- place = func(v reflect.Value) {161+ addFloat(uintptr(*(*uint32)(src)) | 0xFFFFFFFF_00000000)
77- var numFields int162+ case l.isFloat:
78- if v.Kind() == reflect.Struct {163+ addFloat(uintptr(*(*uint64)(src)))
79- numFields = v.Type().NumField()
80- } else {
81- numFields = v.Type().Len()
82- }
83- for k := 0; k < numFields; k++ {
84- flushed = false
85- var f reflect.Value
86- if v.Kind() == reflect.Struct {
87- f = v.Field(k)
88- } else {
89- f = v.Index(k)
90- }
91- align := byte(f.Type().Align()*8 - 1)
92- shift = (shift + align) &^ align
93- if shift >= 64 {
94- flushPackedWord(&val, &shift, &flushed, class == _FLOAT, addFloat, addInt)
95- class = _NO_CLASS
96- }
97- switch f.Type().Kind() {
98- case reflect.Struct:
99- place(f)
100- case reflect.Bool:
101- if f.Bool() {
102- val |= 1 << shift
103- }
104- shift += 8
105- class |= _INT
106- case reflect.Uint8:
107- val |= f.Uint() << shift
108- shift += 8
109- class |= _INT
110- case reflect.Uint16:
111- val |= f.Uint() << shift
112- shift += 16
113- class |= _INT
114- case reflect.Uint32:
115- val |= f.Uint() << shift
116- shift += 32
117- class |= _INT
118- case reflect.Uint64, reflect.Uint, reflect.Uintptr:
119- addInt(uintptr(f.Uint()))
120- shift = 0
121- flushed = true
122- class = _NO_CLASS
123- case reflect.Int8:
124- val |= uint64(f.Int()&0xFF) << shift
125- shift += 8
126- class |= _INT
127- case reflect.Int16:
128- val |= uint64(f.Int()&0xFFFF) << shift
129- shift += 16
130- class |= _INT
131- case reflect.Int32:
132- val |= uint64(f.Int()&0xFFFF_FFFF) << shift
133- shift += 32
134- class |= _INT
135- case reflect.Int64, reflect.Int:
136- addInt(uintptr(f.Int()))
137- shift = 0
138- flushed = true
139- class = _NO_CLASS
140- case reflect.Float32:
141- if class == _FLOAT {
142- addFloat(uintptr(val))
143- val = 0
144- shift = 0
145- }
146- val |= uint64(math.Float32bits(float32(f.Float()))) << shift
147- shift += 32
148- class |= _FLOAT
149- case reflect.Float64:
150- addFloat(uintptr(math.Float64bits(float64(f.Float()))))
151- shift = 0
152- flushed = true
153- class = _NO_CLASS
154- case reflect.Ptr, reflect.UnsafePointer:
155- addInt(f.Pointer())
156- shift = 0
157- flushed = true
158- class = _NO_CLASS
159- case reflect.Array:
160- place(f)
161 default:164 default:
162- panic("purego: unsupported kind " + f.Kind().String())165+ addInt(loong64LoadInt(src, l))
163 }166 }
164 }167 }
168+ return keepAlive
165 }169 }
166- place(v)170+
167- if !flushed {171+ // Integer calling convention: pass the raw aggregate in one or two GARs.
168- if class == _FLOAT {172+ var words [16]byte
169- addFloat(uintptr(val))173+ copy(words[:], unsafe.Slice((*byte)(ptr), size))
170- } else {174+ addInt(*(*uintptr)(unsafe.Pointer(&words[0])))
171- addInt(uintptr(val))175+ if size > 8 {
172- }176+ addInt(*(*uintptr)(unsafe.Pointer(&words[8])))
177+ }
178+ return keepAlive
179+}
180+
181+// loong64LoadInt reads an integer leaf into a register value, sign-extending
182+// signed members and zero-extending the rest.
183+func loong64LoadInt(src unsafe.Pointer, l loong64Leaf) uintptr {
184+ switch l.kind {
185+ case reflect.Int8:
186+ return uintptr(int64(*(*int8)(src)))
187+ case reflect.Int16:
188+ return uintptr(int64(*(*int16)(src)))
189+ case reflect.Int32:
190+ return uintptr(int64(*(*int32)(src)))
191+ case reflect.Int, reflect.Int64:
192+ return uintptr(*(*int64)(src))
193+ case reflect.Bool, reflect.Uint8:
194+ return uintptr(*(*uint8)(src))
195+ case reflect.Uint16:
196+ return uintptr(*(*uint16)(src))
197+ case reflect.Uint32:
198+ return uintptr(*(*uint32)(src))
199+ default:
200+ return uintptr(*(*uint64)(src))
173 }201 }
174 }202 }
175 203
D struct_loong64_regression_test.go +0 -35
@@ -1,35 +0,0 @@
1-//go:build loong64 && linux
2-
3-package purego
4-
5-import (
6- "math"
7- "reflect"
8- "testing"
9-)
10-
11-func TestLoong64GetStructMasksFloat32Pairs(t *testing.T) {
12- type pair struct{ X, Y float32 }
13- sys := syscall15Args{
14- f1: uintptr(0xDEADBEEF00000000 | uint64(math.Float32bits(1.5))),
15- f2: uintptr(math.Float32bits(2.5)),
16- }
17- got := getStruct(reflect.TypeOf(pair{}), sys).Interface().(pair)
18- if got.X != 1.5 || got.Y != 2.5 {
19- t.Fatalf("got %+v, want {1.5 2.5}", got)
20- }
21-}
22-
23-func TestLoong64PlaceRegistersResetsAfterFlush(t *testing.T) {
24- type triple struct{ A, B, C uint32 }
25- var ints []uintptr
26- placeRegisters(reflect.ValueOf(triple{A: 1, B: 2, C: 3}), func(uintptr) {
27- t.Fatal("unexpected float register")
28- }, func(v uintptr) {
29- ints = append(ints, v)
30- })
31- want := []uintptr{0x0000000200000001, 0x0000000000000003}
32- if len(ints) != len(want) || ints[0] != want[0] || ints[1] != want[1] {
33- t.Fatalf("placeRegisters() = %#v, want %#v", ints, want)
34- }
35-}
A struct_other.go +54 -0
@@ -0,0 +1,54 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !(amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)
5+
6+package purego
7+
8+import (
9+ "reflect"
10+ "unsafe"
11+)
12+
13+// This file provides the struct-handling helpers for architectures that do
14+// not support struct arguments or returns: 386, and any architecture reachable
15+// only through the integer-only cgo fallback. Every struct operation panics.
16+
17+func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
18+ panic("purego: struct arguments are not supported on this architecture")
19+}
20+
21+func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value {
22+ panic("purego: struct returns are not supported on this architecture")
23+}
24+
25+// structReturnInMemory reports whether a struct return value is returned through
26+// a caller-allocated hidden pointer. Structs are unsupported on this
27+// architecture, so it always reports false.
28+func structReturnInMemory(reflect.Type) bool {
29+ return false
30+}
31+
32+// shouldBundleStackArgs always returns false because C-style stack argument
33+// bundling is only needed on Darwin ARM64.
34+func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
35+ return false
36+}
37+
38+func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
39+ keepAlive []any, addInt, addFloat, addStack func(uintptr),
40+ pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
41+ panic("purego: collectStackArgs should not be called on this architecture")
42+}
43+
44+func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
45+ panic("purego: bundleStackArgs should not be called on this architecture")
46+}
47+
48+func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
49+ panic("purego: struct callback arguments are not supported on this architecture")
50+}
51+
52+func setStruct(a *callbackArgs, ret reflect.Value) {
53+ panic("purego: struct returns are not supported on this architecture")
54+}
M struct_ppc64le.go +118 -89
@@ -4,77 +4,76 @@
4 package purego4 package purego
5 5
6 import (6 import (
7+ "math"
7 "reflect"8 "reflect"
8 "unsafe"9 "unsafe"
9 )10 )
10 11
11-func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value {12+// ppc64leLeaf is a scalar member of a flattened aggregate at byte offset offset.
12- outSize := outType.Size()13+type ppc64leLeaf struct {
13-14+ kind reflect.Kind
14- switch {15+ offset uintptr
15- case outSize == 0:16+}
16- return reflect.New(outType).Elem()
17-
18- case outSize <= 16:
19- // Reconstruct from registers by copying raw bytes
20- var buf [16]byte
21-
22- // Integer registers
23- *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.a1
24- if outSize > 8 {
25- *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.a2
26- }
27 17
28- // Homogeneous float aggregates override integer regs18+// ppc64leFlatten appends the scalar leaves of t to leaves, expanding nested
29- if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {19+// structs and arrays.
30- if outType.Field(0).Type.Kind() == reflect.Float32 {20+func ppc64leFlatten(t reflect.Type, base uintptr, leaves *[]ppc64leLeaf) {
31- // float32 values in FP regs21+ switch t.Kind() {
32- f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4}22+ case reflect.Struct:
33- for i := 0; i < numFields; i++ {23+ for i := range t.NumField() {
34- *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i])24+ f := t.Field(i)
35- }25+ if f.Name == "_" {
36- } else {26+ // Blank fields are padding, not counted members.
37- // float64: whole register value is valid27+ continue
38- *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.f1
39- if outSize > 8 {
40- *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.f2
41- }
42 }28 }
29+ ppc64leFlatten(f.Type, base+f.Offset, leaves)
30+ }
31+ case reflect.Array:
32+ elem := t.Elem()
33+ for i := range t.Len() {
34+ ppc64leFlatten(elem, base+uintptr(i)*elem.Size(), leaves)
43 }35 }
44-
45- return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem()
46-
47 default:36 default:
48- // Returned indirectly via pointer in a137+ *leaves = append(*leaves, ppc64leLeaf{kind: t.Kind(), offset: base})
49- ptr := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))
50- return reflect.NewAt(outType, ptr).Elem()
51 }38 }
52 }39 }
53 40
54-func addStruct(41+// ppc64leClassifyHFA reports whether t is a homogeneous floating-point
55- v reflect.Value,42+// aggregate: one to eight members all of the same floating-point type.
56- numInts, numFloats, numStack *int,43+func ppc64leClassifyHFA(t reflect.Type) (leaves []ppc64leLeaf, isHFA bool) {
57- addInt, addFloat, addStack func(uintptr),44+ ppc64leFlatten(t, 0, &leaves)
58- keepAlive []any,45+ if len(leaves) == 0 || len(leaves) > 8 {
59-) []any {46+ return leaves, false
60- size := v.Type().Size()
61- if size == 0 {
62- return keepAlive
63 }47 }
64-48+ first := leaves[0].kind
65- if size <= 16 {49+ if first != reflect.Float32 && first != reflect.Float64 {
66- return placeSmallAggregatePPC64LE(v, addFloat, addInt, keepAlive)50+ return leaves, false
51+ }
52+ for _, l := range leaves {
53+ if l.kind != first {
54+ return leaves, false
55+ }
67 }56 }
57+ return leaves, true
58+}
68 59
69- return placeStack(v, keepAlive, addInt)60+// structReturnInMemory reports whether a struct return value is returned through
61+// a caller-allocated hidden pointer rather than in registers. A homogeneous
62+// floating-point aggregate is returned in the floating-point registers whatever
63+// its size.
64+func structReturnInMemory(outType reflect.Type) bool {
65+ if outType.Size() <= maxRegAllocStructSize {
66+ return false
67+ }
68+ _, isHFA := ppc64leClassifyHFA(outType)
69+ return !isHFA
70 }70 }
71 71
72-func placeSmallAggregatePPC64LE(72+func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
73- v reflect.Value,
74- addFloat, addInt func(uintptr),
75- keepAlive []any,
76-) []any {
77 size := v.Type().Size()73 size := v.Type().Size()
74+ if size == 0 {
75+ return keepAlive
76+ }
78 77
79 var ptr unsafe.Pointer78 var ptr unsafe.Pointer
80 if v.CanAddr() {79 if v.CanAddr() {
@@ -86,58 +85,88 @@ func placeSmallAggregatePPC64LE(
86 keepAlive = append(keepAlive, tmp.Interface())85 keepAlive = append(keepAlive, tmp.Interface())
87 }86 }
88 87
89- var buf [16]byte88+ // An HFA passes each member in its own floating-point register.
90- src := unsafe.Slice((*byte)(ptr), size)89+ if leaves, isHFA := ppc64leClassifyHFA(v.Type()); isHFA {
91- copy(buf[:], src)90+ for _, l := range leaves {
92-91+ src := unsafe.Add(ptr, l.offset)
93- w0 := *(*uintptr)(unsafe.Pointer(&buf[0]))92+ if l.kind == reflect.Float32 {
94- w1 := uintptr(0)93+ // Single precision occupies the register in double format.
95- if size > 8 {94+ addFloat(uintptr(math.Float64bits(float64(*(*float32)(src)))))
96- w1 = *(*uintptr)(unsafe.Pointer(&buf[8]))95+ } else {
96+ addFloat(uintptr(*(*uint64)(src)))
97+ }
98+ }
97 }99 }
98 100
99- if isFloats, _ := isAllSameFloat(v.Type()); isFloats {101+ // Every aggregate also occupies its size rounded up to doublewords in the
100- addFloat(w0)102+ // GPRs r3-r10 / parameter save area: shadow (skipped) for an HFA, the value
101- if size > 8 {103+ // otherwise.
102- addFloat(w1)104+ for off := uintptr(0); off < size; off += 8 {
103- }105+ if remaining := size - off; remaining >= 8 {
104- } else {106+ addInt(*(*uintptr)(unsafe.Add(ptr, off)))
105- addInt(w0)107+ } else {
106- if size > 8 {108+ var word [8]byte
107- addInt(w1)109+ copy(word[:], unsafe.Slice((*byte)(unsafe.Add(ptr, off)), int(remaining)))
110+ addInt(*(*uintptr)(unsafe.Pointer(&word[0])))
108 }111 }
109 }112 }
110 113
111 return keepAlive114 return keepAlive
112 }115 }
113 116
114-// placeStack is a fallback for structs that are too large to fit in registers117+func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value {
115-func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any {118+ if outType.Size() == 0 {
116- if v.CanAddr() {119+ return reflect.New(outType).Elem()
117- addInt(v.Addr().Pointer())120+ }
118- return keepAlive121+
122+ // An HFA returns each member in its own floating-point register.
123+ if leaves, isHFA := ppc64leClassifyHFA(outType); isHFA {
124+ floatRegs := [8]uintptr{
125+ syscall.f1, syscall.f2, syscall.f3, syscall.f4,
126+ syscall.f5, syscall.f6, syscall.f7, syscall.f8,
127+ }
128+ ret := reflect.New(outType)
129+ base := ret.UnsafePointer()
130+ for i, l := range leaves {
131+ dst := unsafe.Add(base, l.offset)
132+ if l.kind == reflect.Float32 {
133+ // The register holds the value in double format.
134+ *(*float32)(dst) = float32(math.Float64frombits(uint64(floatRegs[i])))
135+ } else {
136+ *(*uint64)(dst) = uint64(floatRegs[i])
137+ }
138+ }
139+ return ret.Elem()
140+ }
141+
142+ if outType.Size() > 16 {
143+ // Returned via a caller-allocated buffer whose pointer comes back in r3.
144+ return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
145+ }
146+
147+ // Otherwise returned in r3 and r4.
148+ var buf [16]byte
149+ *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.a1
150+ if outType.Size() > 8 {
151+ *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.a2
119 }152 }
120- ptr := reflect.New(v.Type())153+ return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem()
121- ptr.Elem().Set(v)
122- addInt(ptr.Pointer())
123- return append(keepAlive, ptr.Interface())
124 }154 }
125 155
156+// shouldBundleStackArgs always returns false on ppc64le
157+// since C-style stack argument bundling is only needed on Darwin ARM64.
126 func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {158 func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
127- // PPC64LE does not bundle stack args
128 return false159 return false
129 }160 }
130 161
131-func collectStackArgs(162+// collectStackArgs is not used on ppc64le.
132- args []reflect.Value,163+func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
133- i, numInts, numFloats int,164+ keepAlive []any, addInt, addFloat, addStack func(uintptr),
134- keepAlive []any,165+ pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
135- addInt, addFloat, addStack func(uintptr),166+ panic("purego: collectStackArgs should not be called on ppc64le")
136- numIntsPtr, numFloatsPtr, numStackPtr *int,
137-) ([]reflect.Value, []any) {
138- return nil, keepAlive
139 }167 }
140 168
169+// bundleStackArgs is not used on ppc64le.
141 func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {170 func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
142 panic("purego: bundleStackArgs should not be called on ppc64le")171 panic("purego: bundleStackArgs should not be called on ppc64le")
143 }172 }
M struct_riscv64.go +10 -3
@@ -8,7 +8,14 @@ import (
8 "unsafe"8 "unsafe"
9 )9 )
10 10
11-func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value {11+// structReturnInMemory reports whether a struct return value is returned through
12+// a caller-allocated hidden pointer passed as the first integer argument.
13+// Aggregates larger than two eightbytes are returned in memory.
14+func structReturnInMemory(outType reflect.Type) bool {
15+ return outType.Size() > maxRegAllocStructSize
16+}
17+
18+func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value {
12 outSize := outType.Size()19 outSize := outType.Size()
13 20
14 switch {21 switch {
@@ -27,10 +34,10 @@ func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value {
27 34
28 // Homogeneous float aggregates override integer regs35 // Homogeneous float aggregates override integer regs
29 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {36 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
30- if outType.Field(0).Type.Kind() == reflect.Float32 {37+ if abiField(outType, 0).Type.Kind() == reflect.Float32 {
31 // float32 values are NaN-boxed in FP regs; use low 32 bits only38 // float32 values are NaN-boxed in FP regs; use low 32 bits only
32 f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4}39 f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4}
33- for i := 0; i < numFields; i++ {40+ for i := range numFields {
34 *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i])41 *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i])
35 }42 }
36 } else {43 } else {
M struct_s390x.go +11 -4
@@ -8,7 +8,14 @@ import (
8 "unsafe"8 "unsafe"
9 )9 )
10 10
11-func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value {11+// structReturnInMemory reports whether a struct return value is returned through
12+// a caller-allocated hidden pointer passed as the first integer argument.
13+// Aggregates larger than two eightbytes are returned in memory.
14+func structReturnInMemory(outType reflect.Type) bool {
15+ return outType.Size() > maxRegAllocStructSize
16+}
17+
18+func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value {
12 outSize := outType.Size()19 outSize := outType.Size()
13 20
14 switch {21 switch {
@@ -27,11 +34,11 @@ func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value {
27 34
28 // Homogeneous float aggregates override integer regs35 // Homogeneous float aggregates override integer regs
29 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {36 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
30- if outType.Field(0).Type.Kind() == reflect.Float32 {37+ if abiField(outType, 0).Type.Kind() == reflect.Float32 {
31 // float32 values in FP regs38 // float32 values in FP regs
32 f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4}39 f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4}
33- for i := 0; i < numFields; i++ {40+ for i := range numFields {
34- *(*uint32)(unsafe.Pointer(&buf[i*4])) = highFloat32Bits(f[i])41+ *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i])
35 }42 }
36 } else {43 } else {
37 // float64: whole register value is valid44 // float64: whole register value is valid
D struct_s390x_regression_test.go +0 -21
@@ -1,21 +0,0 @@
1-//go:build s390x && linux
2-
3-package purego
4-
5-import (
6- "math"
7- "reflect"
8- "testing"
9-)
10-
11-func TestS390xGetStructUsesHighFloat32Bits(t *testing.T) {
12- type pair struct{ X, Y float32 }
13- sys := syscall15Args{
14- f1: uintptr(uint64(math.Float32bits(1.5)) << 32),
15- f2: uintptr(uint64(math.Float32bits(2.5)) << 32),
16- }
17- got := getStruct(reflect.TypeOf(pair{}), sys).Interface().(pair)
18- if got.X != 1.5 || got.Y != 2.5 {
19- t.Fatalf("got %+v, want {1.5 2.5}", got)
20- }
21-}
M struct_test.go +411 -207
@@ -1,35 +1,44 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3 3
4-//go:build (darwin || linux) && (amd64 || arm64)4+//go:build (darwin || linux || windows) && (amd64 || arm64 || loong64 || ppc64le)
5 5
6 package purego_test6 package purego_test
7 7
8 import (8 import (
9+ "iter"
9 "math"10 "math"
10 "os"11 "os"
11 "path/filepath"12 "path/filepath"
12 "reflect"13 "reflect"
13 "runtime"14 "runtime"
15+ "slices"
16+ "structs"
14 "testing"17 "testing"
15 "unsafe"18 "unsafe"
16 19
17 "github.com/bnema/purego"20 "github.com/bnema/purego"
21+ "github.com/bnema/purego/internal/load"
18 )22 )
19 23
20 func TestRegisterFunc_structArgs(t *testing.T) {24 func TestRegisterFunc_structArgs(t *testing.T) {
21 libFileName := filepath.Join(t.TempDir(), "structtest.so")25 libFileName := filepath.Join(t.TempDir(), "structtest.so")
22 t.Logf("Build %v", libFileName)26 t.Logf("Build %v", libFileName)
23 27
24- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "structtest", "struct_test.c")); err != nil {28+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "structtest", "struct_test.c")); err != nil {
25 t.Fatal(err)29 t.Fatal(err)
26 }30 }
27 defer os.Remove(libFileName)31 defer os.Remove(libFileName)
28 32
29- lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)33+ lib, err := load.OpenLibrary(libFileName)
30 if err != nil {34 if err != nil {
31- t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)35+ t.Fatalf("OpenLibrary(%q) failed: %v", libFileName, err)
32 }36 }
37+ defer func() {
38+ if err := load.CloseLibrary(lib); err != nil {
39+ t.Fatalf("failed to close library: %v", err)
40+ }
41+ }()
33 42
34 const (43 const (
35 expectedUnsigned = 0xdeadbeef44 expectedUnsigned = 0xdeadbeef
@@ -41,8 +50,9 @@ func TestRegisterFunc_structArgs(t *testing.T) {
41 )50 )
42 51
43 implementations := []struct {52 implementations := []struct {
44- name string53+ name string
45- register func(fptr any, handle uintptr, name string, goFn any)54+ usesCallbacks bool
55+ register func(fptr any, handle uintptr, name string, goFn any)
46 }{56 }{
47 {57 {
48 name: "RegisterLibFunc",58 name: "RegisterLibFunc",
@@ -51,7 +61,8 @@ func TestRegisterFunc_structArgs(t *testing.T) {
51 },61 },
52 },62 },
53 {63 {
54- name: "GoCallbackFunc",64+ name: "GoCallbackFunc",
65+ usesCallbacks: true,
55 register: func(fptr any, handle uintptr, name string, goFn any) {66 register: func(fptr any, handle uintptr, name string, goFn any) {
56 fnType := reflect.TypeOf(fptr).Elem()67 fnType := reflect.TypeOf(fptr).Elem()
57 if fnType.NumOut() > 0 {68 if fnType.NumOut() > 0 {
@@ -68,11 +79,20 @@ func TestRegisterFunc_structArgs(t *testing.T) {
68 },79 },
69 }80 }
70 for _, imp := range implementations {81 for _, imp := range implementations {
71- imp := imp82+ if imp.usesCallbacks && runtime.GOOS == "windows" {
83+ // Callbacks on Windows use the stdlib syscall.NewCallback, which does
84+ // not support struct arguments or returns.
85+ continue
86+ }
87+ if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
88+ continue
89+ }
72 t.Run(imp.name, func(t *testing.T) {90 t.Run(imp.name, func(t *testing.T) {
73 register := imp.register91 register := imp.register
74 {92 {
75- type Empty struct{}93+ type Empty struct {
94+ _ structs.HostLayout
95+ }
76 var NoStruct func(Empty) int6496 var NoStruct func(Empty) int64
77 register(&NoStruct, lib, "NoStruct", func(Empty) int64 {97 register(&NoStruct, lib, "NoStruct", func(Empty) int64 {
78 return 0xdeadbeef98 return 0xdeadbeef
@@ -82,7 +102,9 @@ func TestRegisterFunc_structArgs(t *testing.T) {
82 }102 }
83 }103 }
84 {104 {
85- type EmptyEmpty struct{}105+ type EmptyEmpty struct {
106+ _ structs.HostLayout
107+ }
86 var EmptyEmptyFn func(EmptyEmpty) int64108 var EmptyEmptyFn func(EmptyEmpty) int64
87 register(&EmptyEmptyFn, lib, "EmptyEmpty", func(EmptyEmpty) int64 {109 register(&EmptyEmptyFn, lib, "EmptyEmpty", func(EmptyEmpty) int64 {
88 return 0xdeadbeef110 return 0xdeadbeef
@@ -100,6 +122,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
100 }122 }
101 {123 {
102 type GreaterThan16Bytes struct {124 type GreaterThan16Bytes struct {
125+ _ structs.HostLayout
103 x, y, z *int64126 x, y, z *int64
104 }127 }
105 var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000128 var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000
@@ -113,7 +136,9 @@ func TestRegisterFunc_structArgs(t *testing.T) {
113 }136 }
114 {137 {
115 type GreaterThan16BytesStruct struct {138 type GreaterThan16BytesStruct struct {
139+ _ structs.HostLayout
116 a struct {140 a struct {
141+ _ structs.HostLayout
117 x, y, z *int64142 x, y, z *int64
118 }143 }
119 }144 }
@@ -122,12 +147,16 @@ func TestRegisterFunc_structArgs(t *testing.T) {
122 register(&GreaterThan16BytesStructFn, lib, "GreaterThan16BytesStruct", func(g GreaterThan16BytesStruct) int64 {147 register(&GreaterThan16BytesStructFn, lib, "GreaterThan16BytesStruct", func(g GreaterThan16BytesStruct) int64 {
123 return *g.a.x + *g.a.y + *g.a.z148 return *g.a.x + *g.a.y + *g.a.z
124 })149 })
125- if ret := GreaterThan16BytesStructFn(GreaterThan16BytesStruct{a: struct{ x, y, z *int64 }{x: &x, y: &y, z: &z}}); ret != expectedUnsigned {150+ if ret := GreaterThan16BytesStructFn(GreaterThan16BytesStruct{a: struct {
151+ _ structs.HostLayout
152+ x, y, z *int64
153+ }{x: &x, y: &y, z: &z}}); ret != expectedUnsigned {
126 t.Fatalf("GreaterThan16BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned)154 t.Fatalf("GreaterThan16BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned)
127 }155 }
128 }156 }
129 {157 {
130 type GreaterThan16Bytes struct {158 type GreaterThan16Bytes struct {
159+ _ structs.HostLayout
131 x, y, z *int64160 x, y, z *int64
132 }161 }
133 var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000162 var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000
@@ -151,48 +180,52 @@ func TestRegisterFunc_structArgs(t *testing.T) {
151 register(&BeforeRegisters, lib, "BeforeRegisters", func(bytes GreaterThan16Bytes, a, b int64) uint64 {180 register(&BeforeRegisters, lib, "BeforeRegisters", func(bytes GreaterThan16Bytes, a, b int64) uint64 {
152 return uint64(*bytes.x + *bytes.y + *bytes.z + a + b)181 return uint64(*bytes.x + *bytes.y + *bytes.z + a + b)
153 })182 })
154- if ret := BeforeRegisters(GreaterThan16Bytes{&x, &y, &z}, 0x0F, 0xF0); ret != expectedUnsigned {183+ if ret := BeforeRegisters(GreaterThan16Bytes{x: &x, y: &y, z: &z}, 0x0F, 0xF0); ret != expectedUnsigned {
155 t.Fatalf("BeforeRegisters returned %#x wanted %#x", ret, expectedUnsigned)184 t.Fatalf("BeforeRegisters returned %#x wanted %#x", ret, expectedUnsigned)
156 }185 }
157 }186 }
158 {187 {
159 type IntLessThan16Bytes struct {188 type IntLessThan16Bytes struct {
189+ _ structs.HostLayout
160 x, y int64190 x, y int64
161 }191 }
162 var IntLessThan16BytesFn func(bytes IntLessThan16Bytes) int64192 var IntLessThan16BytesFn func(bytes IntLessThan16Bytes) int64
163 register(&IntLessThan16BytesFn, lib, "IntLessThan16Bytes", func(l IntLessThan16Bytes) int64 {193 register(&IntLessThan16BytesFn, lib, "IntLessThan16Bytes", func(l IntLessThan16Bytes) int64 {
164 return l.x + l.y194 return l.x + l.y
165 })195 })
166- if ret := IntLessThan16BytesFn(IntLessThan16Bytes{0xDEAD0000, 0xBEEF}); ret != expectedUnsigned {196+ if ret := IntLessThan16BytesFn(IntLessThan16Bytes{x: 0xDEAD0000, y: 0xBEEF}); ret != expectedUnsigned {
167 t.Fatalf("IntLessThan16BytesFn returned %#x wanted %#x", ret, expectedUnsigned)197 t.Fatalf("IntLessThan16BytesFn returned %#x wanted %#x", ret, expectedUnsigned)
168 }198 }
169 }199 }
170 {200 {
171 type FloatLessThan16Bytes struct {201 type FloatLessThan16Bytes struct {
202+ _ structs.HostLayout
172 x, y float32203 x, y float32
173 }204 }
174 var FloatLessThan16BytesFn func(FloatLessThan16Bytes) float32205 var FloatLessThan16BytesFn func(FloatLessThan16Bytes) float32
175 register(&FloatLessThan16BytesFn, lib, "FloatLessThan16Bytes", func(f FloatLessThan16Bytes) float32 {206 register(&FloatLessThan16BytesFn, lib, "FloatLessThan16Bytes", func(f FloatLessThan16Bytes) float32 {
176 return f.x + f.y207 return f.x + f.y
177 })208 })
178- if ret := FloatLessThan16BytesFn(FloatLessThan16Bytes{3, 7}); ret != expectedFloat {209+ if ret := FloatLessThan16BytesFn(FloatLessThan16Bytes{x: 3, y: 7}); ret != expectedFloat {
179 t.Fatalf("FloatLessThan16Bytes returned %f wanted %f", ret, expectedFloat)210 t.Fatalf("FloatLessThan16Bytes returned %f wanted %f", ret, expectedFloat)
180 }211 }
181 }212 }
182 {213 {
183 type ThreeSmallFields struct {214 type ThreeSmallFields struct {
215+ _ structs.HostLayout
184 x, y, z float32216 x, y, z float32
185 }217 }
186 var ThreeSmallFieldsFn func(ThreeSmallFields) float32218 var ThreeSmallFieldsFn func(ThreeSmallFields) float32
187 register(&ThreeSmallFieldsFn, lib, "ThreeSmallFields", func(f ThreeSmallFields) float32 {219 register(&ThreeSmallFieldsFn, lib, "ThreeSmallFields", func(f ThreeSmallFields) float32 {
188 return f.x + f.y + f.z220 return f.x + f.y + f.z
189 })221 })
190- if ret := ThreeSmallFieldsFn(ThreeSmallFields{1, 2, 7}); ret != expectedFloat {222+ if ret := ThreeSmallFieldsFn(ThreeSmallFields{x: 1, y: 2, z: 7}); ret != expectedFloat {
191 t.Fatalf("ThreeSmallFields returned %f wanted %f", ret, expectedFloat)223 t.Fatalf("ThreeSmallFields returned %f wanted %f", ret, expectedFloat)
192 }224 }
193 }225 }
194 {226 {
195 type FloatAndInt struct {227 type FloatAndInt struct {
228+ _ structs.HostLayout
196 x float32229 x float32
197 y int32230 y int32
198 }231 }
@@ -200,37 +233,41 @@ func TestRegisterFunc_structArgs(t *testing.T) {
200 register(&FloatAndIntFn, lib, "FloatAndInt", func(f FloatAndInt) float32 {233 register(&FloatAndIntFn, lib, "FloatAndInt", func(f FloatAndInt) float32 {
201 return f.x + float32(f.y)234 return f.x + float32(f.y)
202 })235 })
203- if ret := FloatAndIntFn(FloatAndInt{3, 7}); ret != expectedFloat {236+ if ret := FloatAndIntFn(FloatAndInt{x: 3, y: 7}); ret != expectedFloat {
204 t.Fatalf("FloatAndIntFn returned %f wanted %f", ret, expectedFloat)237 t.Fatalf("FloatAndIntFn returned %f wanted %f", ret, expectedFloat)
205 }238 }
206 }239 }
207 {240 {
208 type DoubleStruct struct {241 type DoubleStruct struct {
242+ _ structs.HostLayout
209 x float64243 x float64
210 }244 }
211 var DoubleStructFn func(DoubleStruct) float64245 var DoubleStructFn func(DoubleStruct) float64
212 register(&DoubleStructFn, lib, "DoubleStruct", func(d DoubleStruct) float64 {246 register(&DoubleStructFn, lib, "DoubleStruct", func(d DoubleStruct) float64 {
213 return d.x247 return d.x
214 })248 })
215- if ret := DoubleStructFn(DoubleStruct{10}); ret != expectedDouble {249+ if ret := DoubleStructFn(DoubleStruct{x: 10}); ret != expectedDouble {
216 t.Fatalf("DoubleStruct returned %f wanted %f", ret, expectedDouble)250 t.Fatalf("DoubleStruct returned %f wanted %f", ret, expectedDouble)
217 }251 }
218 }252 }
219 {253 {
220 type TwoDoubleStruct struct {254 type TwoDoubleStruct struct {
255+ _ structs.HostLayout
221 x, y float64256 x, y float64
222 }257 }
223 var TwoDoubleStructFn func(TwoDoubleStruct) float64258 var TwoDoubleStructFn func(TwoDoubleStruct) float64
224 register(&TwoDoubleStructFn, lib, "TwoDoubleStruct", func(d TwoDoubleStruct) float64 {259 register(&TwoDoubleStructFn, lib, "TwoDoubleStruct", func(d TwoDoubleStruct) float64 {
225 return d.x + d.y260 return d.x + d.y
226 })261 })
227- if ret := TwoDoubleStructFn(TwoDoubleStruct{3, 7}); ret != expectedDouble {262+ if ret := TwoDoubleStructFn(TwoDoubleStruct{x: 3, y: 7}); ret != expectedDouble {
228 t.Fatalf("TwoDoubleStruct returned %f wanted %f", ret, expectedDouble)263 t.Fatalf("TwoDoubleStruct returned %f wanted %f", ret, expectedDouble)
229 }264 }
230 }265 }
231 {266 {
232 type TwoDoubleTwoStruct struct {267 type TwoDoubleTwoStruct struct {
268+ _ structs.HostLayout
233 x struct {269 x struct {
270+ _ structs.HostLayout
234 x, y float64271 x, y float64
235 }272 }
236 }273 }
@@ -238,69 +275,84 @@ func TestRegisterFunc_structArgs(t *testing.T) {
238 register(&TwoDoubleTwoStructFn, lib, "TwoDoubleTwoStruct", func(d TwoDoubleTwoStruct) float64 {275 register(&TwoDoubleTwoStructFn, lib, "TwoDoubleTwoStruct", func(d TwoDoubleTwoStruct) float64 {
239 return d.x.x + d.x.y276 return d.x.x + d.x.y
240 })277 })
241- if ret := TwoDoubleTwoStructFn(TwoDoubleTwoStruct{x: struct{ x, y float64 }{x: 3, y: 7}}); ret != expectedDouble {278+ if ret := TwoDoubleTwoStructFn(TwoDoubleTwoStruct{x: struct {
279+ _ structs.HostLayout
280+ x, y float64
281+ }{x: 3, y: 7}}); ret != expectedDouble {
242 t.Fatalf("TwoDoubleTwoStruct returned %f wanted %f", ret, expectedDouble)282 t.Fatalf("TwoDoubleTwoStruct returned %f wanted %f", ret, expectedDouble)
243 }283 }
244 }284 }
245 {285 {
246 type ThreeDoubleStruct struct {286 type ThreeDoubleStruct struct {
287+ _ structs.HostLayout
247 x, y, z float64288 x, y, z float64
248 }289 }
249 var ThreeDoubleStructFn func(ThreeDoubleStruct) float64290 var ThreeDoubleStructFn func(ThreeDoubleStruct) float64
250 register(&ThreeDoubleStructFn, lib, "ThreeDoubleStruct", func(d ThreeDoubleStruct) float64 {291 register(&ThreeDoubleStructFn, lib, "ThreeDoubleStruct", func(d ThreeDoubleStruct) float64 {
251 return d.x + d.y + d.z292 return d.x + d.y + d.z
252 })293 })
253- if ret := ThreeDoubleStructFn(ThreeDoubleStruct{1, 3, 6}); ret != expectedDouble {294+ if ret := ThreeDoubleStructFn(ThreeDoubleStruct{x: 1, y: 3, z: 6}); ret != expectedDouble {
254 t.Fatalf("ThreeDoubleStructFn returned %f wanted %f", ret, expectedDouble)295 t.Fatalf("ThreeDoubleStructFn returned %f wanted %f", ret, expectedDouble)
255 }296 }
256 }297 }
257 {298 {
258 type LargeFloatStruct struct {299 type LargeFloatStruct struct {
300+ _ structs.HostLayout
259 a, b, c, d, e, f float64301 a, b, c, d, e, f float64
260 }302 }
261 var LargeFloatStructFn func(LargeFloatStruct) float64303 var LargeFloatStructFn func(LargeFloatStruct) float64
262 register(&LargeFloatStructFn, lib, "LargeFloatStruct", func(s LargeFloatStruct) float64 {304 register(&LargeFloatStructFn, lib, "LargeFloatStruct", func(s LargeFloatStruct) float64 {
263 return s.a + s.b + s.c + s.d + s.e + s.f305 return s.a + s.b + s.c + s.d + s.e + s.f
264 })306 })
265- if ret := LargeFloatStructFn(LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble {307+ if ret := LargeFloatStructFn(LargeFloatStruct{a: 1, b: 2, c: 3, d: 4, e: 5, f: -5}); ret != expectedDouble {
266 t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat)308 t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat)
267 }309 }
268- var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64310+ if runtime.GOARCH != "ppc64le" {
269- register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 {311+ // Needs 9 float argument registers (3 doubles + a 6-double HFA);
270- return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f312+ // ppc64le wires 8.
271- })313+ var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64
272- if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble {314+ register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 {
273- t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat)315+ return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f
316+ })
317+ if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{a: 1, b: 2, c: 3, d: 4, e: 5, f: -5}); ret != expectedDouble {
318+ t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat)
319+ }
274 }320 }
275 }321 }
276 {322 {
277 type Rect struct {323 type Rect struct {
324+ _ structs.HostLayout
278 x, y, w, h float64325 x, y, w, h float64
279 }326 }
280- var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64327+ if runtime.GOARCH != "ppc64le" {
281- register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 {328+ // Needs 9 float argument registers (5 doubles + a 4-double HFA);
282- return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h329+ // ppc64le wires 8.
283- })330+ var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64
284- if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{1, 2, 3, -4}); ret != expectedDouble {331+ register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 {
285- t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble)332+ return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h
333+ })
334+ if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{x: 1, y: 2, w: 3, h: -4}); ret != expectedDouble {
335+ t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble)
336+ }
286 }337 }
287 var RectangleSubtract func(rect Rect) float64338 var RectangleSubtract func(rect Rect) float64
288 register(&RectangleSubtract, lib, "RectangleSubtract", func(rect Rect) float64 {339 register(&RectangleSubtract, lib, "RectangleSubtract", func(rect Rect) float64 {
289 return (rect.x + rect.y) - (rect.w + rect.h)340 return (rect.x + rect.y) - (rect.w + rect.h)
290 })341 })
291- if ret := RectangleSubtract(Rect{15, 5, 3, 7}); ret != expectedDouble {342+ if ret := RectangleSubtract(Rect{x: 15, y: 5, w: 3, h: 7}); ret != expectedDouble {
292 t.Fatalf("RectangleSubtract returned %f wanted %f", ret, expectedDouble)343 t.Fatalf("RectangleSubtract returned %f wanted %f", ret, expectedDouble)
293 }344 }
294 var Rectangle func(rect Rect) float64345 var Rectangle func(rect Rect) float64
295 register(&Rectangle, lib, "Rectangle", func(rect Rect) float64 {346 register(&Rectangle, lib, "Rectangle", func(rect Rect) float64 {
296 return rect.x + rect.y + rect.w + rect.h347 return rect.x + rect.y + rect.w + rect.h
297 })348 })
298- if ret := Rectangle(Rect{1, 2, 3, 4}); ret != expectedDouble {349+ if ret := Rectangle(Rect{x: 1, y: 2, w: 3, h: 4}); ret != expectedDouble {
299 t.Fatalf("Rectangle returned %f wanted %f", ret, expectedFloat)350 t.Fatalf("Rectangle returned %f wanted %f", ret, expectedFloat)
300 }351 }
301 }352 }
302 {353 {
303 type FloatArray struct {354 type FloatArray struct {
355+ _ structs.HostLayout
304 a [2]float64356 a [2]float64
305 }357 }
306 var FloatArrayFn func(rect FloatArray) float64358 var FloatArrayFn func(rect FloatArray) float64
@@ -313,6 +365,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
313 }365 }
314 {366 {
315 type UnsignedChar4Bytes struct {367 type UnsignedChar4Bytes struct {
368+ _ structs.HostLayout
316 a, b, c, d byte369 a, b, c, d byte
317 }370 }
318 var UnsignedChar4BytesFn func(UnsignedChar4Bytes) uint32371 var UnsignedChar4BytesFn func(UnsignedChar4Bytes) uint32
@@ -325,16 +378,21 @@ func TestRegisterFunc_structArgs(t *testing.T) {
325 }378 }
326 {379 {
327 type UnsignedChar4BytesStruct struct {380 type UnsignedChar4BytesStruct struct {
381+ _ structs.HostLayout
328 x struct {382 x struct {
383+ _ structs.HostLayout
329 a byte384 a byte
330 }385 }
331 y struct {386 y struct {
387+ _ structs.HostLayout
332 b byte388 b byte
333 }389 }
334 z struct {390 z struct {
391+ _ structs.HostLayout
335 c byte392 c byte
336 }393 }
337 w struct {394 w struct {
395+ _ structs.HostLayout
338 d byte396 d byte
339 }397 }
340 }398 }
@@ -343,16 +401,29 @@ func TestRegisterFunc_structArgs(t *testing.T) {
343 return uint32(b.x.a)<<24 | uint32(b.y.b)<<16 | uint32(b.z.c)<<8 | uint32(b.w.d)401 return uint32(b.x.a)<<24 | uint32(b.y.b)<<16 | uint32(b.z.c)<<8 | uint32(b.w.d)
344 })402 })
345 if ret := UnsignedChar4BytesStructFn(UnsignedChar4BytesStruct{403 if ret := UnsignedChar4BytesStructFn(UnsignedChar4BytesStruct{
346- x: struct{ a byte }{a: 0xDE},404+ x: struct {
347- y: struct{ b byte }{b: 0xAD},405+ _ structs.HostLayout
348- z: struct{ c byte }{c: 0xBE},406+ a byte
349- w: struct{ d byte }{d: 0xEF},407+ }{a: 0xDE},
408+ y: struct {
409+ _ structs.HostLayout
410+ b byte
411+ }{b: 0xAD},
412+ z: struct {
413+ _ structs.HostLayout
414+ c byte
415+ }{c: 0xBE},
416+ w: struct {
417+ _ structs.HostLayout
418+ d byte
419+ }{d: 0xEF},
350 }); ret != expectedUnsigned {420 }); ret != expectedUnsigned {
351 t.Fatalf("UnsignedChar4BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned)421 t.Fatalf("UnsignedChar4BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned)
352 }422 }
353 }423 }
354 {424 {
355 type Short struct {425 type Short struct {
426+ _ structs.HostLayout
356 a, b, c, d uint16427 a, b, c, d uint16
357 }428 }
358 var ShortFn func(Short) uint64429 var ShortFn func(Short) uint64
@@ -365,6 +436,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
365 }436 }
366 {437 {
367 type Int struct {438 type Int struct {
439+ _ structs.HostLayout
368 a, b uint32440 a, b uint32
369 }441 }
370 var IntFn func(Int) uint64442 var IntFn func(Int) uint64
@@ -377,6 +449,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
377 }449 }
378 {450 {
379 type Long struct {451 type Long struct {
452+ _ structs.HostLayout
380 a uint64453 a uint64
381 }454 }
382 var LongFn func(Long) uint64455 var LongFn func(Long) uint64
@@ -389,6 +462,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
389 }462 }
390 {463 {
391 type Char8Bytes struct {464 type Char8Bytes struct {
465+ _ structs.HostLayout
392 a, b, c, d, e, f, g, h int8466 a, b, c, d, e, f, g, h int8
393 }467 }
394 var Char8BytesFn func(Char8Bytes) int32468 var Char8BytesFn func(Char8Bytes) int32
@@ -401,6 +475,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
401 }475 }
402 {476 {
403 type Odd struct {477 type Odd struct {
478+ _ structs.HostLayout
404 a, b, c byte479 a, b, c byte
405 }480 }
406 var OddFn func(Odd) int32481 var OddFn func(Odd) int32
@@ -413,6 +488,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
413 }488 }
414 {489 {
415 type Char2Short1 struct {490 type Char2Short1 struct {
491+ _ structs.HostLayout
416 a, b byte492 a, b byte
417 c uint16493 c uint16
418 }494 }
@@ -426,6 +502,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
426 }502 }
427 {503 {
428 type SignedChar2Short1 struct {504 type SignedChar2Short1 struct {
505+ _ structs.HostLayout
429 a, b int8506 a, b int8
430 c int16507 c int16
431 }508 }
@@ -439,6 +516,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
439 }516 }
440 {517 {
441 type Array4UnsignedChars struct {518 type Array4UnsignedChars struct {
519+ _ structs.HostLayout
442 a [4]uint8520 a [4]uint8
443 }521 }
444 var Array4UnsignedCharsFn func(chars Array4UnsignedChars) uint32522 var Array4UnsignedCharsFn func(chars Array4UnsignedChars) uint32
@@ -451,6 +529,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
451 }529 }
452 {530 {
453 type Array3UnsignedChar struct {531 type Array3UnsignedChar struct {
532+ _ structs.HostLayout
454 a [3]uint8533 a [3]uint8
455 }534 }
456 var Array3UnsignedChars func(chars Array3UnsignedChar) uint32535 var Array3UnsignedChars func(chars Array3UnsignedChar) uint32
@@ -463,6 +542,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
463 }542 }
464 {543 {
465 type Array2UnsignedShort struct {544 type Array2UnsignedShort struct {
545+ _ structs.HostLayout
466 a [2]uint16546 a [2]uint16
467 }547 }
468 var Array2UnsignedShorts func(chars Array2UnsignedShort) uint32548 var Array2UnsignedShorts func(chars Array2UnsignedShort) uint32
@@ -475,6 +555,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
475 }555 }
476 {556 {
477 type Array4Chars struct {557 type Array4Chars struct {
558+ _ structs.HostLayout
478 a [4]int8559 a [4]int8
479 }560 }
480 var Array4CharsFn func(chars Array4Chars) int32561 var Array4CharsFn func(chars Array4Chars) int32
@@ -488,6 +569,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
488 }569 }
489 {570 {
490 type Array2Short struct {571 type Array2Short struct {
572+ _ structs.HostLayout
491 a [2]int16573 a [2]int16
492 }574 }
493 var Array2Shorts func(chars Array2Short) int32575 var Array2Shorts func(chars Array2Short) int32
@@ -500,6 +582,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
500 }582 }
501 {583 {
502 type Array3Short struct {584 type Array3Short struct {
585+ _ structs.HostLayout
503 a [3]int16586 a [3]int16
504 }587 }
505 var Array3Shorts func(chars Array3Short) int32588 var Array3Shorts func(chars Array3Short) int32
@@ -512,21 +595,23 @@ func TestRegisterFunc_structArgs(t *testing.T) {
512 }595 }
513 {596 {
514 type BoolStruct struct {597 type BoolStruct struct {
598+ _ structs.HostLayout
515 b bool599 b bool
516 }600 }
517 var BoolStructFn func(BoolStruct) bool601 var BoolStructFn func(BoolStruct) bool
518 register(&BoolStructFn, lib, "BoolStruct", func(b BoolStruct) bool {602 register(&BoolStructFn, lib, "BoolStruct", func(b BoolStruct) bool {
519 return b.b603 return b.b
520 })604 })
521- if ret := BoolStructFn(BoolStruct{true}); ret != true {605+ if ret := BoolStructFn(BoolStruct{b: true}); ret != true {
522 t.Fatalf("BoolStructFn returned %v wanted %v", ret, true)606 t.Fatalf("BoolStructFn returned %v wanted %v", ret, true)
523 }607 }
524- if ret := BoolStructFn(BoolStruct{false}); ret != false {608+ if ret := BoolStructFn(BoolStruct{b: false}); ret != false {
525 t.Fatalf("BoolStructFn returned %v wanted %v", ret, false)609 t.Fatalf("BoolStructFn returned %v wanted %v", ret, false)
526 }610 }
527 }611 }
528 {612 {
529 type BoolFloat struct {613 type BoolFloat struct {
614+ _ structs.HostLayout
530 b bool615 b bool
531 _ [3]byte // purego won't do padding for you so make sure it aligns properly with C struct616 _ [3]byte // purego won't do padding for you so make sure it aligns properly with C struct
532 f float32617 f float32
@@ -546,9 +631,16 @@ func TestRegisterFunc_structArgs(t *testing.T) {
546 }631 }
547 }632 }
548 {633 {
549- type point struct{ x, y float64 }634+ type point struct {
550- type size struct{ width, height float64 }635+ _ structs.HostLayout
636+ x, y float64
637+ }
638+ type size struct {
639+ _ structs.HostLayout
640+ width, height float64
641+ }
551 type Content struct {642 type Content struct {
643+ _ structs.HostLayout
552 point point644 point point
553 size size645 size size
554 }646 }
@@ -564,13 +656,14 @@ func TestRegisterFunc_structArgs(t *testing.T) {
564 })656 })
565 if ret := InitWithContentRect(new(int),657 if ret := InitWithContentRect(new(int),
566 // These numbers are created so that when added together and then divided by 11 it produces 0xdeadbeef658 // These numbers are created so that when added together and then divided by 11 it produces 0xdeadbeef
567- Content{point{x: 41_000_000_000, y: 95_000_000}, size{width: 214_000, height: 149}},659+ Content{point: point{x: 41_000_000_000, y: 95_000_000}, size: size{width: 214_000, height: 149}},
568 15, 4, true); ret != expectedUnsigned {660 15, 4, true); ret != expectedUnsigned {
569 t.Fatalf("InitWithContentRect returned %d wanted %#x", ret, expectedUnsigned)661 t.Fatalf("InitWithContentRect returned %d wanted %#x", ret, expectedUnsigned)
570 }662 }
571 }663 }
572 {664 {
573 type GoInt4 struct {665 type GoInt4 struct {
666+ _ structs.HostLayout
574 A, B, C, D int667 A, B, C, D int
575 }668 }
576 var GoInt4Fn func(GoInt4) int669 var GoInt4Fn func(GoInt4) int
@@ -578,12 +671,13 @@ func TestRegisterFunc_structArgs(t *testing.T) {
578 return g.A + g.B - g.C + g.D671 return g.A + g.B - g.C + g.D
579 })672 })
580 const expected = math.MaxInt - 52 - 3 + 4673 const expected = math.MaxInt - 52 - 3 + 4
581- if ret := GoInt4Fn(GoInt4{math.MaxInt, -52, 3, 4}); ret != expected {674+ if ret := GoInt4Fn(GoInt4{A: math.MaxInt, B: -52, C: 3, D: 4}); ret != expected {
582 t.Fatalf("GoInt4Fn returned %d wanted %#x", ret, expected)675 t.Fatalf("GoInt4Fn returned %d wanted %#x", ret, expected)
583 }676 }
584 }677 }
585 {678 {
586 type GoUint4 struct {679 type GoUint4 struct {
680+ _ structs.HostLayout
587 A, B, C, D uint681 A, B, C, D uint
588 }682 }
589 var GoUint4Fn func(GoUint4) uint683 var GoUint4Fn func(GoUint4) uint
@@ -591,23 +685,27 @@ func TestRegisterFunc_structArgs(t *testing.T) {
591 return g.A + g.B + g.C + g.D685 return g.A + g.B + g.C + g.D
592 })686 })
593 const expected = 1_000_000 + 53 + 71 + 8687 const expected = 1_000_000 + 53 + 71 + 8
594- if ret := GoUint4Fn(GoUint4{1_000_000, 53, 71, 8}); ret != expected {688+ if ret := GoUint4Fn(GoUint4{A: 1_000_000, B: 53, C: 71, D: 8}); ret != expected {
595 t.Fatalf("GoUint4Fn returned %d wanted %#x", ret, expected)689 t.Fatalf("GoUint4Fn returned %d wanted %#x", ret, expected)
596 }690 }
597 }691 }
598 {692 {
599- type OneLong struct{ a uintptr }693+ type OneLong struct {
694+ _ structs.HostLayout
695+ a uintptr
696+ }
600 var TakeGoUintAndReturn func(a OneLong) uint64697 var TakeGoUintAndReturn func(a OneLong) uint64
601 register(&TakeGoUintAndReturn, lib, "TakeGoUintAndReturn", func(a OneLong) uint64 {698 register(&TakeGoUintAndReturn, lib, "TakeGoUintAndReturn", func(a OneLong) uint64 {
602 return uint64(a.a)699 return uint64(a.a)
603 })700 })
604 expected := uint64(7)701 expected := uint64(7)
605- if ret := TakeGoUintAndReturn(OneLong{7}); ret != expected {702+ if ret := TakeGoUintAndReturn(OneLong{a: 7}); ret != expected {
606 t.Fatalf("TakeGoUintAndReturn returned %+v wanted %+v", ret, expected)703 t.Fatalf("TakeGoUintAndReturn returned %+v wanted %+v", ret, expected)
607 }704 }
608 }705 }
609 {706 {
610 type FloatAndBool struct {707 type FloatAndBool struct {
708+ _ structs.HostLayout
611 x float32709 x float32
612 y bool710 y bool
613 }711 }
@@ -627,13 +725,14 @@ func TestRegisterFunc_structArgs(t *testing.T) {
627 }725 }
628 {726 {
629 type FourInt32s struct {727 type FourInt32s struct {
728+ _ structs.HostLayout
630 f0, f1, f2, f3 int32729 f0, f1, f2, f3 int32
631 }730 }
632 var FourInt32sFn func(FourInt32s) int32731 var FourInt32sFn func(FourInt32s) int32
633 register(&FourInt32sFn, lib, "FourInt32s", func(s FourInt32s) int32 {732 register(&FourInt32sFn, lib, "FourInt32s", func(s FourInt32s) int32 {
634 return s.f0 + s.f1 + s.f2 + s.f3733 return s.f0 + s.f1 + s.f2 + s.f3
635 })734 })
636- result := FourInt32sFn(FourInt32s{100, -127, 4, -100})735+ result := FourInt32sFn(FourInt32s{f0: 100, f1: -127, f2: 4, f3: -100})
637 const want = 100 - 127 + 4 - 100736 const want = 100 - 127 + 4 - 100
638 if result != want {737 if result != want {
639 t.Fatalf("FourInt32s returned %d wanted %d", result, want)738 t.Fatalf("FourInt32s returned %d wanted %d", result, want)
@@ -641,6 +740,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
641 }740 }
642 {741 {
643 type PointerWrapper struct {742 type PointerWrapper struct {
743+ _ structs.HostLayout
644 ctx unsafe.Pointer744 ctx unsafe.Pointer
645 }745 }
646 var ExtractPointer func(wrapper PointerWrapper) uintptr746 var ExtractPointer func(wrapper PointerWrapper) uintptr
@@ -658,6 +758,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
658 }758 }
659 {759 {
660 type TwoPointers struct {760 type TwoPointers struct {
761+ _ structs.HostLayout
661 ptr1, ptr2 unsafe.Pointer762 ptr1, ptr2 unsafe.Pointer
662 }763 }
663 var AddPointers func(wrapper TwoPointers) uintptr764 var AddPointers func(wrapper TwoPointers) uintptr
@@ -669,7 +770,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
669 ptr1 := unsafe.Pointer(&v1)770 ptr1 := unsafe.Pointer(&v1)
670 ptr2 := unsafe.Pointer(&v2)771 ptr2 := unsafe.Pointer(&v2)
671 expected := uintptr(ptr1) + uintptr(ptr2)772 expected := uintptr(ptr1) + uintptr(ptr2)
672- result := AddPointers(TwoPointers{ptr1, ptr2})773+ result := AddPointers(TwoPointers{ptr1: ptr1, ptr2: ptr2})
673 if result != expected {774 if result != expected {
674 t.Fatalf("AddPointers returned %#x wanted %#x", result, expected)775 t.Fatalf("AddPointers returned %#x wanted %#x", result, expected)
675 }776 }
@@ -678,48 +779,60 @@ func TestRegisterFunc_structArgs(t *testing.T) {
678 // Identity struct arg tests779 // Identity struct arg tests
679 {780 {
680 // Single int64: one integer register.781 // Single int64: one integer register.
681- type OneInt64 struct{ A int64 }782+ type OneInt64 struct {
783+ _ structs.HostLayout
784+ A int64
785+ }
682 var fn func(OneInt64) OneInt64786 var fn func(OneInt64) OneInt64
683 register(&fn, lib, "IdentityOneInt64", func(s OneInt64) OneInt64 {787 register(&fn, lib, "IdentityOneInt64", func(s OneInt64) OneInt64 {
684 return s788 return s
685 })789 })
686- expected := OneInt64{0xDEADBEEF}790+ expected := OneInt64{A: 0xDEADBEEF}
687 if ret := fn(expected); ret != expected {791 if ret := fn(expected); ret != expected {
688 t.Fatalf("IdentityOneInt64 returned %+v wanted %+v", ret, expected)792 t.Fatalf("IdentityOneInt64 returned %+v wanted %+v", ret, expected)
689 }793 }
690 }794 }
691 {795 {
692 // Two int64 fields: two integer registers.796 // Two int64 fields: two integer registers.
693- type IntLessThan16Bytes struct{ X, Y int64 }797+ type IntLessThan16Bytes struct {
798+ _ structs.HostLayout
799+ X, Y int64
800+ }
694 var fn func(IntLessThan16Bytes) IntLessThan16Bytes801 var fn func(IntLessThan16Bytes) IntLessThan16Bytes
695 register(&fn, lib, "IdentityIntLessThan16Bytes", func(s IntLessThan16Bytes) IntLessThan16Bytes {802 register(&fn, lib, "IdentityIntLessThan16Bytes", func(s IntLessThan16Bytes) IntLessThan16Bytes {
696 return s803 return s
697 })804 })
698- expected := IntLessThan16Bytes{0xDEADBEEF, -42}805+ expected := IntLessThan16Bytes{X: 0xDEADBEEF, Y: -42}
699 if ret := fn(expected); ret != expected {806 if ret := fn(expected); ret != expected {
700 t.Fatalf("IdentityIntLessThan16Bytes returned %+v wanted %+v", ret, expected)807 t.Fatalf("IdentityIntLessThan16Bytes returned %+v wanted %+v", ret, expected)
701 }808 }
702 }809 }
703 {810 {
704 // Two float64 fields: SSE on amd64, HFA on arm64.811 // Two float64 fields: SSE on amd64, HFA on arm64.
705- type TwoDoubleStruct struct{ X, Y float64 }812+ type TwoDoubleStruct struct {
813+ _ structs.HostLayout
814+ X, Y float64
815+ }
706 var fn func(TwoDoubleStruct) TwoDoubleStruct816 var fn func(TwoDoubleStruct) TwoDoubleStruct
707 register(&fn, lib, "IdentityTwoDoubleStruct", func(s TwoDoubleStruct) TwoDoubleStruct {817 register(&fn, lib, "IdentityTwoDoubleStruct", func(s TwoDoubleStruct) TwoDoubleStruct {
708 return s818 return s
709 })819 })
710- expected := TwoDoubleStruct{3.14, -2.71}820+ expected := TwoDoubleStruct{X: 3.14, Y: -2.71}
711 if ret := fn(expected); ret != expected {821 if ret := fn(expected); ret != expected {
712 t.Fatalf("IdentityTwoDoubleStruct returned %+v wanted %+v", ret, expected)822 t.Fatalf("IdentityTwoDoubleStruct returned %+v wanted %+v", ret, expected)
713 }823 }
714 }824 }
715 {825 {
716 // Four float32 fields: HFA on arm64, SSE on amd64.826 // Four float32 fields: HFA on arm64, SSE on amd64.
717- type FourFloat32 struct{ A, B, C, D float32 }827+ type FourFloat32 struct {
828+ _ structs.HostLayout
829+ A, B, C, D float32
830+ }
718 var fn func(FourFloat32) FourFloat32831 var fn func(FourFloat32) FourFloat32
719 register(&fn, lib, "IdentityFourFloat32", func(s FourFloat32) FourFloat32 {832 register(&fn, lib, "IdentityFourFloat32", func(s FourFloat32) FourFloat32 {
720 return s833 return s
721 })834 })
722- expected := FourFloat32{1.0, 2.0, 3.0, 4.0}835+ expected := FourFloat32{A: 1.0, B: 2.0, C: 3.0, D: 4.0}
723 if ret := fn(expected); ret != expected {836 if ret := fn(expected); ret != expected {
724 t.Fatalf("IdentityFourFloat32 returned %+v wanted %+v", ret, expected)837 t.Fatalf("IdentityFourFloat32 returned %+v wanted %+v", ret, expected)
725 }838 }
@@ -727,6 +840,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
727 {840 {
728 // Mixed float32 + int32: INTEGER class eightbyte on amd64, non-HFA int regs on arm64.841 // Mixed float32 + int32: INTEGER class eightbyte on amd64, non-HFA int regs on arm64.
729 type FloatAndInt struct {842 type FloatAndInt struct {
843+ _ structs.HostLayout
730 X float32844 X float32
731 Y int32845 Y int32
732 }846 }
@@ -734,19 +848,22 @@ func TestRegisterFunc_structArgs(t *testing.T) {
734 register(&fn, lib, "IdentityFloatAndInt", func(s FloatAndInt) FloatAndInt {848 register(&fn, lib, "IdentityFloatAndInt", func(s FloatAndInt) FloatAndInt {
735 return s849 return s
736 })850 })
737- expected := FloatAndInt{7.5, 42}851+ expected := FloatAndInt{X: 7.5, Y: 42}
738 if ret := fn(expected); ret != expected {852 if ret := fn(expected); ret != expected {
739 t.Fatalf("IdentityFloatAndInt returned %+v wanted %+v", ret, expected)853 t.Fatalf("IdentityFloatAndInt returned %+v wanted %+v", ret, expected)
740 }854 }
741 }855 }
742 {856 {
743 // Struct > 16 bytes: hidden pointer on amd64, pointer in int register on arm64.857 // Struct > 16 bytes: hidden pointer on amd64, pointer in int register on arm64.
744- type ThreeInt64 struct{ A, B, C int64 }858+ type ThreeInt64 struct {
859+ _ structs.HostLayout
860+ A, B, C int64
861+ }
745 var fn func(ThreeInt64) ThreeInt64862 var fn func(ThreeInt64) ThreeInt64
746 register(&fn, lib, "IdentityThreeInt64", func(s ThreeInt64) ThreeInt64 {863 register(&fn, lib, "IdentityThreeInt64", func(s ThreeInt64) ThreeInt64 {
747 return s864 return s
748 })865 })
749- expected := ThreeInt64{1, 2, 3}866+ expected := ThreeInt64{A: 1, B: 2, C: 3}
750 if ret := fn(expected); ret != expected {867 if ret := fn(expected); ret != expected {
751 t.Fatalf("IdentityThreeInt64 returned %+v wanted %+v", ret, expected)868 t.Fatalf("IdentityThreeInt64 returned %+v wanted %+v", ret, expected)
752 }869 }
@@ -754,6 +871,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
754 {871 {
755 // Struct > 16 bytes with pointer fields.872 // Struct > 16 bytes with pointer fields.
756 type PtrInt64Ptr struct {873 type PtrInt64Ptr struct {
874+ _ structs.HostLayout
757 A *int64875 A *int64
758 B int64876 B int64
759 C *int64877 C *int64
@@ -764,7 +882,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
764 register(&fn, lib, "IdentityPtrInt64Ptr", func(s PtrInt64Ptr) PtrInt64Ptr {882 register(&fn, lib, "IdentityPtrInt64Ptr", func(s PtrInt64Ptr) PtrInt64Ptr {
765 return s883 return s
766 })884 })
767- expected := PtrInt64Ptr{a, 200, c}885+ expected := PtrInt64Ptr{A: a, B: 200, C: c}
768 ret := fn(expected)886 ret := fn(expected)
769 if *ret.A != *expected.A || ret.B != expected.B || *ret.C != *expected.C {887 if *ret.A != *expected.A || ret.B != expected.B || *ret.C != *expected.C {
770 t.Fatalf("IdentityPtrInt64Ptr returned %+v wanted %+v", ret, expected)888 t.Fatalf("IdentityPtrInt64Ptr returned %+v wanted %+v", ret, expected)
@@ -772,26 +890,34 @@ func TestRegisterFunc_structArgs(t *testing.T) {
772 runtime.KeepAlive(a)890 runtime.KeepAlive(a)
773 runtime.KeepAlive(c)891 runtime.KeepAlive(c)
774 }892 }
775- {893+ if runtime.GOARCH != "ppc64le" {
776 // Struct arg after some primitive args: register offset correctness.894 // Struct arg after some primitive args: register offset correctness.
777- type IntLessThan16Bytes struct{ X, Y int64 }895+ // ppc64le is skipped: a float argument shadows a GPR, which purego
896+ // does not model for scalars, so the trailing struct is misplaced.
897+ type IntLessThan16Bytes struct {
898+ _ structs.HostLayout
899+ X, Y int64
900+ }
778 var fn func(int64, float64, IntLessThan16Bytes) IntLessThan16Bytes901 var fn func(int64, float64, IntLessThan16Bytes) IntLessThan16Bytes
779 register(&fn, lib, "IdentityTwoInt64AfterPrims", func(x int64, y float64, s IntLessThan16Bytes) IntLessThan16Bytes {902 register(&fn, lib, "IdentityTwoInt64AfterPrims", func(x int64, y float64, s IntLessThan16Bytes) IntLessThan16Bytes {
780 return s903 return s
781 })904 })
782- expected := IntLessThan16Bytes{100, 200}905+ expected := IntLessThan16Bytes{X: 100, Y: 200}
783 if ret := fn(1, 2.0, expected); ret != expected {906 if ret := fn(1, 2.0, expected); ret != expected {
784 t.Fatalf("IdentityTwoInt64AfterPrims returned %+v wanted %+v", ret, expected)907 t.Fatalf("IdentityTwoInt64AfterPrims returned %+v wanted %+v", ret, expected)
785 }908 }
786 }909 }
787 {910 {
788 // Float struct arg after float primitive args: float register offset.911 // Float struct arg after float primitive args: float register offset.
789- type FloatLessThan16Bytes struct{ X, Y float32 }912+ type FloatLessThan16Bytes struct {
913+ _ structs.HostLayout
914+ X, Y float32
915+ }
790 var fn func(float64, float64, FloatLessThan16Bytes) FloatLessThan16Bytes916 var fn func(float64, float64, FloatLessThan16Bytes) FloatLessThan16Bytes
791 register(&fn, lib, "IdentityTwoFloat32AfterFloats", func(x, y float64, s FloatLessThan16Bytes) FloatLessThan16Bytes {917 register(&fn, lib, "IdentityTwoFloat32AfterFloats", func(x, y float64, s FloatLessThan16Bytes) FloatLessThan16Bytes {
792 return s918 return s
793 })919 })
794- expected := FloatLessThan16Bytes{5.0, 6.0}920+ expected := FloatLessThan16Bytes{X: 5.0, Y: 6.0}
795 if ret := fn(1.0, 2.0, expected); ret != expected {921 if ret := fn(1.0, 2.0, expected); ret != expected {
796 t.Fatalf("IdentityTwoFloat32AfterFloats returned %+v wanted %+v", ret, expected)922 t.Fatalf("IdentityTwoFloat32AfterFloats returned %+v wanted %+v", ret, expected)
797 }923 }
@@ -799,6 +925,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
799 {925 {
800 // Mixed struct with pointer: pointer + int32 + float32 + int32.926 // Mixed struct with pointer: pointer + int32 + float32 + int32.
801 type Mixed5Args struct {927 type Mixed5Args struct {
928+ _ structs.HostLayout
802 A *int64929 A *int64
803 B int32930 B int32
804 C float32931 C float32
@@ -810,7 +937,7 @@ func TestRegisterFunc_structArgs(t *testing.T) {
810 register(&fn, lib, "IdentityMixed5Args", func(s Mixed5Args) Mixed5Args {937 register(&fn, lib, "IdentityMixed5Args", func(s Mixed5Args) Mixed5Args {
811 return s938 return s
812 })939 })
813- expected := Mixed5Args{ptr, 1, 7.2, 9}940+ expected := Mixed5Args{A: ptr, B: 1, C: 7.2, D: 9}
814 ret := fn(expected)941 ret := fn(expected)
815 if ret.A != expected.A || ret.B != expected.B || ret.C != expected.C || ret.D != expected.D {942 if ret.A != expected.A || ret.B != expected.B || ret.C != expected.C || ret.D != expected.D {
816 t.Fatalf("IdentityMixed5Args returned %+v wanted %+v", ret, expected)943 t.Fatalf("IdentityMixed5Args returned %+v wanted %+v", ret, expected)
@@ -821,38 +948,23 @@ func TestRegisterFunc_structArgs(t *testing.T) {
821 }948 }
822 }949 }
823 950
824-// TODO: this could use the iter.Seq interface when purego supports Go 1.23951+func fields(v reflect.Value) iter.Seq[reflect.Value] {
825-func nextFieldFn(v reflect.Value) func() (reflect.Value, bool) {952+ return func(yield func(reflect.Value) bool) {
826- var fieldIndex int953+ for i := range v.NumField() {
827- var tracker func() (reflect.Value, bool)954+ if v.Type().Field(i).Name == "_" {
828- return func() (reflect.Value, bool) {
829- if v.NumField() == 0 {
830- return reflect.Value{}, false
831- }
832- if tracker != nil {
833- if field, ok := tracker(); ok {
834- return field, ok
835- }
836- tracker = nil
837- }
838- for fieldIndex < v.NumField() {
839- if v.Type().Field(fieldIndex).Name == "_" {
840- fieldIndex++
841 continue955 continue
842 }956 }
843- field := v.Field(fieldIndex)957+ field := v.Field(i)
844- fieldIndex++
845 if field.Kind() == reflect.Struct {958 if field.Kind() == reflect.Struct {
846- tracker = nextFieldFn(field)959+ for inner := range fields(field) {
847- if inner, ok := tracker(); ok {960+ if !yield(inner) {
848- return inner, ok961+ return
962+ }
849 }963 }
850- tracker = nil964+ } else if !yield(field) {
851- } else {965+ return
852- return field, true
853 }966 }
854 }967 }
855- return reflect.Value{}, false
856 }968 }
857 }969 }
858 970
@@ -860,36 +972,42 @@ func TestRegisterFunc_structReturns(t *testing.T) {
860 libFileName := filepath.Join(t.TempDir(), "structreturntest.so")972 libFileName := filepath.Join(t.TempDir(), "structreturntest.so")
861 t.Logf("Build %v", libFileName)973 t.Logf("Build %v", libFileName)
862 974
863- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "structtest", "structreturn_test.c")); err != nil {975+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "structtest", "structreturn_test.c")); err != nil {
864 t.Fatal(err)976 t.Fatal(err)
865 }977 }
866 defer os.Remove(libFileName)978 defer os.Remove(libFileName)
867 979
868- lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)980+ lib, err := load.OpenLibrary(libFileName)
869 if err != nil {981 if err != nil {
870- t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)982+ t.Fatalf("OpenLibrary(%q) failed: %v", libFileName, err)
871 }983 }
984+ defer func() {
985+ if err := load.CloseLibrary(lib); err != nil {
986+ t.Fatalf("failed to close library: %v", err)
987+ }
988+ }()
872 implementations := []struct {989 implementations := []struct {
873- name string990+ name string
874- register func(fptr any, handle uintptr, name string)991+ usesCallbacks bool
992+ register func(fptr any, handle uintptr, name string)
875 }{993 }{
876 {994 {
877 name: "RegisterLibFunc",995 name: "RegisterLibFunc",
878 register: purego.RegisterLibFunc,996 register: purego.RegisterLibFunc,
879 },997 },
880 {998 {
881- name: "GoCallbackFunc",999+ name: "GoCallbackFunc",
1000+ usesCallbacks: true,
882 register: func(fptr any, _ uintptr, _ string) {1001 register: func(fptr any, _ uintptr, _ string) {
883 fn := reflect.MakeFunc(reflect.TypeOf(fptr).Elem(), func(args []reflect.Value) []reflect.Value {1002 fn := reflect.MakeFunc(reflect.TypeOf(fptr).Elem(), func(args []reflect.Value) []reflect.Value {
884 retType := reflect.TypeOf(fptr).Elem().Out(0)1003 retType := reflect.TypeOf(fptr).Elem().Out(0)
885 ret := reflect.New(retType).Elem()1004 ret := reflect.New(retType).Elem()
886- next := nextFieldFn(ret)1005+ leaves := slices.Collect(fields(ret))
887- for _, a := range args {1006+ for i, a := range args {
888- field, ok := next()1007+ if i >= len(leaves) {
889- if !ok {
890 panic("purego: no more fields")1008 panic("purego: no more fields")
891 }1009 }
892- field.Set(a)1010+ leaves[i].Set(a)
893 }1011 }
894 return []reflect.Value{ret}1012 return []reflect.Value{ret}
895 })1013 })
@@ -898,170 +1016,239 @@ func TestRegisterFunc_structReturns(t *testing.T) {
898 },1016 },
899 }1017 }
900 for _, imp := range implementations {1018 for _, imp := range implementations {
901- imp := imp1019+ if imp.usesCallbacks && runtime.GOOS == "windows" {
1020+ // Callbacks on Windows use the stdlib syscall.NewCallback, which does
1021+ // not support struct arguments or returns.
1022+ continue
1023+ }
1024+ if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
1025+ continue
1026+ }
902 t.Run(imp.name, func(t *testing.T) {1027 t.Run(imp.name, func(t *testing.T) {
903 register := imp.register1028 register := imp.register
904 {1029 {
905- type Empty struct{}1030+ type Empty struct {
1031+ _ structs.HostLayout
1032+ }
906 var ReturnEmpty func() Empty1033 var ReturnEmpty func() Empty
907 register(&ReturnEmpty, lib, "ReturnEmpty")1034 register(&ReturnEmpty, lib, "ReturnEmpty")
908 ret := ReturnEmpty()1035 ret := ReturnEmpty()
909 _ = ret1036 _ = ret
910 }1037 }
911 {1038 {
912- type inner struct{ A int16 }1039+ type inner struct {
1040+ _ structs.HostLayout
1041+ A int16
1042+ }
913 type StructInStruct struct {1043 type StructInStruct struct {
1044+ _ structs.HostLayout
914 A inner1045 A inner
915 B inner1046 B inner
916 C inner1047 C inner
917 }1048 }
918 var ReturnStructInStruct func(a, b, c int16) StructInStruct1049 var ReturnStructInStruct func(a, b, c int16) StructInStruct
919 register(&ReturnStructInStruct, lib, "ReturnStructInStruct")1050 register(&ReturnStructInStruct, lib, "ReturnStructInStruct")
920- expected := StructInStruct{inner{^int16(0)}, inner{2}, inner{3}}1051+ expected := StructInStruct{A: inner{A: ^int16(0)}, B: inner{A: 2}, C: inner{A: 3}}
921 if ret := ReturnStructInStruct(^int16(0), 2, 3); ret != expected {1052 if ret := ReturnStructInStruct(^int16(0), 2, 3); ret != expected {
922 t.Fatalf("StructInStruct returned %+v wanted %+v", ret, expected)1053 t.Fatalf("StructInStruct returned %+v wanted %+v", ret, expected)
923 }1054 }
924 }1055 }
925 {1056 {
926- type ThreeShorts struct{ A, B, C int16 }1057+ type ThreeShorts struct {
1058+ _ structs.HostLayout
1059+ A, B, C int16
1060+ }
927 var ReturnThreeShorts func(a, b, c int16) ThreeShorts1061 var ReturnThreeShorts func(a, b, c int16) ThreeShorts
928 register(&ReturnThreeShorts, lib, "ReturnThreeShorts")1062 register(&ReturnThreeShorts, lib, "ReturnThreeShorts")
929- expected := ThreeShorts{^int16(0), 2, 3}1063+ expected := ThreeShorts{A: ^int16(0), B: 2, C: 3}
930 if ret := ReturnThreeShorts(^int16(0), 2, 3); ret != expected {1064 if ret := ReturnThreeShorts(^int16(0), 2, 3); ret != expected {
931 t.Fatalf("ReturnThreeShorts returned %+v wanted %+v", ret, expected)1065 t.Fatalf("ReturnThreeShorts returned %+v wanted %+v", ret, expected)
932 }1066 }
933 }1067 }
934 {1068 {
935- type FourShorts struct{ A, B, C, D int16 }1069+ type FourShorts struct {
1070+ _ structs.HostLayout
1071+ A, B, C, D int16
1072+ }
936 var ReturnFourShorts func(a, b, c, d int16) FourShorts1073 var ReturnFourShorts func(a, b, c, d int16) FourShorts
937 register(&ReturnFourShorts, lib, "ReturnFourShorts")1074 register(&ReturnFourShorts, lib, "ReturnFourShorts")
938- expected := FourShorts{^int16(0), 2, 3, 4}1075+ expected := FourShorts{A: ^int16(0), B: 2, C: 3, D: 4}
939 if ret := ReturnFourShorts(^int16(0), 2, 3, 4); ret != expected {1076 if ret := ReturnFourShorts(^int16(0), 2, 3, 4); ret != expected {
940 t.Fatalf("ReturnFourShorts returned %+v wanted %+v", ret, expected)1077 t.Fatalf("ReturnFourShorts returned %+v wanted %+v", ret, expected)
941 }1078 }
942 }1079 }
943 {1080 {
944- type OneLong struct{ A int64 }1081+ type OneLong struct {
1082+ _ structs.HostLayout
1083+ A int64
1084+ }
945 var ReturnOneLong func(a int64) OneLong1085 var ReturnOneLong func(a int64) OneLong
946 register(&ReturnOneLong, lib, "ReturnOneLong")1086 register(&ReturnOneLong, lib, "ReturnOneLong")
947- expected := OneLong{5}1087+ expected := OneLong{A: 5}
948 if ret := ReturnOneLong(5); ret != expected {1088 if ret := ReturnOneLong(5); ret != expected {
949 t.Fatalf("ReturnOneLong returned %+v wanted %+v", ret, expected)1089 t.Fatalf("ReturnOneLong returned %+v wanted %+v", ret, expected)
950 }1090 }
951 }1091 }
952 {1092 {
953- type TwoLongs struct{ A, B int64 }1093+ type TwoLongs struct {
1094+ _ structs.HostLayout
1095+ A, B int64
1096+ }
954 var ReturnTwoLongs func(a, b int64) TwoLongs1097 var ReturnTwoLongs func(a, b int64) TwoLongs
955 register(&ReturnTwoLongs, lib, "ReturnTwoLongs")1098 register(&ReturnTwoLongs, lib, "ReturnTwoLongs")
956- expected := TwoLongs{1, 2}1099+ expected := TwoLongs{A: 1, B: 2}
957 if ret := ReturnTwoLongs(1, 2); ret != expected {1100 if ret := ReturnTwoLongs(1, 2); ret != expected {
958 t.Fatalf("ReturnTwoLongs returned %+v wanted %+v", ret, expected)1101 t.Fatalf("ReturnTwoLongs returned %+v wanted %+v", ret, expected)
959 }1102 }
960 }1103 }
961 {1104 {
962- type ThreeLongs struct{ A, B, C int64 }1105+ type ThreeLongs struct {
1106+ _ structs.HostLayout
1107+ A, B, C int64
1108+ }
963 var ReturnThreeLongs func(a, b, c int64) ThreeLongs1109 var ReturnThreeLongs func(a, b, c int64) ThreeLongs
964 register(&ReturnThreeLongs, lib, "ReturnThreeLongs")1110 register(&ReturnThreeLongs, lib, "ReturnThreeLongs")
965- expected := ThreeLongs{1, 2, 3}1111+ expected := ThreeLongs{A: 1, B: 2, C: 3}
966 if ret := ReturnThreeLongs(1, 2, 3); ret != expected {1112 if ret := ReturnThreeLongs(1, 2, 3); ret != expected {
967 t.Fatalf("ReturnThreeLongs returned %+v wanted %+v", ret, expected)1113 t.Fatalf("ReturnThreeLongs returned %+v wanted %+v", ret, expected)
968 }1114 }
969 }1115 }
970 {1116 {
971- type OneFloat struct{ A float32 }1117+ type OneFloat struct {
1118+ _ structs.HostLayout
1119+ A float32
1120+ }
972 var ReturnOneFloat func(a float32) OneFloat1121 var ReturnOneFloat func(a float32) OneFloat
973 register(&ReturnOneFloat, lib, "ReturnOneFloat")1122 register(&ReturnOneFloat, lib, "ReturnOneFloat")
974- expected := OneFloat{1}1123+ expected := OneFloat{A: 1}
975 if ret := ReturnOneFloat(1); ret != expected {1124 if ret := ReturnOneFloat(1); ret != expected {
976 t.Fatalf("ReturnOneFloat returned %+v wanted %+v", ret, expected)1125 t.Fatalf("ReturnOneFloat returned %+v wanted %+v", ret, expected)
977 }1126 }
978 }1127 }
979 {1128 {
980- type TwoFloats struct{ A, B float32 }1129+ type TwoFloats struct {
1130+ _ structs.HostLayout
1131+ A, B float32
1132+ }
981 var ReturnTwoFloats func(a, b float32) TwoFloats1133 var ReturnTwoFloats func(a, b float32) TwoFloats
982 register(&ReturnTwoFloats, lib, "ReturnTwoFloats")1134 register(&ReturnTwoFloats, lib, "ReturnTwoFloats")
983- expected := TwoFloats{5, 2}1135+ expected := TwoFloats{A: 5, B: 2}
984 if ret := ReturnTwoFloats(5, 2); ret != expected {1136 if ret := ReturnTwoFloats(5, 2); ret != expected {
985 t.Fatalf("ReturnTwoFloats returned %+v wanted %+v", ret, expected)1137 t.Fatalf("ReturnTwoFloats returned %+v wanted %+v", ret, expected)
986 }1138 }
987 }1139 }
988 {1140 {
989- type ThreeFloats struct{ A, B, C float32 }1141+ type ThreeFloats struct {
1142+ _ structs.HostLayout
1143+ A, B, C float32
1144+ }
990 var ReturnThreeFloats func(a, b, c float32) ThreeFloats1145 var ReturnThreeFloats func(a, b, c float32) ThreeFloats
991 register(&ReturnThreeFloats, lib, "ReturnThreeFloats")1146 register(&ReturnThreeFloats, lib, "ReturnThreeFloats")
992- expected := ThreeFloats{1, 2, 3}1147+ expected := ThreeFloats{A: 1, B: 2, C: 3}
993 if ret := ReturnThreeFloats(1, 2, 3); ret != expected {1148 if ret := ReturnThreeFloats(1, 2, 3); ret != expected {
994 t.Fatalf("ReturnThreeFloats returned %+v wanted %+v", ret, expected)1149 t.Fatalf("ReturnThreeFloats returned %+v wanted %+v", ret, expected)
995 }1150 }
996 }1151 }
997 {1152 {
998- type FourFloats struct{ A, B, C, D float32 }1153+ type FourFloats struct {
1154+ _ structs.HostLayout
1155+ A, B, C, D float32
1156+ }
999 var ReturnFourFloats func(a, b, c, d float32) FourFloats1157 var ReturnFourFloats func(a, b, c, d float32) FourFloats
1000 register(&ReturnFourFloats, lib, "ReturnFourFloats")1158 register(&ReturnFourFloats, lib, "ReturnFourFloats")
1001- expected := FourFloats{1, 2, 3, 4}1159+ expected := FourFloats{A: 1, B: 2, C: 3, D: 4}
1002 if ret := ReturnFourFloats(1, 2, 3, 4); ret != expected {1160 if ret := ReturnFourFloats(1, 2, 3, 4); ret != expected {
1003 t.Fatalf("ReturnFourFloats returned %+v wanted %+v", ret, expected)1161 t.Fatalf("ReturnFourFloats returned %+v wanted %+v", ret, expected)
1004 }1162 }
1005 }1163 }
1006 {1164 {
1007- type OneDouble struct{ A float64 }1165+ type OneDouble struct {
1166+ _ structs.HostLayout
1167+ A float64
1168+ }
1008 var ReturnOneDouble func(a float64) OneDouble1169 var ReturnOneDouble func(a float64) OneDouble
1009 register(&ReturnOneDouble, lib, "ReturnOneDouble")1170 register(&ReturnOneDouble, lib, "ReturnOneDouble")
1010- expected := OneDouble{1}1171+ expected := OneDouble{A: 1}
1011 if ret := ReturnOneDouble(1); ret != expected {1172 if ret := ReturnOneDouble(1); ret != expected {
1012 t.Fatalf("ReturnOneDouble returned %+v wanted %+v", ret, expected)1173 t.Fatalf("ReturnOneDouble returned %+v wanted %+v", ret, expected)
1013 }1174 }
1014 }1175 }
1015 {1176 {
1016- type TwoDoubles struct{ A, B float64 }1177+ type TwoDoubles struct {
1178+ _ structs.HostLayout
1179+ A, B float64
1180+ }
1017 var ReturnTwoDoubles func(a, b float64) TwoDoubles1181 var ReturnTwoDoubles func(a, b float64) TwoDoubles
1018 register(&ReturnTwoDoubles, lib, "ReturnTwoDoubles")1182 register(&ReturnTwoDoubles, lib, "ReturnTwoDoubles")
1019- expected := TwoDoubles{7, 3}1183+ expected := TwoDoubles{A: 7, B: 3}
1020 if ret := ReturnTwoDoubles(7, 3); ret != expected {1184 if ret := ReturnTwoDoubles(7, 3); ret != expected {
1021 t.Fatalf("ReturnTwoDoubles returned %+v wanted %+v", ret, expected)1185 t.Fatalf("ReturnTwoDoubles returned %+v wanted %+v", ret, expected)
1022 }1186 }
1023 }1187 }
1024 {1188 {
1025- type ThreeDoubles struct{ A, B, C float64 }1189+ type ThreeDoubles struct {
1190+ _ structs.HostLayout
1191+ A, B, C float64
1192+ }
1026 var ReturnThreeDoubles func(a, b, c float64) ThreeDoubles1193 var ReturnThreeDoubles func(a, b, c float64) ThreeDoubles
1027 register(&ReturnThreeDoubles, lib, "ReturnThreeDoubles")1194 register(&ReturnThreeDoubles, lib, "ReturnThreeDoubles")
1028- expected := ThreeDoubles{1, 2, 3}1195+ expected := ThreeDoubles{A: 1, B: 2, C: 3}
1029 if ret := ReturnThreeDoubles(1, 2, 3); ret != expected {1196 if ret := ReturnThreeDoubles(1, 2, 3); ret != expected {
1030 t.Fatalf("ReturnThreeDoubles returned %+v wanted %+v", ret, expected)1197 t.Fatalf("ReturnThreeDoubles returned %+v wanted %+v", ret, expected)
1031 }1198 }
1032 }1199 }
1033 {1200 {
1034- type FourDoubles struct{ A, B, C, D float64 }1201+ type FourDoubles struct {
1202+ _ structs.HostLayout
1203+ A, B, C, D float64
1204+ }
1035 var ReturnFourDoubles func(a, b, c, d float64) FourDoubles1205 var ReturnFourDoubles func(a, b, c, d float64) FourDoubles
1036 register(&ReturnFourDoubles, lib, "ReturnFourDoubles")1206 register(&ReturnFourDoubles, lib, "ReturnFourDoubles")
1037- expected := FourDoubles{1, 2, 3, 4}1207+ expected := FourDoubles{A: 1, B: 2, C: 3, D: 4}
1038 if ret := ReturnFourDoubles(1, 2, 3, 4); ret != expected {1208 if ret := ReturnFourDoubles(1, 2, 3, 4); ret != expected {
1039 t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected)1209 t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected)
1040 }1210 }
1041 }1211 }
1042 {1212 {
1043 type FourDoublesInternal struct {1213 type FourDoublesInternal struct {
1044- F struct{ A, B float64 }1214+ _ structs.HostLayout
1045- G struct{ C, D float64 }1215+ F struct {
1216+ _ structs.HostLayout
1217+ A, B float64
1218+ }
1219+ G struct {
1220+ _ structs.HostLayout
1221+ C, D float64
1222+ }
1046 }1223 }
1047 var ReturnFourDoublesInternal func(a, b, c, d float64) FourDoublesInternal1224 var ReturnFourDoublesInternal func(a, b, c, d float64) FourDoublesInternal
1048 register(&ReturnFourDoublesInternal, lib, "ReturnFourDoublesInternal")1225 register(&ReturnFourDoublesInternal, lib, "ReturnFourDoublesInternal")
1049- expected := FourDoublesInternal{F: struct{ A, B float64 }{A: 1, B: 2}, G: struct{ C, D float64 }{C: 3, D: 4}}1226+ expected := FourDoublesInternal{F: struct {
1227+ _ structs.HostLayout
1228+ A, B float64
1229+ }{A: 1, B: 2}, G: struct {
1230+ _ structs.HostLayout
1231+ C, D float64
1232+ }{C: 3, D: 4}}
1050 if ret := ReturnFourDoublesInternal(1, 2, 3, 4); ret != expected {1233 if ret := ReturnFourDoublesInternal(1, 2, 3, 4); ret != expected {
1051 t.Fatalf("ReturnFourDoublesInternal returned %+v wanted %+v", ret, expected)1234 t.Fatalf("ReturnFourDoublesInternal returned %+v wanted %+v", ret, expected)
1052 }1235 }
1053 }1236 }
1054 {1237 {
1055- type FiveDoubles struct{ A, B, C, D, E float64 }1238+ type FiveDoubles struct {
1239+ _ structs.HostLayout
1240+ A, B, C, D, E float64
1241+ }
1056 var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles1242 var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles
1057 register(&ReturnFiveDoubles, lib, "ReturnFiveDoubles")1243 register(&ReturnFiveDoubles, lib, "ReturnFiveDoubles")
1058- expected := FiveDoubles{1, 2, 3, 4, 5}1244+ expected := FiveDoubles{A: 1, B: 2, C: 3, D: 4, E: 5}
1059 if ret := ReturnFiveDoubles(1, 2, 3, 4, 5); ret != expected {1245 if ret := ReturnFiveDoubles(1, 2, 3, 4, 5); ret != expected {
1060 t.Fatalf("ReturnFiveDoubles returned %+v wanted %+v", ret, expected)1246 t.Fatalf("ReturnFiveDoubles returned %+v wanted %+v", ret, expected)
1061 }1247 }
1062 }1248 }
1063 {1249 {
1064 type OneFloatOneDouble struct {1250 type OneFloatOneDouble struct {
1251+ _ structs.HostLayout
1065 A float321252 A float32
1066 _ float321253 _ float32
1067 B float641254 B float64
@@ -1075,18 +1262,20 @@ func TestRegisterFunc_structReturns(t *testing.T) {
1075 }1262 }
1076 {1263 {
1077 type OneDoubleOneFloat struct {1264 type OneDoubleOneFloat struct {
1265+ _ structs.HostLayout
1078 A float641266 A float64
1079 B float321267 B float32
1080 }1268 }
1081 var ReturnOneDoubleOneFloat func(a float64, b float32) OneDoubleOneFloat1269 var ReturnOneDoubleOneFloat func(a float64, b float32) OneDoubleOneFloat
1082 register(&ReturnOneDoubleOneFloat, lib, "ReturnOneDoubleOneFloat")1270 register(&ReturnOneDoubleOneFloat, lib, "ReturnOneDoubleOneFloat")
1083- expected := OneDoubleOneFloat{1, 2}1271+ expected := OneDoubleOneFloat{A: 1, B: 2}
1084 if ret := ReturnOneDoubleOneFloat(1, 2); ret != expected {1272 if ret := ReturnOneDoubleOneFloat(1, 2); ret != expected {
1085 t.Fatalf("ReturnOneDoubleOneFloat returned %+v wanted %+v", ret, expected)1273 t.Fatalf("ReturnOneDoubleOneFloat returned %+v wanted %+v", ret, expected)
1086 }1274 }
1087 }1275 }
1088 {1276 {
1089 type Unaligned1 struct {1277 type Unaligned1 struct {
1278+ _ structs.HostLayout
1090 A int81279 A int8
1091 _ [1]int81280 _ [1]int8
1092 B int161281 B int16
@@ -1101,76 +1290,88 @@ func TestRegisterFunc_structReturns(t *testing.T) {
1101 }1290 }
1102 }1291 }
1103 1292
1104- {1293+ if runtime.GOARCH != "ppc64le" {
1105- type Mixed1 struct {1294+ // ppc64le is skipped: a float argument shadows a GPR, which purego
1106- A float321295+ // does not model for scalars, so a trailing integer arg is misplaced.
1107- B int321296+ // TODO: Support mixed scalar floating-point and integer arguments on
1108- }1297+ // ppc64le by modeling the ELFv2 general-purpose register shadowing.
1109- var ReturnMixed1 func(a float32, b int32) Mixed11298+ {
1110- register(&ReturnMixed1, lib, "ReturnMixed1")1299+ type Mixed1 struct {
1111- expected := Mixed1{1, 2}1300+ _ structs.HostLayout
1112- if ret := ReturnMixed1(1, 2); ret != expected {1301+ A float32
1113- t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected)1302+ B int32
1114- }1303+ }
1115- }1304+ var ReturnMixed1 func(a float32, b int32) Mixed1
1116- {1305+ register(&ReturnMixed1, lib, "ReturnMixed1")
1117- type Mixed2 struct {1306+ expected := Mixed1{A: 1, B: 2}
1118- A float321307+ if ret := ReturnMixed1(1, 2); ret != expected {
1119- B int321308+ t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected)
1120- C float321309+ }
1121- D int32
1122- }
1123- var ReturnMixed2 func(a float32, b int32, c float32, d int32) Mixed2
1124- register(&ReturnMixed2, lib, "ReturnMixed2")
1125- expected := Mixed2{1, 2, 3, 4}
1126- if ret := ReturnMixed2(1, 2, 3, 4); ret != expected {
1127- t.Fatalf("ReturnMixed2 returned %+v wanted %+v", ret, expected)
1128- }
1129- }
1130- {
1131- type Mixed3 struct {
1132- A float32
1133- B uint32
1134- C float64
1135- }
1136- var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3
1137- register(&ReturnMixed3, lib, "ReturnMixed3")
1138- expected := Mixed3{1, 2, 3}
1139- if ret := ReturnMixed3(1, 2, 3); ret != expected {
1140- t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected)
1141 }1310 }
1142- }1311+ {
1143- {1312+ type Mixed2 struct {
1144- type Mixed4 struct {1313+ _ structs.HostLayout
1145- A float641314+ A float32
1146- B uint321315+ B int32
1147- C float321316+ C float32
1317+ D int32
1318+ }
1319+ var ReturnMixed2 func(a float32, b int32, c float32, d int32) Mixed2
1320+ register(&ReturnMixed2, lib, "ReturnMixed2")
1321+ expected := Mixed2{A: 1, B: 2, C: 3, D: 4}
1322+ if ret := ReturnMixed2(1, 2, 3, 4); ret != expected {
1323+ t.Fatalf("ReturnMixed2 returned %+v wanted %+v", ret, expected)
1324+ }
1148 }1325 }
1149- var ReturnMixed4 func(a float64, b uint32, c float32) Mixed41326+ {
1150- register(&ReturnMixed4, lib, "ReturnMixed4")1327+ type Mixed3 struct {
1151- expected := Mixed4{1, 2, 3}1328+ _ structs.HostLayout
1152- if ret := ReturnMixed4(1, 2, 3); ret != expected {1329+ A float32
1153- t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected)1330+ B uint32
1331+ C float64
1332+ }
1333+ var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3
1334+ register(&ReturnMixed3, lib, "ReturnMixed3")
1335+ expected := Mixed3{A: 1, B: 2, C: 3}
1336+ if ret := ReturnMixed3(1, 2, 3); ret != expected {
1337+ t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected)
1338+ }
1154 }1339 }
1155- }1340+ {
1156- {1341+ type Mixed4 struct {
1157- type Mixed5 struct {1342+ _ structs.HostLayout
1158- A *int641343+ A float64
1159- B int321344+ B uint32
1160- C float321345+ C float32
1161- D int321346+ }
1347+ var ReturnMixed4 func(a float64, b uint32, c float32) Mixed4
1348+ register(&ReturnMixed4, lib, "ReturnMixed4")
1349+ expected := Mixed4{A: 1, B: 2, C: 3}
1350+ if ret := ReturnMixed4(1, 2, 3); ret != expected {
1351+ t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected)
1352+ }
1162 }1353 }
1163- var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed51354+ {
1164- register(&ReturnMixed5, lib, "ReturnMixed5")1355+ type Mixed5 struct {
1165- ptr := new(int64)1356+ _ structs.HostLayout
1166- expected := Mixed5{ptr, 1, 7.2, 9}1357+ A *int64
1167- if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected {1358+ B int32
1168- t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected)1359+ C float32
1360+ D int32
1361+ }
1362+ var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5
1363+ register(&ReturnMixed5, lib, "ReturnMixed5")
1364+ ptr := new(int64)
1365+ expected := Mixed5{A: ptr, B: 1, C: 7.2, D: 9}
1366+ if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected {
1367+ t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected)
1368+ }
1369+ runtime.KeepAlive(ptr)
1169 }1370 }
1170- runtime.KeepAlive(ptr)
1171 }1371 }
1172 {1372 {
1173 type Mixed5 struct {1373 type Mixed5 struct {
1374+ _ structs.HostLayout
1174 A *int641375 A *int64
1175 B int321376 B int32
1176 C float321377 C float32
@@ -1182,7 +1383,7 @@ func TestRegisterFunc_structReturns(t *testing.T) {
1182 // Struct callback args are tested in TestRegisterFunc_structArgs.1383 // Struct callback args are tested in TestRegisterFunc_structArgs.
1183 purego.RegisterLibFunc(&IdentityMixed5, lib, "IdentityMixed5")1384 purego.RegisterLibFunc(&IdentityMixed5, lib, "IdentityMixed5")
1184 ptr := new(int64)1385 ptr := new(int64)
1185- expected := Mixed5{ptr, 1, 7.2, 9}1386+ expected := Mixed5{A: ptr, B: 1, C: 7.2, D: 9}
1186 if ret := IdentityMixed5(expected); ret != expected {1387 if ret := IdentityMixed5(expected); ret != expected {
1187 t.Fatalf("IdentityMixed5 returned %+v wanted %+v", ret, expected)1388 t.Fatalf("IdentityMixed5 returned %+v wanted %+v", ret, expected)
1188 }1389 }
@@ -1190,19 +1391,21 @@ func TestRegisterFunc_structReturns(t *testing.T) {
1190 }1391 }
1191 {1392 {
1192 type SmallBool struct {1393 type SmallBool struct {
1394+ _ structs.HostLayout
1193 A bool1395 A bool
1194 B int321396 B int32
1195 C int641397 C int64
1196 }1398 }
1197 var ReturnSmallBool func(a bool, b int32, c int64) SmallBool1399 var ReturnSmallBool func(a bool, b int32, c int64) SmallBool
1198 register(&ReturnSmallBool, lib, "ReturnSmallBool")1400 register(&ReturnSmallBool, lib, "ReturnSmallBool")
1199- expected := SmallBool{true, 42, 123456789}1401+ expected := SmallBool{A: true, B: 42, C: 123456789}
1200 if ret := ReturnSmallBool(true, 42, 123456789); ret != expected {1402 if ret := ReturnSmallBool(true, 42, 123456789); ret != expected {
1201 t.Fatalf("ReturnSmallBool returned %+v wanted %+v", ret, expected)1403 t.Fatalf("ReturnSmallBool returned %+v wanted %+v", ret, expected)
1202 }1404 }
1203 }1405 }
1204 {1406 {
1205 type LargeBool struct {1407 type LargeBool struct {
1408+ _ structs.HostLayout
1206 A bool1409 A bool
1207 B int321410 B int32
1208 C int641411 C int64
@@ -1210,20 +1413,21 @@ func TestRegisterFunc_structReturns(t *testing.T) {
1210 }1413 }
1211 var ReturnLargeBool func(a bool, b int32, c int64, d int64) LargeBool1414 var ReturnLargeBool func(a bool, b int32, c int64, d int64) LargeBool
1212 register(&ReturnLargeBool, lib, "ReturnLargeBool")1415 register(&ReturnLargeBool, lib, "ReturnLargeBool")
1213- expected := LargeBool{false, -99, 987654321, 111222333444}1416+ expected := LargeBool{A: false, B: -99, C: 987654321, D: 111222333444}
1214 if ret := ReturnLargeBool(false, -99, 987654321, 111222333444); ret != expected {1417 if ret := ReturnLargeBool(false, -99, 987654321, 111222333444); ret != expected {
1215 t.Fatalf("ReturnLargeBool returned %+v wanted %+v", ret, expected)1418 t.Fatalf("ReturnLargeBool returned %+v wanted %+v", ret, expected)
1216 }1419 }
1217 }1420 }
1218 {1421 {
1219 type Ptr1 struct {1422 type Ptr1 struct {
1423+ _ structs.HostLayout
1220 a *int641424 a *int64
1221 b unsafe.Pointer1425 b unsafe.Pointer
1222 }1426 }
1223 var ReturnPtr1 func(a *int64, b unsafe.Pointer) Ptr11427 var ReturnPtr1 func(a *int64, b unsafe.Pointer) Ptr1
1224 purego.RegisterLibFunc(&ReturnPtr1, lib, "ReturnPtr1")1428 purego.RegisterLibFunc(&ReturnPtr1, lib, "ReturnPtr1")
1225 a, b := new(int64), new(struct{})1429 a, b := new(int64), new(struct{})
1226- expected := Ptr1{a, unsafe.Pointer(b)}1430+ expected := Ptr1{a: a, b: unsafe.Pointer(b)}
1227 if ret := ReturnPtr1(a, unsafe.Pointer(b)); ret != expected {1431 if ret := ReturnPtr1(a, unsafe.Pointer(b)); ret != expected {
1228 t.Fatalf("ReturnPtr1 returned %+v wanted %+v", ret, expected)1432 t.Fatalf("ReturnPtr1 returned %+v wanted %+v", ret, expected)
1229 }1433 }
M sys_386.s +44 -43
@@ -10,8 +10,8 @@
10 #define STACK_SIZE 16010 #define STACK_SIZE 160
11 #define PTR_ADDRESS (STACK_SIZE - 4)11 #define PTR_ADDRESS (STACK_SIZE - 4)
12 12
13-// syscall15X calls a function in libc on behalf of the syscall package.13+// syscallX calls a function in libc on behalf of the syscall package.
14-// syscall15X takes a pointer to a struct like:14+// syscallX takes a pointer to a struct like:
15 // struct {15 // struct {
16 // fn uintptr16 // fn uintptr
17 // a1 uintptr17 // a1 uintptr
@@ -22,18 +22,18 @@
22 // f16 uintptr22 // f16 uintptr
23 // arm64_r8 uintptr23 // arm64_r8 uintptr
24 // }24 // }
25-// syscall15X must be called on the g0 stack with the25+// syscallX must be called on the g0 stack with the
26 // C calling convention (use libcCall).26 // C calling convention (use libcCall).
27 //27 //
28 // On i386 System V ABI, all arguments are passed on the stack.28 // On i386 System V ABI, all arguments are passed on the stack.
29 // Return value is in EAX (and EDX for 64-bit values).29 // Return value is in EAX (and EDX for 64-bit values).
30-GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $430+GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $4
31-DATA ·syscall15XABI0(SB)/4, $syscall15X(SB)31+DATA ·syscallXABI0(SB)/4, $syscallX(SB)
32-TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-032+TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0
33 // Called via C calling convention: argument pointer at 4(SP)33 // Called via C calling convention: argument pointer at 4(SP)
34 // NOT via Go calling convention34 // NOT via Go calling convention
35 // On i386, the first argument is at 4(SP) after CALL pushes return address35 // On i386, the first argument is at 4(SP) after CALL pushes return address
36- MOVL 4(SP), AX // get pointer to syscall15Args36+ MOVL 4(SP), AX // get pointer to syscallArgs
37 37
38 // Save callee-saved registers38 // Save callee-saved registers
39 PUSHL BP39 PUSHL BP
@@ -50,75 +50,75 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0
50 MOVL BX, PTR_ADDRESS(SP) // save args pointer50 MOVL BX, PTR_ADDRESS(SP) // save args pointer
51 51
52 // Load function pointer52 // Load function pointer
53- MOVL syscall15Args_fn(BX), AX53+ MOVL syscallArgs_fn(BX), AX
54 MOVL AX, (PTR_ADDRESS-4)(SP) // save fn pointer54 MOVL AX, (PTR_ADDRESS-4)(SP) // save fn pointer
55 55
56 // Push all integer arguments onto the stack (a1-a32)56 // Push all integer arguments onto the stack (a1-a32)
57 // i386 SysV ABI: arguments pushed right-to-left, but we're57 // i386 SysV ABI: arguments pushed right-to-left, but we're
58 // setting up the stack from low to high addresses58 // setting up the stack from low to high addresses
59- MOVL syscall15Args_a1(BX), AX59+ MOVL syscallArgs_a1(BX), AX
60 MOVL AX, 0(SP)60 MOVL AX, 0(SP)
61- MOVL syscall15Args_a2(BX), AX61+ MOVL syscallArgs_a2(BX), AX
62 MOVL AX, 4(SP)62 MOVL AX, 4(SP)
63- MOVL syscall15Args_a3(BX), AX63+ MOVL syscallArgs_a3(BX), AX
64 MOVL AX, 8(SP)64 MOVL AX, 8(SP)
65- MOVL syscall15Args_a4(BX), AX65+ MOVL syscallArgs_a4(BX), AX
66 MOVL AX, 12(SP)66 MOVL AX, 12(SP)
67- MOVL syscall15Args_a5(BX), AX67+ MOVL syscallArgs_a5(BX), AX
68 MOVL AX, 16(SP)68 MOVL AX, 16(SP)
69- MOVL syscall15Args_a6(BX), AX69+ MOVL syscallArgs_a6(BX), AX
70 MOVL AX, 20(SP)70 MOVL AX, 20(SP)
71- MOVL syscall15Args_a7(BX), AX71+ MOVL syscallArgs_a7(BX), AX
72 MOVL AX, 24(SP)72 MOVL AX, 24(SP)
73- MOVL syscall15Args_a8(BX), AX73+ MOVL syscallArgs_a8(BX), AX
74 MOVL AX, 28(SP)74 MOVL AX, 28(SP)
75- MOVL syscall15Args_a9(BX), AX75+ MOVL syscallArgs_a9(BX), AX
76 MOVL AX, 32(SP)76 MOVL AX, 32(SP)
77- MOVL syscall15Args_a10(BX), AX77+ MOVL syscallArgs_a10(BX), AX
78 MOVL AX, 36(SP)78 MOVL AX, 36(SP)
79- MOVL syscall15Args_a11(BX), AX79+ MOVL syscallArgs_a11(BX), AX
80 MOVL AX, 40(SP)80 MOVL AX, 40(SP)
81- MOVL syscall15Args_a12(BX), AX81+ MOVL syscallArgs_a12(BX), AX
82 MOVL AX, 44(SP)82 MOVL AX, 44(SP)
83- MOVL syscall15Args_a13(BX), AX83+ MOVL syscallArgs_a13(BX), AX
84 MOVL AX, 48(SP)84 MOVL AX, 48(SP)
85- MOVL syscall15Args_a14(BX), AX85+ MOVL syscallArgs_a14(BX), AX
86 MOVL AX, 52(SP)86 MOVL AX, 52(SP)
87- MOVL syscall15Args_a15(BX), AX87+ MOVL syscallArgs_a15(BX), AX
88 MOVL AX, 56(SP)88 MOVL AX, 56(SP)
89- MOVL syscall15Args_a16(BX), AX89+ MOVL syscallArgs_a16(BX), AX
90 MOVL AX, 60(SP)90 MOVL AX, 60(SP)
91- MOVL syscall15Args_a17(BX), AX91+ MOVL syscallArgs_a17(BX), AX
92 MOVL AX, 64(SP)92 MOVL AX, 64(SP)
93- MOVL syscall15Args_a18(BX), AX93+ MOVL syscallArgs_a18(BX), AX
94 MOVL AX, 68(SP)94 MOVL AX, 68(SP)
95- MOVL syscall15Args_a19(BX), AX95+ MOVL syscallArgs_a19(BX), AX
96 MOVL AX, 72(SP)96 MOVL AX, 72(SP)
97- MOVL syscall15Args_a20(BX), AX97+ MOVL syscallArgs_a20(BX), AX
98 MOVL AX, 76(SP)98 MOVL AX, 76(SP)
99- MOVL syscall15Args_a21(BX), AX99+ MOVL syscallArgs_a21(BX), AX
100 MOVL AX, 80(SP)100 MOVL AX, 80(SP)
101- MOVL syscall15Args_a22(BX), AX101+ MOVL syscallArgs_a22(BX), AX
102 MOVL AX, 84(SP)102 MOVL AX, 84(SP)
103- MOVL syscall15Args_a23(BX), AX103+ MOVL syscallArgs_a23(BX), AX
104 MOVL AX, 88(SP)104 MOVL AX, 88(SP)
105- MOVL syscall15Args_a24(BX), AX105+ MOVL syscallArgs_a24(BX), AX
106 MOVL AX, 92(SP)106 MOVL AX, 92(SP)
107- MOVL syscall15Args_a25(BX), AX107+ MOVL syscallArgs_a25(BX), AX
108 MOVL AX, 96(SP)108 MOVL AX, 96(SP)
109- MOVL syscall15Args_a26(BX), AX109+ MOVL syscallArgs_a26(BX), AX
110 MOVL AX, 100(SP)110 MOVL AX, 100(SP)
111- MOVL syscall15Args_a27(BX), AX111+ MOVL syscallArgs_a27(BX), AX
112 MOVL AX, 104(SP)112 MOVL AX, 104(SP)
113- MOVL syscall15Args_a28(BX), AX113+ MOVL syscallArgs_a28(BX), AX
114 MOVL AX, 108(SP)114 MOVL AX, 108(SP)
115- MOVL syscall15Args_a29(BX), AX115+ MOVL syscallArgs_a29(BX), AX
116 MOVL AX, 112(SP)116 MOVL AX, 112(SP)
117- MOVL syscall15Args_a30(BX), AX117+ MOVL syscallArgs_a30(BX), AX
118 MOVL AX, 116(SP)118 MOVL AX, 116(SP)
119- MOVL syscall15Args_a31(BX), AX119+ MOVL syscallArgs_a31(BX), AX
120 MOVL AX, 120(SP)120 MOVL AX, 120(SP)
121- MOVL syscall15Args_a32(BX), AX121+ MOVL syscallArgs_a32(BX), AX
122 MOVL AX, 124(SP)122 MOVL AX, 124(SP)
123 123
124 // Call the C function124 // Call the C function
@@ -127,13 +127,14 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0
127 127
128 // Get args pointer back and save results128 // Get args pointer back and save results
129 MOVL PTR_ADDRESS(SP), BX129 MOVL PTR_ADDRESS(SP), BX
130- MOVL AX, syscall15Args_a1(BX) // return value r1130+ MOVL AX, syscallArgs_a1(BX) // return value r1
131- MOVL DX, syscall15Args_a2(BX) // return value r2 (for 64-bit returns)131+ MOVL DX, syscallArgs_a2(BX) // return value r2 (for 64-bit returns)
132+ MOVL $0, syscallArgs_a3(BX) // no errno here: clear the input argument
132 133
133 // Save x87 FPU return value (ST0) to f1 field134 // Save x87 FPU return value (ST0) to f1 field
134 // On i386 System V ABI, float/double returns are in ST(0)135 // On i386 System V ABI, float/double returns are in ST(0)
135 // We save as float64 (8 bytes) to preserve precision136 // We save as float64 (8 bytes) to preserve precision
136- FMOVDP F0, syscall15Args_f1(BX)137+ FMOVDP F0, syscallArgs_f1(BX)
137 138
138 // Clean up stack139 // Clean up stack
139 ADDL $STACK_SIZE, SP140 ADDL $STACK_SIZE, SP
M sys_amd64.s +73 -37
@@ -8,11 +8,11 @@
8 #include "go_asm.h"8 #include "go_asm.h"
9 #include "funcdata.h"9 #include "funcdata.h"
10 10
11-#define STACK_SIZE 8011+#define STACK_SIZE 224
12 #define PTR_ADDRESS (STACK_SIZE - 8)12 #define PTR_ADDRESS (STACK_SIZE - 8)
13 13
14-// syscall15X calls a function in libc on behalf of the syscall package.14+// syscallX calls a function in libc on behalf of the syscall package.
15-// syscall15X takes a pointer to a struct like:15+// syscallX takes a pointer to a struct like:
16 // struct {16 // struct {
17 // fn uintptr17 // fn uintptr
18 // a1 uintptr18 // a1 uintptr
@@ -34,65 +34,101 @@
34 // r2 uintptr34 // r2 uintptr
35 // err uintptr35 // err uintptr
36 // }36 // }
37-// syscall15X must be called on the g0 stack with the37+// syscallX must be called on the g0 stack with the
38 // C calling convention (use libcCall).38 // C calling convention (use libcCall).
39-GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $839+GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8
40-DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)40+DATA ·syscallXABI0(SB)/8, $syscallX(SB)
41-TEXT syscall15X(SB), NOSPLIT, $STACK_SIZE41+TEXT syscallX(SB), NOSPLIT, $STACK_SIZE
42 MOVQ DI, PTR_ADDRESS(SP) // save the pointer42 MOVQ DI, PTR_ADDRESS(SP) // save the pointer
43 MOVQ DI, R1143 MOVQ DI, R11
44 44
45- MOVQ syscall15Args_f1(R11), X0 // f145+ MOVQ syscallArgs_f1(R11), X0 // f1
46- MOVQ syscall15Args_f2(R11), X1 // f246+ MOVQ syscallArgs_f2(R11), X1 // f2
47- MOVQ syscall15Args_f3(R11), X2 // f347+ MOVQ syscallArgs_f3(R11), X2 // f3
48- MOVQ syscall15Args_f4(R11), X3 // f448+ MOVQ syscallArgs_f4(R11), X3 // f4
49- MOVQ syscall15Args_f5(R11), X4 // f549+ MOVQ syscallArgs_f5(R11), X4 // f5
50- MOVQ syscall15Args_f6(R11), X5 // f650+ MOVQ syscallArgs_f6(R11), X5 // f6
51- MOVQ syscall15Args_f7(R11), X6 // f751+ MOVQ syscallArgs_f7(R11), X6 // f7
52- MOVQ syscall15Args_f8(R11), X7 // f852+ MOVQ syscallArgs_f8(R11), X7 // f8
53-53+
54- MOVQ syscall15Args_a1(R11), DI // a154+ MOVQ syscallArgs_a1(R11), DI // a1
55- MOVQ syscall15Args_a2(R11), SI // a255+ MOVQ syscallArgs_a2(R11), SI // a2
56- MOVQ syscall15Args_a3(R11), DX // a356+ MOVQ syscallArgs_a3(R11), DX // a3
57- MOVQ syscall15Args_a4(R11), CX // a457+ MOVQ syscallArgs_a4(R11), CX // a4
58- MOVQ syscall15Args_a5(R11), R8 // a558+ MOVQ syscallArgs_a5(R11), R8 // a5
59- MOVQ syscall15Args_a6(R11), R9 // a659+ MOVQ syscallArgs_a6(R11), R9 // a6
60 60
61 // push the remaining parameters onto the stack61 // push the remaining parameters onto the stack
62- MOVQ syscall15Args_a7(R11), R1262+ MOVQ syscallArgs_a7(R11), R12
63 MOVQ R12, 0(SP) // push a763 MOVQ R12, 0(SP) // push a7
64- MOVQ syscall15Args_a8(R11), R1264+ MOVQ syscallArgs_a8(R11), R12
65 MOVQ R12, 8(SP) // push a865 MOVQ R12, 8(SP) // push a8
66- MOVQ syscall15Args_a9(R11), R1266+ MOVQ syscallArgs_a9(R11), R12
67 MOVQ R12, 16(SP) // push a967 MOVQ R12, 16(SP) // push a9
68- MOVQ syscall15Args_a10(R11), R1268+ MOVQ syscallArgs_a10(R11), R12
69 MOVQ R12, 24(SP) // push a1069 MOVQ R12, 24(SP) // push a10
70- MOVQ syscall15Args_a11(R11), R1270+ MOVQ syscallArgs_a11(R11), R12
71 MOVQ R12, 32(SP) // push a1171 MOVQ R12, 32(SP) // push a11
72- MOVQ syscall15Args_a12(R11), R1272+ MOVQ syscallArgs_a12(R11), R12
73 MOVQ R12, 40(SP) // push a1273 MOVQ R12, 40(SP) // push a12
74- MOVQ syscall15Args_a13(R11), R1274+ MOVQ syscallArgs_a13(R11), R12
75 MOVQ R12, 48(SP) // push a1375 MOVQ R12, 48(SP) // push a13
76- MOVQ syscall15Args_a14(R11), R1276+ MOVQ syscallArgs_a14(R11), R12
77 MOVQ R12, 56(SP) // push a1477 MOVQ R12, 56(SP) // push a14
78- MOVQ syscall15Args_a15(R11), R1278+ MOVQ syscallArgs_a15(R11), R12
79 MOVQ R12, 64(SP) // push a1579 MOVQ R12, 64(SP) // push a15
80+ MOVQ syscallArgs_a16(R11), R12
81+ MOVQ R12, 72(SP) // push a16
82+ MOVQ syscallArgs_a17(R11), R12
83+ MOVQ R12, 80(SP) // push a17
84+ MOVQ syscallArgs_a18(R11), R12
85+ MOVQ R12, 88(SP) // push a18
86+ MOVQ syscallArgs_a19(R11), R12
87+ MOVQ R12, 96(SP) // push a19
88+ MOVQ syscallArgs_a20(R11), R12
89+ MOVQ R12, 104(SP) // push a20
90+ MOVQ syscallArgs_a21(R11), R12
91+ MOVQ R12, 112(SP) // push a21
92+ MOVQ syscallArgs_a22(R11), R12
93+ MOVQ R12, 120(SP) // push a22
94+ MOVQ syscallArgs_a23(R11), R12
95+ MOVQ R12, 128(SP) // push a23
96+ MOVQ syscallArgs_a24(R11), R12
97+ MOVQ R12, 136(SP) // push a24
98+ MOVQ syscallArgs_a25(R11), R12
99+ MOVQ R12, 144(SP) // push a25
100+ MOVQ syscallArgs_a26(R11), R12
101+ MOVQ R12, 152(SP) // push a26
102+ MOVQ syscallArgs_a27(R11), R12
103+ MOVQ R12, 160(SP) // push a27
104+ MOVQ syscallArgs_a28(R11), R12
105+ MOVQ R12, 168(SP) // push a28
106+ MOVQ syscallArgs_a29(R11), R12
107+ MOVQ R12, 176(SP) // push a29
108+ MOVQ syscallArgs_a30(R11), R12
109+ MOVQ R12, 184(SP) // push a30
110+ MOVQ syscallArgs_a31(R11), R12
111+ MOVQ R12, 192(SP) // push a31
112+ MOVQ syscallArgs_a32(R11), R12
113+ MOVQ R12, 200(SP) // push a32
80 XORL AX, AX // vararg: say "no float args"114 XORL AX, AX // vararg: say "no float args"
81 115
82- MOVQ syscall15Args_fn(R11), R10 // fn116+ MOVQ syscallArgs_fn(R11), R10 // fn
83 CALL R10117 CALL R10
84 118
85 MOVQ PTR_ADDRESS(SP), DI // get the pointer back119 MOVQ PTR_ADDRESS(SP), DI // get the pointer back
86- MOVQ AX, syscall15Args_a1(DI) // r1120+ MOVQ AX, syscallArgs_a1(DI) // r1
87- MOVQ DX, syscall15Args_a2(DI) // r2121+ MOVQ DX, syscallArgs_a2(DI) // r2
88- MOVQ X0, syscall15Args_f1(DI) // f1122+ MOVQ X0, syscallArgs_f1(DI) // f1
89- MOVQ X1, syscall15Args_f2(DI) // f2123+ MOVQ X1, syscallArgs_f2(DI) // f2
90 124
91 #ifdef GOOS_darwin125 #ifdef GOOS_darwin
92 CALL purego_error(SB)126 CALL purego_error(SB)
93 MOVQ PTR_ADDRESS(SP), DI // reload (DI clobbered by call)127 MOVQ PTR_ADDRESS(SP), DI // reload (DI clobbered by call)
94 MOVQ (AX), AX128 MOVQ (AX), AX
95- MOVQ AX, syscall15Args_a3(DI) // save errno129+ MOVQ AX, syscallArgs_a3(DI) // save errno
130+#else
131+ MOVQ $0, syscallArgs_a3(DI) // no errno here: clear the input argument
96 #endif132 #endif
97 133
98 XORL AX, AX // no error (it's ignored anyway)134 XORL AX, AX // no error (it's ignored anyway)
M sys_arm.s +65 -54
@@ -10,8 +10,8 @@
10 #define STACK_SIZE 12810 #define STACK_SIZE 128
11 #define PTR_ADDRESS (STACK_SIZE - 4)11 #define PTR_ADDRESS (STACK_SIZE - 4)
12 12
13-// syscall15X calls a function in libc on behalf of the syscall package.13+// syscallX calls a function in libc on behalf of the syscall package.
14-// syscall15X takes a pointer to a struct like:14+// syscallX takes a pointer to a struct like:
15 // struct {15 // struct {
16 // fn uintptr16 // fn uintptr
17 // a1 uintptr17 // a1 uintptr
@@ -22,12 +22,12 @@
22 // f16 uintptr22 // f16 uintptr
23 // arm64_r8 uintptr23 // arm64_r8 uintptr
24 // }24 // }
25-// syscall15X must be called on the g0 stack with the25+// syscallX must be called on the g0 stack with the
26 // C calling convention (use libcCall).26 // C calling convention (use libcCall).
27-GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $427+GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $4
28-DATA ·syscall15XABI0(SB)/4, $syscall15X(SB)28+DATA ·syscallXABI0(SB)/4, $syscallX(SB)
29-TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-029+TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0
30- // Called via C calling convention: R0 = pointer to syscall15Args30+ // Called via C calling convention: R0 = pointer to syscallArgs
31 // NOT via Go calling convention31 // NOT via Go calling convention
32 // Save link register and callee-saved registers first32 // Save link register and callee-saved registers first
33 MOVW.W R14, -4(R13) // save LR (decrement and store)33 MOVW.W R14, -4(R13) // save LR (decrement and store)
@@ -38,82 +38,87 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0
38 MOVW R8, PTR_ADDRESS(R13)38 MOVW R8, PTR_ADDRESS(R13)
39 39
40 // Load function pointer first (before anything can corrupt R8)40 // Load function pointer first (before anything can corrupt R8)
41- MOVW syscall15Args_fn(R8), R541+ MOVW syscallArgs_fn(R8), R5
42 MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 5642 MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 56
43 43
44+ // Skip floating point registers if runtime.goarmsoftfp!=0.
45+ MOVB runtime·goarmsoftfp(SB), R11
46+ CMP $0, R11
47+ BNE skipfpload
44 // Load floating point arguments48 // Load floating point arguments
45 // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 249 // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 2
46- MOVD syscall15Args_f1(R8), F0 // f1+f2 -> D050+ MOVD syscallArgs_f1(R8), F0 // f1+f2 -> D0
47- MOVD syscall15Args_f3(R8), F1 // f3+f4 -> D151+ MOVD syscallArgs_f3(R8), F1 // f3+f4 -> D1
48- MOVD syscall15Args_f5(R8), F2 // f5+f6 -> D252+ MOVD syscallArgs_f5(R8), F2 // f5+f6 -> D2
49- MOVD syscall15Args_f7(R8), F3 // f7+f8 -> D353+ MOVD syscallArgs_f7(R8), F3 // f7+f8 -> D3
50- MOVD syscall15Args_f9(R8), F4 // f9+f10 -> D454+ MOVD syscallArgs_f9(R8), F4 // f9+f10 -> D4
51- MOVD syscall15Args_f11(R8), F5 // f11+f12 -> D555+ MOVD syscallArgs_f11(R8), F5 // f11+f12 -> D5
52- MOVD syscall15Args_f13(R8), F6 // f13+f14 -> D656+ MOVD syscallArgs_f13(R8), F6 // f13+f14 -> D6
53- MOVD syscall15Args_f15(R8), F7 // f15+f16 -> D757+ MOVD syscallArgs_f15(R8), F7 // f15+f16 -> D7
54 58
59+skipfpload:
55 // Load integer arguments into registers (R0-R3 for ARM EABI)60 // Load integer arguments into registers (R0-R3 for ARM EABI)
56- MOVW syscall15Args_a1(R8), R0 // a161+ MOVW syscallArgs_a1(R8), R0 // a1
57- MOVW syscall15Args_a2(R8), R1 // a262+ MOVW syscallArgs_a2(R8), R1 // a2
58- MOVW syscall15Args_a3(R8), R2 // a363+ MOVW syscallArgs_a3(R8), R2 // a3
59- MOVW syscall15Args_a4(R8), R3 // a464+ MOVW syscallArgs_a4(R8), R3 // a4
60 65
61 // push a5-a32 onto stack66 // push a5-a32 onto stack
62- MOVW syscall15Args_a5(R8), R467+ MOVW syscallArgs_a5(R8), R4
63 MOVW R4, 0(R13)68 MOVW R4, 0(R13)
64- MOVW syscall15Args_a6(R8), R469+ MOVW syscallArgs_a6(R8), R4
65 MOVW R4, 4(R13)70 MOVW R4, 4(R13)
66- MOVW syscall15Args_a7(R8), R471+ MOVW syscallArgs_a7(R8), R4
67 MOVW R4, 8(R13)72 MOVW R4, 8(R13)
68- MOVW syscall15Args_a8(R8), R473+ MOVW syscallArgs_a8(R8), R4
69 MOVW R4, 12(R13)74 MOVW R4, 12(R13)
70- MOVW syscall15Args_a9(R8), R475+ MOVW syscallArgs_a9(R8), R4
71 MOVW R4, 16(R13)76 MOVW R4, 16(R13)
72- MOVW syscall15Args_a10(R8), R477+ MOVW syscallArgs_a10(R8), R4
73 MOVW R4, 20(R13)78 MOVW R4, 20(R13)
74- MOVW syscall15Args_a11(R8), R479+ MOVW syscallArgs_a11(R8), R4
75 MOVW R4, 24(R13)80 MOVW R4, 24(R13)
76- MOVW syscall15Args_a12(R8), R481+ MOVW syscallArgs_a12(R8), R4
77 MOVW R4, 28(R13)82 MOVW R4, 28(R13)
78- MOVW syscall15Args_a13(R8), R483+ MOVW syscallArgs_a13(R8), R4
79 MOVW R4, 32(R13)84 MOVW R4, 32(R13)
80- MOVW syscall15Args_a14(R8), R485+ MOVW syscallArgs_a14(R8), R4
81 MOVW R4, 36(R13)86 MOVW R4, 36(R13)
82- MOVW syscall15Args_a15(R8), R487+ MOVW syscallArgs_a15(R8), R4
83 MOVW R4, 40(R13)88 MOVW R4, 40(R13)
84- MOVW syscall15Args_a16(R8), R489+ MOVW syscallArgs_a16(R8), R4
85 MOVW R4, 44(R13)90 MOVW R4, 44(R13)
86- MOVW syscall15Args_a17(R8), R491+ MOVW syscallArgs_a17(R8), R4
87 MOVW R4, 48(R13)92 MOVW R4, 48(R13)
88- MOVW syscall15Args_a18(R8), R493+ MOVW syscallArgs_a18(R8), R4
89 MOVW R4, 52(R13)94 MOVW R4, 52(R13)
90- MOVW syscall15Args_a19(R8), R495+ MOVW syscallArgs_a19(R8), R4
91 MOVW R4, 56(R13)96 MOVW R4, 56(R13)
92- MOVW syscall15Args_a20(R8), R497+ MOVW syscallArgs_a20(R8), R4
93 MOVW R4, 60(R13)98 MOVW R4, 60(R13)
94- MOVW syscall15Args_a21(R8), R499+ MOVW syscallArgs_a21(R8), R4
95 MOVW R4, 64(R13)100 MOVW R4, 64(R13)
96- MOVW syscall15Args_a22(R8), R4101+ MOVW syscallArgs_a22(R8), R4
97 MOVW R4, 68(R13)102 MOVW R4, 68(R13)
98- MOVW syscall15Args_a23(R8), R4103+ MOVW syscallArgs_a23(R8), R4
99 MOVW R4, 72(R13)104 MOVW R4, 72(R13)
100- MOVW syscall15Args_a24(R8), R4105+ MOVW syscallArgs_a24(R8), R4
101 MOVW R4, 76(R13)106 MOVW R4, 76(R13)
102- MOVW syscall15Args_a25(R8), R4107+ MOVW syscallArgs_a25(R8), R4
103 MOVW R4, 80(R13)108 MOVW R4, 80(R13)
104- MOVW syscall15Args_a26(R8), R4109+ MOVW syscallArgs_a26(R8), R4
105 MOVW R4, 84(R13)110 MOVW R4, 84(R13)
106- MOVW syscall15Args_a27(R8), R4111+ MOVW syscallArgs_a27(R8), R4
107 MOVW R4, 88(R13)112 MOVW R4, 88(R13)
108- MOVW syscall15Args_a28(R8), R4113+ MOVW syscallArgs_a28(R8), R4
109 MOVW R4, 92(R13)114 MOVW R4, 92(R13)
110- MOVW syscall15Args_a29(R8), R4115+ MOVW syscallArgs_a29(R8), R4
111 MOVW R4, 96(R13)116 MOVW R4, 96(R13)
112- MOVW syscall15Args_a30(R8), R4117+ MOVW syscallArgs_a30(R8), R4
113 MOVW R4, 100(R13)118 MOVW R4, 100(R13)
114- MOVW syscall15Args_a31(R8), R4119+ MOVW syscallArgs_a31(R8), R4
115 MOVW R4, 104(R13)120 MOVW R4, 104(R13)
116- MOVW syscall15Args_a32(R8), R4121+ MOVW syscallArgs_a32(R8), R4
117 MOVW R4, 108(R13)122 MOVW R4, 108(R13)
118 123
119 // Load saved function pointer and call124 // Load saved function pointer and call
@@ -128,15 +133,21 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0
128 ADD $STACK_SIZE, R13133 ADD $STACK_SIZE, R13
129 134
130 // save R0, R1135 // save R0, R1
131- MOVW R0, syscall15Args_a1(R8)136+ MOVW R0, syscallArgs_a1(R8)
132- MOVW R1, syscall15Args_a2(R8)137+ MOVW R1, syscallArgs_a2(R8)
138+ MOVW $0, R1
139+ MOVW R1, syscallArgs_a3(R8) // no errno here: clear the input argument
133 140
141+ MOVB runtime·goarmsoftfp(SB), R11
142+ CMP $0, R11
143+ BNE skipfpsave
134 // save f0-f3 (each float64 spans 2 uintptr slots on ARM32)144 // save f0-f3 (each float64 spans 2 uintptr slots on ARM32)
135- MOVD F0, syscall15Args_f1(R8)145+ MOVD F0, syscallArgs_f1(R8)
136- MOVD F1, syscall15Args_f3(R8)146+ MOVD F1, syscallArgs_f3(R8)
137- MOVD F2, syscall15Args_f5(R8)147+ MOVD F2, syscallArgs_f5(R8)
138- MOVD F3, syscall15Args_f7(R8)148+ MOVD F3, syscallArgs_f7(R8)
139 149
150+skipfpsave:
140 // Restore callee-saved registers and return151 // Restore callee-saved registers and return
141 MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11]152 MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11]
142 MOVW.P 4(R13), R15 // pop LR into PC (return)153 MOVW.P 4(R13), R15 // pop LR into PC (return)
M sys_arm64.s +75 -40
@@ -7,11 +7,11 @@
7 #include "go_asm.h"7 #include "go_asm.h"
8 #include "funcdata.h"8 #include "funcdata.h"
9 9
10-#define STACK_SIZE 6410+#define STACK_SIZE 208
11 #define PTR_ADDRESS (STACK_SIZE - 8)11 #define PTR_ADDRESS (STACK_SIZE - 8)
12 12
13-// syscall15X calls a function in libc on behalf of the syscall package.13+// syscallX calls a function in libc on behalf of the syscall package.
14-// syscall15X takes a pointer to a struct like:14+// syscallX takes a pointer to a struct like:
15 // struct {15 // struct {
16 // fn uintptr16 // fn uintptr
17 // a1 uintptr17 // a1 uintptr
@@ -33,66 +33,101 @@
33 // r2 uintptr33 // r2 uintptr
34 // err uintptr34 // err uintptr
35 // }35 // }
36-// syscall15X must be called on the g0 stack with the36+// syscallX must be called on the g0 stack with the
37 // C calling convention (use libcCall).37 // C calling convention (use libcCall).
38-GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $838+GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8
39-DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)39+DATA ·syscallXABI0(SB)/8, $syscallX(SB)
40-TEXT syscall15X(SB), NOSPLIT, $040+TEXT syscallX(SB), NOSPLIT, $0
41 SUB $STACK_SIZE, RSP // push structure pointer41 SUB $STACK_SIZE, RSP // push structure pointer
42 MOVD R0, PTR_ADDRESS(RSP)42 MOVD R0, PTR_ADDRESS(RSP)
43 MOVD R0, R943 MOVD R0, R9
44 44
45- FMOVD syscall15Args_f1(R9), F0 // f145+ FMOVD syscallArgs_f1(R9), F0 // f1
46- FMOVD syscall15Args_f2(R9), F1 // f246+ FMOVD syscallArgs_f2(R9), F1 // f2
47- FMOVD syscall15Args_f3(R9), F2 // f347+ FMOVD syscallArgs_f3(R9), F2 // f3
48- FMOVD syscall15Args_f4(R9), F3 // f448+ FMOVD syscallArgs_f4(R9), F3 // f4
49- FMOVD syscall15Args_f5(R9), F4 // f549+ FMOVD syscallArgs_f5(R9), F4 // f5
50- FMOVD syscall15Args_f6(R9), F5 // f650+ FMOVD syscallArgs_f6(R9), F5 // f6
51- FMOVD syscall15Args_f7(R9), F6 // f751+ FMOVD syscallArgs_f7(R9), F6 // f7
52- FMOVD syscall15Args_f8(R9), F7 // f852+ FMOVD syscallArgs_f8(R9), F7 // f8
53 53
54- MOVD syscall15Args_a1(R9), R0 // a154+ MOVD syscallArgs_a1(R9), R0 // a1
55- MOVD syscall15Args_a2(R9), R1 // a255+ MOVD syscallArgs_a2(R9), R1 // a2
56- MOVD syscall15Args_a3(R9), R2 // a356+ MOVD syscallArgs_a3(R9), R2 // a3
57- MOVD syscall15Args_a4(R9), R3 // a457+ MOVD syscallArgs_a4(R9), R3 // a4
58- MOVD syscall15Args_a5(R9), R4 // a558+ MOVD syscallArgs_a5(R9), R4 // a5
59- MOVD syscall15Args_a6(R9), R5 // a659+ MOVD syscallArgs_a6(R9), R5 // a6
60- MOVD syscall15Args_a7(R9), R6 // a760+ MOVD syscallArgs_a7(R9), R6 // a7
61- MOVD syscall15Args_a8(R9), R7 // a861+ MOVD syscallArgs_a8(R9), R7 // a8
62- MOVD syscall15Args_arm64_r8(R9), R8 // r862+ MOVD syscallArgs_arm64_r8(R9), R8 // r8
63 63
64- MOVD syscall15Args_a9(R9), R1064+ MOVD syscallArgs_a9(R9), R10
65 MOVD R10, 0(RSP) // push a9 onto stack65 MOVD R10, 0(RSP) // push a9 onto stack
66- MOVD syscall15Args_a10(R9), R1066+ MOVD syscallArgs_a10(R9), R10
67 MOVD R10, 8(RSP) // push a10 onto stack67 MOVD R10, 8(RSP) // push a10 onto stack
68- MOVD syscall15Args_a11(R9), R1068+ MOVD syscallArgs_a11(R9), R10
69 MOVD R10, 16(RSP) // push a11 onto stack69 MOVD R10, 16(RSP) // push a11 onto stack
70- MOVD syscall15Args_a12(R9), R1070+ MOVD syscallArgs_a12(R9), R10
71 MOVD R10, 24(RSP) // push a12 onto stack71 MOVD R10, 24(RSP) // push a12 onto stack
72- MOVD syscall15Args_a13(R9), R1072+ MOVD syscallArgs_a13(R9), R10
73 MOVD R10, 32(RSP) // push a13 onto stack73 MOVD R10, 32(RSP) // push a13 onto stack
74- MOVD syscall15Args_a14(R9), R1074+ MOVD syscallArgs_a14(R9), R10
75 MOVD R10, 40(RSP) // push a14 onto stack75 MOVD R10, 40(RSP) // push a14 onto stack
76- MOVD syscall15Args_a15(R9), R1076+ MOVD syscallArgs_a15(R9), R10
77 MOVD R10, 48(RSP) // push a15 onto stack77 MOVD R10, 48(RSP) // push a15 onto stack
78+ MOVD syscallArgs_a16(R9), R10
79+ MOVD R10, 56(RSP) // push a16 onto stack
80+ MOVD syscallArgs_a17(R9), R10
81+ MOVD R10, 64(RSP) // push a17 onto stack
82+ MOVD syscallArgs_a18(R9), R10
83+ MOVD R10, 72(RSP) // push a18 onto stack
84+ MOVD syscallArgs_a19(R9), R10
85+ MOVD R10, 80(RSP) // push a19 onto stack
86+ MOVD syscallArgs_a20(R9), R10
87+ MOVD R10, 88(RSP) // push a20 onto stack
88+ MOVD syscallArgs_a21(R9), R10
89+ MOVD R10, 96(RSP) // push a21 onto stack
90+ MOVD syscallArgs_a22(R9), R10
91+ MOVD R10, 104(RSP) // push a22 onto stack
92+ MOVD syscallArgs_a23(R9), R10
93+ MOVD R10, 112(RSP) // push a23 onto stack
94+ MOVD syscallArgs_a24(R9), R10
95+ MOVD R10, 120(RSP) // push a24 onto stack
96+ MOVD syscallArgs_a25(R9), R10
97+ MOVD R10, 128(RSP) // push a25 onto stack
98+ MOVD syscallArgs_a26(R9), R10
99+ MOVD R10, 136(RSP) // push a26 onto stack
100+ MOVD syscallArgs_a27(R9), R10
101+ MOVD R10, 144(RSP) // push a27 onto stack
102+ MOVD syscallArgs_a28(R9), R10
103+ MOVD R10, 152(RSP) // push a28 onto stack
104+ MOVD syscallArgs_a29(R9), R10
105+ MOVD R10, 160(RSP) // push a29 onto stack
106+ MOVD syscallArgs_a30(R9), R10
107+ MOVD R10, 168(RSP) // push a30 onto stack
108+ MOVD syscallArgs_a31(R9), R10
109+ MOVD R10, 176(RSP) // push a31 onto stack
110+ MOVD syscallArgs_a32(R9), R10
111+ MOVD R10, 184(RSP) // push a32 onto stack
78 112
79- MOVD syscall15Args_fn(R9), R10 // fn113+ MOVD syscallArgs_fn(R9), R10 // fn
80 BL (R10)114 BL (R10)
81 115
82 MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer116 MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer
83 ADD $STACK_SIZE, RSP117 ADD $STACK_SIZE, RSP
84 118
85- MOVD R0, syscall15Args_a1(R2) // save r1119+ MOVD R0, syscallArgs_a1(R2) // save r1
86- MOVD R1, syscall15Args_a2(R2) // save r3120+ MOVD R1, syscallArgs_a2(R2) // save r3
87- FMOVD F0, syscall15Args_f1(R2) // save f0121+ FMOVD F0, syscallArgs_f1(R2) // save f0
88- FMOVD F1, syscall15Args_f2(R2) // save f1122+ FMOVD F1, syscallArgs_f2(R2) // save f1
89- FMOVD F2, syscall15Args_f3(R2) // save f2123+ FMOVD F2, syscallArgs_f3(R2) // save f2
90- FMOVD F3, syscall15Args_f4(R2) // save f3124+ FMOVD F3, syscallArgs_f4(R2) // save f3
91 125
92 #ifdef GOOS_darwin126 #ifdef GOOS_darwin
93 BL purego_error(SB)127 BL purego_error(SB)
94 MOVD (R0), R0128 MOVD (R0), R0
95- MOVD R0, syscall15Args_a3(R2) // save errno129+ MOVD R0, syscallArgs_a3(R2) // save errno
96-130+#else
131+ MOVD $0, syscallArgs_a3(R2) // no errno here: clear the input argument
97 #endif132 #endif
98 RET133 RET
M sys_loong64.s +72 -37
@@ -7,11 +7,11 @@
7 #include "go_asm.h"7 #include "go_asm.h"
8 #include "funcdata.h"8 #include "funcdata.h"
9 9
10-#define STACK_SIZE 6410+#define STACK_SIZE 208
11 #define PTR_ADDRESS (STACK_SIZE - 8)11 #define PTR_ADDRESS (STACK_SIZE - 8)
12 12
13-// syscall15X calls a function in libc on behalf of the syscall package.13+// syscallX calls a function in libc on behalf of the syscall package.
14-// syscall15X takes a pointer to a struct like:14+// syscallX takes a pointer to a struct like:
15 // struct {15 // struct {
16 // fn uintptr16 // fn uintptr
17 // a1 uintptr17 // a1 uintptr
@@ -33,51 +33,85 @@
33 // r2 uintptr33 // r2 uintptr
34 // err uintptr34 // err uintptr
35 // }35 // }
36-// syscall15X must be called on the g0 stack with the36+// syscallX must be called on the g0 stack with the
37 // C calling convention (use libcCall).37 // C calling convention (use libcCall).
38-GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $838+GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8
39-DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)39+DATA ·syscallXABI0(SB)/8, $syscallX(SB)
40-TEXT syscall15X(SB), NOSPLIT, $040+TEXT syscallX(SB), NOSPLIT, $0
41 // push structure pointer41 // push structure pointer
42 SUBV $STACK_SIZE, R342 SUBV $STACK_SIZE, R3
43 MOVV R4, PTR_ADDRESS(R3)43 MOVV R4, PTR_ADDRESS(R3)
44 MOVV R4, R1344 MOVV R4, R13
45 45
46- MOVD syscall15Args_f1(R13), F0 // f146+ MOVD syscallArgs_f1(R13), F0 // f1
47- MOVD syscall15Args_f2(R13), F1 // f247+ MOVD syscallArgs_f2(R13), F1 // f2
48- MOVD syscall15Args_f3(R13), F2 // f348+ MOVD syscallArgs_f3(R13), F2 // f3
49- MOVD syscall15Args_f4(R13), F3 // f449+ MOVD syscallArgs_f4(R13), F3 // f4
50- MOVD syscall15Args_f5(R13), F4 // f550+ MOVD syscallArgs_f5(R13), F4 // f5
51- MOVD syscall15Args_f6(R13), F5 // f651+ MOVD syscallArgs_f6(R13), F5 // f6
52- MOVD syscall15Args_f7(R13), F6 // f752+ MOVD syscallArgs_f7(R13), F6 // f7
53- MOVD syscall15Args_f8(R13), F7 // f853+ MOVD syscallArgs_f8(R13), F7 // f8
54 54
55- MOVV syscall15Args_a1(R13), R4 // a155+ MOVV syscallArgs_a1(R13), R4 // a1
56- MOVV syscall15Args_a2(R13), R5 // a256+ MOVV syscallArgs_a2(R13), R5 // a2
57- MOVV syscall15Args_a3(R13), R6 // a357+ MOVV syscallArgs_a3(R13), R6 // a3
58- MOVV syscall15Args_a4(R13), R7 // a458+ MOVV syscallArgs_a4(R13), R7 // a4
59- MOVV syscall15Args_a5(R13), R8 // a559+ MOVV syscallArgs_a5(R13), R8 // a5
60- MOVV syscall15Args_a6(R13), R9 // a660+ MOVV syscallArgs_a6(R13), R9 // a6
61- MOVV syscall15Args_a7(R13), R10 // a761+ MOVV syscallArgs_a7(R13), R10 // a7
62- MOVV syscall15Args_a8(R13), R11 // a862+ MOVV syscallArgs_a8(R13), R11 // a8
63 63
64 // push a9-a15 onto stack64 // push a9-a15 onto stack
65- MOVV syscall15Args_a9(R13), R1265+ MOVV syscallArgs_a9(R13), R12
66 MOVV R12, 0(R3)66 MOVV R12, 0(R3)
67- MOVV syscall15Args_a10(R13), R1267+ MOVV syscallArgs_a10(R13), R12
68 MOVV R12, 8(R3)68 MOVV R12, 8(R3)
69- MOVV syscall15Args_a11(R13), R1269+ MOVV syscallArgs_a11(R13), R12
70 MOVV R12, 16(R3)70 MOVV R12, 16(R3)
71- MOVV syscall15Args_a12(R13), R1271+ MOVV syscallArgs_a12(R13), R12
72 MOVV R12, 24(R3)72 MOVV R12, 24(R3)
73- MOVV syscall15Args_a13(R13), R1273+ MOVV syscallArgs_a13(R13), R12
74 MOVV R12, 32(R3)74 MOVV R12, 32(R3)
75- MOVV syscall15Args_a14(R13), R1275+ MOVV syscallArgs_a14(R13), R12
76 MOVV R12, 40(R3)76 MOVV R12, 40(R3)
77- MOVV syscall15Args_a15(R13), R1277+ MOVV syscallArgs_a15(R13), R12
78 MOVV R12, 48(R3)78 MOVV R12, 48(R3)
79+ MOVV syscallArgs_a16(R13), R12
80+ MOVV R12, 56(R3)
81+ MOVV syscallArgs_a17(R13), R12
82+ MOVV R12, 64(R3)
83+ MOVV syscallArgs_a18(R13), R12
84+ MOVV R12, 72(R3)
85+ MOVV syscallArgs_a19(R13), R12
86+ MOVV R12, 80(R3)
87+ MOVV syscallArgs_a20(R13), R12
88+ MOVV R12, 88(R3)
89+ MOVV syscallArgs_a21(R13), R12
90+ MOVV R12, 96(R3)
91+ MOVV syscallArgs_a22(R13), R12
92+ MOVV R12, 104(R3)
93+ MOVV syscallArgs_a23(R13), R12
94+ MOVV R12, 112(R3)
95+ MOVV syscallArgs_a24(R13), R12
96+ MOVV R12, 120(R3)
97+ MOVV syscallArgs_a25(R13), R12
98+ MOVV R12, 128(R3)
99+ MOVV syscallArgs_a26(R13), R12
100+ MOVV R12, 136(R3)
101+ MOVV syscallArgs_a27(R13), R12
102+ MOVV R12, 144(R3)
103+ MOVV syscallArgs_a28(R13), R12
104+ MOVV R12, 152(R3)
105+ MOVV syscallArgs_a29(R13), R12
106+ MOVV R12, 160(R3)
107+ MOVV syscallArgs_a30(R13), R12
108+ MOVV R12, 168(R3)
109+ MOVV syscallArgs_a31(R13), R12
110+ MOVV R12, 176(R3)
111+ MOVV syscallArgs_a32(R13), R12
112+ MOVV R12, 184(R3)
79 113
80- MOVV syscall15Args_fn(R13), R12114+ MOVV syscallArgs_fn(R13), R12
81 JAL (R12)115 JAL (R12)
82 116
83 // pop structure pointer117 // pop structure pointer
@@ -85,12 +119,13 @@ TEXT syscall15X(SB), NOSPLIT, $0
85 ADDV $STACK_SIZE, R3119 ADDV $STACK_SIZE, R3
86 120
87 // save R4, R5121 // save R4, R5
88- MOVV R4, syscall15Args_a1(R13)122+ MOVV R4, syscallArgs_a1(R13)
89- MOVV R5, syscall15Args_a2(R13)123+ MOVV R5, syscallArgs_a2(R13)
124+ MOVV R0, syscallArgs_a3(R13) // no errno here: clear the input argument
90 125
91 // save f0-f3126 // save f0-f3
92- MOVD F0, syscall15Args_f1(R13)127+ MOVD F0, syscallArgs_f1(R13)
93- MOVD F1, syscall15Args_f2(R13)128+ MOVD F1, syscallArgs_f2(R13)
94- MOVD F2, syscall15Args_f3(R13)129+ MOVD F2, syscallArgs_f3(R13)
95- MOVD F3, syscall15Args_f4(R13)130+ MOVD F3, syscallArgs_f4(R13)
96 RET131 RET
M sys_ppc64le.s +42 -36
@@ -35,10 +35,10 @@
35 #define TOC_SAVE 16035 #define TOC_SAVE 160
36 #define ARGP_SAVE 16836 #define ARGP_SAVE 168
37 37
38-GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $838+GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8
39-DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)39+DATA ·syscallXABI0(SB)/8, $syscallX(SB)
40 40
41-TEXT syscall15X(SB), NOSPLIT, $041+TEXT syscallX(SB), NOSPLIT, $0
42 // Prologue: create stack frame42 // Prologue: create stack frame
43 // R3 contains the args pointer on entry43 // R3 contains the args pointer on entry
44 MOVD R1, R12 // save old SP44 MOVD R1, R12 // save old SP
@@ -51,49 +51,49 @@ TEXT syscall15X(SB), NOSPLIT, $0
51 // Save args pointer (in R3)51 // Save args pointer (in R3)
52 MOVD R3, ARGP_SAVE(R1)52 MOVD R3, ARGP_SAVE(R1)
53 53
54- // R11 := args pointer (syscall15Args*)54+ // R11 := args pointer (syscallArgs*)
55 MOVD R3, R1155 MOVD R3, R11
56 56
57 // Load float args into F1-F857 // Load float args into F1-F8
58- FMOVD syscall15Args_f1(R11), F158+ FMOVD syscallArgs_f1(R11), F1
59- FMOVD syscall15Args_f2(R11), F259+ FMOVD syscallArgs_f2(R11), F2
60- FMOVD syscall15Args_f3(R11), F360+ FMOVD syscallArgs_f3(R11), F3
61- FMOVD syscall15Args_f4(R11), F461+ FMOVD syscallArgs_f4(R11), F4
62- FMOVD syscall15Args_f5(R11), F562+ FMOVD syscallArgs_f5(R11), F5
63- FMOVD syscall15Args_f6(R11), F663+ FMOVD syscallArgs_f6(R11), F6
64- FMOVD syscall15Args_f7(R11), F764+ FMOVD syscallArgs_f7(R11), F7
65- FMOVD syscall15Args_f8(R11), F865+ FMOVD syscallArgs_f8(R11), F8
66 66
67 // Load integer args into R3-R1067 // Load integer args into R3-R10
68- MOVD syscall15Args_a1(R11), R368+ MOVD syscallArgs_a1(R11), R3
69- MOVD syscall15Args_a2(R11), R469+ MOVD syscallArgs_a2(R11), R4
70- MOVD syscall15Args_a3(R11), R570+ MOVD syscallArgs_a3(R11), R5
71- MOVD syscall15Args_a4(R11), R671+ MOVD syscallArgs_a4(R11), R6
72- MOVD syscall15Args_a5(R11), R772+ MOVD syscallArgs_a5(R11), R7
73- MOVD syscall15Args_a6(R11), R873+ MOVD syscallArgs_a6(R11), R8
74- MOVD syscall15Args_a7(R11), R974+ MOVD syscallArgs_a7(R11), R9
75- MOVD syscall15Args_a8(R11), R1075+ MOVD syscallArgs_a8(R11), R10
76 76
77 // Spill a9-a15 onto the stack (stack parameters start at 96(R1))77 // Spill a9-a15 onto the stack (stack parameters start at 96(R1))
78 // Per ELFv2: parameter save area is 32-95, stack args start at 9678 // Per ELFv2: parameter save area is 32-95, stack args start at 96
79 MOVD ARGP_SAVE(R1), R11 // reload args pointer79 MOVD ARGP_SAVE(R1), R11 // reload args pointer
80- MOVD syscall15Args_a9(R11), R1280+ MOVD syscallArgs_a9(R11), R12
81 MOVD R12, 96(R1) // a9 at 96(R1)81 MOVD R12, 96(R1) // a9 at 96(R1)
82- MOVD syscall15Args_a10(R11), R1282+ MOVD syscallArgs_a10(R11), R12
83 MOVD R12, 104(R1) // a10 at 104(R1)83 MOVD R12, 104(R1) // a10 at 104(R1)
84- MOVD syscall15Args_a11(R11), R1284+ MOVD syscallArgs_a11(R11), R12
85 MOVD R12, 112(R1) // a11 at 112(R1)85 MOVD R12, 112(R1) // a11 at 112(R1)
86- MOVD syscall15Args_a12(R11), R1286+ MOVD syscallArgs_a12(R11), R12
87 MOVD R12, 120(R1) // a12 at 120(R1)87 MOVD R12, 120(R1) // a12 at 120(R1)
88- MOVD syscall15Args_a13(R11), R1288+ MOVD syscallArgs_a13(R11), R12
89 MOVD R12, 128(R1) // a13 at 128(R1)89 MOVD R12, 128(R1) // a13 at 128(R1)
90- MOVD syscall15Args_a14(R11), R1290+ MOVD syscallArgs_a14(R11), R12
91 MOVD R12, 136(R1) // a14 at 136(R1)91 MOVD R12, 136(R1) // a14 at 136(R1)
92- MOVD syscall15Args_a15(R11), R1292+ MOVD syscallArgs_a15(R11), R12
93 MOVD R12, 144(R1) // a15 at 144(R1)93 MOVD R12, 144(R1) // a15 at 144(R1)
94 94
95 // Call function: load fn and call95 // Call function: load fn and call
96- MOVD syscall15Args_fn(R11), R1296+ MOVD syscallArgs_fn(R11), R12
97 MOVD R12, CTR97 MOVD R12, CTR
98 BL (CTR)98 BL (CTR)
99 99
@@ -104,14 +104,20 @@ TEXT syscall15X(SB), NOSPLIT, $0
104 MOVD ARGP_SAVE(R1), R11104 MOVD ARGP_SAVE(R1), R11
105 105
106 // Store integer results back (R3, R4)106 // Store integer results back (R3, R4)
107- MOVD R3, syscall15Args_a1(R11)107+ MOVD R3, syscallArgs_a1(R11)
108- MOVD R4, syscall15Args_a2(R11)108+ MOVD R4, syscallArgs_a2(R11)
109-109+ MOVD $0, R3
110- // Store float return values (F1-F4)110+ MOVD R3, syscallArgs_a3(R11) // no errno here: clear the input argument
111- FMOVD F1, syscall15Args_f1(R11)111+
112- FMOVD F2, syscall15Args_f2(R11)112+ // Store float return values F1-F8 (an HFA may span up to eight).
113- FMOVD F3, syscall15Args_f3(R11)113+ FMOVD F1, syscallArgs_f1(R11)
114- FMOVD F4, syscall15Args_f4(R11)114+ FMOVD F2, syscallArgs_f2(R11)
115+ FMOVD F3, syscallArgs_f3(R11)
116+ FMOVD F4, syscallArgs_f4(R11)
117+ FMOVD F5, syscallArgs_f5(R11)
118+ FMOVD F6, syscallArgs_f6(R11)
119+ FMOVD F7, syscallArgs_f7(R11)
120+ FMOVD F8, syscallArgs_f8(R11)
115 121
116 // Epilogue: restore and return122 // Epilogue: restore and return
117 MOVD LR_SAVE(R1), R12123 MOVD LR_SAVE(R1), R12
M sys_riscv64.s +84 -50
@@ -8,22 +8,21 @@
8 #include "funcdata.h"8 #include "funcdata.h"
9 9
10 // Stack usage:10 // Stack usage:
11-// 0(SP) - 56(SP): stack args a9-a15 (7 * 8 bytes = 56)11+// 0(SP) - 192(SP): stack args a9-a32 (24 * 8 bytes)
12-// 56(SP) - 64(SP): saved RA (x1)12+// 192(SP) - 200(SP): saved RA (x1)
13-// 64(SP) - 72(SP): saved X9 (s1)13+// 200(SP) - 208(SP): saved X9 (s1)
14-// 72(SP) - 80(SP): saved X18 (s2)14+// 208(SP) - 216(SP): saved X18 (s2)
15-// 80(SP) - 88(SP): saved args pointer (original X10)15+// 216(SP) - 224(SP): saved args pointer (original X10)
16-// 88(SP) - 96(SP): padding16+#define STACK_SIZE 224
17-#define STACK_SIZE 9617+#define SAVE_RA 192
18-#define SAVE_RA 5618+#define SAVE_X9 200
19-#define SAVE_X9 6419+#define SAVE_X18 208
20-#define SAVE_X18 7220+#define SAVE_ARGP 216
21-#define SAVE_ARGP 8021+
22-22+GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8
23-GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $823+DATA ·syscallXABI0(SB)/8, $syscallX(SB)
24-DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)24+
25-25+TEXT syscallX(SB), NOSPLIT, $0
26-TEXT syscall15X(SB), NOSPLIT, $0
27 // Allocate stack frame (keeps 16-byte alignment)26 // Allocate stack frame (keeps 16-byte alignment)
28 SUB $STACK_SIZE, SP27 SUB $STACK_SIZE, SP
29 28
@@ -35,62 +34,97 @@ TEXT syscall15X(SB), NOSPLIT, $0
35 // Save original args pointer (in a0/X10)34 // Save original args pointer (in a0/X10)
36 MOV X10, SAVE_ARGP(SP)35 MOV X10, SAVE_ARGP(SP)
37 36
38- // X9 := args pointer (syscall15Args*)37+ // X9 := args pointer (syscallArgs*)
39 MOV X10, X938 MOV X10, X9
40 39
41 // Load float args into fa0-fa7 (F10-F17)40 // Load float args into fa0-fa7 (F10-F17)
42- MOVD syscall15Args_f1(X9), F1041+ MOVD syscallArgs_f1(X9), F10
43- MOVD syscall15Args_f2(X9), F1142+ MOVD syscallArgs_f2(X9), F11
44- MOVD syscall15Args_f3(X9), F1243+ MOVD syscallArgs_f3(X9), F12
45- MOVD syscall15Args_f4(X9), F1344+ MOVD syscallArgs_f4(X9), F13
46- MOVD syscall15Args_f5(X9), F1445+ MOVD syscallArgs_f5(X9), F14
47- MOVD syscall15Args_f6(X9), F1546+ MOVD syscallArgs_f6(X9), F15
48- MOVD syscall15Args_f7(X9), F1647+ MOVD syscallArgs_f7(X9), F16
49- MOVD syscall15Args_f8(X9), F1748+ MOVD syscallArgs_f8(X9), F17
50 49
51 // Load integer args into a0-a7 (X10-X17)50 // Load integer args into a0-a7 (X10-X17)
52- MOV syscall15Args_a1(X9), X1051+ MOV syscallArgs_a1(X9), X10
53- MOV syscall15Args_a2(X9), X1152+ MOV syscallArgs_a2(X9), X11
54- MOV syscall15Args_a3(X9), X1253+ MOV syscallArgs_a3(X9), X12
55- MOV syscall15Args_a4(X9), X1354+ MOV syscallArgs_a4(X9), X13
56- MOV syscall15Args_a5(X9), X1455+ MOV syscallArgs_a5(X9), X14
57- MOV syscall15Args_a6(X9), X1556+ MOV syscallArgs_a6(X9), X15
58- MOV syscall15Args_a7(X9), X1657+ MOV syscallArgs_a7(X9), X16
59- MOV syscall15Args_a8(X9), X1758+ MOV syscallArgs_a8(X9), X17
60-59+
61- // Spill a9-a15 onto the stack (C ABI)60+ // Spill a9-a32 onto the stack (C ABI)
62- MOV syscall15Args_a9(X9), X1861+ MOV syscallArgs_a9(X9), X18
63 MOV X18, 0(SP)62 MOV X18, 0(SP)
64- MOV syscall15Args_a10(X9), X1863+ MOV syscallArgs_a10(X9), X18
65 MOV X18, 8(SP)64 MOV X18, 8(SP)
66- MOV syscall15Args_a11(X9), X1865+ MOV syscallArgs_a11(X9), X18
67 MOV X18, 16(SP)66 MOV X18, 16(SP)
68- MOV syscall15Args_a12(X9), X1867+ MOV syscallArgs_a12(X9), X18
69 MOV X18, 24(SP)68 MOV X18, 24(SP)
70- MOV syscall15Args_a13(X9), X1869+ MOV syscallArgs_a13(X9), X18
71 MOV X18, 32(SP)70 MOV X18, 32(SP)
72- MOV syscall15Args_a14(X9), X1871+ MOV syscallArgs_a14(X9), X18
73 MOV X18, 40(SP)72 MOV X18, 40(SP)
74- MOV syscall15Args_a15(X9), X1873+ MOV syscallArgs_a15(X9), X18
75 MOV X18, 48(SP)74 MOV X18, 48(SP)
75+ MOV syscallArgs_a16(X9), X18
76+ MOV X18, 56(SP)
77+ MOV syscallArgs_a17(X9), X18
78+ MOV X18, 64(SP)
79+ MOV syscallArgs_a18(X9), X18
80+ MOV X18, 72(SP)
81+ MOV syscallArgs_a19(X9), X18
82+ MOV X18, 80(SP)
83+ MOV syscallArgs_a20(X9), X18
84+ MOV X18, 88(SP)
85+ MOV syscallArgs_a21(X9), X18
86+ MOV X18, 96(SP)
87+ MOV syscallArgs_a22(X9), X18
88+ MOV X18, 104(SP)
89+ MOV syscallArgs_a23(X9), X18
90+ MOV X18, 112(SP)
91+ MOV syscallArgs_a24(X9), X18
92+ MOV X18, 120(SP)
93+ MOV syscallArgs_a25(X9), X18
94+ MOV X18, 128(SP)
95+ MOV syscallArgs_a26(X9), X18
96+ MOV X18, 136(SP)
97+ MOV syscallArgs_a27(X9), X18
98+ MOV X18, 144(SP)
99+ MOV syscallArgs_a28(X9), X18
100+ MOV X18, 152(SP)
101+ MOV syscallArgs_a29(X9), X18
102+ MOV X18, 160(SP)
103+ MOV syscallArgs_a30(X9), X18
104+ MOV X18, 168(SP)
105+ MOV syscallArgs_a31(X9), X18
106+ MOV X18, 176(SP)
107+ MOV syscallArgs_a32(X9), X18
108+ MOV X18, 184(SP)
76 109
77 // Call fn110 // Call fn
78 // IMPORTANT: preserve RA across this call (we saved it above)111 // IMPORTANT: preserve RA across this call (we saved it above)
79- MOV syscall15Args_fn(X9), X18112+ MOV syscallArgs_fn(X9), X18
80 CALL X18113 CALL X18
81 114
82- // Restore args pointer (syscall15Args*) for storing results115+ // Restore args pointer (syscallArgs*) for storing results
83 MOV SAVE_ARGP(SP), X9116 MOV SAVE_ARGP(SP), X9
84 117
85 // Store results back118 // Store results back
86- MOV X10, syscall15Args_a1(X9)119+ MOV X10, syscallArgs_a1(X9)
87- MOV X11, syscall15Args_a2(X9)120+ MOV X11, syscallArgs_a2(X9)
121+ MOV X0, syscallArgs_a3(X9) // no errno here: clear the input argument
88 122
89 // Store back float return regs if used by your ABI contract123 // Store back float return regs if used by your ABI contract
90- MOVD F10, syscall15Args_f1(X9)124+ MOVD F10, syscallArgs_f1(X9)
91- MOVD F11, syscall15Args_f2(X9)125+ MOVD F11, syscallArgs_f2(X9)
92- MOVD F12, syscall15Args_f3(X9)126+ MOVD F12, syscallArgs_f3(X9)
93- MOVD F13, syscall15Args_f4(X9)127+ MOVD F13, syscallArgs_f4(X9)
94 128
95 // Restore callee-saved regs and return address129 // Restore callee-saved regs and return address
96 MOV SAVE_X18(SP), X18130 MOV SAVE_X18(SP), X18
M sys_s390x.s +70 -36
@@ -24,19 +24,18 @@
24 //24 //
25 // We need space for:25 // We need space for:
26 // - 160 bytes standard frame (with register save area)26 // - 160 bytes standard frame (with register save area)
27-// - Stack args a6-a15 (10 * 8 = 80 bytes)27+// - Stack args a6-a32 (27 * 8 = 216 bytes)
28 // - Saved args pointer (8 bytes)28 // - Saved args pointer (8 bytes)
29-// - Padding for alignment29+// Total: 384 bytes
30-// Total: 264 bytes (rounded to 8-byte alignment)
31 30
32-#define STACK_SIZE 26431+#define STACK_SIZE 384
33 #define STACK_ARGS 16032 #define STACK_ARGS 160
34-#define ARGP_SAVE 24833+#define ARGP_SAVE 376
35 34
36-GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $835+GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8
37-DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)36+DATA ·syscallXABI0(SB)/8, $syscallX(SB)
38 37
39-TEXT syscall15X(SB), NOSPLIT, $038+TEXT syscallX(SB), NOSPLIT, $0
40 // On entry, R2 contains the args pointer39 // On entry, R2 contains the args pointer
41 // Save callee-saved registers in caller's frame (per ABI)40 // Save callee-saved registers in caller's frame (per ABI)
42 STMG R6, R15, 48(R15)41 STMG R6, R15, 48(R15)
@@ -49,61 +48,96 @@ TEXT syscall15X(SB), NOSPLIT, $0
49 // Save args pointer48 // Save args pointer
50 MOVD R2, ARGP_SAVE(R15)49 MOVD R2, ARGP_SAVE(R15)
51 50
52- // R9 := args pointer (syscall15Args*)51+ // R9 := args pointer (syscallArgs*)
53 MOVD R2, R952 MOVD R2, R9
54 53
55 // Load float args into F0, F2, F4, F6 (s390x uses even-numbered FPRs)54 // Load float args into F0, F2, F4, F6 (s390x uses even-numbered FPRs)
56- FMOVD syscall15Args_f1(R9), F055+ FMOVD syscallArgs_f1(R9), F0
57- FMOVD syscall15Args_f2(R9), F256+ FMOVD syscallArgs_f2(R9), F2
58- FMOVD syscall15Args_f3(R9), F457+ FMOVD syscallArgs_f3(R9), F4
59- FMOVD syscall15Args_f4(R9), F658+ FMOVD syscallArgs_f4(R9), F6
60 59
61 // Load integer args into R2-R6 (5 registers)60 // Load integer args into R2-R6 (5 registers)
62- MOVD syscall15Args_a1(R9), R261+ MOVD syscallArgs_a1(R9), R2
63- MOVD syscall15Args_a2(R9), R362+ MOVD syscallArgs_a2(R9), R3
64- MOVD syscall15Args_a3(R9), R463+ MOVD syscallArgs_a3(R9), R4
65- MOVD syscall15Args_a4(R9), R564+ MOVD syscallArgs_a4(R9), R5
66- MOVD syscall15Args_a5(R9), R665+ MOVD syscallArgs_a5(R9), R6
67 66
68- // Spill remaining args (a6-a15) onto the stack at 160(R15)67+ // Spill remaining args (a6-a32) onto the stack at 160(R15)
69 MOVD ARGP_SAVE(R15), R9 // reload args pointer68 MOVD ARGP_SAVE(R15), R9 // reload args pointer
70- MOVD syscall15Args_a6(R9), R169+ MOVD syscallArgs_a6(R9), R1
71 MOVD R1, (STACK_ARGS+0*8)(R15)70 MOVD R1, (STACK_ARGS+0*8)(R15)
72- MOVD syscall15Args_a7(R9), R171+ MOVD syscallArgs_a7(R9), R1
73 MOVD R1, (STACK_ARGS+1*8)(R15)72 MOVD R1, (STACK_ARGS+1*8)(R15)
74- MOVD syscall15Args_a8(R9), R173+ MOVD syscallArgs_a8(R9), R1
75 MOVD R1, (STACK_ARGS+2*8)(R15)74 MOVD R1, (STACK_ARGS+2*8)(R15)
76- MOVD syscall15Args_a9(R9), R175+ MOVD syscallArgs_a9(R9), R1
77 MOVD R1, (STACK_ARGS+3*8)(R15)76 MOVD R1, (STACK_ARGS+3*8)(R15)
78- MOVD syscall15Args_a10(R9), R177+ MOVD syscallArgs_a10(R9), R1
79 MOVD R1, (STACK_ARGS+4*8)(R15)78 MOVD R1, (STACK_ARGS+4*8)(R15)
80- MOVD syscall15Args_a11(R9), R179+ MOVD syscallArgs_a11(R9), R1
81 MOVD R1, (STACK_ARGS+5*8)(R15)80 MOVD R1, (STACK_ARGS+5*8)(R15)
82- MOVD syscall15Args_a12(R9), R181+ MOVD syscallArgs_a12(R9), R1
83 MOVD R1, (STACK_ARGS+6*8)(R15)82 MOVD R1, (STACK_ARGS+6*8)(R15)
84- MOVD syscall15Args_a13(R9), R183+ MOVD syscallArgs_a13(R9), R1
85 MOVD R1, (STACK_ARGS+7*8)(R15)84 MOVD R1, (STACK_ARGS+7*8)(R15)
86- MOVD syscall15Args_a14(R9), R185+ MOVD syscallArgs_a14(R9), R1
87 MOVD R1, (STACK_ARGS+8*8)(R15)86 MOVD R1, (STACK_ARGS+8*8)(R15)
88- MOVD syscall15Args_a15(R9), R187+ MOVD syscallArgs_a15(R9), R1
89 MOVD R1, (STACK_ARGS+9*8)(R15)88 MOVD R1, (STACK_ARGS+9*8)(R15)
89+ MOVD syscallArgs_a16(R9), R1
90+ MOVD R1, (STACK_ARGS+10*8)(R15)
91+ MOVD syscallArgs_a17(R9), R1
92+ MOVD R1, (STACK_ARGS+11*8)(R15)
93+ MOVD syscallArgs_a18(R9), R1
94+ MOVD R1, (STACK_ARGS+12*8)(R15)
95+ MOVD syscallArgs_a19(R9), R1
96+ MOVD R1, (STACK_ARGS+13*8)(R15)
97+ MOVD syscallArgs_a20(R9), R1
98+ MOVD R1, (STACK_ARGS+14*8)(R15)
99+ MOVD syscallArgs_a21(R9), R1
100+ MOVD R1, (STACK_ARGS+15*8)(R15)
101+ MOVD syscallArgs_a22(R9), R1
102+ MOVD R1, (STACK_ARGS+16*8)(R15)
103+ MOVD syscallArgs_a23(R9), R1
104+ MOVD R1, (STACK_ARGS+17*8)(R15)
105+ MOVD syscallArgs_a24(R9), R1
106+ MOVD R1, (STACK_ARGS+18*8)(R15)
107+ MOVD syscallArgs_a25(R9), R1
108+ MOVD R1, (STACK_ARGS+19*8)(R15)
109+ MOVD syscallArgs_a26(R9), R1
110+ MOVD R1, (STACK_ARGS+20*8)(R15)
111+ MOVD syscallArgs_a27(R9), R1
112+ MOVD R1, (STACK_ARGS+21*8)(R15)
113+ MOVD syscallArgs_a28(R9), R1
114+ MOVD R1, (STACK_ARGS+22*8)(R15)
115+ MOVD syscallArgs_a29(R9), R1
116+ MOVD R1, (STACK_ARGS+23*8)(R15)
117+ MOVD syscallArgs_a30(R9), R1
118+ MOVD R1, (STACK_ARGS+24*8)(R15)
119+ MOVD syscallArgs_a31(R9), R1
120+ MOVD R1, (STACK_ARGS+25*8)(R15)
121+ MOVD syscallArgs_a32(R9), R1
122+ MOVD R1, (STACK_ARGS+26*8)(R15)
90 123
91 // Call function124 // Call function
92- MOVD syscall15Args_fn(R9), R1125+ MOVD syscallArgs_fn(R9), R1
93 BL (R1)126 BL (R1)
94 127
95 // Restore args pointer for storing results128 // Restore args pointer for storing results
96 MOVD ARGP_SAVE(R15), R9129 MOVD ARGP_SAVE(R15), R9
97 130
98 // Store integer results back (R2, R3)131 // Store integer results back (R2, R3)
99- MOVD R2, syscall15Args_a1(R9)132+ MOVD R2, syscallArgs_a1(R9)
100- MOVD R3, syscall15Args_a2(R9)133+ MOVD R3, syscallArgs_a2(R9)
134+ MOVD $0, syscallArgs_a3(R9) // no errno here: clear the input argument
101 135
102 // Store float return values (F0, F2, F4, F6)136 // Store float return values (F0, F2, F4, F6)
103- FMOVD F0, syscall15Args_f1(R9)137+ FMOVD F0, syscallArgs_f1(R9)
104- FMOVD F2, syscall15Args_f2(R9)138+ FMOVD F2, syscallArgs_f2(R9)
105- FMOVD F4, syscall15Args_f3(R9)139+ FMOVD F4, syscallArgs_f3(R9)
106- FMOVD F6, syscall15Args_f4(R9)140+ FMOVD F6, syscallArgs_f4(R9)
107 141
108 // Deallocate stack frame142 // Deallocate stack frame
109 ADD $STACK_SIZE, R15143 ADD $STACK_SIZE, R15
M sys_unix_386.s +82 -74
@@ -29,9 +29,10 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
29 // 0-15: saved callee-saved registers (BX, SI, DI, BP)29 // 0-15: saved callee-saved registers (BX, SI, DI, BP)
30 // 16-19: saved callback index30 // 16-19: saved callback index
31 // 20-23: saved return address31 // 20-23: saved return address
32- // 24-35: callbackArgs struct (12 bytes)32+ // 24-47: callbackArgs struct (24 bytes: index, args, result[4],
33- // 36-291: copy of C arguments (256 bytes for 64 args, matching callbackMaxFrame)33+ // matching Go's callbackArgs with room for 64-bit returns)
34- // Total: 292 bytes, round up to 304 for alignment34+ // 48-303: copy of C arguments (256 bytes for 64 args, matching callbackMaxFrame)
35+ // Total: 304 bytes, already 16-byte aligned
35 SUBL $304, SP36 SUBL $304, SP
36 37
37 // Save callee-saved registers38 // Save callee-saved registers
@@ -46,148 +47,152 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
46 47
47 // Copy C arguments from original stack location to our frame48 // Copy C arguments from original stack location to our frame
48 // Original args start at 304+4(SP) = 308(SP) (past our frame + original return addr)49 // Original args start at 304+4(SP) = 308(SP) (past our frame + original return addr)
49- // Copy to our frame at 36(SP)50+ // Copy to our frame at 48(SP)
50 // Copy 64 arguments (256 bytes, matching callbackMaxFrame = 64 * ptrSize)51 // Copy 64 arguments (256 bytes, matching callbackMaxFrame = 64 * ptrSize)
51 MOVL 308(SP), AX52 MOVL 308(SP), AX
52- MOVL AX, 36(SP)53+ MOVL AX, 48(SP)
53 MOVL 312(SP), AX54 MOVL 312(SP), AX
54- MOVL AX, 40(SP)55+ MOVL AX, 52(SP)
55 MOVL 316(SP), AX56 MOVL 316(SP), AX
56- MOVL AX, 44(SP)57+ MOVL AX, 56(SP)
57 MOVL 320(SP), AX58 MOVL 320(SP), AX
58- MOVL AX, 48(SP)59+ MOVL AX, 60(SP)
59 MOVL 324(SP), AX60 MOVL 324(SP), AX
60- MOVL AX, 52(SP)61+ MOVL AX, 64(SP)
61 MOVL 328(SP), AX62 MOVL 328(SP), AX
62- MOVL AX, 56(SP)63+ MOVL AX, 68(SP)
63 MOVL 332(SP), AX64 MOVL 332(SP), AX
64- MOVL AX, 60(SP)65+ MOVL AX, 72(SP)
65 MOVL 336(SP), AX66 MOVL 336(SP), AX
66- MOVL AX, 64(SP)67+ MOVL AX, 76(SP)
67 MOVL 340(SP), AX68 MOVL 340(SP), AX
68- MOVL AX, 68(SP)69+ MOVL AX, 80(SP)
69 MOVL 344(SP), AX70 MOVL 344(SP), AX
70- MOVL AX, 72(SP)71+ MOVL AX, 84(SP)
71 MOVL 348(SP), AX72 MOVL 348(SP), AX
72- MOVL AX, 76(SP)73+ MOVL AX, 88(SP)
73 MOVL 352(SP), AX74 MOVL 352(SP), AX
74- MOVL AX, 80(SP)75+ MOVL AX, 92(SP)
75 MOVL 356(SP), AX76 MOVL 356(SP), AX
76- MOVL AX, 84(SP)77+ MOVL AX, 96(SP)
77 MOVL 360(SP), AX78 MOVL 360(SP), AX
78- MOVL AX, 88(SP)79+ MOVL AX, 100(SP)
79 MOVL 364(SP), AX80 MOVL 364(SP), AX
80- MOVL AX, 92(SP)81+ MOVL AX, 104(SP)
81 MOVL 368(SP), AX82 MOVL 368(SP), AX
82- MOVL AX, 96(SP)83+ MOVL AX, 108(SP)
83 MOVL 372(SP), AX84 MOVL 372(SP), AX
84- MOVL AX, 100(SP)85+ MOVL AX, 112(SP)
85 MOVL 376(SP), AX86 MOVL 376(SP), AX
86- MOVL AX, 104(SP)87+ MOVL AX, 116(SP)
87 MOVL 380(SP), AX88 MOVL 380(SP), AX
88- MOVL AX, 108(SP)89+ MOVL AX, 120(SP)
89 MOVL 384(SP), AX90 MOVL 384(SP), AX
90- MOVL AX, 112(SP)91+ MOVL AX, 124(SP)
91 MOVL 388(SP), AX92 MOVL 388(SP), AX
92- MOVL AX, 116(SP)93+ MOVL AX, 128(SP)
93 MOVL 392(SP), AX94 MOVL 392(SP), AX
94- MOVL AX, 120(SP)95+ MOVL AX, 132(SP)
95 MOVL 396(SP), AX96 MOVL 396(SP), AX
96- MOVL AX, 124(SP)97+ MOVL AX, 136(SP)
97 MOVL 400(SP), AX98 MOVL 400(SP), AX
98- MOVL AX, 128(SP)99+ MOVL AX, 140(SP)
99 MOVL 404(SP), AX100 MOVL 404(SP), AX
100- MOVL AX, 132(SP)101+ MOVL AX, 144(SP)
101 MOVL 408(SP), AX102 MOVL 408(SP), AX
102- MOVL AX, 136(SP)103+ MOVL AX, 148(SP)
103 MOVL 412(SP), AX104 MOVL 412(SP), AX
104- MOVL AX, 140(SP)105+ MOVL AX, 152(SP)
105 MOVL 416(SP), AX106 MOVL 416(SP), AX
106- MOVL AX, 144(SP)107+ MOVL AX, 156(SP)
107 MOVL 420(SP), AX108 MOVL 420(SP), AX
108- MOVL AX, 148(SP)109+ MOVL AX, 160(SP)
109 MOVL 424(SP), AX110 MOVL 424(SP), AX
110- MOVL AX, 152(SP)111+ MOVL AX, 164(SP)
111 MOVL 428(SP), AX112 MOVL 428(SP), AX
112- MOVL AX, 156(SP)113+ MOVL AX, 168(SP)
113 MOVL 432(SP), AX114 MOVL 432(SP), AX
114- MOVL AX, 160(SP)115+ MOVL AX, 172(SP)
115 MOVL 436(SP), AX116 MOVL 436(SP), AX
116- MOVL AX, 164(SP)117+ MOVL AX, 176(SP)
117 MOVL 440(SP), AX118 MOVL 440(SP), AX
118- MOVL AX, 168(SP)119+ MOVL AX, 180(SP)
119 MOVL 444(SP), AX120 MOVL 444(SP), AX
120- MOVL AX, 172(SP)121+ MOVL AX, 184(SP)
121 MOVL 448(SP), AX122 MOVL 448(SP), AX
122- MOVL AX, 176(SP)123+ MOVL AX, 188(SP)
123 MOVL 452(SP), AX124 MOVL 452(SP), AX
124- MOVL AX, 180(SP)125+ MOVL AX, 192(SP)
125 MOVL 456(SP), AX126 MOVL 456(SP), AX
126- MOVL AX, 184(SP)127+ MOVL AX, 196(SP)
127 MOVL 460(SP), AX128 MOVL 460(SP), AX
128- MOVL AX, 188(SP)129+ MOVL AX, 200(SP)
129 MOVL 464(SP), AX130 MOVL 464(SP), AX
130- MOVL AX, 192(SP)131+ MOVL AX, 204(SP)
131 MOVL 468(SP), AX132 MOVL 468(SP), AX
132- MOVL AX, 196(SP)133+ MOVL AX, 208(SP)
133 MOVL 472(SP), AX134 MOVL 472(SP), AX
134- MOVL AX, 200(SP)135+ MOVL AX, 212(SP)
135 MOVL 476(SP), AX136 MOVL 476(SP), AX
136- MOVL AX, 204(SP)137+ MOVL AX, 216(SP)
137 MOVL 480(SP), AX138 MOVL 480(SP), AX
138- MOVL AX, 208(SP)139+ MOVL AX, 220(SP)
139 MOVL 484(SP), AX140 MOVL 484(SP), AX
140- MOVL AX, 212(SP)141+ MOVL AX, 224(SP)
141 MOVL 488(SP), AX142 MOVL 488(SP), AX
142- MOVL AX, 216(SP)143+ MOVL AX, 228(SP)
143 MOVL 492(SP), AX144 MOVL 492(SP), AX
144- MOVL AX, 220(SP)145+ MOVL AX, 232(SP)
145 MOVL 496(SP), AX146 MOVL 496(SP), AX
146- MOVL AX, 224(SP)147+ MOVL AX, 236(SP)
147 MOVL 500(SP), AX148 MOVL 500(SP), AX
148- MOVL AX, 228(SP)149+ MOVL AX, 240(SP)
149 MOVL 504(SP), AX150 MOVL 504(SP), AX
150- MOVL AX, 232(SP)151+ MOVL AX, 244(SP)
151 MOVL 508(SP), AX152 MOVL 508(SP), AX
152- MOVL AX, 236(SP)153+ MOVL AX, 248(SP)
153 MOVL 512(SP), AX154 MOVL 512(SP), AX
154- MOVL AX, 240(SP)155+ MOVL AX, 252(SP)
155 MOVL 516(SP), AX156 MOVL 516(SP), AX
156- MOVL AX, 244(SP)157+ MOVL AX, 256(SP)
157 MOVL 520(SP), AX158 MOVL 520(SP), AX
158- MOVL AX, 248(SP)159+ MOVL AX, 260(SP)
159 MOVL 524(SP), AX160 MOVL 524(SP), AX
160- MOVL AX, 252(SP)161+ MOVL AX, 264(SP)
161 MOVL 528(SP), AX162 MOVL 528(SP), AX
162- MOVL AX, 256(SP)163+ MOVL AX, 268(SP)
163 MOVL 532(SP), AX164 MOVL 532(SP), AX
164- MOVL AX, 260(SP)165+ MOVL AX, 272(SP)
165 MOVL 536(SP), AX166 MOVL 536(SP), AX
166- MOVL AX, 264(SP)167+ MOVL AX, 276(SP)
167 MOVL 540(SP), AX168 MOVL 540(SP), AX
168- MOVL AX, 268(SP)169+ MOVL AX, 280(SP)
169 MOVL 544(SP), AX170 MOVL 544(SP), AX
170- MOVL AX, 272(SP)171+ MOVL AX, 284(SP)
171 MOVL 548(SP), AX172 MOVL 548(SP), AX
172- MOVL AX, 276(SP)173+ MOVL AX, 288(SP)
173 MOVL 552(SP), AX174 MOVL 552(SP), AX
174- MOVL AX, 280(SP)175+ MOVL AX, 292(SP)
175 MOVL 556(SP), AX176 MOVL 556(SP), AX
176- MOVL AX, 284(SP)177+ MOVL AX, 296(SP)
177 MOVL 560(SP), AX178 MOVL 560(SP), AX
178- MOVL AX, 288(SP)179+ MOVL AX, 300(SP)
179 180
180 // Set up callbackArgs struct at 24(SP)181 // Set up callbackArgs struct at 24(SP)
181 // struct callbackArgs {182 // struct callbackArgs {
182- // index uintptr // offset 0183+ // index uintptr // offset 0
183- // args *byte // offset 4184+ // args unsafe.Pointer // offset 4
184- // result uintptr // offset 8185+ // result [4]uintptr // offset 8: result[0] and result[1] hold
186+ // a 64-bit return, result[2:] are unused
185 // }187 // }
186 MOVL 16(SP), AX // callback index188 MOVL 16(SP), AX // callback index
187 MOVL AX, 24(SP) // callbackArgs.index189 MOVL AX, 24(SP) // callbackArgs.index
188- LEAL 36(SP), AX // pointer to copied arguments190+ LEAL 48(SP), AX // pointer to copied arguments
189 MOVL AX, 28(SP) // callbackArgs.args191 MOVL AX, 28(SP) // callbackArgs.args
190- MOVL $0, 32(SP) // callbackArgs.result = 0192+ MOVL $0, 32(SP) // callbackArgs.result[0] = 0
193+ MOVL $0, 36(SP) // callbackArgs.result[1] = 0
194+ MOVL $0, 40(SP) // callbackArgs.result[2] = 0
195+ MOVL $0, 44(SP) // callbackArgs.result[3] = 0
191 196
192 // Call crosscall2(fn, frame, 0, ctxt)197 // Call crosscall2(fn, frame, 0, ctxt)
193 // crosscall2 expects arguments on stack:198 // crosscall2 expects arguments on stack:
@@ -209,8 +214,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
209 214
210 ADDL $16, SP215 ADDL $16, SP
211 216
212- // Get result from callbackArgs.result217+ // Get result from callbackArgs.result. i386 cdecl returns 64-bit
218+ // values in EDX:EAX, so forward both halves written by
219+ // setInt64Result/setUint64Result.
213 MOVL 32(SP), AX220 MOVL 32(SP), AX
221+ MOVL 36(SP), DX
214 222
215 // Restore callee-saved registers223 // Restore callee-saved registers
216 MOVL 0(SP), BX224 MOVL 0(SP), BX
M sys_unix_arm.s +11 -1
@@ -33,8 +33,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
33 MOVW R2, 136(R13)33 MOVW R2, 136(R13)
34 MOVW R3, 140(R13)34 MOVW R3, 140(R13)
35 35
36+ // Skip floating point registers if runtime.goarmsoftfp!=0.
37+ MOVB runtime·goarmsoftfp(SB), R11
38+ CMP $0, R11
39+ BNE skipfpsave
36 // Save floating point registers F0-F7 at SP+64 (frame[0..15])40 // Save floating point registers F0-F7 at SP+64 (frame[0..15])
37- // Note: We always save these since we target hard-float ABI.
38 MOVD F0, 64(R13)41 MOVD F0, 64(R13)
39 MOVD F1, 72(R13)42 MOVD F1, 72(R13)
40 MOVD F2, 80(R13)43 MOVD F2, 80(R13)
@@ -44,6 +47,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
44 MOVD F6, 112(R13)47 MOVD F6, 112(R13)
45 MOVD F7, 120(R13)48 MOVD F7, 120(R13)
46 49
50+skipfpsave:
47 // Set up callbackArgs at SP+4851 // Set up callbackArgs at SP+48
48 MOVW 36(R13), R452 MOVW 36(R13), R4
49 MOVW R4, 48(R13)53 MOVW R4, 48(R13)
@@ -51,6 +55,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
51 MOVW R4, 52(R13)55 MOVW R4, 52(R13)
52 MOVW $0, R456 MOVW $0, R4
53 MOVW R4, 56(R13)57 MOVW R4, 56(R13)
58+ MOVW R4, 60(R13) // high word of a 64-bit return
54 59
55 // Call crosscall2(fn, frame, 0, ctxt)60 // Call crosscall2(fn, frame, 0, ctxt)
56 MOVW ·callbackWrap_call(SB), R061 MOVW ·callbackWrap_call(SB), R0
@@ -63,7 +68,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
63 68
64 // Get result69 // Get result
65 MOVW 56(R13), R070 MOVW 56(R13), R0
71+ MOVW 60(R13), R1 // high word of a 64-bit return
66 72
73+ MOVB runtime·goarmsoftfp(SB), R11
74+ CMP $0, R11
75+ BNE skipfprest
67 // Restore float registers76 // Restore float registers
68 MOVD 64(R13), F077 MOVD 64(R13), F0
69 MOVD 72(R13), F178 MOVD 72(R13), F1
@@ -74,6 +83,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
74 MOVD 112(R13), F683 MOVD 112(R13), F6
75 MOVD 120(R13), F784 MOVD 120(R13), F7
76 85
86+skipfprest:
77 // Restore callee-saved registers87 // Restore callee-saved registers
78 MOVW 0(R13), R488 MOVW 0(R13), R4
79 MOVW 4(R13), R589 MOVW 4(R13), R5
M sys_unix_s390x.s +10 -8
@@ -18,22 +18,24 @@
18 //18 //
19 // S390X uses R2-R6 for integer arguments (5 registers) and F0,F2,F4,F6 for floats (4 registers).19 // S390X uses R2-R6 for integer arguments (5 registers) and F0,F2,F4,F6 for floats (4 registers).
20 //20 //
21-// Our frame layout (total 264 bytes, 8-byte aligned):21+// Our frame layout (total 200 bytes, 8-byte aligned). To stay under the
22+// 800-byte nosplit limit on Go 1.27 the callbackArgs struct is stored in the
23+// caller's free linkage slot (old_R15+16..48) instead of our own frame:
22 // 0(R15) - back chain24 // 0(R15) - back chain
23 // 48(R15) - saved R6-R15 (done by STMG)25 // 48(R15) - saved R6-R15 (done by STMG)
24-// 160(R15) - callbackArgs struct (32 bytes: index, args, result, stackArgs)26+// 128(R15) - args array start
25-// 192(R15) - args array start
26 //27 //
27 // Args array layout:28 // Args array layout:
28 // - floats F0,F2,F4,F6 (32 bytes)29 // - floats F0,F2,F4,F6 (32 bytes)
29 // - ints R2-R6 (40 bytes)30 // - ints R2-R6 (40 bytes)
30-// Total args array: 72 bytes, ends at 26431+// Total args array: 72 bytes, ends at 200
31 //32 //
33+// callbackArgs struct lives at old_R15+16 (32 bytes: index, args, result, stackArgs)
32 // Stack args in caller's frame start at old_R15+16034 // Stack args in caller's frame start at old_R15+160
33 35
34-#define FRAME_SIZE 26436+#define FRAME_SIZE 200
35-#define CB_ARGS 16037+#define ARGS_ARRAY 128
36-#define ARGS_ARRAY 19238+#define CB_ARGS (FRAME_SIZE+16)
37 #define FLOAT_OFF 039 #define FLOAT_OFF 0
38 #define INT_OFF 3240 #define INT_OFF 32
39 41
@@ -71,7 +73,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
71 MOVD R5, (ARGS_ARRAY+INT_OFF+3*8)(R15)73 MOVD R5, (ARGS_ARRAY+INT_OFF+3*8)(R15)
72 74
73 // R6 (5th int arg) was saved at 48(old_R15) by STMG75 // R6 (5th int arg) was saved at 48(old_R15) by STMG
74- // old_R15 = current R15 + FRAME_SIZE, so R6 is at 48+FRAME_SIZE(R15) = 312(R15)76+ // old_R15 = current R15 + FRAME_SIZE
75 MOVD (48+FRAME_SIZE)(R15), R177 MOVD (48+FRAME_SIZE)(R15), R1
76 MOVD R1, (ARGS_ARRAY+INT_OFF+4*8)(R15)78 MOVD R1, (ARGS_ARRAY+INT_OFF+4*8)(R15)
77 79
M syscall.go +56 -65
@@ -1,66 +1,65 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4-//go:build !386 && !arm && (darwin || freebsd || linux || netbsd || windows)4+//go:build !386 && !arm && !ppc64le && (darwin || freebsd || linux || netbsd || windows)
5 5
6 package purego6 package purego
7 7
8-// CDecl marks a function as being called using the __cdecl calling convention as defined in8+import (
9-// the [MSDocs] when passed to NewCallback. It must be the first argument to the function.9+ "runtime"
10-// This is only useful on 386 Windows, but it is safe to use on other platforms.10+ "structs"
11-//11+ "unsafe"
12-// [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-17012+)
13-type CDecl struct{}
14 13
15 const (14 const (
16- maxArgs = 1515+ maxArgs = 32
17 )16 )
18 17
19-type syscall15Args struct {18+// syscallArgs is the argument block handed to the syscall15 trampoline. On
20- _ hostLayout19+// platforms that go through internal/cgo it is passed to C as a
21- fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr20+// struct syscallArgs, so its layout must match the C ABI.
22- f1, f2, f3, f4, f5, f6, f7, f8 uintptr21+//
23- arm64_r8 uintptr22+// The trampoline reports its results through this block: the return values go
23+// to a1 and a2 and, where the platform captures errno, the error code goes to
24+// a3. A trampoline that cannot capture errno must clear a3.
25+type syscallArgs struct {
26+ _ structs.HostLayout
27+
28+ fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
29+ a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32 uintptr
30+ f1, f2, f3, f4, f5, f6, f7, f8 uintptr
31+ arm64_r8 uintptr
24 }32 }
25 33
26-func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uintptr) {34+func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
27- s.fn = fn35+ s := thePool.Get().(*syscallArgs)
28- s.a1 = ints[0]36+ *s = syscallArgs{
29- s.a2 = ints[1]37+ fn: fn,
30- s.a3 = ints[2]38+ a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
31- s.a4 = ints[3]39+ a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
32- s.a5 = ints[4]40+ a9: sysargs[8], a10: sysargs[9], a11: sysargs[10], a12: sysargs[11],
33- s.a6 = ints[5]41+ a13: sysargs[12], a14: sysargs[13], a15: sysargs[14], a16: sysargs[15],
34- s.a7 = ints[6]42+ a17: sysargs[16], a18: sysargs[17], a19: sysargs[18], a20: sysargs[19],
35- s.a8 = ints[7]43+ a21: sysargs[20], a22: sysargs[21], a23: sysargs[22], a24: sysargs[23],
36- s.a9 = ints[8]44+ a25: sysargs[24], a26: sysargs[25], a27: sysargs[26], a28: sysargs[27],
37- s.a10 = ints[9]45+ a29: sysargs[28], a30: sysargs[29], a31: sysargs[30], a32: sysargs[31],
38- s.a11 = ints[10]46+ f1: floats[0], f2: floats[1], f3: floats[2], f4: floats[3],
39- s.a12 = ints[11]47+ f5: floats[4], f6: floats[5], f7: floats[6], f8: floats[7],
40- s.a13 = ints[12]48+ arm64_r8: r8,
41- s.a14 = ints[13]49+ }
42- s.a15 = ints[14]50+ runtime_cgocall(syscallXABI0, unsafe.Pointer(s))
43- s.f1 = floats[0]51+ return s
44- s.f2 = floats[1]
45- s.f3 = floats[2]
46- s.f4 = floats[3]
47- s.f5 = floats[4]
48- s.f6 = floats[5]
49- s.f7 = floats[6]
50- s.f8 = floats[7]
51- s.arm64_r8 = r8
52 }52 }
53 53
54 // SyscallN takes fn, a C function pointer and a list of arguments as uintptr.54 // SyscallN takes fn, a C function pointer and a list of arguments as uintptr.
55-// There is an internal maximum number of arguments that SyscallN can take.55+// There is an internal maximum number of arguments that SyscallN can take. It panics
56-// On 386 and arm, SyscallN supports at most 15 uintptr arguments.56+// when the maximum is exceeded. It returns the result and GetLastError on Windows or the errno on
57-// It panics when the maximum is exceeded. It returns the result and the libc error code if there is one.57+// platforms that capture it, otherwise 0.
58 //58 //
59 // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer]59 // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer]
60 // especially point 4.60 // especially point 4.
61 //61 //
62 // NOTE: SyscallN does not properly call functions that have both integer and float parameters.62 // NOTE: SyscallN does not properly call functions that have both integer and float parameters.
63-// Use RegisterFunc instead when the target function mixes integer and floating-point arguments.
64 // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-112805760763 // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607
65 // for an explanation of why that is.64 // for an explanation of why that is.
66 //65 //
@@ -79,30 +78,22 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
79 if len(args) > maxArgs {78 if len(args) > maxArgs {
80 panic("purego: too many arguments to SyscallN")79 panic("purego: too many arguments to SyscallN")
81 }80 }
82- // add padding so there is no out-of-bounds slicing81+
82+ // Add padding so there is no out-of-bounds slicing.
83 var tmp [maxArgs]uintptr83 var tmp [maxArgs]uintptr
84- copy(tmp[:], args)84+ n := copy(tmp[:], args)
85- return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14])85+ return syscallPadded(fn, &tmp, n)
86 }86 }
87 87
88-// SyscallSelf is like SyscallN but efficiently prepends self to args without allocation.88+// syscallPadded calls fn with the first n values of tmp as arguments.
89-// It is designed for method-like calls where the first argument is always a receiver pointer.89+func syscallPadded(fn uintptr, tmp *[maxArgs]uintptr, n int) (r1, r2, err uintptr) {
90-// It shares SyscallN's argument-placement limits and mixed integer/float restriction.90+ // Windows uses syscall.SyscallN in syscall_windows.go.
91-// On 386 and arm, the total count including self is limited to 15 uintptr arguments.91+ if runtime.GOOS == "windows" {
92-// This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...).92+ return syscall_syscallN(fn, tmp[:n]...)
93-//
94-//go:uintptrescapes
95-func SyscallSelf(fn, self uintptr, args ...uintptr) (r1, r2, err uintptr) {
96- if fn == 0 {
97- panic("purego: fn is nil")
98- }
99- if len(args) > maxArgs-1 {
100- panic("purego: too many arguments to SyscallSelf")
101 }93 }
102- // Use a stack-allocated array to prepend self without heap allocation.94+ var floats [maxArgs]uintptr
103- // self goes in tmp[0], remaining args follow.95+ copy(floats[:], tmp[:])
104- var tmp [maxArgs]uintptr96+ s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
105- tmp[0] = self97+ defer thePool.Put(s)
106- copy(tmp[1:], args)98+ return s.a1, s.a2, s.a3
107- return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14])
108 }99 }
M syscall_32bit.go +53 -67
@@ -5,69 +5,63 @@
5 5
6 package purego6 package purego
7 7
8-// CDecl marks a function as being called using the __cdecl calling convention as defined in8+import (
9-// the [MSDocs] when passed to NewCallback. It must be the first argument to the function.9+ "runtime"
10-// This is only useful on 386 Windows, but it is safe to use on other platforms.10+ "structs"
11-//11+ "unsafe"
12-// [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-17012+)
13-type CDecl struct{}
14 13
15 const (14 const (
16- maxArgs = 1515+ maxArgs = 32
17 )16 )
18 17
19-type syscall15Args struct {18+// syscallArgs is the argument block handed to the syscall15 trampoline. On
20- _ hostLayout19+// platforms that go through internal/cgo it is passed to C as a
20+// struct syscallArgs, so its layout must match the C ABI.
21+//
22+// The trampoline reports its results through this block: the return values go
23+// to a1 and a2 and, where the platform captures errno, the error code goes to
24+// a3. A trampoline that cannot capture errno must clear a3.
25+type syscallArgs struct {
26+ _ structs.HostLayout
27+
21 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr28 fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
22 a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32 uintptr29 a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32 uintptr
23 f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16 uintptr30 f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16 uintptr
24 arm64_r8 uintptr31 arm64_r8 uintptr
25 }32 }
26 33
27-func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uintptr) {34+func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
28- s.fn = fn35+ s := thePool.Get().(*syscallArgs)
29- s.a1 = ints[0]36+ *s = syscallArgs{
30- s.a2 = ints[1]37+ fn: fn,
31- s.a3 = ints[2]38+ a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
32- s.a4 = ints[3]39+ a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
33- s.a5 = ints[4]40+ a9: sysargs[8], a10: sysargs[9], a11: sysargs[10], a12: sysargs[11],
34- s.a6 = ints[5]41+ a13: sysargs[12], a14: sysargs[13], a15: sysargs[14], a16: sysargs[15],
35- s.a7 = ints[6]42+ a17: sysargs[16], a18: sysargs[17], a19: sysargs[18], a20: sysargs[19],
36- s.a8 = ints[7]43+ a21: sysargs[20], a22: sysargs[21], a23: sysargs[22], a24: sysargs[23],
37- s.a9 = ints[8]44+ a25: sysargs[24], a26: sysargs[25], a27: sysargs[26], a28: sysargs[27],
38- s.a10 = ints[9]45+ a29: sysargs[28], a30: sysargs[29], a31: sysargs[30], a32: sysargs[31],
39- s.a11 = ints[10]46+ f1: floats[0], f2: floats[1], f3: floats[2], f4: floats[3],
40- s.a12 = ints[11]47+ f5: floats[4], f6: floats[5], f7: floats[6], f8: floats[7],
41- s.a13 = ints[12]48+ f9: floats[8], f10: floats[9], f11: floats[10], f12: floats[11],
42- s.a14 = ints[13]49+ f13: floats[12], f14: floats[13], f15: floats[14], f16: floats[15],
43- s.a15 = ints[14]50+ arm64_r8: r8,
44- s.f1 = floats[0]51+ }
45- s.f2 = floats[1]52+ runtime_cgocall(syscallXABI0, unsafe.Pointer(s))
46- s.f3 = floats[2]53+ return s
47- s.f4 = floats[3]
48- s.f5 = floats[4]
49- s.f6 = floats[5]
50- s.f7 = floats[6]
51- s.f8 = floats[7]
52- s.f9 = floats[8]
53- s.f10 = floats[9]
54- s.f11 = floats[10]
55- s.f12 = floats[11]
56- s.f13 = floats[12]
57- s.f14 = floats[13]
58- s.f15 = floats[14]
59- s.arm64_r8 = r8
60 }54 }
61 55
62 // SyscallN takes fn, a C function pointer and a list of arguments as uintptr.56 // SyscallN takes fn, a C function pointer and a list of arguments as uintptr.
63-// On 386 and arm, SyscallN supports at most 15 uintptr arguments.57+// There is an internal maximum number of arguments that SyscallN can take. It panics
64-// It panics when the maximum is exceeded. It returns the result and the libc error code if there is one.58+// when the maximum is exceeded. It returns the result and GetLastError on Windows or the errno on
59+// platforms that capture it, otherwise 0.
65 //60 //
66 // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer]61 // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer]
67 // especially point 4.62 // especially point 4.
68 //63 //
69 // NOTE: SyscallN does not properly call functions that have both integer and float parameters.64 // NOTE: SyscallN does not properly call functions that have both integer and float parameters.
70-// Use RegisterFunc instead when the target function mixes integer and floating-point arguments.
71 // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-112805760765 // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607
72 // for an explanation of why that is.66 // for an explanation of why that is.
73 //67 //
@@ -86,30 +80,22 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
86 if len(args) > maxArgs {80 if len(args) > maxArgs {
87 panic("purego: too many arguments to SyscallN")81 panic("purego: too many arguments to SyscallN")
88 }82 }
89- // add padding so there is no out-of-bounds slicing83+
84+ // Add padding so there is no out-of-bounds slicing.
90 var tmp [maxArgs]uintptr85 var tmp [maxArgs]uintptr
91- copy(tmp[:], args)86+ n := copy(tmp[:], args)
92- return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14])87+ return syscallPadded(fn, &tmp, n)
93 }88 }
94 89
95-// SyscallSelf is like SyscallN but efficiently prepends self to args without allocation.90+// syscallPadded calls fn with the first n values of tmp as arguments.
96-// It is designed for method-like calls where the first argument is always a receiver pointer.91+func syscallPadded(fn uintptr, tmp *[maxArgs]uintptr, n int) (r1, r2, err uintptr) {
97-// It shares SyscallN's argument-placement limits and mixed integer/float restriction.92+ // Windows uses syscall.SyscallN in syscall_windows.go.
98-// On 386 and arm, the total count including self is limited to 15 uintptr arguments.93+ if runtime.GOOS == "windows" {
99-// This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...).94+ return syscall_syscallN(fn, tmp[:n]...)
100-//
101-//go:uintptrescapes
102-func SyscallSelf(fn, self uintptr, args ...uintptr) (r1, r2, err uintptr) {
103- if fn == 0 {
104- panic("purego: fn is nil")
105- }
106- if len(args) > maxArgs-1 {
107- panic("purego: too many arguments to SyscallSelf")
108 }95 }
109- // Use a stack-allocated array to prepend self without heap allocation.96+ var floats [16]uintptr
110- // self goes in tmp[0], remaining args follow.97+ copy(floats[:], tmp[:16])
111- var tmp [maxArgs]uintptr98+ s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
112- tmp[0] = self99+ defer thePool.Put(s)
113- copy(tmp[1:], args)100+ return s.a1, s.a2, s.a3
114- return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14])
115 }101 }
D syscall_32bit_internal_test.go +0 -16
@@ -1,16 +0,0 @@
1-//go:build (386 || arm) && (freebsd || linux || netbsd || windows)
2-
3-package purego
4-
5-import "testing"
6-
7-func TestSyscall15ArgsSetDoesNotPanicWithMaxArgs(t *testing.T) {
8- defer func() {
9- if r := recover(); r != nil {
10- t.Fatalf("Set panicked: %v", r)
11- }
12- }()
13-
14- var s syscall15Args
15- s.Set(1, make([]uintptr, maxArgs), make([]uintptr, maxArgs), 8)
16-}
D syscall_32bit_test.go +0 -36
@@ -1,36 +0,0 @@
1-//go:build (386 || arm) && (freebsd || linux || netbsd || windows)
2-
3-package purego_test
4-
5-import (
6- "fmt"
7- "testing"
8-
9- "github.com/bnema/purego"
10-)
11-
12-func TestSyscallN32BitPanicsAt16Args(t *testing.T) {
13- defer func() {
14- r := recover()
15- if r == nil {
16- t.Fatal("expected panic")
17- }
18- if got := fmt.Sprint(r); got != "purego: too many arguments to SyscallN" {
19- t.Fatalf("panic = %q, want %q", got, "purego: too many arguments to SyscallN")
20- }
21- }()
22- purego.SyscallN(uintptr(1), make([]uintptr, 16)...)
23-}
24-
25-func TestSyscallSelf32BitPanicsAt16Args(t *testing.T) {
26- defer func() {
27- r := recover()
28- if r == nil {
29- t.Fatal("expected panic")
30- }
31- if got := fmt.Sprint(r); got != "purego: too many arguments to SyscallSelf" {
32- t.Fatalf("panic = %q, want %q", got, "purego: too many arguments to SyscallSelf")
33- }
34- }()
35- purego.SyscallSelf(uintptr(1), 1, make([]uintptr, 15)...)
36-}
M syscall_bench_test.go +11 -11
@@ -37,7 +37,7 @@ func BenchmarkCallingMethods(b *testing.B) {
37 37
38 // Build C library for benchmarking38 // Build C library for benchmarking
39 libFileName := filepath.Join(b.TempDir(), "libbenchmark.so")39 libFileName := filepath.Join(b.TempDir(), "libbenchmark.so")
40- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "benchmarktest", "benchmark.c")); err != nil {40+ if err := buildSharedLib(b, "CC", libFileName, filepath.Join("testdata", "benchmarktest", "benchmark.c")); err != nil {
41 b.Fatalf("Failed to build C library: %v", err)41 b.Fatalf("Failed to build C library: %v", err)
42 }42 }
43 b.Cleanup(func() {43 b.Cleanup(func() {
@@ -116,7 +116,7 @@ func BenchmarkCallingMethods(b *testing.B) {
116 args := int64sToUintptrs(tc.args)116 args := int64sToUintptrs(tc.args)
117 var result uintptr117 var result uintptr
118 b.ResetTimer()118 b.ResetTimer()
119- for i := 0; i < b.N; i++ {119+ for range b.N {
120 result, _, _ = purego.SyscallN(tc.goFnPtr, args...)120 result, _, _ = purego.SyscallN(tc.goFnPtr, args...)
121 }121 }
122 b.StopTimer()122 b.StopTimer()
@@ -135,7 +135,7 @@ func BenchmarkCallingMethods(b *testing.B) {
135 args := int64sToUintptrs(tc.args)135 args := int64sToUintptrs(tc.args)
136 var result uintptr136 var result uintptr
137 b.ResetTimer()137 b.ResetTimer()
138- for i := 0; i < b.N; i++ {138+ for range b.N {
139 result, _, _ = purego.SyscallN(tc.cFnPtr, args...)139 result, _, _ = purego.SyscallN(tc.cFnPtr, args...)
140 }140 }
141 b.StopTimer()141 b.StopTimer()
@@ -165,7 +165,7 @@ func BenchmarkCallingMethods(b *testing.B) {
165 165
166 var result uintptr166 var result uintptr
167 b.ResetTimer()167 b.ResetTimer()
168- for i := 0; i < b.N; i++ {168+ for range b.N {
169 result, _, _ = purego.SyscallN(tc.cCallbackPtr, callbackArgs...)169 result, _, _ = purego.SyscallN(tc.cCallbackPtr, callbackArgs...)
170 }170 }
171 b.StopTimer()171 b.StopTimer()
@@ -205,40 +205,40 @@ func callRegisterFunc(registerFn any, n int, args []int64, iterations int) int64
205 switch n {205 switch n {
206 case 1:206 case 1:
207 f := registerFn.(*func(int64) int64)207 f := registerFn.(*func(int64) int64)
208- for i := 0; i < iterations; i++ {208+ for range iterations {
209 result = (*f)(args[0])209 result = (*f)(args[0])
210 }210 }
211 case 2:211 case 2:
212 f := registerFn.(*func(int64, int64) int64)212 f := registerFn.(*func(int64, int64) int64)
213- for i := 0; i < iterations; i++ {213+ for range iterations {
214 result = (*f)(args[0], args[1])214 result = (*f)(args[0], args[1])
215 }215 }
216 case 3:216 case 3:
217 f := registerFn.(*func(int64, int64, int64) int64)217 f := registerFn.(*func(int64, int64, int64) int64)
218- for i := 0; i < iterations; i++ {218+ for range iterations {
219 result = (*f)(args[0], args[1], args[2])219 result = (*f)(args[0], args[1], args[2])
220 }220 }
221 case 5:221 case 5:
222 f := registerFn.(*func(int64, int64, int64, int64, int64) int64)222 f := registerFn.(*func(int64, int64, int64, int64, int64) int64)
223- for i := 0; i < iterations; i++ {223+ for range iterations {
224 result = (*f)(args[0], args[1], args[2], args[3], args[4])224 result = (*f)(args[0], args[1], args[2], args[3], args[4])
225 }225 }
226 case 10:226 case 10:
227 f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)227 f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
228- for i := 0; i < iterations; i++ {228+ for range iterations {
229 result = (*f)(args[0], args[1], args[2], args[3], args[4],229 result = (*f)(args[0], args[1], args[2], args[3], args[4],
230 args[5], args[6], args[7], args[8], args[9])230 args[5], args[6], args[7], args[8], args[9])
231 }231 }
232 case 14:232 case 14:
233 f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)233 f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
234- for i := 0; i < iterations; i++ {234+ for range iterations {
235 result = (*f)(args[0], args[1], args[2], args[3], args[4],235 result = (*f)(args[0], args[1], args[2], args[3], args[4],
236 args[5], args[6], args[7], args[8], args[9],236 args[5], args[6], args[7], args[8], args[9],
237 args[10], args[11], args[12], args[13])237 args[10], args[11], args[12], args[13])
238 }238 }
239 case 15:239 case 15:
240 f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)240 f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
241- for i := 0; i < iterations; i++ {241+ for range iterations {
242 result = (*f)(args[0], args[1], args[2], args[3], args[4],242 result = (*f)(args[0], args[1], args[2], args[3], args[4],
243 args[5], args[6], args[7], args[8], args[9],243 args[5], args[6], args[7], args[8], args[9],
244 args[10], args[11], args[12], args[13], args[14])244 args[10], args[11], args[12], args[13], args[14])
M syscall_cgo_linux.go +5 -6
@@ -9,13 +9,12 @@ import (
9 "github.com/bnema/purego/internal/cgo"9 "github.com/bnema/purego/internal/cgo"
10 )10 )
11 11
12-var syscall15XABI0 = uintptr(cgo.Syscall15XABI0)12+var syscallXABI0 = uintptr(cgo.SyscallXABI0)
13-
14-//go:nosplit
15-func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
16- return cgo.Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)
17-}
18 13
19 func NewCallback(_ any) uintptr {14 func NewCallback(_ any) uintptr {
20 panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64/s390x")15 panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64/s390x")
21 }16 }
17+
18+func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
19+ panic("purego: syscall_syscallN is only supported on windows")
20+}
M syscall_linux_test.go +7 -7
@@ -10,7 +10,7 @@ import (
10 "os"10 "os"
11 "path/filepath"11 "path/filepath"
12 "runtime"12 "runtime"
13- "sort"13+ "slices"
14 "strings"14 "strings"
15 "syscall"15 "syscall"
16 "testing"16 "testing"
@@ -119,8 +119,8 @@ func compareStatus(filter, expect string) error {
119 // "... : bad file descriptor.119 // "... : bad file descriptor.
120 continue120 continue
121 }121 }
122- lines := strings.Split(string(d), "\n")122+ lines := strings.SplitSeq(string(d), "\n")
123- for _, line := range lines {123+ for line := range lines {
124 // Different kernel vintages pad differently.124 // Different kernel vintages pad differently.
125 line = strings.TrimSpace(line)125 line = strings.TrimSpace(line)
126 if strings.HasPrefix(line, "Pid:\t") {126 if strings.HasPrefix(line, "Pid:\t") {
@@ -149,7 +149,7 @@ func compareStatus(filter, expect string) error {
149 // https://github.com/golang/go/issues/46145149 // https://github.com/golang/go/issues/46145
150 // Containers don't reliably output this line in sorted order so manually sort and compare that.150 // Containers don't reliably output this line in sorted order so manually sort and compare that.
151 a := strings.Split(line[8:], " ")151 a := strings.Split(line[8:], " ")
152- sort.Slice(a, func(i, j int) bool { return a[i] < a[j] })152+ slices.Sort(a)
153 got := strings.Join(a, " ")153 got := strings.Join(a, " ")
154 if got == expected[8:] {154 if got == expected[8:] {
155 foundAThread = true155 foundAThread = true
@@ -173,7 +173,7 @@ func compareStatus(filter, expect string) error {
173 func TestDlopenThenAllThreadsSyscall(t *testing.T) {173 func TestDlopenThenAllThreadsSyscall(t *testing.T) {
174 // Step 1: Build and load a shared C library that calls back into Go.174 // Step 1: Build and load a shared C library that calls back into Go.
175 libFileName := filepath.Join(t.TempDir(), "libcbtest.so")175 libFileName := filepath.Join(t.TempDir(), "libcbtest.so")
176- if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {176+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {
177 t.Fatal(err)177 t.Fatal(err)
178 }178 }
179 defer os.Remove(libFileName)179 defer os.Remove(libFileName)
@@ -192,12 +192,12 @@ func TestDlopenThenAllThreadsSyscall(t *testing.T) {
192 }192 }
193 193
194 cb := purego.NewCallback(goFunc)194 cb := purego.NewCallback(goFunc)
195- for i := 0; i < 10; i++ {195+ for range 10 {
196 callCallback(cb, "hello")196 callCallback(cb, "hello")
197 }197 }
198 198
199 // Step 2: Generate thread churn + AllThreadsSyscall (via Setuid).199 // Step 2: Generate thread churn + AllThreadsSyscall (via Setuid).
200- for i := 0; i < 20; i++ {200+ for range 20 {
201 c := make(chan struct{})201 c := make(chan struct{})
202 go func() {202 go func() {
203 runtime.LockOSThread()203 runtime.LockOSThread()
M syscall_notstackargs.go +18 -1
@@ -17,7 +17,6 @@ import "unsafe"
17 // the runtime's own callback mechanism, so this type is compiled but unused,17 // the runtime's own callback mechanism, so this type is compiled but unused,
18 // serving only as a stub to satisfy cross-platform compilation.18 // serving only as a stub to satisfy cross-platform compilation.
19 type callbackArgs struct {19 type callbackArgs struct {
20- _ hostLayout
21 index uintptr20 index uintptr
22 // args points to the argument block.21 // args points to the argument block.
23 //22 //
@@ -36,3 +35,21 @@ type callbackArgs struct {
36 func (c *callbackArgs) stackFrame() unsafe.Pointer {35 func (c *callbackArgs) stackFrame() unsafe.Pointer {
37 return nil36 return nil
38 }37 }
38+
39+func (c *callbackArgs) intFrame() unsafe.Pointer {
40+ return nil
41+}
42+
43+func (c *callbackArgs) setInt64Result(result int64) {
44+ c.result[0] = uintptr(result)
45+ if unsafe.Sizeof(uintptr(0)) == 4 {
46+ c.result[1] = uintptr(result >> 32)
47+ }
48+}
49+
50+func (c *callbackArgs) setUint64Result(result uint64) {
51+ c.result[0] = uintptr(result)
52+ if unsafe.Sizeof(uintptr(0)) == 4 {
53+ c.result[1] = uintptr(result >> 32)
54+ }
55+}
A syscall_ppc64le.go +79 -0
@@ -0,0 +1,79 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+//go:build linux
5+
6+package purego
7+
8+import (
9+ "unsafe"
10+)
11+
12+const (
13+ maxArgs = 15
14+)
15+
16+type syscallArgs struct {
17+ fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
18+ f1, f2, f3, f4, f5, f6, f7, f8 uintptr
19+ arm64_r8 uintptr
20+}
21+
22+func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs {
23+ s := thePool.Get().(*syscallArgs)
24+ *s = syscallArgs{
25+ fn: fn,
26+ a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3],
27+ a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7],
28+ a9: sysargs[8], a10: sysargs[9], a11: sysargs[10], a12: sysargs[11],
29+ a13: sysargs[12], a14: sysargs[13], a15: sysargs[14],
30+ f1: floats[0], f2: floats[1], f3: floats[2], f4: floats[3],
31+ f5: floats[4], f6: floats[5], f7: floats[6], f8: floats[7],
32+ arm64_r8: r8,
33+ }
34+ runtime_cgocall(syscallXABI0, unsafe.Pointer(s))
35+ return s
36+}
37+
38+// SyscallN takes fn, a C function pointer and a list of arguments as uintptr.
39+// There is an internal maximum number of arguments that SyscallN can take. It panics
40+// when the maximum is exceeded. It returns the result. This file builds only for
41+// linux/ppc64le, where the trampoline does not capture errno, so err is always 0.
42+//
43+// In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer]
44+// especially point 4.
45+//
46+// NOTE: SyscallN does not properly call functions that have both integer and float parameters.
47+// See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607
48+// for an explanation of why that is.
49+//
50+// On amd64, if there are more than 8 floats the 9th and so on will be placed incorrectly on the
51+// stack.
52+//
53+// The pragma go:nosplit is not needed at this function declaration because it uses go:uintptrescapes
54+// which forces all the objects that the uintptrs point to onto the heap where a stack split won't affect
55+// their memory location.
56+//
57+//go:uintptrescapes
58+func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
59+ if fn == 0 {
60+ panic("purego: fn is nil")
61+ }
62+ if len(args) > maxArgs {
63+ panic("purego: too many arguments to SyscallN")
64+ }
65+
66+ // Add padding so there is no out-of-bounds slicing.
67+ var tmp [maxArgs]uintptr
68+ n := copy(tmp[:], args)
69+ return syscallPadded(fn, &tmp, n)
70+}
71+
72+// syscallPadded calls fn with the first n values of tmp as arguments.
73+func syscallPadded(fn uintptr, tmp *[maxArgs]uintptr, n int) (r1, r2, err uintptr) {
74+ var floats [maxArgs]uintptr
75+ copy(floats[:], tmp[:])
76+ s := syscall_SyscallN(fn, tmp[:], floats[:], 0)
77+ defer thePool.Put(s)
78+ return s.a1, s.a2, s.a3
79+}
A syscall_self.go +24 -0
@@ -0,0 +1,24 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+//go:build darwin || freebsd || linux || netbsd || windows
5+
6+package purego
7+
8+// SyscallSelf calls fn with self as the first argument followed by args.
9+// It is equivalent to SyscallN(fn, append([]uintptr{self}, args...)...)
10+// without allocating the combined argument slice.
11+//
12+//go:uintptrescapes
13+func SyscallSelf(fn, self uintptr, args ...uintptr) (r1, r2, err uintptr) {
14+ if fn == 0 {
15+ panic("purego: fn is nil")
16+ }
17+ if len(args) > maxArgs-1 {
18+ panic("purego: too many arguments to SyscallSelf")
19+ }
20+ var tmp [maxArgs]uintptr
21+ tmp[0] = self
22+ n := copy(tmp[1:], args)
23+ return syscallPadded(fn, &tmp, n+1)
24+}
A syscall_self_test.go +69 -0
@@ -0,0 +1,69 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+//go:build darwin || freebsd || linux || netbsd || windows
5+
6+package purego_test
7+
8+import (
9+ "fmt"
10+ "testing"
11+
12+ "github.com/bnema/purego"
13+)
14+
15+func TestSyscallSelf(t *testing.T) {
16+ sum2 := purego.NewCallback(func(self, a1 uintptr) uintptr { return self + a1 })
17+ sum4 := purego.NewCallback(func(self, a1, a2, a3 uintptr) uintptr { return self + a1 + a2 + a3 })
18+ triple := purego.NewCallback(func(self uintptr) uintptr { return self * 3 })
19+
20+ tests := []struct {
21+ name string
22+ fn uintptr
23+ self uintptr
24+ args []uintptr
25+ want uintptr
26+ }{
27+ {"one arg", sum2, 10, []uintptr{20}, 30},
28+ {"zero self", sum2, 0, []uintptr{42}, 42},
29+ {"multiple args", sum4, 1, []uintptr{2, 3, 4}, 10},
30+ {"no variadic args", triple, 7, nil, 21},
31+ }
32+ for _, tc := range tests {
33+ t.Run(tc.name, func(t *testing.T) {
34+ if r1, _, _ := purego.SyscallSelf(tc.fn, tc.self, tc.args...); r1 != tc.want {
35+ t.Errorf("SyscallSelf returned %d, want %d", r1, tc.want)
36+ }
37+ })
38+ }
39+}
40+
41+func TestSyscallSelfPanics(t *testing.T) {
42+ t.Run("nil fn", func(t *testing.T) {
43+ defer func() {
44+ if recover() == nil {
45+ t.Error("expected panic for nil fn")
46+ }
47+ }()
48+ purego.SyscallSelf(0, 1)
49+ })
50+
51+ t.Run("too many args", func(t *testing.T) {
52+ defer func() {
53+ if got := fmt.Sprint(recover()); got != "purego: too many arguments to SyscallSelf" {
54+ t.Errorf("unexpected panic: %s", got)
55+ }
56+ }()
57+ fn := purego.NewCallback(func(uintptr) uintptr { return 0 })
58+ purego.SyscallSelf(fn, 1, make([]uintptr, purego.MaxArgs)...)
59+ })
60+}
61+
62+func TestSyscallSelfAllocs(t *testing.T) {
63+ fn := purego.NewCallback(func(self, a1, a2 uintptr) uintptr { return self + a1 + a2 })
64+ directAllocs := testing.AllocsPerRun(100, func() { purego.SyscallN(fn, 1, 2, 3) })
65+ selfAllocs := testing.AllocsPerRun(100, func() { purego.SyscallSelf(fn, 1, 2, 3) })
66+ if selfAllocs > directAllocs {
67+ t.Fatalf("SyscallSelf allocs = %v, SyscallN allocs = %v", selfAllocs, directAllocs)
68+ }
69+}
D syscall_stackargs.go +0 -43
@@ -1,43 +0,0 @@
1-// SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3-
4-//go:build ppc64le || s390x
5-
6-package purego
7-
8-import "unsafe"
9-
10-// callbackArgs is the argument block passed from the assembly trampoline
11-// to callbackWrap when C code calls a Go callback registered with NewCallback.
12-// The assembly fills in the fields before calling callbackWrap, which uses
13-// them to determine which Go function to invoke and where to read its
14-// arguments from, and writes the return value back into result.
15-//
16-// callbackArgs is only used on Unix. On Windows, callbacks are handled by
17-// the runtime's own callback mechanism, so this type is compiled but unused,
18-// serving only as a stub to satisfy cross-platform compilation.
19-type callbackArgs struct {
20- _ hostLayout
21- index uintptr
22- // args points to the argument block.
23- //
24- // The structure of the arguments goes
25- // float registers followed by the
26- // integer registers followed by the stack.
27- //
28- // This variable is treated as a contiguous
29- // block of memory containing all of the arguments
30- // for this callback.
31- args unsafe.Pointer
32- // Below are out-args from callbackWrap
33- result [1]uintptr
34- // stackArgs points to stack-passed arguments for architectures where
35- // they can't be made contiguous with register args (e.g., ppc64le).
36- // On other architectures, this is nil and stack args are read from
37- // the end of the args block.
38- stackArgs unsafe.Pointer
39-}
40-
41-func (c *callbackArgs) stackFrame() unsafe.Pointer {
42- return c.stackArgs
43-}
A syscall_stackargs_ppc64le.go +38 -0
@@ -0,0 +1,38 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+package purego
5+
6+import "unsafe"
7+
8+// callbackArgs is the argument block passed from the assembly trampoline
9+// to callbackWrap when C code calls a Go callback registered with NewCallback.
10+type callbackArgs struct {
11+ index uintptr
12+ // args points to the argument block.
13+ //
14+ // The structure of the arguments goes
15+ // float registers followed by the
16+ // integer registers.
17+ args unsafe.Pointer
18+ // Below are out-args from callbackWrap.
19+ result [1]uintptr
20+ // stackArgs points to stack-passed arguments.
21+ stackArgs unsafe.Pointer
22+}
23+
24+func (c *callbackArgs) stackFrame() unsafe.Pointer {
25+ return c.stackArgs
26+}
27+
28+func (c *callbackArgs) intFrame() unsafe.Pointer {
29+ return nil
30+}
31+
32+func (c *callbackArgs) setInt64Result(result int64) {
33+ c.result[0] = uintptr(result)
34+}
35+
36+func (c *callbackArgs) setUint64Result(result uint64) {
37+ c.result[0] = uintptr(result)
38+}
A syscall_stackargs_s390x.go +38 -0
@@ -0,0 +1,38 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+package purego
5+
6+import "unsafe"
7+
8+// callbackArgs is the argument block passed from the assembly trampoline
9+// to callbackWrap when C code calls a Go callback registered with NewCallback.
10+type callbackArgs struct {
11+ index uintptr
12+ // args points to the argument block.
13+ //
14+ // The structure of the arguments goes
15+ // float registers followed by the
16+ // integer registers followed by the stack.
17+ args unsafe.Pointer
18+ // Below are out-args from callbackWrap.
19+ result [1]uintptr
20+ // stackArgs points to stack-passed arguments.
21+ stackArgs unsafe.Pointer
22+}
23+
24+func (c *callbackArgs) stackFrame() unsafe.Pointer {
25+ return c.stackArgs
26+}
27+
28+func (c *callbackArgs) intFrame() unsafe.Pointer {
29+ return nil
30+}
31+
32+func (c *callbackArgs) setInt64Result(result int64) {
33+ c.result[0] = uintptr(result)
34+}
35+
36+func (c *callbackArgs) setUint64Result(result uint64) {
37+ c.result[0] = uintptr(result)
38+}
M syscall_test.go +36 -83
@@ -4,11 +4,10 @@
4 package purego_test4 package purego_test
5 5
6 import (6 import (
7- "fmt"
8 "os"7 "os"
8+ "path/filepath"
9 "runtime"9 "runtime"
10 "testing"10 "testing"
11- "unsafe"
12 11
13 "github.com/bnema/purego"12 "github.com/bnema/purego"
14 "github.com/bnema/purego/internal/load"13 "github.com/bnema/purego/internal/load"
@@ -26,24 +25,54 @@ func TestOS(t *testing.T) {
26 }25 }
27 }26 }
28 27
28+// errnoIsCaptured reports whether SyscallN returns the libc errno as its third
29+// value on this platform.
30+func errnoIsCaptured() bool {
31+ switch runtime.GOOS {
32+ case "darwin":
33+ return true
34+ case "linux":
35+ switch runtime.GOARCH {
36+ case "mips", "mipsle", "mips64", "mips64le", "ppc64":
37+ return true
38+ }
39+ }
40+ return false
41+}
42+
29 func TestErrno(t *testing.T) {43 func TestErrno(t *testing.T) {
30- if runtime.GOOS != "darwin" {44+ if !errnoIsCaptured() {
31 t.Skip("platform does not support returning errno from syscall")45 t.Skip("platform does not support returning errno from syscall")
32 }46 }
33 47
34- libc, err := load.OpenLibrary("/usr/lib/libSystem.B.dylib")48+ libFileName := filepath.Join(t.TempDir(), "liberrnotest.so")
49+ if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "liberrnotest", "errno_test.c")); err != nil {
50+ t.Fatal(err)
51+ }
52+ defer os.Remove(libFileName)
53+
54+ lib, err := load.OpenLibrary(libFileName)
35 if err != nil {55 if err != nil {
36 t.Fatal(err)56 t.Fatal(err)
37 }57 }
38 58
39- openSym, err := load.OpenSymbol(libc, "open")59+ setErrno, err := load.OpenSymbol(lib, "setErrno")
40 if err != nil {60 if err != nil {
41 t.Fatal(err)61 t.Fatal(err)
42 }62 }
43 63
44- r1, _, errno := purego.SyscallN(openSym, uintptr(unsafe.Pointer(&[]byte("_file_that_does_not_exist_\x00")[0])), uintptr(os.O_RDWR))64+ r1, _, errno := purego.SyscallN(setErrno)
45 if int32(r1) != -1 {65 if int32(r1) != -1 {
46- t.Errorf("open returned %d, wanted -1", r1)66+ t.Errorf("setErrno returned %d, wanted -1", r1)
67+ }
68+
69+ libcName, err := getSystemLibrary()
70+ if err != nil {
71+ t.Fatal(err)
72+ }
73+ libc, err := load.OpenLibrary(libcName)
74+ if err != nil {
75+ t.Fatal(err)
47 }76 }
48 77
49 var strerror func(int32) string78 var strerror func(int32) string
@@ -55,79 +84,3 @@ func TestErrno(t *testing.T) {
55 t.Errorf("strerror returned %q, wanted \"%s\"", got, expected)84 t.Errorf("strerror returned %q, wanted \"%s\"", got, expected)
56 }85 }
57 }86 }
58-
59-func TestSyscallSelf(t *testing.T) {
60- // Test that SyscallSelf(fn, self, args...) produces the same result as
61- // SyscallN(fn, self, args...) for various argument counts.
62-
63- // sum2 takes (self, a1) and returns self + a1
64- sum2 := purego.NewCallback(func(self, a1 uintptr) uintptr {
65- return self + a1
66- })
67- // sum4 takes (self, a1, a2, a3) and returns self + a1 + a2 + a3
68- sum4 := purego.NewCallback(func(self, a1, a2, a3 uintptr) uintptr {
69- return self + a1 + a2 + a3
70- })
71-
72- tests := []struct {
73- name string
74- fn uintptr
75- self uintptr
76- args []uintptr
77- want uintptr
78- }{
79- {"no extra args", sum2, 10, []uintptr{20}, 30},
80- {"zero self", sum2, 0, []uintptr{42}, 42},
81- {"multiple args", sum4, 1, []uintptr{2, 3, 4}, 10},
82- {"no variadic args",
83- purego.NewCallback(func(self uintptr) uintptr { return self * 3 }),
84- 7, nil, 21},
85- }
86-
87- for _, tc := range tests {
88- t.Run(tc.name, func(t *testing.T) {
89- r1, _, _ := purego.SyscallSelf(tc.fn, tc.self, tc.args...)
90- if r1 != tc.want {
91- t.Errorf("SyscallSelf returned %d, want %d", r1, tc.want)
92- }
93-
94- // Verify it matches SyscallN with manually prepended self
95- allArgs := make([]uintptr, 0, 1+len(tc.args))
96- allArgs = append(allArgs, tc.self)
97- allArgs = append(allArgs, tc.args...)
98- r1n, _, _ := purego.SyscallN(tc.fn, allArgs...)
99- if r1 != r1n {
100- t.Errorf("SyscallSelf result %d differs from SyscallN result %d", r1, r1n)
101- }
102- })
103- }
104-}
105-
106-func TestSyscallSelfPanics(t *testing.T) {
107- t.Run("nil fn", func(t *testing.T) {
108- defer func() {
109- if r := recover(); r == nil {
110- t.Error("expected panic for nil fn")
111- }
112- }()
113- purego.SyscallSelf(0, 1)
114- })
115-
116- t.Run("too many args", func(t *testing.T) {
117- defer func() {
118- r := recover()
119- if r == nil {
120- t.Error("expected panic for too many args")
121- }
122- msg := fmt.Sprint(r)
123- if msg != "purego: too many arguments to SyscallSelf" {
124- t.Errorf("unexpected panic message: %s", msg)
125- }
126- }()
127- // Create a valid callback so we don't panic on nil fn
128- fn := purego.NewCallback(func(uintptr) uintptr { return 0 })
129- // Pass more args than maxArgs-1 allows (maxArgs is 15 on 64-bit)
130- args := make([]uintptr, 15)
131- purego.SyscallSelf(fn, 1, args...)
132- })
133-}
M syscall_unix.go +105 -62
@@ -1,8 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4-// TODO: remove s390x cgo dependency once golang/go#77449 is resolved4+//go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (s390x && (cgo || go1.27)))) || netbsd
5-//go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (cgo && s390x))) || netbsd
6 5
7 package purego6 package purego
8 7
@@ -10,6 +9,7 @@ import (
10 "encoding/json"9 "encoding/json"
11 "errors"10 "errors"
12 "fmt"11 "fmt"
12+ "math"
13 "os"13 "os"
14 "reflect"14 "reflect"
15 "runtime"15 "runtime"
@@ -22,31 +22,31 @@ import (
22 internalstrings "github.com/bnema/purego/internal/strings"22 internalstrings "github.com/bnema/purego/internal/strings"
23 )23 )
24 24
25-var syscall15XABI0 uintptr25+var syscallXABI0 uintptr
26 26
27-func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {27+func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
28- args := &syscall15Args{28+ panic("purego: syscall_syscallN is only supported on windows")
29- fn: fn,
30- a1: a1, a2: a2, a3: a3, a4: a4, a5: a5, a6: a6, a7: a7, a8: a8,
31- a9: a9, a10: a10, a11: a11, a12: a12, a13: a13, a14: a14, a15: a15,
32- f1: a1, f2: a2, f3: a3, f4: a4, f5: a5, f6: a6, f7: a7, f8: a8,
33- }
34-
35- runtime_cgocall(syscall15XABI0, unsafe.Pointer(args))
36- return args.a1, args.a2, args.a3
37 }29 }
38 30
39 // NewCallback converts a Go function to a function pointer conforming to the C calling convention.31 // NewCallback converts a Go function to a function pointer conforming to the C calling convention.
40 // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a32 // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a
41 // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size33 // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size
42 // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated34 // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated
43-// for these callbacks is never released. At least 2000 callbacks can always be created. Although this function35+// for these callbacks is never released. At least 1024 callbacks can always be created. Although this function
44 // provides similar functionality to windows.NewCallback it is distinct.36 // provides similar functionality to windows.NewCallback it is distinct.
37+//
38+// Every call to NewCallback creates a new callback even for the same function value, so passing a Go callback to C
39+// inside a loop (e.g. a qsort comparator) keeps consuming callbacks and eventually panics once they are exhausted.
40+// The same happens when a func value is passed to a C function, as [RegisterFunc] creates a new callback for each
41+// call. Create the callback once with NewCallback and reuse the returned pointer instead.
45 func NewCallback(fn any) uintptr {42 func NewCallback(fn any) uintptr {
46 ty := reflect.TypeOf(fn)43 ty := reflect.TypeOf(fn)
47- for i := 0; i < ty.NumIn(); i++ {44+ if ty == nil || ty.Kind() != reflect.Func {
45+ panic("purego: the type must be a function but was not")
46+ }
47+ for i := range ty.NumIn() {
48 in := ty.In(i)48 in := ty.In(i)
49- if !in.AssignableTo(reflect.TypeOf(CDecl{})) {49+ if !in.AssignableTo(reflect.TypeFor[CDecl]()) {
50 continue50 continue
51 }51 }
52 if i != 0 {52 if i != 0 {
@@ -101,7 +101,6 @@ func UnrefCallback(cb uintptr) error {
101 delete(cbs.knownIdx, cb)101 delete(cbs.knownIdx, cb)
102 cbs.holes[idx] = struct{}{}102 cbs.holes[idx] = struct{}{}
103 cbs.funcs[idx] = reflect.Value{}103 cbs.funcs[idx] = reflect.Value{}
104- cbs.argPools[idx] = nil
105 recordCallbackLedger("release", idx, cb, key, val, false, "")104 recordCallbackLedger("release", idx, cb, key, val, false, "")
106 return nil105 return nil
107 }106 }
@@ -134,7 +133,6 @@ func UnrefCallbackFnPtr(fnPtr any) error {
134 cbs.fnPtrKeys[idx] = 0133 cbs.fnPtrKeys[idx] = 0
135 cbs.holes[idx] = struct{}{}134 cbs.holes[idx] = struct{}{}
136 cbs.funcs[idx] = reflect.Value{}135 cbs.funcs[idx] = reflect.Value{}
137- cbs.argPools[idx] = nil
138 recordCallbackLedger("release-fnptr", idx, addr, val.Pointer(), callbackVal, false, "")136 recordCallbackLedger("release-fnptr", idx, addr, val.Pointer(), callbackVal, false, "")
139 return nil137 return nil
140 }138 }
@@ -148,7 +146,6 @@ var cbs = struct {
148 numFn int // the highest allocated callback index + 1146 numFn int // the highest allocated callback index + 1
149 holes map[int]struct{} // reusable callback slots147 holes map[int]struct{} // reusable callback slots
150 funcs [maxCB]reflect.Value // the saved callbacks148 funcs [maxCB]reflect.Value // the saved callbacks
151- argPools [maxCB]*sync.Pool // pre-allocated argument buffers per callback
152 knownIdx map[uintptr]int // callback address -> slot index149 knownIdx map[uintptr]int // callback address -> slot index
153 knownFnPtr map[uintptr]uintptr // function pointer variable address -> callback address150 knownFnPtr map[uintptr]uintptr // function pointer variable address -> callback address
154 fnPtrKeys [maxCB]uintptr // slot index -> function pointer variable address151 fnPtrKeys [maxCB]uintptr // slot index -> function pointer variable address
@@ -342,10 +339,6 @@ func traceCallbackAllocation(event string, val reflect.Value, remaining int) {
342 if traceFile == "" {339 if traceFile == "" {
343 traceFile = fmt.Sprintf("/tmp/purego-callback-trace-%d.log", os.Getpid())340 traceFile = fmt.Sprintf("/tmp/purego-callback-trace-%d.log", os.Getpid())
344 }341 }
345- // traceCallbackAllocation intentionally accepts the opt-in
346- // PUREGO_CALLBACK_TRACE_FILE path. Users must set PUREGO_CALLBACK_TRACE=1 to
347- // enable tracing, and callers who override the path control their debugging
348- // environment. This documents the deliberate gosec G304 tradeoff for audits.
349 if f, err := os.OpenFile(traceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); err == nil {342 if f, err := os.OpenFile(traceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); err == nil {
350 _, _ = f.WriteString(message)343 _, _ = f.WriteString(message)
351 _ = f.Close()344 _ = f.Close()
@@ -361,14 +354,14 @@ func compileCallback(fn any) uintptr {
361 panic("purego: function must not be nil")354 panic("purego: function must not be nil")
362 }355 }
363 ty := val.Type()356 ty := val.Type()
364- for i := 0; i < ty.NumIn(); i++ {357+ for i := range ty.NumIn() {
365 in := ty.In(i)358 in := ty.In(i)
366 switch in.Kind() {359 switch in.Kind() {
367 case reflect.Struct:360 case reflect.Struct:
368- if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) {361+ if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) {
369 continue362 continue
370 }363 }
371- ensureStructSupported()364+ ensureCallbackStructSupported()
372 checkStructFieldsSupported(in)365 checkStructFieldsSupported(in)
373 continue366 continue
374 case reflect.Interface, reflect.Func, reflect.Slice,367 case reflect.Interface, reflect.Func, reflect.Slice,
@@ -381,9 +374,13 @@ output:
381 switch {374 switch {
382 case ty.NumOut() == 1:375 case ty.NumOut() == 1:
383 switch ty.Out(0).Kind() {376 switch ty.Out(0).Kind() {
377+ case reflect.Struct:
378+ ensureCallbackStructSupported()
379+ checkStructFieldsSupported(ty.Out(0))
380+ break output
384 case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,381 case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
385 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,382 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
386- reflect.Bool, reflect.UnsafePointer, reflect.Struct:383+ reflect.Bool, reflect.UnsafePointer:
387 break output384 break output
388 }385 }
389 panic("purego: unsupported return type: " + ty.String())386 panic("purego: unsupported return type: " + ty.String())
@@ -414,12 +411,6 @@ output:
414 cbs.numFn++411 cbs.numFn++
415 }412 }
416 cbs.funcs[index] = val413 cbs.funcs[index] = val
417- numIn := ty.NumIn()
418- cbs.argPools[index] = &sync.Pool{
419- New: func() any {
420- return make([]reflect.Value, numIn)
421- },
422- }
423 addr := callbackasmAddr(index)414 addr := callbackasmAddr(index)
424 cbs.knownIdx[addr] = index415 cbs.knownIdx[addr] = index
425 if reused {416 if reused {
@@ -449,26 +440,23 @@ var callbackWrap_call = callbackWrap
449 // callbackWrap is called by assembly code which determines which Go function to call.440 // callbackWrap is called by assembly code which determines which Go function to call.
450 // This function takes the arguments and passes them to the Go function and returns the result.441 // This function takes the arguments and passes them to the Go function and returns the result.
451 func callbackWrap(a *callbackArgs) {442 func callbackWrap(a *callbackArgs) {
452- cbs.lock.RLock()443+ cbs.lock.Lock()
453 fn := cbs.funcs[a.index]444 fn := cbs.funcs[a.index]
454- pool := cbs.argPools[a.index]445+ cbs.lock.Unlock()
455- cbs.lock.RUnlock()
456 fnType := fn.Type()446 fnType := fn.Type()
457- args := pool.Get().([]reflect.Value)447+ args := make([]reflect.Value, fnType.NumIn())
458- defer func() {
459- for i := range args {
460- args[i] = reflect.Value{}
461- }
462- pool.Put(args)
463- }()
464 frame := (*[callbackMaxFrame]uintptr)(a.args)448 frame := (*[callbackMaxFrame]uintptr)(a.args)
465 // stackFrame points to stack-passed arguments. On most architectures this is449 // stackFrame points to stack-passed arguments. On most architectures this is
466 // contiguous with frame (after register args), but on ppc64le it's separate.450 // contiguous with frame (after register args), but on ppc64le it's separate.
467 var stackFrame *[callbackMaxFrame]uintptr451 var stackFrame *[callbackMaxFrame]uintptr
452+ var intFrame *[callbackMaxFrame]uintptr
468 if sf := a.stackFrame(); sf != nil {453 if sf := a.stackFrame(); sf != nil {
469 // Only ppc64le uses separate stackArgs pointer due to NOSPLIT constraints454 // Only ppc64le uses separate stackArgs pointer due to NOSPLIT constraints
470 stackFrame = (*[callbackMaxFrame]uintptr)(sf)455 stackFrame = (*[callbackMaxFrame]uintptr)(sf)
471 }456 }
457+ if intf := a.intFrame(); intf != nil {
458+ intFrame = (*[callbackMaxFrame]uintptr)(intf)
459+ }
472 // floatsN and intsN track the number of register slots used, not argument count.460 // floatsN and intsN track the number of register slots used, not argument count.
473 // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers).461 // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers).
474 var floatsN int462 var floatsN int
@@ -476,7 +464,7 @@ func callbackWrap(a *callbackArgs) {
476 // On amd64/loong64/ppc64le/riscv64/s390x, when returning a struct larger than464 // On amd64/loong64/ppc64le/riscv64/s390x, when returning a struct larger than
477 // maxRegAllocStructSize, the caller passes a hidden pointer in the first integer465 // maxRegAllocStructSize, the caller passes a hidden pointer in the first integer
478 // register. Skip it to avoid misreading it as the first function argument.466 // register. Skip it to avoid misreading it as the first function argument.
479- if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") &&467+ if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") &&
480 fnType.NumOut() == 1 && fnType.Out(0).Kind() == reflect.Struct &&468 fnType.NumOut() == 1 && fnType.Out(0).Kind() == reflect.Struct &&
481 fnType.Out(0).Size() > maxRegAllocStructSize {469 fnType.Out(0).Size() > maxRegAllocStructSize {
482 intsN = 1470 intsN = 1
@@ -494,33 +482,62 @@ func callbackWrap(a *callbackArgs) {
494 stackByteOffset := uintptr(0)482 stackByteOffset := uintptr(0)
495 for i := range args {483 for i := range args {
496 // slots is the number of pointer-sized slots the argument takes484 // slots is the number of pointer-sized slots the argument takes
497- var slots int
498 inType := fnType.In(i)485 inType := fnType.In(i)
486+ slots := int((inType.Size() + ptrSize - 1) / ptrSize)
499 switch inType.Kind() {487 switch inType.Kind() {
500 case reflect.Float32, reflect.Float64:488 case reflect.Float32, reflect.Float64:
501- slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize)489+ if isARMSoftFloat() {
490+ // we should restore from integer slot, can skip unnecessary branching here
491+ if isARMPaddingNeeded(inType, -1, intsN) {
492+ intsN++
493+ }
494+ if intsN+slots <= numOfIntegerRegisters() {
495+ // the integers begin after the floats in frame
496+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[intsN+numOfFloatRegisters()])).Elem()
497+ intsN += slots
498+ continue
499+ }
500+ if isARMPaddingNeeded(inType, -1, stackSlot) {
501+ stackSlot++
502+ }
503+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
504+ stackSlot += slots
505+ intsN += slots
506+ continue
507+ }
508+
502 if floatsN+slots > numOfFloatRegisters() {509 if floatsN+slots > numOfFloatRegisters() {
503- if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {510+ if isDarwin && runtime.GOARCH == "arm64" {
504 // Darwin ARM64: read from packed stack with proper alignment511 // Darwin ARM64: read from packed stack with proper alignment
505 args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)512 args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)
506 } else if stackFrame != nil {513 } else if stackFrame != nil {
507 // ppc64le/s390x: stack args are in separate stackFrame514 // ppc64le/s390x: stack args are in separate stackFrame
508- if runtime.GOARCH == "s390x" {515+ switch runtime.GOARCH {
516+ case "ppc64le":
517+ args[i] = callbackFloatFromDoubleSlot(unsafe.Pointer(&stackFrame[stackSlot]), inType)
518+ case "s390x":
509 // s390x big-endian: sub-8-byte values are right-justified519 // s390x big-endian: sub-8-byte values are right-justified
510 args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType)520 args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType)
511- } else {521+ default:
512 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()522 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
513 }523 }
514 stackSlot += slots524 stackSlot += slots
525+ } else if isARMFloatPaddingNeeded(inType, -1, stackSlot) {
526+ stackSlot++
527+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
528+ stackSlot += slots
515 } else {529 } else {
516 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()530 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
517 stackSlot += slots531 stackSlot += slots
518 }532 }
519 } else {533 } else {
520- if runtime.GOARCH == "s390x" {534+ switch runtime.GOARCH {
535+ case "ppc64le":
536+ args[i] = callbackFloatFromDoubleSlot(unsafe.Pointer(&frame[floatsN]), inType)
537+ case "s390x":
521 // s390x big-endian: float32 is right-justified in 8-byte FPR slot538 // s390x big-endian: float32 is right-justified in 8-byte FPR slot
522 args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType)539 args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType)
523- } else {540+ default:
524 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem()541 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem()
525 }542 }
526 }543 }
@@ -550,7 +567,7 @@ func callbackWrap(a *callbackArgs) {
550 args[i] = reflect.ValueOf(internalstrings.GoString(ptr))567 args[i] = reflect.ValueOf(internalstrings.GoString(ptr))
551 continue568 continue
552 case reflect.Struct:569 case reflect.Struct:
553- if i == 0 && inType.AssignableTo(reflect.TypeOf(CDecl{})) {570+ if i == 0 && inType.AssignableTo(reflect.TypeFor[CDecl]()) {
554 args[i] = reflect.Zero(inType)571 args[i] = reflect.Zero(inType)
555 continue572 continue
556 }573 }
@@ -561,9 +578,11 @@ func callbackWrap(a *callbackArgs) {
561 args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset)578 args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset)
562 continue579 continue
563 default:580 default:
564- slots = int((inType.Size() + ptrSize - 1) / ptrSize)581+ if isARMPaddingNeeded(inType, -1, intsN) {
582+ intsN++
583+ }
565 if intsN+slots > numOfIntegerRegisters() {584 if intsN+slots > numOfIntegerRegisters() {
566- if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {585+ if isDarwin && runtime.GOARCH == "arm64" {
567 // Darwin ARM64: read from packed stack with proper alignment586 // Darwin ARM64: read from packed stack with proper alignment
568 args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)587 args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)
569 } else if stackFrame != nil {588 } else if stackFrame != nil {
@@ -575,18 +594,26 @@ func callbackWrap(a *callbackArgs) {
575 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()594 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
576 }595 }
577 stackSlot += slots596 stackSlot += slots
597+ } else if isARMPaddingNeeded(inType, -1, stackSlot) {
598+ stackSlot++
599+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
600+ stackSlot += slots
578 } else {601 } else {
579 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()602 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
580 stackSlot += slots603 stackSlot += slots
581 }604 }
582 } else {605 } else {
583- // the integers begin after the floats in frame606+ if intFrame != nil {
584- pos := intsN + numOfFloatRegisters()607+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&intFrame[intsN])).Elem()
585- if runtime.GOARCH == "s390x" {
586- // s390x big-endian: sub-8-byte values are right-justified in GPR slot
587- args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[pos]), inType)
588 } else {608 } else {
589- args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem()609+ // the integers begin after the floats in frame
610+ pos := intsN + numOfFloatRegisters()
611+ if runtime.GOARCH == "s390x" {
612+ // s390x big-endian: sub-8-byte values are right-justified in GPR slot
613+ args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[pos]), inType)
614+ } else {
615+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem()
616+ }
590 }617 }
591 }618 }
592 intsN += slots619 intsN += slots
@@ -595,9 +622,13 @@ func callbackWrap(a *callbackArgs) {
595 ret := fn.Call(args)622 ret := fn.Call(args)
596 if len(ret) > 0 {623 if len(ret) > 0 {
597 switch k := ret[0].Kind(); k {624 switch k := ret[0].Kind(); k {
598- case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:625+ case reflect.Uint64:
626+ a.setUint64Result(ret[0].Uint())
627+ case reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
599 a.result[0] = uintptr(ret[0].Uint())628 a.result[0] = uintptr(ret[0].Uint())
600- case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:629+ case reflect.Int64:
630+ a.setInt64Result(ret[0].Int())
631+ case reflect.Int, reflect.Int32, reflect.Int16, reflect.Int8:
601 a.result[0] = uintptr(ret[0].Int())632 a.result[0] = uintptr(ret[0].Int())
602 case reflect.Bool:633 case reflect.Bool:
603 if ret[0].Bool() {634 if ret[0].Bool() {
@@ -640,6 +671,18 @@ func callbackArgFromStack(argsBase unsafe.Pointer, stackSlot int, stackByteOffse
640 return reflect.NewAt(inType, ptr).Elem()671 return reflect.NewAt(inType, ptr).Elem()
641 }672 }
642 673
674+// callbackFloatFromDoubleSlot reads a floating-point callback argument from an
675+// 8-byte register or stack slot on ppc64le, where a single-precision value is
676+// held in double-precision format.
677+func callbackFloatFromDoubleSlot(slotPtr unsafe.Pointer, inType reflect.Type) reflect.Value {
678+ if inType.Kind() != reflect.Float32 {
679+ return reflect.NewAt(inType, slotPtr).Elem()
680+ }
681+ v := reflect.New(inType).Elem()
682+ v.SetFloat(math.Float64frombits(*(*uint64)(slotPtr)))
683+ return v
684+}
685+
643 // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures.686 // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures.
644 // On s390x:687 // On s390x:
645 // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size)688 // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size)
M syscall_windows.go +14 -6
@@ -11,10 +11,10 @@ import (
11 "unsafe"11 "unsafe"
12 )12 )
13 13
14-var syscall15XABI0 uintptr14+var syscallXABI0 uintptr
15 15
16-func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {16+func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
17- r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)17+ r1, r2, errno := syscall.SyscallN(fn, args...)
18 return r1, r2, uintptr(errno)18 return r1, r2, uintptr(errno)
19 }19 }
20 20
@@ -23,14 +23,22 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a
23 // function with one uintptr-sized result. The function must not have arguments with size larger than the23 // function with one uintptr-sized result. The function must not have arguments with size larger than the
24 // size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory24 // size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory
25 // allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 102425 // allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024
26-// callbacks can always be created. Although this function is similiar to the darwin version it may act26+// callbacks can always be created. Although this function is similar to the darwin version it may act
27 // differently.27 // differently.
28+//
29+// Every call to NewCallback creates a new callback even for the same function value, so passing a Go callback to C
30+// inside a loop (e.g. a qsort comparator) keeps consuming callbacks and eventually panics once they are exhausted.
31+// The same happens when a func value is passed to a C function, as [RegisterFunc] creates a new callback for each
32+// call. Create the callback once with NewCallback and reuse the returned pointer instead.
28 func NewCallback(fn any) uintptr {33 func NewCallback(fn any) uintptr {
29 isCDecl := false34 isCDecl := false
30 ty := reflect.TypeOf(fn)35 ty := reflect.TypeOf(fn)
31- for i := 0; i < ty.NumIn(); i++ {36+ if ty == nil || ty.Kind() != reflect.Func {
37+ panic("purego: the type must be a function but was not")
38+ }
39+ for i := range ty.NumIn() {
32 in := ty.In(i)40 in := ty.In(i)
33- if !in.AssignableTo(reflect.TypeOf(CDecl{})) {41+ if !in.AssignableTo(reflect.TypeFor[CDecl]()) {
34 continue42 continue
35 }43 }
36 if i != 0 {44 if i != 0 {
M testdata/abitest/abi_test.c +82 -0
@@ -129,3 +129,85 @@ void stack_25_int64_exceeds(char *buf, size_t bufsize, int64_t a1, int64_t a2, i
129 snprintf(buf, bufsize, "%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64,129 snprintf(buf, bufsize, "%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64,
130 a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25);130 a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25);
131 }131 }
132+
133+uintptr_t stack_20_uintptr(
134+ uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5,
135+ uintptr_t a6, uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10,
136+ uintptr_t a11, uintptr_t a12, uintptr_t a13, uintptr_t a14, uintptr_t a15,
137+ uintptr_t a16, uintptr_t a17, uintptr_t a18, uintptr_t a19, uintptr_t a20
138+) {
139+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 +
140+ a11 + a12 + a13 + a14 + a15 + a16 + a17 + a18 + a19 + a20;
141+}
142+
143+uintptr_t stack_32_uintptr(
144+ uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, uintptr_t a7, uintptr_t a8,
145+ uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, uintptr_t a13, uintptr_t a14, uintptr_t a15, uintptr_t a16,
146+ uintptr_t a17, uintptr_t a18, uintptr_t a19, uintptr_t a20, uintptr_t a21, uintptr_t a22, uintptr_t a23, uintptr_t a24,
147+ uintptr_t a25, uintptr_t a26, uintptr_t a27, uintptr_t a28, uintptr_t a29, uintptr_t a30, uintptr_t a31, uintptr_t a32
148+) {
149+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 +
150+ a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16 +
151+ a17 + a18 + a19 + a20 + a21 + a22 + a23 + a24 +
152+ a25 + a26 + a27 + a28 + a29 + a30 + a31 + a32;
153+}
154+
155+double stack_32_mixed_int_float(
156+ uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, uintptr_t a7, uintptr_t a8,
157+ uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, uintptr_t a13, uintptr_t a14, uintptr_t a15, uintptr_t a16,
158+ double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8,
159+ double f9, double f10, double f11, double f12, double f13, double f14, double f15, double f16
160+) {
161+ return (double)a1 * 1 + (double)a2 * 2 + (double)a3 * 3 + (double)a4 * 4 +
162+ (double)a5 * 5 + (double)a6 * 6 + (double)a7 * 7 + (double)a8 * 8 +
163+ (double)a9 * 9 + (double)a10 * 10 + (double)a11 * 11 + (double)a12 * 12 +
164+ (double)a13 * 13 + (double)a14 * 14 + (double)a15 * 15 + (double)a16 * 16 +
165+ f1 * 17 + f2 * 18 + f3 * 19 + f4 * 20 +
166+ f5 * 21 + f6 * 22 + f7 * 23 + f8 * 24 +
167+ f9 * 25 + f10 * 26 + f11 * 27 + f12 * 28 +
168+ f13 * 29 + f14 * 30 + f15 * 31 + f16 * 32;
169+}
170+
171+int64_t arm_int64_unaligned_in_registers(uintptr_t a1, int64_t a2) {
172+ return (int64_t)a1 * 123 + a2;
173+}
174+
175+int64_t arm_int64_unaligned_on_stack(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, int64_t a6) {
176+ return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 +
177+ (int64_t)a5 * 5 + a6;
178+}
179+
180+int64_t arm_int64_unaligned_on_stack_after_floats(
181+ uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, /* these args are going to integer registers */
182+ float f1, float f2, float f3, float f4, /* these args are going to float registers */
183+ float f5, float f6, float f7, float f8,
184+ float f9, float f10, float f11, float f12,
185+ float f13, float f14, float f15, float f16,
186+ float f17, /* this is a first arg on stack */
187+ int64_t a5 /* this arg must be padded */
188+) {
189+ return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + a5;
190+}
191+
192+double arm_float64_unaligned_in_registers(uintptr_t a1, double a2) {
193+ return (double)a1 * 123.5 + a2;
194+}
195+
196+double arm_float64_unaligned_on_stack(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, double a6) {
197+ return (double)a1 * 1 + (double)a2 * 2 + (double)a3 * 3 + (double)a4 * 4 +
198+ (double)a5 * 5 + a6;
199+}
200+
201+typedef int32_t (*AddFunc)(int32_t, int32_t);
202+
203+int32_t returned_add(int32_t a, int32_t b) {
204+ return a + b;
205+}
206+
207+AddFunc return_func_ptr(void) {
208+ return returned_add;
209+}
210+
211+AddFunc return_null_func_ptr(void) {
212+ return NULL;
213+}
A testdata/liberrnotest/errno_test.c +10 -0
@@ -0,0 +1,10 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+#include <errno.h>
5+
6+// setErrno fails the way a C library call does: it returns -1 and sets errno.
7+int setErrno(void) {
8+ errno = ENOENT;
9+ return -1;
10+}
A testdata/slicetest/slice_test.c +34 -0
@@ -0,0 +1,34 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+#include <stddef.h>
5+#include <string.h>
6+
7+static char buf[256];
8+
9+const char *join_strings(const char **strs) {
10+ buf[0] = '\0';
11+ if (strs == NULL) {
12+ return "(null)";
13+ }
14+ for (size_t i = 0; strs[i] != NULL; i++) {
15+ if (i > 0) {
16+ strcat(buf, ",");
17+ }
18+ strcat(buf, strs[i]);
19+ }
20+ return buf;
21+}
22+
23+static const char *items[] = {"alpha", "beta", "gamma", NULL};
24+
25+const char **get_strings(int empty) {
26+ if (empty) {
27+ return NULL;
28+ }
29+ return items;
30+}
31+
32+float sum_floats4(const float v[4]) {
33+ return v[0] + v[1] + v[2] + v[3];
34+}
M testdata/structtest/struct_test.c +33 -38
@@ -5,16 +5,11 @@
5 #include <stdio.h>5 #include <stdio.h>
6 #include <stdlib.h>6 #include <stdlib.h>
7 7
8-#if defined(__x86_64__) || defined(__aarch64__)
9-typedef int64_t GoInt;
10-typedef uint64_t GoUint;
11-#endif
12-
13 // Empty is empty8 // Empty is empty
14 struct Empty {};9 struct Empty {};
15 10
16 // NoStruct tests that an empty struct doesn't cause issues11 // NoStruct tests that an empty struct doesn't cause issues
17-unsigned long NoStruct(struct Empty e) {12+uint64_t NoStruct(struct Empty e) {
18 return 0xdeadbeef;13 return 0xdeadbeef;
19 }14 }
20 15
@@ -22,28 +17,28 @@ struct EmptyEmpty {
22 struct Empty x;17 struct Empty x;
23 };18 };
24 19
25-unsigned long EmptyEmpty(struct EmptyEmpty e) {20+uint64_t EmptyEmpty(struct EmptyEmpty e) {
26 return 0xdeadbeef;21 return 0xdeadbeef;
27 }22 }
28 23
29-unsigned long EmptyEmptyWithReg(unsigned int x, struct EmptyEmpty e, unsigned int y) {24+uint64_t EmptyEmptyWithReg(unsigned int x, struct EmptyEmpty e, unsigned int y) {
30 return (x << 16) | y;25 return (x << 16) | y;
31 }26 }
32 27
33 // GreaterThan16Bytes is 24 bytes on 64 bit systems28 // GreaterThan16Bytes is 24 bytes on 64 bit systems
34 struct GreaterThan16Bytes {29 struct GreaterThan16Bytes {
35- long *x, *y, *z;30+ int64_t *x, *y, *z;
36 };31 };
37 32
38 // GreaterThan16Bytes is a basic test for structs bigger than 16 bytes33 // GreaterThan16Bytes is a basic test for structs bigger than 16 bytes
39-unsigned long GreaterThan16Bytes(struct GreaterThan16Bytes g) {34+uint64_t GreaterThan16Bytes(struct GreaterThan16Bytes g) {
40 return *g.x + *g.y + *g.z;35 return *g.x + *g.y + *g.z;
41 }36 }
42 37
43 // AfterRegisters tests to make sure that structs placed on the stack work properly38 // AfterRegisters tests to make sure that structs placed on the stack work properly
44-unsigned long AfterRegisters(long a, long b, long c, long d, long e, long f, long g, long h, struct GreaterThan16Bytes bytes) {39+uint64_t AfterRegisters(intptr_t a, intptr_t b, intptr_t c, intptr_t d, intptr_t e, intptr_t f, intptr_t g, intptr_t h, struct GreaterThan16Bytes bytes) {
45- long registers = a + b + c + d + e + f + g + h;40+ intptr_t registers = a + b + c + d + e + f + g + h;
46- long stack = *bytes.x + *bytes.y + *bytes.z;41+ int64_t stack = *bytes.x + *bytes.y + *bytes.z;
47 if (registers != stack) {42 if (registers != stack) {
48 return 0xbadbad;43 return 0xbadbad;
49 }44 }
@@ -53,25 +48,25 @@ unsigned long AfterRegisters(long a, long b, long c, long d, long e, long f, lon
53 return stack;48 return stack;
54 }49 }
55 50
56-unsigned long BeforeRegisters(struct GreaterThan16Bytes bytes, long a, long b) {51+uint64_t BeforeRegisters(struct GreaterThan16Bytes bytes, int64_t a, int64_t b) {
57 return *bytes.x + *bytes.y + *bytes.z + a + b;52 return *bytes.x + *bytes.y + *bytes.z + a + b;
58 }53 }
59 54
60 struct GreaterThan16BytesStruct {55 struct GreaterThan16BytesStruct {
61 struct {56 struct {
62- long *x, *y, *z;57+ int64_t *x, *y, *z;
63 } a ;58 } a ;
64 };59 };
65 60
66-unsigned long GreaterThan16BytesStruct(struct GreaterThan16BytesStruct g) {61+uint64_t GreaterThan16BytesStruct(struct GreaterThan16BytesStruct g) {
67 return *(g.a.x) + *(g.a.y) + *(g.a.z);62 return *(g.a.x) + *(g.a.y) + *(g.a.z);
68 }63 }
69 64
70 struct IntLessThan16Bytes {65 struct IntLessThan16Bytes {
71- long x, y;66+ int64_t x, y;
72 };67 };
73 68
74-unsigned long IntLessThan16Bytes(struct IntLessThan16Bytes l) {69+uint64_t IntLessThan16Bytes(struct IntLessThan16Bytes l) {
75 return l.x + l.y;70 return l.x + l.y;
76 }71 }
77 72
@@ -201,23 +196,23 @@ struct Short {
201 unsigned short a, b, c, d;196 unsigned short a, b, c, d;
202 };197 };
203 198
204-unsigned long Short(struct Short s) {199+uint64_t Short(struct Short s) {
205- return (long)s.a << 48 | (long)s.b << 32 | (long)s.c << 16 | (long)s.d << 0;200+ return (uint64_t)s.a << 48 | (uint64_t)s.b << 32 | (uint64_t)s.c << 16 | (uint64_t)s.d << 0;
206 }201 }
207 202
208 struct Int {203 struct Int {
209 unsigned int a, b;204 unsigned int a, b;
210 };205 };
211 206
212-unsigned long Int(struct Int i) {207+uint64_t Int(struct Int i) {
213- return (long)i.a << 32 | (long)i.b << 0;208+ return (uint64_t)i.a << 32 | (uint64_t)i.b << 0;
214 }209 }
215 210
216 struct Long {211 struct Long {
217- unsigned long a;212+ uint64_t a;
218 };213 };
219 214
220-unsigned long Long(struct Long l) {215+uint64_t Long(struct Long l) {
221 return l.a;216 return l.a;
222 }217 }
223 218
@@ -327,31 +322,31 @@ struct Content {
327 struct { double width, height; } size;322 struct { double width, height; } size;
328 };323 };
329 324
330-unsigned long InitWithContentRect(int *win, struct Content c, int style, int backing, _Bool flag) {325+uint64_t InitWithContentRect(int *win, struct Content c, int style, int backing, _Bool flag) {
331 if (win == 0)326 if (win == 0)
332 return 0xBAD;327 return 0xBAD;
333 if (!flag)328 if (!flag)
334 return 0xF1A6; // FLAG329 return 0xF1A6; // FLAG
335- return (unsigned long)(c.point.x + c.point.y + c.size.width + c.size.height) / (style - backing);330+ return (uint64_t)(c.point.x + c.point.y + c.size.width + c.size.height) / (style - backing);
336 }331 }
337 332
338 struct GoInt4 {333 struct GoInt4 {
339- GoInt a, b, c, d;334+ intptr_t a, b, c, d;
340 };335 };
341 336
342-GoInt GoInt4(struct GoInt4 g) {337+intptr_t GoInt4(struct GoInt4 g) {
343 return g.a + g.b - g.c + g.d;338 return g.a + g.b - g.c + g.d;
344 }339 }
345 340
346 struct GoUint4 {341 struct GoUint4 {
347- GoUint a, b, c, d;342+ uintptr_t a, b, c, d;
348 };343 };
349 344
350-GoUint GoUint4(struct GoUint4 g) {345+uintptr_t GoUint4(struct GoUint4 g) {
351 return g.a + g.b + g.c + g.d;346 return g.a + g.b + g.c + g.d;
352 }347 }
353 348
354-GoUint TakeGoUintAndReturn(GoUint a) {349+uintptr_t TakeGoUintAndReturn(uintptr_t a) {
355 return a;350 return a;
356 }351 }
357 352
@@ -395,7 +390,7 @@ uintptr_t AddPointers(struct TwoPointers wrapper) {
395 // Identity functions for round-trip testing of struct arguments390 // Identity functions for round-trip testing of struct arguments
396 391
397 struct OneInt64 {392 struct OneInt64 {
398- long long a;393+ int64_t a;
399 };394 };
400 395
401 struct OneInt64 IdentityOneInt64(struct OneInt64 s) {396 struct OneInt64 IdentityOneInt64(struct OneInt64 s) {
@@ -423,7 +418,7 @@ struct FloatAndInt IdentityFloatAndInt(struct FloatAndInt s) {
423 }418 }
424 419
425 struct ThreeInt64 {420 struct ThreeInt64 {
426- long long a, b, c;421+ int64_t a, b, c;
427 };422 };
428 423
429 struct ThreeInt64 IdentityThreeInt64(struct ThreeInt64 s) {424 struct ThreeInt64 IdentityThreeInt64(struct ThreeInt64 s) {
@@ -431,16 +426,16 @@ struct ThreeInt64 IdentityThreeInt64(struct ThreeInt64 s) {
431 }426 }
432 427
433 struct PtrInt64Ptr {428 struct PtrInt64Ptr {
434- long long *a;429+ int64_t *a;
435- long long b;430+ int64_t b;
436- long long *c;431+ int64_t *c;
437 };432 };
438 433
439 struct PtrInt64Ptr IdentityPtrInt64Ptr(struct PtrInt64Ptr s) {434 struct PtrInt64Ptr IdentityPtrInt64Ptr(struct PtrInt64Ptr s) {
440 return s;435 return s;
441 }436 }
442 437
443-struct IntLessThan16Bytes IdentityTwoInt64AfterPrims(long long x, double y, struct IntLessThan16Bytes s) {438+struct IntLessThan16Bytes IdentityTwoInt64AfterPrims(int64_t x, double y, struct IntLessThan16Bytes s) {
444 return s;439 return s;
445 }440 }
446 441
@@ -449,7 +444,7 @@ struct FloatLessThan16Bytes IdentityTwoFloat32AfterFloats(double x, double y, st
449 }444 }
450 445
451 struct Mixed5Args {446 struct Mixed5Args {
452- long long *a;447+ int64_t *a;
453 int32_t b;448 int32_t b;
454 float c;449 float c;
455 int32_t d;450 int32_t d;
M wincallback.go +8 -8
@@ -35,7 +35,7 @@ func genasm386() {
35 35
36 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $036 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
37 `)37 `)
38- for i := 0; i < maxCallback; i++ {38+ for i := range maxCallback {
39 fmt.Fprintf(&buf, "\tMOVL $%d, CX\n", i)39 fmt.Fprintf(&buf, "\tMOVL $%d, CX\n", i)
40 buf.WriteString("\tJMP callbackasm1(SB)\n")40 buf.WriteString("\tJMP callbackasm1(SB)\n")
41 }41 }
@@ -63,7 +63,7 @@ func genasmAmd64() {
63 63
64 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $064 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
65 `)65 `)
66- for i := 0; i < maxCallback; i++ {66+ for range maxCallback {
67 buf.WriteString("\tCALL callbackasm1(SB)\n")67 buf.WriteString("\tCALL callbackasm1(SB)\n")
68 }68 }
69 if err := os.WriteFile("zcallback_amd64.s", buf.Bytes(), 0644); err != nil {69 if err := os.WriteFile("zcallback_amd64.s", buf.Bytes(), 0644); err != nil {
@@ -90,7 +90,7 @@ func genasmArm() {
90 90
91 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $091 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
92 `)92 `)
93- for i := 0; i < maxCallback; i++ {93+ for i := range maxCallback {
94 fmt.Fprintf(&buf, "\tMOVW $%d, R12\n", i)94 fmt.Fprintf(&buf, "\tMOVW $%d, R12\n", i)
95 buf.WriteString("\tB callbackasm1(SB)\n")95 buf.WriteString("\tB callbackasm1(SB)\n")
96 }96 }
@@ -118,7 +118,7 @@ func genasmArm64() {
118 118
119 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0119 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
120 `)120 `)
121- for i := 0; i < maxCallback; i++ {121+ for i := range maxCallback {
122 fmt.Fprintf(&buf, "\tMOVD $%d, R12\n", i)122 fmt.Fprintf(&buf, "\tMOVD $%d, R12\n", i)
123 buf.WriteString("\tB callbackasm1(SB)\n")123 buf.WriteString("\tB callbackasm1(SB)\n")
124 }124 }
@@ -146,7 +146,7 @@ func genasmLoong64() {
146 146
147 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0147 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
148 `)148 `)
149- for i := 0; i < maxCallback; i++ {149+ for i := range maxCallback {
150 fmt.Fprintf(&buf, "\tMOVV $%d, R12\n", i)150 fmt.Fprintf(&buf, "\tMOVV $%d, R12\n", i)
151 buf.WriteString("\tJMP callbackasm1(SB)\n")151 buf.WriteString("\tJMP callbackasm1(SB)\n")
152 }152 }
@@ -174,7 +174,7 @@ func genasmPpc64le() {
174 174
175 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0175 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
176 `)176 `)
177- for i := 0; i < maxCallback; i++ {177+ for i := range maxCallback {
178 fmt.Fprintf(&buf, "\tMOVD $%d, R11\n", i)178 fmt.Fprintf(&buf, "\tMOVD $%d, R11\n", i)
179 buf.WriteString("\tBR callbackasm1(SB)\n")179 buf.WriteString("\tBR callbackasm1(SB)\n")
180 }180 }
@@ -206,7 +206,7 @@ func genasmRiscv64() {
206 206
207 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0207 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
208 `)208 `)
209- for i := 0; i < maxCallback; i++ {209+ for i := range maxCallback {
210 space := " "210 space := " "
211 if i <= 32 {211 if i <= 32 {
212 fmt.Fprintf(&buf, "\tPCALIGN $8\n")212 fmt.Fprintf(&buf, "\tPCALIGN $8\n")
@@ -240,7 +240,7 @@ func genasmS390x() {
240 240
241 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0241 TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
242 `)242 `)
243- for i := 0; i < maxCallback; i++ {243+ for i := range maxCallback {
244 fmt.Fprintf(&buf, "\tMOVD $%d, R0\n", i)244 fmt.Fprintf(&buf, "\tMOVD $%d, R0\n", i)
245 buf.WriteString("\tBR callbackasm1(SB)\n")245 buf.WriteString("\tBR callbackasm1(SB)\n")
246 }246 }

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