Merge pull request #3 from bnema/realign-upstream
chore: realign fork with upstream ebitengine/purego
104 files · +4501 -2327
M .github/ISSUE_TEMPLATE/config.yml +1 -1
| @@ -2,4 +2,4 @@ blank_issues_enabled: false | |||
| 2 | 2 | contact_links: | |
| 3 | 3 | - name: Questions | |
| 4 | 4 | about: Please use one of the forums for questions or general discussions | |
| 5 | - | url: https://github.com/bnema/purego/discussions | |
| 5 | + | url: https://github.com/ebitengine/purego/discussions | |
M .github/workflows/test.yml +168 -37
| @@ -3,15 +3,15 @@ name: Test | |||
| 3 | 3 | on: [push, pull_request] | |
| 4 | 4 | ||
| 5 | 5 | env: | |
| 6 | - | QEMU_VERSION: "11.0.1+ds-1" | |
| 6 | + | QEMU_MAJOR_VERSION: "10" | |
| 7 | 7 | ||
| 8 | 8 | jobs: | |
| 9 | 9 | test: | |
| 10 | 10 | strategy: | |
| 11 | 11 | fail-fast: false | |
| 12 | 12 | matrix: | |
| 13 | - | os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest] | |
| 14 | - | go: ['1.20.x', '1.21.x', '1.22.x', '1.23.x', '1.24.x', '1.25.x', '1.26.x'] | |
| 13 | + | os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest, windows-11-arm] | |
| 14 | + | go: ['1.25.x', '1.26.x', '1.27.x'] | |
| 15 | 15 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | |
| 16 | 16 | runs-on: ${{ matrix.os }} | |
| 17 | 17 | defaults: | |
| @@ -32,8 +32,8 @@ jobs: | |||
| 32 | 32 | with: | |
| 33 | 33 | go-version: ${{ matrix.go }} | |
| 34 | 34 | ||
| 35 | - | - name: Set up the prerequisites | |
| 36 | - | if: runner.os == 'Windows' | |
| 35 | + | - name: Set up Msys2 | |
| 36 | + | if: runner.os == 'Windows' && runner.arch != 'ARM64' | |
| 37 | 37 | uses: msys2/setup-msys2@v2 | |
| 38 | 38 | ||
| 39 | 39 | - name: go vet | |
| @@ -76,13 +76,21 @@ jobs: | |||
| 76 | 76 | ||
| 77 | 77 | - name: go build (Linux minor architectures) | |
| 78 | 78 | # Test them only on the latest Go to reduce CI time. | |
| 79 | - | # s390x cannot be tested here as it requires Cgo. | |
| 80 | - | if: startsWith(matrix.go, '1.26.') | |
| 79 | + | if: startsWith(matrix.go, '1.27.') | |
| 81 | 80 | run: | | |
| 82 | 81 | # Check cross-compiling Linux binaries for minor architectures. | |
| 83 | 82 | env GOOS=linux GOARCH=loong64 go build -v ./... | |
| 84 | 83 | env GOOS=linux GOARCH=ppc64le go build -v ./... | |
| 85 | 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 | 95 | - name: go build (plugin) | |
| 88 | 96 | if: runner.os == 'Linux' || runner.os == 'macOS' | |
| @@ -120,32 +128,68 @@ jobs: | |||
| 120 | 128 | if: runner.os == 'Windows' | |
| 121 | 129 | run: | | |
| 122 | 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 | 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 | 146 | - name: go test (Linux arm) | |
| 126 | 147 | if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} | |
| 127 | 148 | run: | | |
| 128 | - | sudo apt-get update | |
| 129 | - | sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross | |
| 130 | - | ||
| 131 | - | # Install QEMU 10.x from Debian trixie. Ubuntu 24.04 ships QEMU 8.2 | |
| 132 | - | # which has a bug where inter-thread signal delivery (tgkill) hangs, | |
| 133 | - | # causing TestSetuidEtc to deadlock. | |
| 134 | - | wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user_${QEMU_VERSION}_arm64.deb -O /tmp/qemu-user.deb | |
| 135 | - | wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_${QEMU_VERSION}_arm64.deb -O /tmp/qemu-user-binfmt.deb | |
| 136 | - | sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.deb | |
| 137 | - | sudo apt-get -f install -y | |
| 138 | - | ||
| 139 | - | # Create symlink for the dynamic linker that QEMU expects | |
| 140 | - | sudo ln -sf /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3 | |
| 141 | - | go env -w CC=arm-linux-gnueabihf-gcc | |
| 142 | - | go env -w CXX=arm-linux-gnueabihf-g++ | |
| 143 | - | env GOOS=linux GOARCH=arm CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |
| 144 | - | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |
| 145 | - | env GOOS=linux GOARCH=arm CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |
| 146 | - | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |
| 147 | - | go env -u CC | |
| 148 | - | go env -u CXX | |
| 149 | + | sudo apt-get update | |
| 150 | + | sudo apt-get install -y \ | |
| 151 | + | gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross \ | |
| 152 | + | gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libc6-armel-cross | |
| 153 | + | ||
| 154 | + | # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2 | |
| 155 | + | # which has a bug where inter-thread signal delivery (tgkill) hangs, | |
| 156 | + | # causing TestSetuidEtc to deadlock. | |
| 157 | + | arch=$(dpkg --print-architecture) | |
| 158 | + | qemu_user_deb=$( | |
| 159 | + | curl -fsSL https://ftp.debian.org/debian/pool/main/q/qemu/ | | |
| 160 | + | grep -Eo "qemu-user_${QEMU_MAJOR_VERSION}\.[0-9.]+\+ds-[0-9]+([+~][A-Za-z0-9.+:~-]+)*_${arch}\.deb" | | |
| 161 | + | sort -V | | |
| 162 | + | tail -n 1 | |
| 163 | + | ) | |
| 164 | + | if [ -z "${qemu_user_deb}" ]; then | |
| 165 | + | echo "missing qemu-user ${QEMU_MAJOR_VERSION}.x package for ${arch}" >&2 | |
| 166 | + | exit 1 | |
| 167 | + | fi | |
| 168 | + | wget -q "https://ftp.debian.org/debian/pool/main/q/qemu/${qemu_user_deb}" -O /tmp/qemu-user.deb | |
| 169 | + | 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 | 194 | - name: go test race (no Cgo) | |
| 151 | 195 | if: runner.os == 'macOS' | |
| @@ -154,6 +198,7 @@ jobs: | |||
| 154 | 198 | env CGO_ENABLED=0 go test -race -shuffle=on -v -count=10 ./... | |
| 155 | 199 | ||
| 156 | 200 | - name: go test race (Cgo) | |
| 201 | + | if: ${{ !(runner.os == 'Windows' && runner.arch == 'ARM64') }} | |
| 157 | 202 | run: | | |
| 158 | 203 | env CGO_ENABLED=1 go test -race -shuffle=on -v -count=10 ./... | |
| 159 | 204 | ||
| @@ -161,9 +206,7 @@ jobs: | |||
| 161 | 206 | strategy: | |
| 162 | 207 | fail-fast: false | |
| 163 | 208 | matrix: | |
| 164 | - | # Test only the latest two Go versions to save CI time. | |
| 165 | - | # See https://go.dev/doc/devel/release#policy | |
| 166 | - | go: ['1.25.x', '1.26.x'] | |
| 209 | + | go: ['1.25.x', '1.26.x', '1.27.x'] | |
| 167 | 210 | name: Test with Go ${{ matrix.go }} on Linux minor architectures | |
| 168 | 211 | runs-on: ubuntu-latest | |
| 169 | 212 | defaults: | |
| @@ -187,12 +230,22 @@ jobs: | |||
| 187 | 230 | gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \ | |
| 188 | 231 | gcc-s390x-linux-gnu g++-s390x-linux-gnu | |
| 189 | 232 | ||
| 190 | - | # Install QEMU 10.x from Debian trixie. Ubuntu 24.04 ships QEMU 8.2 | |
| 233 | + | # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2 | |
| 191 | 234 | # which has a bug where inter-thread signal delivery (tgkill) hangs, | |
| 192 | 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.deb | |
| 194 | - | wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_${QEMU_VERSION}_amd64.deb -O /tmp/qemu-user-binfmt.deb | |
| 195 | - | sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.deb | |
| 236 | + | arch=$(dpkg --print-architecture) | |
| 237 | + | qemu_user_deb=$( | |
| 238 | + | 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 | 249 | sudo apt-get -f install -y | |
| 197 | 250 | ||
| 198 | 251 | # Verify that the expected QEMU binaries are installed and usable. | |
| @@ -240,6 +293,11 @@ jobs: | |||
| 240 | 293 | run: | | |
| 241 | 294 | go env -w CC=s390x-linux-gnu-gcc | |
| 242 | 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 | 301 | env GOOS=linux GOARCH=s390x CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |
| 244 | 302 | env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |
| 245 | 303 | go env -u CC | |
| @@ -250,8 +308,6 @@ jobs: | |||
| 250 | 308 | fail-fast: false | |
| 251 | 309 | matrix: | |
| 252 | 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 | 311 | go: ['1.25.8', '1.26.1'] | |
| 256 | 312 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | |
| 257 | 313 | runs-on: ubuntu-22.04 | |
| @@ -281,3 +337,78 @@ jobs: | |||
| 281 | 337 | rm -fr /usr/local/go && tar -C /usr/local -xf go${{matrix.go}}.netbsd-amd64.tar.gz | |
| 282 | 338 | chmod +x $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh | |
| 283 | 339 | 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 | 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 | 6 | > This is beta software so expect bugs and potentially API breaking changes | |
| 9 | 7 | > but each release will be tagged to avoid breaking people's code. | |
| 10 | 8 | > Bug reports are encouraged. | |
| @@ -36,24 +34,27 @@ Tier 1 platforms are the primary targets officially supported by PureGo. When a | |||
| 36 | 34 | - **iOS**: amd64<sup>1</sup>, arm64<sup>1</sup> | |
| 37 | 35 | - **Linux**: amd64, arm64 | |
| 38 | 36 | - **macOS**: amd64, arm64 | |
| 39 | - | - **Windows**: amd64, arm64 | |
| 37 | + | - **Windows**: amd64<sup>2</sup>, arm64<sup>2</sup> | |
| 40 | 38 | ||
| 41 | 39 | ### Tier 2 | |
| 42 | 40 | ||
| 43 | 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> | |
| 46 | - | - **FreeBSD**: amd64<sup>2</sup>, arm64<sup>2</sup> | |
| 47 | - | - **Linux**: 386, arm, loong64, ppc64le, riscv64, s390x<sup>1</sup> | |
| 48 | - | - **NetBSD**: amd64<sup>2</sup>, arm64<sup>2</sup> | |
| 49 | - | - **Windows**: 386<sup>3</sup>, arm<sup>3,4</sup> | |
| 43 | + | - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup> | |
| 44 | + | - **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</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> | |
| 46 | + | - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> | |
| 47 | + | - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup> | |
| 50 | 48 | ||
| 51 | 49 | #### Support Notes | |
| 52 | 50 | ||
| 53 | 51 | 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=0 | |
| 55 | - | 3. These architectures only support `SyscallN` and `NewCallback` | |
| 56 | - | 4. These architectures are no longer supported as of Go 1.26 | |
| 52 | + | 2. These architectures support passing structs by value as arguments and return values when calling C functions, but not in callbacks created with `NewCallback` | |
| 53 | + | 3. These architectures do not support passing structs by value as arguments or return values | |
| 54 | + | 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 | 59 | ## Example | |
| 59 | 60 | ||
| @@ -101,21 +102,9 @@ exhaustion on Unix-like platforms, set `PUREGO_CALLBACK_LEDGER=1` to append JSON | |||
| 101 | 102 | callback lifecycle events to `/tmp/purego-callback-ledger-<pid>.jsonl`, or set | |
| 102 | 103 | `PUREGO_CALLBACK_LEDGER_FILE=/path/to/file.jsonl` to choose the output file. Stack | |
| 103 | 104 | 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 be | |
| 105 | - | reserved for active debugging sessions. For ad-hoc stderr/file traces near callback | |
| 106 | - | exhaustion, set `PUREGO_CALLBACK_TRACE=1`; `PUREGO_CALLBACK_TRACE_FILE` overrides | |
| 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. | |
| 105 | + | site attribution is needed. For ad-hoc stderr/file traces near callback exhaustion, | |
| 106 | + | set `PUREGO_CALLBACK_TRACE=1`; `PUREGO_CALLBACK_TRACE_FILE` overrides the default | |
| 107 | + | `/tmp/purego-callback-trace-<pid>.log` path. | |
| 119 | 108 | ||
| 120 | 109 | ## Questions | |
| 121 | 110 | ||
| @@ -130,7 +119,6 @@ This is a list of the copied files: | |||
| 130 | 119 | ||
| 131 | 120 | * `abi_*.h` from package `runtime/cgo` | |
| 132 | 121 | * `wincallback.go` from package `runtime` | |
| 133 | - | * `zcallback_darwin_*.s` from package `runtime` | |
| 134 | 122 | * `internal/fakecgo/abi_*.h` from package `runtime/cgo` | |
| 135 | 123 | * `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo` | |
| 136 | 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 | 22 | libFileName := filepath.Join(t.TempDir(), "libcbtest.so") | |
| 23 | 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 | 26 | t.Fatal(err) | |
| 27 | 27 | } | |
| 28 | 28 | defer os.Remove(libFileName) | |
| @@ -43,7 +43,7 @@ func TestCallGoFromSharedLib(t *testing.T) { | |||
| 43 | 43 | ||
| 44 | 44 | const want = 10101 | |
| 45 | 45 | cb := purego.NewCallback(goFunc) | |
| 46 | - | for i := 0; i < 10; i++ { | |
| 46 | + | for i := range 10 { | |
| 47 | 47 | got := callCallback(cb, "a test string") | |
| 48 | 48 | if got != want { | |
| 49 | 49 | t.Fatalf("%d: callCallback() got %v want %v", i, got, want) | |
| @@ -57,7 +57,7 @@ func TestCallbackStringConversion(t *testing.T) { | |||
| 57 | 57 | libFileName := filepath.Join(t.TempDir(), "libcbtest.so") | |
| 58 | 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 | 61 | t.Fatal(err) | |
| 62 | 62 | } | |
| 63 | 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 | 241 | func ExampleNewCallback() { | |
| 195 | 242 | cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int) int { | |
| 196 | 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 | 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 | 274 | t.Fatal(err) | |
| 228 | 275 | } | |
| 229 | 276 | defer os.Remove(libFileName) | |
| @@ -255,7 +302,7 @@ func TestCallbackMixedStackPacking(t *testing.T) { | |||
| 255 | 302 | } | |
| 256 | 303 | ||
| 257 | 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 | 306 | t.Fatal(err) | |
| 260 | 307 | } | |
| 261 | 308 | defer os.Remove(libFileName) | |
| @@ -288,7 +335,7 @@ func TestCallbackSmallTypesPacking(t *testing.T) { | |||
| 288 | 335 | } | |
| 289 | 336 | ||
| 290 | 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 | 339 | t.Fatal(err) | |
| 293 | 340 | } | |
| 294 | 341 | defer os.Remove(libFileName) | |
| @@ -356,7 +403,7 @@ func TestCallback10Int32Packing(t *testing.T) { | |||
| 356 | 403 | } | |
| 357 | 404 | ||
| 358 | 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 | 407 | t.Fatal(err) | |
| 361 | 408 | } | |
| 362 | 409 | defer os.Remove(libFileName) | |
| @@ -387,7 +434,7 @@ func TestCallbackFloat64StackPacking(t *testing.T) { | |||
| 387 | 434 | } | |
| 388 | 435 | ||
| 389 | 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 | 438 | t.Fatal(err) | |
| 392 | 439 | } | |
| 393 | 440 | defer os.Remove(libFileName) | |
| @@ -421,7 +468,7 @@ func TestCallbackFloat32StackPacking(t *testing.T) { | |||
| 421 | 468 | } | |
| 422 | 469 | ||
| 423 | 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 | 472 | t.Fatal(err) | |
| 426 | 473 | } | |
| 427 | 474 | defer os.Remove(libFileName) | |
| @@ -448,3 +495,49 @@ func TestCallbackFloat32StackPacking(t *testing.T) { | |||
| 448 | 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 | 8 | import ( | |
| 9 | 9 | "os" | |
| 10 | 10 | "path/filepath" | |
| 11 | + | "syscall" | |
| 11 | 12 | "testing" | |
| 12 | 13 | "unsafe" | |
| 13 | 14 | ||
| @@ -24,7 +25,7 @@ func TestNestedDlopenCall(t *testing.T) { | |||
| 24 | 25 | libFileName := filepath.Join(t.TempDir(), "libdlnested.so") | |
| 25 | 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 | 29 | t.Fatal(err) | |
| 29 | 30 | } | |
| 30 | 31 | defer os.Remove(libFileName) | |
| @@ -48,3 +49,32 @@ func TestSyscallN(t *testing.T) { | |||
| 48 | 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 | 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 | 24 | func main() { | |
| @@ -36,7 +36,7 @@ func main() { | |||
| 36 | 36 | []objc.FieldDef{ | |
| 37 | 37 | { | |
| 38 | 38 | Name: "bar", | |
| 39 | - | Type: reflect.TypeOf(int(0)), | |
| 39 | + | Type: reflect.TypeFor[int](), | |
| 40 | 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 | 6 | import ( | |
| 7 | 7 | "runtime" | |
| 8 | + | "structs" | |
| 8 | 9 | ||
| 9 | 10 | "github.com/bnema/purego" | |
| 10 | 11 | "github.com/bnema/purego/objc" | |
| @@ -17,14 +18,17 @@ const ( | |||
| 17 | 18 | ) | |
| 18 | 19 | ||
| 19 | 20 | type NSPoint struct { | |
| 21 | + | _ structs.HostLayout | |
| 20 | 22 | X, Y float64 | |
| 21 | 23 | } | |
| 22 | 24 | ||
| 23 | 25 | type NSSize struct { | |
| 26 | + | _ structs.HostLayout | |
| 24 | 27 | Width, Height float64 | |
| 25 | 28 | } | |
| 26 | 29 | ||
| 27 | 30 | type NSRect struct { | |
| 31 | + | _ structs.HostLayout | |
| 28 | 32 | Origin NSPoint | |
| 29 | 33 | Size NSSize | |
| 30 | 34 | } | |
| @@ -56,5 +60,5 @@ func main() { | |||
| 56 | 60 | } | |
| 57 | 61 | ||
| 58 | 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 | 6 | import ( | |
| 7 | 7 | "runtime" | |
| 8 | + | "structs" | |
| 8 | 9 | "syscall" | |
| 9 | 10 | "unsafe" | |
| 10 | 11 | ||
| @@ -30,6 +31,7 @@ type ( | |||
| 30 | 31 | ) | |
| 31 | 32 | ||
| 32 | 33 | type WNDCLASSEX struct { | |
| 34 | + | _ structs.HostLayout | |
| 33 | 35 | Size uint32 | |
| 34 | 36 | Style uint32 | |
| 35 | 37 | WndProc uintptr | |
| @@ -45,14 +47,17 @@ type WNDCLASSEX struct { | |||
| 45 | 47 | } | |
| 46 | 48 | ||
| 47 | 49 | type RECT struct { | |
| 50 | + | _ structs.HostLayout | |
| 48 | 51 | Left, Top, Right, Bottom int32 | |
| 49 | 52 | } | |
| 50 | 53 | ||
| 51 | 54 | type POINT struct { | |
| 55 | + | _ structs.HostLayout | |
| 52 | 56 | X, Y int32 | |
| 53 | 57 | } | |
| 54 | 58 | ||
| 55 | 59 | type MSG struct { | |
| 60 | + | _ structs.HostLayout | |
| 56 | 61 | Hwnd HWND | |
| 57 | 62 | Message uint32 | |
| 58 | 63 | 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 | 10 | "math" | |
| 11 | 11 | "reflect" | |
| 12 | 12 | "runtime" | |
| 13 | + | "structs" | |
| 14 | + | "sync" | |
| 13 | 15 | "unsafe" | |
| 14 | 16 | ||
| 15 | 17 | "github.com/bnema/purego/internal/strings" | |
| 16 | - | "github.com/bnema/purego/internal/xreflect" | |
| 17 | 18 | ) | |
| 18 | 19 | ||
| 19 | 20 | const ( | |
| @@ -21,6 +22,14 @@ const ( | |||
| 21 | 22 | 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 | 33 | // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). | |
| 25 | 34 | // It panics if it can't find the name symbol. | |
| 26 | 35 | func RegisterLibFunc(fptr any, handle uintptr, name string) { | |
| @@ -59,15 +68,17 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||
| 59 | 68 | // int64 <=> int64_t | |
| 60 | 69 | // float32 <=> float | |
| 61 | 70 | // float64 <=> double | |
| 62 | - | // struct <=> struct (darwin amd64/arm64, linux amd64/arm64) | |
| 71 | + | // struct <=> struct (android, darwin, ios, linux, and windows on amd64/arm64) | |
| 63 | 72 | // func <=> C function | |
| 64 | 73 | // unsafe.Pointer, *T <=> void* | |
| 65 | 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 | 77 | // it will be expanded into a call to the C function as if it had the arguments in that slice. | |
| 69 | 78 | // This means that using arg ...any is like a cast to the function with the arguments inside arg. | |
| 70 | 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 | 83 | // # Memory | |
| 73 | 84 | // | |
| @@ -94,8 +105,11 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||
| 94 | 105 | // it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure | |
| 95 | 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, | |
| 98 | - | // following the C ABI's byte-level packing rules. | |
| 108 | + | // On Apple ARM64 platforms (macOS and iOS), purego handles proper alignment of struct arguments | |
| 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 | 114 | // # Example | |
| 101 | 115 | // | |
| @@ -116,7 +130,11 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||
| 116 | 130 | // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C | |
| 117 | 131 | func RegisterFunc(fptr any, cfn uintptr) { | |
| 118 | 132 | 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 | 138 | ty := fn.Type() | |
| 121 | 139 | if ty.Kind() != reflect.Func { | |
| 122 | 140 | panic("purego: fptr must be a function pointer") | |
| @@ -136,40 +154,71 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 136 | 154 | // to avoid crashing with too many arguments | |
| 137 | 155 | var ints int | |
| 138 | 156 | var floats int | |
| 157 | + | floatArgRegs := numOfFloatRegisters() | |
| 158 | + | ptrSize := unsafe.Sizeof(uintptr(0)) | |
| 139 | 159 | var stack int | |
| 140 | - | for i := 0; i < ty.NumIn(); i++ { | |
| 160 | + | for i := range ty.NumIn() { | |
| 141 | 161 | arg := ty.In(i) | |
| 142 | 162 | switch arg.Kind() { | |
| 143 | 163 | case reflect.Func: | |
| 144 | 164 | // This only does preliminary testing to ensure the CDecl argument | |
| 145 | 165 | // is the first argument. Full testing is done when the callback is actually | |
| 146 | 166 | // created in NewCallback. | |
| 147 | - | for j := 0; j < arg.NumIn(); j++ { | |
| 167 | + | for j := range arg.NumIn() { | |
| 148 | 168 | in := arg.In(j) | |
| 149 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | |
| 169 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 150 | 170 | continue | |
| 151 | 171 | } | |
| 152 | 172 | if j != 0 { | |
| 153 | 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, | |
| 157 | - | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer, | |
| 176 | + | case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, | |
| 177 | + | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Pointer, reflect.UnsafePointer, | |
| 158 | 178 | reflect.Slice, reflect.Array, reflect.Bool: | |
| 159 | 179 | if ints < numOfIntegerRegisters() { | |
| 160 | 180 | ints++ | |
| 161 | 181 | } else { | |
| 162 | 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 | 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 | 214 | floats++ | |
| 167 | 215 | } else { | |
| 168 | - | stack++ | |
| 216 | + | stack += usesSlots | |
| 169 | 217 | } | |
| 170 | 218 | case reflect.Struct: | |
| 171 | 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 | 222 | continue | |
| 174 | 223 | } | |
| 175 | 224 | addInt := func(u uintptr) { | |
| @@ -190,17 +239,28 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 190 | 239 | ensureStructSupported() | |
| 191 | 240 | outType := ty.Out(0) | |
| 192 | 241 | checkStructFieldsSupported(outType) | |
| 193 | - | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | |
| 194 | - | // on amd64 if struct is bigger than 16 bytes allocate the return struct | |
| 195 | - | // and pass it in as a hidden first argument. | |
| 196 | - | ints++ | |
| 242 | + | if structReturnInMemory(outType) { | |
| 243 | + | // A struct returned in memory is allocated by the caller and its | |
| 244 | + | // pointer is passed as a hidden first integer argument. When the | |
| 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() | |
| 201 | - | // On Darwin ARM64, use byte-based validation since arguments pack efficiently. | |
| 202 | - | // See https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms | |
| 203 | - | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | |
| 255 | + | argsLimit := maxArgs | |
| 256 | + | sizeOfStack := argsLimit - numOfIntegerRegisters() | |
| 257 | + | if runtime.GOOS == "windows" { | |
| 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 | 264 | stackBytes := estimateStackBytes(ty) | |
| 205 | 265 | maxStackBytes := sizeOfStack * 8 | |
| 206 | 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 | 276 | v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | |
| 240 | 277 | var sysargs [maxArgs]uintptr | |
| 241 | 278 | // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free, | |
| 242 | 279 | // since numOfFloatRegisters() is a function call, not a constant. | |
| 243 | 280 | // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe. | |
| 244 | 281 | var floats [maxArgs]uintptr | |
| 282 | + | floatArgRegs := numOfFloatRegisters() | |
| 245 | 283 | var numInts int | |
| 246 | 284 | var numFloats int | |
| 247 | 285 | var numStack int | |
| @@ -261,7 +299,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 261 | 299 | } | |
| 262 | 300 | } | |
| 263 | 301 | addFloat = func(x uintptr) { | |
| 264 | - | if numFloats < numOfFloatRegisters() { | |
| 302 | + | if numFloats < floatArgRegs { | |
| 265 | 303 | floats[numFloats] = x | |
| 266 | 304 | numFloats++ | |
| 267 | 305 | } else { | |
| @@ -275,6 +313,9 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 275 | 313 | // This is in contrast to how macOS and Linux pass arguments which | |
| 276 | 314 | // tries to use as many registers as possible in the calling convention. | |
| 277 | 315 | addStack = func(x uintptr) { | |
| 316 | + | if numStack >= maxArgs { | |
| 317 | + | panic("purego: too many stack arguments") | |
| 318 | + | } | |
| 278 | 319 | sysargs[numStack] = x | |
| 279 | 320 | numStack++ | |
| 280 | 321 | } | |
| @@ -282,189 +323,169 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 282 | 323 | addFloat = addStack | |
| 283 | 324 | } | |
| 284 | 325 | ||
| 285 | - | if needsKeepAlive { | |
| 286 | - | // Slow path: allocate keepAlive slice and defer cleanup. | |
| 287 | - | // Used when args include strings, structs, arrays, or variadic []any. | |
| 288 | - | var keepAlive []any | |
| 289 | - | defer func() { | |
| 290 | - | runtime.KeepAlive(keepAlive) | |
| 291 | - | runtime.KeepAlive(args) | |
| 292 | - | }() | |
| 326 | + | var keepAlive []any | |
| 327 | + | defer func() { | |
| 328 | + | runtime.KeepAlive(keepAlive) | |
| 329 | + | runtime.KeepAlive(args) | |
| 330 | + | }() | |
| 293 | 331 | ||
| 294 | - | var arm64_r8 uintptr | |
| 295 | - | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 296 | - | outType := ty.Out(0) | |
| 297 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize { | |
| 332 | + | var arm64_r8 uintptr | |
| 333 | + | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 334 | + | outType := ty.Out(0) | |
| 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 | 344 | val := reflect.New(outType) | |
| 299 | 345 | keepAlive = append(keepAlive, val) | |
| 300 | - | addInt(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 | - | } | |
| 346 | + | arm64_r8 = val.Pointer() | |
| 308 | 347 | } | |
| 309 | 348 | } | |
| 310 | - | for i, v := range args { | |
| 311 | - | if variadic, ok := xreflect.TypeAssert[[]any](v); ok { | |
| 312 | - | if i != len(args)-1 { | |
| 313 | - | panic("purego: can only expand last parameter") | |
| 314 | - | } | |
| 349 | + | } | |
| 350 | + | for i, v := range args { | |
| 351 | + | // Only the last argument is expanded into several C arguments. | |
| 352 | + | // Both a declared ...any parameter and a final []any parameter | |
| 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 | 357 | for _, x := range variadic { | |
| 316 | 358 | keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 317 | 359 | } | |
| 318 | 360 | 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 | 363 | // Check if we need to start Darwin ARM64 C-style stack packing | |
| 351 | - | if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && shouldBundleStackArgs(v, numInts, numFloats) { | |
| 352 | - | stackArgs, _ := collectStackArgs(args, i, numInts, numFloats, | |
| 353 | - | nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |
| 364 | + | if runtime.GOARCH == "arm64" && isDarwin && shouldBundleStackArgs(v, numInts, numFloats) { | |
| 365 | + | // Collect and separate remaining args into register vs stack | |
| 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 | 371 | bundleStackArgs(stackArgs, addStack) | |
| 355 | 372 | 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) | |
| 361 | - | }) | |
| 362 | - | fn.Set(v) | |
| 363 | - | } | |
| 364 | - | ||
| 365 | - | // callAndReturn performs the actual C function call and constructs the return value. | |
| 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))) | |
| 377 | + | var syscall *syscallArgs | |
| 378 | + | if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" { | |
| 379 | + | // Windows amd64, 386, and arm use syscall.SyscallN. | |
| 380 | + | syscall = thePool.Get().(*syscallArgs) | |
| 381 | + | syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...) | |
| 382 | + | syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support | |
| 432 | 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: | |
| 436 | - | v = getStruct(outType, *syscall) | |
| 437 | - | case reflect.Slice: | |
| 438 | - | if outType.Elem().Kind() == reflect.String { | |
| 439 | - | // C returns char** (NULL-terminated array of strings) | |
| 440 | - | if syscall.a1 == 0 { | |
| 441 | - | v = reflect.Zero(outType) | |
| 386 | + | defer thePool.Put(syscall) | |
| 387 | + | if ty.NumOut() == 0 { | |
| 388 | + | return nil | |
| 389 | + | } | |
| 390 | + | outType := ty.Out(0) | |
| 391 | + | v := reflect.New(outType).Elem() | |
| 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 | 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 | 470 | var result []string | |
| 444 | 471 | p := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)) | |
| 445 | - | for { | |
| 446 | - | strPtr := *(*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))) | |
| 472 | + | for ; *(*uintptr)(p) != 0; p = unsafe.Add(p, unsafe.Sizeof(uintptr(0))) { | |
| 473 | + | result = append(result, strings.GoString(*(*uintptr)(p))) | |
| 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 | 484 | } else { | |
| 456 | - | panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String()) | |
| 485 | + | return []reflect.Value{v} | |
| 457 | 486 | } | |
| 458 | - | default: | |
| 459 | - | panic("purego: unsupported return kind: " + outType.Kind().String()) | |
| 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 | - | } | |
| 487 | + | }) | |
| 488 | + | fn.Set(v) | |
| 468 | 489 | } | |
| 469 | 490 | ||
| 470 | 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 | 495 | ptr := strings.CString(v.String()) | |
| 475 | 496 | keepAlive = append(keepAlive, ptr) | |
| 476 | 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 | 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 | 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 | 518 | case reflect.Slice: | |
| 485 | - | if v.Type().Elem().Kind() == reflect.String { | |
| 486 | - | ss := v.Interface().([]string) | |
| 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 { | |
| 519 | + | if v.Type().Elem().Kind() != reflect.String { | |
| 520 | + | // There is no need to keepAlive this pointer separately because it is kept alive in the args variable | |
| 502 | 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 | 544 | case reflect.Func: | |
| 505 | 545 | addInt(NewCallback(v.Interface())) | |
| 506 | 546 | case reflect.Bool: | |
| @@ -510,30 +550,41 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||
| 510 | 550 | addInt(0) | |
| 511 | 551 | } | |
| 512 | 552 | case reflect.Float32: | |
| 513 | - | // On S390X big-endian, float32 goes in upper 32 bits of 64-bit FP register | |
| 514 | - | if runtime.GOARCH == "s390x" { | |
| 553 | + | switch runtime.GOARCH { | |
| 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 | 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 | 568 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |
| 518 | 569 | } | |
| 519 | 570 | case reflect.Float64: | |
| 520 | - | if is32bit { | |
| 521 | - | bits := math.Float64bits(v.Float()) | |
| 571 | + | 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 | 584 | addFloat(uintptr(bits)) | |
| 523 | 585 | addFloat(uintptr(bits >> 32)) | |
| 524 | 586 | } else { | |
| 525 | - | addFloat(uintptr(math.Float64bits(v.Float()))) | |
| 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()) | |
| 587 | + | addFloat(uintptr(bits)) | |
| 537 | 588 | } | |
| 538 | 589 | case reflect.Struct: | |
| 539 | 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 | 601 | // If you change this make sure to update it in objc_runtime_darwin.go | |
| 551 | 602 | 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 | 639 | func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { | |
| 554 | 640 | allFloats = true | |
| 555 | - | root := ty.Field(0).Type | |
| 641 | + | if numABIFields(ty) == 0 { | |
| 642 | + | return false, 0 | |
| 643 | + | } | |
| 644 | + | root := abiField(ty, 0).Type | |
| 556 | 645 | for root.Kind() == reflect.Struct { | |
| 557 | - | root = root.Field(0).Type | |
| 646 | + | if numABIFields(root) == 0 { | |
| 647 | + | return false, 0 | |
| 648 | + | } | |
| 649 | + | root = abiField(root, 0).Type | |
| 558 | 650 | } | |
| 559 | 651 | first := root.Kind() | |
| 560 | 652 | if first != reflect.Float32 && first != reflect.Float64 { | |
| 561 | 653 | 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 | 659 | f := ty.Field(i).Type | |
| 565 | 660 | if f.Kind() == reflect.Struct { | |
| 566 | 661 | var structNumFields int | |
| @@ -577,7 +672,10 @@ func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { | |||
| 577 | 672 | } | |
| 578 | 673 | ||
| 579 | 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 | 679 | f := ty.Field(i).Type | |
| 582 | 680 | if f.Kind() == reflect.Array { | |
| 583 | 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 | 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 | 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" { | |
| 604 | - | panic("purego: struct arguments/returns are only supported on darwin and linux") | |
| 719 | + | switch runtime.GOOS { | |
| 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 | 730 | func roundUpTo8(val uintptr) uintptr { | |
| 609 | 731 | return (val + align8ByteMask) &^ align8ByteMask | |
| 610 | 732 | } | |
| @@ -616,6 +738,7 @@ func numOfFloatRegisters() int { | |||
| 616 | 738 | case "s390x": | |
| 617 | 739 | return 4 | |
| 618 | 740 | case "arm": | |
| 741 | + | // 8 doubles (16 words) are always reserved by asm trampolines, even if softfloat is used | |
| 619 | 742 | return 16 | |
| 620 | 743 | case "386": | |
| 621 | 744 | // i386 SysV ABI passes all arguments on the stack, including floats | |
| @@ -654,7 +777,7 @@ func estimateStackBytes(ty reflect.Type) int { | |||
| 654 | 777 | var numInts, numFloats int | |
| 655 | 778 | var stackBytes int | |
| 656 | 779 | ||
| 657 | - | for i := 0; i < ty.NumIn(); i++ { | |
| 780 | + | for i := range ty.NumIn() { | |
| 658 | 781 | arg := ty.In(i) | |
| 659 | 782 | size := int(arg.Size()) | |
| 660 | 783 | ||
| @@ -665,7 +788,6 @@ func estimateStackBytes(ty reflect.Type) int { | |||
| 665 | 788 | } else if !usesInt && numFloats < numOfFloatRegisters() { | |
| 666 | 789 | numFloats++ | |
| 667 | 790 | } else { | |
| 668 | - | // Goes to stack - accumulate total bytes | |
| 669 | 791 | stackBytes += size | |
| 670 | 792 | } | |
| 671 | 793 | } | |
| @@ -675,3 +797,29 @@ func estimateStackBytes(ty reflect.Type) int { | |||
| 675 | 797 | } | |
| 676 | 798 | 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 | 7 | "bytes" | |
| 8 | 8 | "errors" | |
| 9 | 9 | "fmt" | |
| 10 | + | "math" | |
| 11 | + | "os" | |
| 10 | 12 | "os/exec" | |
| 11 | 13 | "path/filepath" | |
| 14 | + | "reflect" | |
| 12 | 15 | "runtime" | |
| 16 | + | "strconv" | |
| 13 | 17 | "strings" | |
| 18 | + | "structs" | |
| 14 | 19 | "sync" | |
| 15 | 20 | "testing" | |
| 16 | 21 | "unsafe" | |
| @@ -21,7 +26,9 @@ import ( | |||
| 21 | 26 | ||
| 22 | 27 | func getSystemLibrary() (string, error) { | |
| 23 | 28 | switch runtime.GOOS { | |
| 24 | - | case "darwin": | |
| 29 | + | case "android": | |
| 30 | + | return "libc.so", nil | |
| 31 | + | case "darwin", "ios": | |
| 25 | 32 | return "/usr/lib/libSystem.B.dylib", nil | |
| 26 | 33 | case "freebsd": | |
| 27 | 34 | return "libc.so.7", nil | |
| @@ -52,19 +59,21 @@ func TestRegisterFunc_ConcurrentPointerReturn(t *testing.T) { | |||
| 52 | 59 | purego.RegisterLibFunc(&free, libc, "free") | |
| 53 | 60 | ||
| 54 | 61 | var wg sync.WaitGroup | |
| 55 | - | for i := 0; i < runtime.NumCPU(); i++ { | |
| 62 | + | ||
| 63 | + | for i := range runtime.NumCPU() { | |
| 56 | 64 | wg.Add(1) | |
| 57 | - | go func() { | |
| 65 | + | go func(id int) { | |
| 58 | 66 | defer wg.Done() | |
| 59 | - | for j := 0; j < 400_000; j++ { | |
| 67 | + | for range 400_000 { | |
| 60 | 68 | ptr := alloc(5) | |
| 61 | 69 | if ptr == nil { | |
| 62 | 70 | continue | |
| 63 | 71 | } | |
| 64 | 72 | free(ptr) | |
| 65 | 73 | } | |
| 66 | - | }() | |
| 74 | + | }(i) | |
| 67 | 75 | } | |
| 76 | + | ||
| 68 | 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) { | |
| 176 | - | if runtime.GOOS == "windows" && runtime.GOARCH == "386" { | |
| 177 | - | t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test | |
| 184 | + | func TestNewCallback_NotAFunction(t *testing.T) { | |
| 185 | + | for _, tc := range []struct { | |
| 186 | + | 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 | 239 | libFileName := filepath.Join(t.TempDir(), "abitest.so") | |
| 180 | 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 | 243 | t.Fatal(err) | |
| 184 | 244 | } | |
| 185 | 245 | ||
| @@ -234,14 +294,29 @@ func TestABI(t *testing.T) { | |||
| 234 | 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 | 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 | 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 | 320 | t.Fatal(err) | |
| 246 | 321 | } | |
| 247 | 322 | lib, err := load.OpenLibrary(libFileName) | |
| @@ -373,11 +448,20 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||
| 373 | 448 | }, | |
| 374 | 449 | { | |
| 375 | 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 })), | |
| 377 | - | cFn: "stack_8int_hfa4_stack", | |
| 451 | + | fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct { | |
| 452 | + | _ structs.HostLayout | |
| 453 | + | x, y, z, w float32 | |
| 454 | + | })), | |
| 455 | + | cFn: "stack_8int_hfa4_stack", | |
| 378 | 456 | call: func(f any) string { | |
| 379 | 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 | 465 | return string(buf[:bytes.IndexByte(buf, 0)]) | |
| 382 | 466 | }, | |
| 383 | 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 | 470 | name: "8int_mixed_struct", | |
| 387 | 471 | fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct { | |
| 472 | + | _ structs.HostLayout | |
| 388 | 473 | a int32 | |
| 389 | 474 | b float32 | |
| 390 | 475 | })), | |
| @@ -392,24 +477,108 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||
| 392 | 477 | call: func(f any) string { | |
| 393 | 478 | buf := make([]byte, 256) | |
| 394 | 479 | (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct { | |
| 480 | + | _ structs.HostLayout | |
| 395 | 481 | a int32 | |
| 396 | 482 | b float32 | |
| 397 | 483 | })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct { | |
| 484 | + | _ structs.HostLayout | |
| 398 | 485 | a int32 | |
| 399 | 486 | b float32 | |
| 400 | - | }{9, 10.0}) | |
| 487 | + | }{a: 9, b: 10.0}) | |
| 401 | 488 | return string(buf[:bytes.IndexByte(buf, 0)]) | |
| 402 | 489 | }, | |
| 403 | 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 | 575 | for _, tt := range tests { | |
| 408 | 576 | t.Run(tt.name, func(t *testing.T) { | |
| 409 | - | if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") { | |
| 410 | - | t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking") | |
| 577 | + | if tt.name == "20_int32" && runtime.GOARCH == "ppc64le" { | |
| 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 | 582 | t.Skip("float32 stack arguments not yet supported on this platform") | |
| 414 | 583 | } | |
| 415 | 584 | // 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) { | |
| 433 | - | if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" { | |
| 434 | - | t.Skip("This test is specific to Darwin ARM64") | |
| 435 | - | } | |
| 600 | + | t.Run("20_uintptr", func(t *testing.T) { | |
| 601 | + | if runtime.GOARCH == "ppc64le" { | |
| 602 | + | t.Skip("ppc64le retains the 15-argument limit") | |
| 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") | |
| 438 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil { | |
| 439 | - | t.Fatal(err) | |
| 440 | - | } | |
| 441 | - | lib, err := load.OpenLibrary(libFileName) | |
| 442 | - | if err != nil { | |
| 443 | - | t.Fatalf("Failed to open library %q: %v", libFileName, err) | |
| 444 | - | } | |
| 445 | - | t.Cleanup(func() { | |
| 446 | - | if err := load.CloseLibrary(lib); err != nil { | |
| 447 | - | t.Errorf("Failed to close library: %v", err) | |
| 613 | + | t.Run("32_uintptr", func(t *testing.T) { | |
| 614 | + | if runtime.GOARCH == "ppc64le" { | |
| 615 | + | t.Skip("ppc64le retains the 15-argument limit") | |
| 616 | + | } | |
| 617 | + | var fn func( | |
| 618 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, | |
| 619 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, | |
| 620 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, | |
| 621 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, | |
| 622 | + | ) uintptr | |
| 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 limit | |
| 452 | - | t.Run("35_int64_exceeds_limit", func(t *testing.T) { | |
| 654 | + | t.Run("syscalln_32_uintptr", 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 | 705 | defer func() { | |
| 454 | - | if r := recover(); r != nil { | |
| 455 | - | t.Logf("Got expected panic: %v", r) | |
| 456 | - | } else { | |
| 457 | - | t.Errorf("Expected panic but didn't get one") | |
| 706 | + | r := recover() | |
| 707 | + | if r == nil { | |
| 708 | + | t.Fatalf("expected panic %q, got none", want) | |
| 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) | |
| 462 | - | purego.RegisterLibFunc(&fn, lib, "stack_35_int64_exceeds") | |
| 718 | + | // 33 int64 parameters exceeds maxArgs=32. | |
| 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 | 834 | out, err := exec.Command("go", "env", compilerEnv).Output() | |
| 468 | 835 | if err != nil { | |
| 469 | 836 | return fmt.Errorf("go env %s error: %w", compilerEnv, err) | |
M go.mod +1 -1
| @@ -1,3 +1,3 @@ | |||
| 1 | 1 | module github.com/bnema/purego | |
| 2 | 2 | ||
| 3 | - | go 1.20 | |
| 3 | + | go 1.25.0 | |
M go_runtime.go +6 -0
| @@ -11,3 +11,9 @@ import ( | |||
| 11 | 11 | ||
| 12 | 12 | //go:linkname runtime_cgocall runtime.cgocall | |
| 13 | 13 | 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 | 16 | #include <errno.h> | |
| 17 | 17 | #include <assert.h> | |
| 18 | 18 | ||
| 19 | - | typedef struct syscall15Args { | |
| 19 | + | typedef struct syscallArgs { | |
| 20 | 20 | uintptr_t fn; | |
| 21 | 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 | 23 | uintptr_t f1, f2, f3, f4, f5, f6, f7, f8; | |
| 23 | - | uintptr_t err; | |
| 24 | - | } syscall15Args; | |
| 24 | + | uintptr_t arm64_r8; | |
| 25 | + | } syscallArgs; | |
| 25 | 26 | ||
| 26 | - | void syscall15(struct syscall15Args *args) { | |
| 27 | + | void syscall15(struct syscallArgs *args) { | |
| 27 | 28 | assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0); | |
| 28 | 29 | uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, | |
| 29 | 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 | 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, | |
| 33 | - | args->a10,args->a11,args->a12,args->a13,args->a14,args->a15); | |
| 36 | + | uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9, | |
| 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 | 40 | args->a1 = r1; | |
| 35 | - | args->err = errno; | |
| 41 | + | args->a3 = errno; | |
| 36 | 42 | } | |
| 37 | 43 | ||
| 38 | 44 | */ | |
| 39 | 45 | import "C" | |
| 40 | 46 | import "unsafe" | |
| 41 | 47 | ||
| 42 | - | // assign purego.syscall15XABI0 to the C version of this function. | |
| 43 | - | var Syscall15XABI0 = 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 | - | } | |
| 48 | + | // assign purego.syscallXABI0 to the C version of this function. | |
| 49 | + | var SyscallXABI0 = unsafe.Pointer(C.syscall15) | |
M internal/fakecgo/asm_arm.s +9 -3
| @@ -21,8 +21,11 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 21 | 21 | // starting at 4(R13). | |
| 22 | 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 | 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 | 29 | MOVD F8, (13*4+8*1)(R13) | |
| 27 | 30 | MOVD F9, (13*4+8*2)(R13) | |
| 28 | 31 | MOVD F10, (13*4+8*3)(R13) | |
| @@ -32,11 +35,13 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 32 | 35 | MOVD F14, (13*4+8*7)(R13) | |
| 33 | 36 | MOVD F15, (13*4+8*8)(R13) | |
| 34 | 37 | ||
| 35 | - | BL runtime·load_g(SB) | |
| 36 | - | ||
| 38 | + | skipfpsave: | |
| 37 | 39 | // We set up the arguments to cgocallback when saving registers above. | |
| 38 | 40 | BL runtime·cgocallback(SB) | |
| 39 | 41 | ||
| 42 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 43 | + | CMP $0, R11 | |
| 44 | + | BNE skipfprest | |
| 40 | 45 | MOVD (13*4+8*1)(R13), F8 | |
| 41 | 46 | MOVD (13*4+8*2)(R13), F9 | |
| 42 | 47 | MOVD (13*4+8*3)(R13), F10 | |
| @@ -46,6 +51,7 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 46 | 51 | MOVD (13*4+8*7)(R13), F14 | |
| 47 | 52 | MOVD (13*4+8*8)(R13), F15 | |
| 48 | 53 | ||
| 54 | + | skipfprest: | |
| 49 | 55 | MOVW.P 4(R13), R14 | |
| 50 | 56 | MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12] | |
| 51 | 57 | ADD $(8*9), R13 | |
M internal/fakecgo/asm_arm64.s +0 -1
| @@ -25,7 +25,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 25 | 25 | STP (R29, R30), (8*22)(RSP) | |
| 26 | 26 | ||
| 27 | 27 | // Initialize Go ABI environment | |
| 28 | - | BL runtime·load_g(SB) | |
| 29 | 28 | BL runtime·cgocallback(SB) | |
| 30 | 29 | ||
| 31 | 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 | 27 | MOVV R1, (22*8)(R3) | |
| 28 | 28 | ||
| 29 | 29 | // Initialize Go ABI environment | |
| 30 | - | JAL runtime·load_g(SB) | |
| 31 | - | ||
| 32 | 30 | JAL runtime·cgocallback(SB) | |
| 33 | 31 | ||
| 34 | 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 | 50 | // Initialize R0 to 0 as expected by Go | |
| 51 | 51 | MOVD $0, R0 | |
| 52 | 52 | ||
| 53 | - | // Load the current g. | |
| 54 | - | BL runtime·load_g(SB) | |
| 55 | - | ||
| 56 | 53 | // Set up arguments for cgocallback | |
| 57 | 54 | MOVD R3, FIXED_FRAME+0(R1) // fn unsafe.Pointer | |
| 58 | 55 | 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 | 25 | SAVE_FPR((8*17)) | |
| 26 | 26 | ||
| 27 | 27 | // Initialize Go ABI environment | |
| 28 | - | CALL runtime·load_g(SB) | |
| 29 | 28 | CALL runtime·cgocallback(SB) | |
| 30 | 29 | ||
| 31 | 30 | RESTORE_GPR((8*4)) | |
M internal/fakecgo/asm_s390x.s +0 -3
| @@ -27,9 +27,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 27 | 27 | FMOVD F14, 80(R15) | |
| 28 | 28 | FMOVD F15, 88(R15) | |
| 29 | 29 | ||
| 30 | - | // Initialize Go ABI environment. | |
| 31 | - | BL runtime·load_g(SB) | |
| 32 | - | ||
| 33 | 30 | MOVD R2, 8(R15) // fn unsafe.Pointer | |
| 34 | 31 | MOVD R3, 16(R15) // a unsafe.Pointer | |
| 35 | 32 | ||
M internal/fakecgo/gen.go +1 -1
| @@ -268,7 +268,7 @@ func run() error { | |||
| 268 | 268 | pthreadSO = "/usr/lib/libSystem.B.dylib" | |
| 269 | 269 | case "freebsd": | |
| 270 | 270 | libcSO = "libc.so.7" | |
| 271 | - | pthreadSO = "libpthread.so" | |
| 271 | + | pthreadSO = "libthr.so.3" | |
| 272 | 272 | case "linux": | |
| 273 | 273 | libcSO = "libc.so.6" | |
| 274 | 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 | 55 | //#endif | |
| 56 | 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 :) | |
| 59 | - | fn := uintptr(unsafe.Pointer(&ts.fn)) | |
| 60 | - | (*(*func())(unsafe.Pointer(&fn)))() | |
| 58 | + | callThreadEntryFn(ts.fn) | |
| 61 | 59 | ||
| 62 | 60 | return nil | |
| 63 | 61 | } | |
M internal/fakecgo/go_freebsd.go +2 -4
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | // Use of this source code is governed by a BSD-style | |
| 3 | 3 | // license that can be found in the LICENSE file. | |
| 4 | 4 | ||
| 5 | - | //go:build !cgo | |
| 5 | + | //go:build !cgo && (amd64 || arm64) | |
| 6 | 6 | ||
| 7 | 7 | package fakecgo | |
| 8 | 8 | ||
| @@ -49,9 +49,7 @@ func threadentry(v unsafe.Pointer) unsafe.Pointer { | |||
| 49 | 49 | ||
| 50 | 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 :) | |
| 53 | - | fn := uintptr(unsafe.Pointer(&ts.fn)) | |
| 54 | - | (*(*func())(unsafe.Pointer(&fn)))() | |
| 52 | + | callThreadEntryFn(ts.fn) | |
| 55 | 53 | ||
| 56 | 54 | 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 | 52 | tries := ts.Nsec | |
| 53 | 53 | var err int | |
| 54 | 54 | ||
| 55 | - | for tries = 0; tries < 20; tries++ { | |
| 55 | + | for tries = range 20 { | |
| 56 | 56 | // inlined this call because it ran out of stack when inlining was disabled | |
| 57 | 57 | err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0)) | |
| 58 | 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 | 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 :) | |
| 53 | - | fn := uintptr(unsafe.Pointer(&ts.fn)) | |
| 54 | - | (*(*func())(unsafe.Pointer(&fn)))() | |
| 52 | + | callThreadEntryFn(ts.fn) | |
| 55 | 53 | ||
| 56 | 54 | 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 | 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 :) | |
| 64 | - | fn := uintptr(unsafe.Pointer(&ts.fn)) | |
| 65 | - | (*(*func())(unsafe.Pointer(&fn)))() | |
| 63 | + | callThreadEntryFn(ts.fn) | |
| 66 | 64 | ||
| 67 | 65 | return nil | |
| 68 | 66 | } | |
M internal/fakecgo/libcgo.go +4 -0
| @@ -5,6 +5,8 @@ | |||
| 5 | 5 | ||
| 6 | 6 | package fakecgo | |
| 7 | 7 | ||
| 8 | + | import "structs" | |
| 9 | + | ||
| 8 | 10 | type ( | |
| 9 | 11 | size_t uintptr | |
| 10 | 12 | // Sources: | |
| @@ -28,11 +30,13 @@ const ( | |||
| 28 | 30 | ) | |
| 29 | 31 | ||
| 30 | 32 | type G struct { | |
| 33 | + | _ structs.HostLayout | |
| 31 | 34 | stacklo uintptr | |
| 32 | 35 | stackhi uintptr | |
| 33 | 36 | } | |
| 34 | 37 | ||
| 35 | 38 | type ThreadStart struct { | |
| 39 | + | _ structs.HostLayout | |
| 36 | 40 | g *G | |
| 37 | 41 | tls *uintptr | |
| 38 | 42 | fn uintptr | |
M internal/fakecgo/libcgo_darwin.go +4 -0
| @@ -5,12 +5,16 @@ | |||
| 5 | 5 | ||
| 6 | 6 | package fakecgo | |
| 7 | 7 | ||
| 8 | + | import "structs" | |
| 9 | + | ||
| 8 | 10 | type ( | |
| 9 | 11 | pthread_mutex_t struct { | |
| 12 | + | _ structs.HostLayout | |
| 10 | 13 | sig int64 | |
| 11 | 14 | opaque [56]byte | |
| 12 | 15 | } | |
| 13 | 16 | pthread_cond_t struct { | |
| 17 | + | _ structs.HostLayout | |
| 14 | 18 | sig int64 | |
| 15 | 19 | opaque [40]byte | |
| 16 | 20 | } | |
M internal/fakecgo/libcgo_netbsd.go +3 -0
| @@ -5,6 +5,8 @@ | |||
| 5 | 5 | ||
| 6 | 6 | package fakecgo | |
| 7 | 7 | ||
| 8 | + | import "structs" | |
| 9 | + | ||
| 8 | 10 | type ( | |
| 9 | 11 | pthread_cond_t uintptr | |
| 10 | 12 | pthread_mutex_t uintptr | |
| @@ -17,6 +19,7 @@ var ( | |||
| 17 | 19 | ||
| 18 | 20 | // Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/sys/signal.h#L225 | |
| 19 | 21 | type stack_t struct { | |
| 22 | + | _ structs.HostLayout | |
| 20 | 23 | ss_sp uintptr | |
| 21 | 24 | ss_size uintptr | |
| 22 | 25 | ss_flags int32 | |
M internal/fakecgo/linux.go +7 -2
| @@ -6,10 +6,15 @@ | |||
| 6 | 6 | ||
| 7 | 7 | 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 | 16 | type argset struct { | |
| 17 | + | _ structs.HostLayout | |
| 13 | 18 | args *uintptr | |
| 14 | 19 | 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 | 83 | POP_REGS_HOST_TO_ABI0() | |
| 84 | 84 | 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 | 103 | TEXT ·call5(SB), NOSPLIT, $0-56 | |
| 87 | 104 | MOVQ fn+0(FP), R11 | |
| 88 | 105 | 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 | 70 | MOVW g, 32(R13) // R10 | |
| 71 | 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 | 77 | MOVD F8, 40(R13) | |
| 74 | 78 | MOVD F9, 48(R13) | |
| 75 | 79 | MOVD F10, 56(R13) | |
| @@ -79,10 +83,14 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 | |||
| 79 | 83 | MOVD F14, 88(R13) | |
| 80 | 84 | MOVD F15, 96(R13) | |
| 81 | 85 | ||
| 86 | + | skipfpsave: | |
| 82 | 87 | MOVW ·threadentry_call(SB), R12 | |
| 83 | 88 | MOVW (R12), R12 | |
| 84 | 89 | CALL (R12) | |
| 85 | 90 | ||
| 91 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 92 | + | CMP $0, R11 | |
| 93 | + | BNE skipfprest | |
| 86 | 94 | MOVD 40(R13), F8 | |
| 87 | 95 | MOVD 48(R13), F9 | |
| 88 | 96 | MOVD 56(R13), F10 | |
| @@ -92,6 +100,7 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 | |||
| 92 | 100 | MOVD 88(R13), F14 | |
| 93 | 101 | MOVD 96(R13), F15 | |
| 94 | 102 | ||
| 103 | + | skipfprest: | |
| 95 | 104 | MOVW 8(R13), R4 | |
| 96 | 105 | MOVW 12(R13), R5 | |
| 97 | 106 | 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 | 48 | // func setg_trampoline(setg uintptr, g uintptr) | |
| 49 | 49 | 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 | 52 | MOVD setg+0(FP), R12 | |
| 53 | 53 | MOVD newg+8(FP), R3 | |
| 54 | 54 | ||
| 55 | + | MOVD R3, 16(R1) // save newg before call | |
| 56 | + | ||
| 55 | 57 | MOVD R12, CTR | |
| 56 | 58 | 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 | 64 | MOVD 8(R1), R31 | |
| 61 | 65 | 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 | 16 | //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7" | |
| 17 | 17 | //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7" | |
| 18 | 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" | |
| 20 | - | //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so" | |
| 21 | - | //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so" | |
| 22 | - | //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so" | |
| 23 | - | //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so" | |
| 24 | - | //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so" | |
| 25 | - | //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so" | |
| 26 | - | //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so" | |
| 27 | - | //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so" | |
| 28 | - | //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "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 "libthr.so.3" | |
| 21 | + | //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libthr.so.3" | |
| 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 "libthr.so.3" | |
| 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 "libthr.so.3" | |
| 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 "libthr.so.3" | |
| 28 | + | //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libthr.so.3" | |
| 29 | 29 | ||
| 30 | 30 | //go:nosplit | |
| 31 | 31 | //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 | 6 | import ( | |
| 7 | 7 | "fmt" | |
| 8 | 8 | "reflect" | |
| 9 | + | "structs" | |
| 9 | 10 | "sync" | |
| 10 | 11 | "unsafe" | |
| 11 | 12 | ||
| 12 | 13 | "github.com/bnema/purego" | |
| 13 | - | "github.com/bnema/purego/internal/xreflect" | |
| 14 | 14 | ) | |
| 15 | 15 | ||
| 16 | 16 | const ( | |
| @@ -19,7 +19,10 @@ const ( | |||
| 19 | 19 | // when the reference count drops to zero, so the associated function is also unreferenced. | |
| 20 | 20 | ||
| 21 | 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 | 26 | // blockFlags is the set of flags that block objects will be initialized with. | |
| 24 | 27 | blockFlags = blockHasCopyDispose | blockHasSignature | |
| 25 | 28 | ||
| @@ -35,6 +38,7 @@ const ( | |||
| 35 | 38 | // | |
| 36 | 39 | // The layout of this struct matches Block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level | |
| 37 | 40 | type blockDescriptor struct { | |
| 41 | + | _ structs.HostLayout | |
| 38 | 42 | _ uintptr | |
| 39 | 43 | size uintptr | |
| 40 | 44 | _ uintptr | |
| @@ -48,6 +52,7 @@ type blockDescriptor struct { | |||
| 48 | 52 | // | |
| 49 | 53 | // The layout of this struct matches __block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level | |
| 50 | 54 | type blockLayout struct { | |
| 55 | + | _ structs.HostLayout | |
| 51 | 56 | isa Class | |
| 52 | 57 | flags uint32 | |
| 53 | 58 | _ uint32 | |
| @@ -125,7 +130,7 @@ func (*blockCache) encode(typ reflect.Type) *uint8 { | |||
| 125 | 130 | 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 | 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 | 173 | reflect.MakeFunc( | |
| 169 | 174 | typ, | |
| 170 | 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 | 177 | if !ok { | |
| 173 | 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 | 268 | callResult := fn.Call(reflectedArgs) | |
| 264 | 269 | ||
| 265 | 270 | var ok bool | |
| 266 | - | result, ok = xreflect.TypeAssert[T](callResult[0]) | |
| 271 | + | result, ok = reflect.TypeAssert[T](callResult[0]) | |
| 267 | 272 | if !ok { | |
| 268 | 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 | 6 | import ( | |
| 7 | 7 | "fmt" | |
| 8 | + | "structs" | |
| 8 | 9 | "testing" | |
| 9 | 10 | ||
| 10 | 11 | "github.com/bnema/purego" | |
| @@ -39,6 +40,7 @@ func ExampleNewBlock() { | |||
| 39 | 40 | ||
| 40 | 41 | func ExampleInvokeBlock() { | |
| 41 | 42 | type vector struct { | |
| 43 | + | _ structs.HostLayout | |
| 42 | 44 | X, Y, Z float64 | |
| 43 | 45 | } | |
| 44 | 46 | ||
M objc/objc_runtime_darwin.go +58 -43
| @@ -12,15 +12,18 @@ import ( | |||
| 12 | 12 | "reflect" | |
| 13 | 13 | "regexp" | |
| 14 | 14 | "runtime" | |
| 15 | + | "slices" | |
| 15 | 16 | stdstrings "strings" | |
| 17 | + | "structs" | |
| 16 | 18 | "unicode" | |
| 17 | 19 | "unsafe" | |
| 18 | 20 | ||
| 19 | 21 | "github.com/bnema/purego" | |
| 20 | 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 | 27 | // TODO: support try/catch? | |
| 25 | 28 | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | |
| 26 | 29 | var ( | |
| @@ -168,32 +171,44 @@ func Send[T any](id ID, sel SEL, args ...any) T { | |||
| 168 | 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 keyword | |
| 172 | - | // as the receiver of a message. It specifies the class definition of the particular superclass that should | |
| 173 | - | // be messaged. | |
| 174 | + | // objc_super supplies the receiver and defining class to objc_msgSendSuper2, | |
| 175 | + | // which begins method lookup in that class's superclass. | |
| 174 | 176 | type objc_super struct { | |
| 177 | + | _ structs.HostLayout | |
| 175 | 178 | receiver ID | |
| 176 | 179 | superClass Class | |
| 177 | 180 | } | |
| 178 | 181 | ||
| 179 | - | // SendSuper is a convenience method for sending message to object's super. This function takes a SEL | |
| 180 | - | // instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result | |
| 181 | - | // of RegisterName. | |
| 182 | + | // SendSuper sends a message starting lookup in the receiver's runtime superclass. | |
| 183 | + | // | |
| 184 | + | // Deprecated: Use [ID.SendSuper2] with the class defining the executing method. | |
| 182 | 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 | 192 | super := &objc_super{ | |
| 184 | 193 | receiver: id, | |
| 185 | - | superClass: id.Class(), | |
| 194 | + | superClass: class, | |
| 186 | 195 | } | |
| 187 | 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. | |
| 191 | - | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | |
| 192 | - | // It is best to cache the result of RegisterName. | |
| 199 | + | // SendSuper sends a message starting lookup in the receiver's runtime superclass. | |
| 200 | + | // | |
| 201 | + | // Deprecated: Use [SendSuper2] with the class defining the executing method. | |
| 193 | 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 | 209 | super := &objc_super{ | |
| 195 | 210 | receiver: id, | |
| 196 | - | superClass: id.Class(), | |
| 211 | + | superClass: class, | |
| 197 | 212 | } | |
| 198 | 213 | var fn func(objcSuper *objc_super, sel SEL, args ...any) T | |
| 199 | 214 | var zero T | |
| @@ -265,7 +280,7 @@ type FieldDef struct { | |||
| 265 | 280 | } | |
| 266 | 281 | ||
| 267 | 282 | // 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 | 285 | // RegisterClass takes the name of the class to create, the superclass, a list of protocols this class | |
| 271 | 286 | // 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 | 337 | case ReadWrite: | |
| 323 | 338 | ty := reflect.FuncOf( | |
| 324 | 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 | 342 | nil, false, | |
| 328 | 343 | ) | |
| @@ -343,7 +358,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||
| 343 | 358 | // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 | |
| 344 | 359 | // | |
| 345 | 360 | // 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 | 362 | if !ok { | |
| 348 | 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 | 373 | case ReadOnly: | |
| 359 | 374 | ty := reflect.FuncOf( | |
| 360 | 375 | []reflect.Type{ | |
| 361 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), | |
| 376 | + | reflect.TypeFor[ID](), reflect.TypeFor[SEL](), | |
| 362 | 377 | }, | |
| 363 | 378 | []reflect.Type{ivar.Type}, false, | |
| 364 | 379 | ) | |
| @@ -371,7 +386,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||
| 371 | 386 | if len(args) != 2 { | |
| 372 | 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 | 390 | if !ok { | |
| 376 | 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 | 417 | encUShort = "S" | |
| 403 | 418 | encInt = "i" | |
| 404 | 419 | encUInt = "I" | |
| 405 | - | encLong = "l" | |
| 406 | - | encULong = "L" | |
| 420 | + | encLongLong = "q" | |
| 421 | + | encULongLong = "Q" | |
| 407 | 422 | encFloat = "f" | |
| 408 | 423 | encDouble = "d" | |
| 409 | 424 | encBool = "B" | |
| @@ -419,11 +434,11 @@ const ( | |||
| 419 | 434 | // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 | |
| 420 | 435 | func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |
| 421 | 436 | switch typ { | |
| 422 | - | case reflect.TypeOf(Class(0)): | |
| 437 | + | case reflect.TypeFor[Class](): | |
| 423 | 438 | return encClass, nil | |
| 424 | - | case reflect.TypeOf(ID(0)), reflect.TypeOf(Block(0)): | |
| 439 | + | case reflect.TypeFor[ID](), reflect.TypeFor[Block](): | |
| 425 | 440 | return encId, nil | |
| 426 | - | case reflect.TypeOf(SEL(0)): | |
| 441 | + | case reflect.TypeFor[SEL](): | |
| 427 | 442 | return encSelector, nil | |
| 428 | 443 | } | |
| 429 | 444 | ||
| @@ -432,7 +447,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||
| 432 | 447 | case reflect.Bool: | |
| 433 | 448 | return encBool, nil | |
| 434 | 449 | case reflect.Int: | |
| 435 | - | return encLong, nil | |
| 450 | + | return encLongLong, nil | |
| 436 | 451 | case reflect.Int8: | |
| 437 | 452 | return encChar, nil | |
| 438 | 453 | case reflect.Int16: | |
| @@ -440,9 +455,9 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||
| 440 | 455 | case reflect.Int32: | |
| 441 | 456 | return encInt, nil | |
| 442 | 457 | case reflect.Int64: | |
| 443 | - | return encULong, nil | |
| 458 | + | return encLongLong, nil | |
| 444 | 459 | case reflect.Uint: | |
| 445 | - | return encULong, nil | |
| 460 | + | return encULongLong, nil | |
| 446 | 461 | case reflect.Uint8: | |
| 447 | 462 | return encUChar, nil | |
| 448 | 463 | case reflect.Uint16: | |
| @@ -450,7 +465,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||
| 450 | 465 | case reflect.Uint32: | |
| 451 | 466 | return encUInt, nil | |
| 452 | 467 | case reflect.Uint64: | |
| 453 | - | return encULong, nil | |
| 468 | + | return encULongLong, nil | |
| 454 | 469 | case reflect.Uintptr: | |
| 455 | 470 | return encPtr, nil | |
| 456 | 471 | case reflect.Float32: | |
| @@ -468,8 +483,15 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||
| 468 | 483 | encoding.WriteString(encStructBegin) | |
| 469 | 484 | encoding.WriteString(typ.Name()) | |
| 470 | 485 | encoding.WriteString("=") | |
| 471 | - | for i := 0; i < typ.NumField(); i++ { | |
| 486 | + | for i := range typ.NumField() { | |
| 472 | 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 | 495 | tmp, err := encodeType(f.Type, false) | |
| 474 | 496 | if err != nil { | |
| 475 | 497 | return "", err | |
| @@ -571,7 +593,9 @@ func (i Ivar) Name() string { | |||
| 571 | 593 | } | |
| 572 | 594 | ||
| 573 | 595 | // MethodDescription holds the name and type definition of a method. | |
| 596 | + | // It matches the Objective-C runtime's struct objc_method_description. | |
| 574 | 597 | type MethodDescription struct { | |
| 598 | + | _ structs.HostLayout | |
| 575 | 599 | name, types uintptr | |
| 576 | 600 | } | |
| 577 | 601 | ||
| @@ -586,7 +610,9 @@ func (m MethodDescription) Types() string { | |||
| 586 | 610 | } | |
| 587 | 611 | ||
| 588 | 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 | 614 | type PropertyAttribute struct { | |
| 615 | + | _ structs.HostLayout | |
| 590 | 616 | Name, Value *byte | |
| 591 | 617 | } | |
| 592 | 618 | ||
| @@ -626,7 +652,7 @@ func (p *Protocol) Register() { | |||
| 626 | 652 | func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription { | |
| 627 | 653 | count := uint32(0) | |
| 628 | 654 | desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count) | |
| 629 | - | methods := clone(unsafe.Slice(desc, count)) | |
| 655 | + | methods := slices.Clone(unsafe.Slice(desc, count)) | |
| 630 | 656 | free(unsafe.Pointer(desc)) | |
| 631 | 657 | return methods | |
| 632 | 658 | } | |
| @@ -635,7 +661,7 @@ func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod | |||
| 635 | 661 | func (p *Protocol) CopyProtocolList() []*Protocol { | |
| 636 | 662 | count := uint32(0) | |
| 637 | 663 | desc := protocol_copyProtocolList(p, &count) | |
| 638 | - | protocols := clone(unsafe.Slice(desc, count)) | |
| 664 | + | protocols := slices.Clone(unsafe.Slice(desc, count)) | |
| 639 | 665 | free(unsafe.Pointer(desc)) | |
| 640 | 666 | return protocols | |
| 641 | 667 | } | |
| @@ -644,7 +670,7 @@ func (p *Protocol) CopyProtocolList() []*Protocol { | |||
| 644 | 670 | func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property { | |
| 645 | 671 | count := uint32(0) | |
| 646 | 672 | desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty) | |
| 647 | - | protocols := clone(unsafe.Slice(desc, count)) | |
| 673 | + | protocols := slices.Clone(unsafe.Slice(desc, count)) | |
| 648 | 674 | free(unsafe.Pointer(desc)) | |
| 649 | 675 | return protocols | |
| 650 | 676 | } | |
| @@ -691,21 +717,10 @@ func NewIMP(fn any) IMP { | |||
| 691 | 717 | switch { | |
| 692 | 718 | case ty.NumIn() < 2: | |
| 693 | 719 | fallthrough | |
| 694 | - | case ty.In(0) != reflect.TypeOf(ID(0)): | |
| 720 | + | case ty.In(0) != reflect.TypeFor[ID](): | |
| 695 | 721 | fallthrough | |
| 696 | - | case ty.In(1) != reflect.TypeOf(SEL(0)): | |
| 722 | + | case ty.In(1) != reflect.TypeFor[SEL](): | |
| 697 | 723 | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | |
| 698 | 724 | } | |
| 699 | 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 | 7 | "fmt" | |
| 8 | 8 | "log" | |
| 9 | 9 | "reflect" | |
| 10 | + | "structs" | |
| 10 | 11 | "testing" | |
| 11 | 12 | ||
| 12 | 13 | "github.com/bnema/purego" | |
| @@ -38,6 +39,7 @@ func ExampleRegisterClass_helloworld() { | |||
| 38 | 39 | } | |
| 39 | 40 | ||
| 40 | 41 | func ExampleRegisterClass() { | |
| 42 | + | var class objc.Class | |
| 41 | 43 | var ( | |
| 42 | 44 | sel_new = objc.RegisterName("new") | |
| 43 | 45 | sel_init = objc.RegisterName("init") | |
| @@ -45,11 +47,12 @@ func ExampleRegisterClass() { | |||
| 45 | 47 | sel_bar = objc.RegisterName("bar") | |
| 46 | 48 | ||
| 47 | 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 | 56 | "BarObject", | |
| 54 | 57 | objc.GetClass("NSObject"), | |
| 55 | 58 | []*objc.Protocol{ | |
| @@ -58,12 +61,12 @@ func ExampleRegisterClass() { | |||
| 58 | 61 | []objc.FieldDef{ | |
| 59 | 62 | { | |
| 60 | 63 | Name: "bar", | |
| 61 | - | Type: reflect.TypeOf(int(0)), | |
| 64 | + | Type: reflect.TypeFor[int](), | |
| 62 | 65 | Attribute: objc.ReadWrite, | |
| 63 | 66 | }, | |
| 64 | 67 | { | |
| 65 | 68 | Name: "foo", | |
| 66 | - | Type: reflect.TypeOf(false), | |
| 69 | + | Type: reflect.TypeFor[bool](), | |
| 67 | 70 | Attribute: objc.ReadWrite, | |
| 68 | 71 | }, | |
| 69 | 72 | }, | |
| @@ -94,17 +97,25 @@ func ExampleIMP() { | |||
| 94 | 97 | // Output: IMP: 105 567 9 2 3 -5 4 8 9 | |
| 95 | 98 | } | |
| 96 | 99 | ||
| 97 | - | func ExampleID_SendSuper() { | |
| 98 | - | super, err := objc.RegisterClass( | |
| 99 | - | "SuperObject", | |
| 100 | + | // This example shows three-level inheritance with Base <- Child1 <- Child2. | |
| 101 | + | // SendSuper2 takes Child1, the class defining the method. | |
| 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 | 111 | objc.GetClass("NSObject"), | |
| 101 | 112 | nil, | |
| 102 | 113 | nil, | |
| 103 | 114 | []objc.MethodDef{ | |
| 104 | 115 | { | |
| 105 | - | Cmd: objc.RegisterName("doSomething"), | |
| 106 | - | Fn: func(self objc.ID, _cmd objc.SEL) { | |
| 107 | - | fmt.Println("In Super!") | |
| 116 | + | Cmd: sel, | |
| 117 | + | Fn: func(self objc.ID, cmd objc.SEL) { | |
| 118 | + | fmt.Println("Base") | |
| 108 | 119 | }, | |
| 109 | 120 | }, | |
| 110 | 121 | }, | |
| @@ -113,17 +124,19 @@ func ExampleID_SendSuper() { | |||
| 113 | 124 | panic(err) | |
| 114 | 125 | } | |
| 115 | 126 | ||
| 116 | - | child, err := objc.RegisterClass( | |
| 117 | - | "ChildObject", | |
| 118 | - | super, | |
| 127 | + | var child1 objc.Class | |
| 128 | + | child1, err = objc.RegisterClass( | |
| 129 | + | "SuperExampleChild1", | |
| 130 | + | base, | |
| 119 | 131 | nil, | |
| 120 | 132 | nil, | |
| 121 | 133 | []objc.MethodDef{ | |
| 122 | 134 | { | |
| 123 | - | Cmd: objc.RegisterName("doSomething"), | |
| 124 | - | Fn: func(self objc.ID, _cmd objc.SEL) { | |
| 125 | - | fmt.Println("In Child") | |
| 126 | - | self.SendSuper(_cmd) | |
| 135 | + | Cmd: sel, | |
| 136 | + | Fn: func(self objc.ID, cmd objc.SEL) { | |
| 137 | + | // Start lookup in the superclass of Child1. | |
| 138 | + | fmt.Print("SendSuper2: ") | |
| 139 | + | self.SendSuper2(child1, cmd) | |
| 127 | 140 | }, | |
| 128 | 141 | }, | |
| 129 | 142 | }, | |
| @@ -132,9 +145,27 @@ func ExampleID_SendSuper() { | |||
| 132 | 145 | panic(err) | |
| 133 | 146 | } | |
| 134 | 147 | ||
| 135 | - | objc.ID(child).Send(objc.RegisterName("new")).Send(objc.RegisterName("doSomething")) | |
| 136 | - | // Output: In Child | |
| 137 | - | // In Super! | |
| 148 | + | // Child2 inherits Child1's implementation of run. | |
| 149 | + | child2, err := objc.RegisterClass("SuperExampleChild2", child1, nil, nil, nil) | |
| 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 | 171 | func TestSend(t *testing.T) { | |
| @@ -155,6 +186,7 @@ func TestSend(t *testing.T) { | |||
| 155 | 186 | ||
| 156 | 187 | func ExampleSend() { | |
| 157 | 188 | type NSRange struct { | |
| 189 | + | _ structs.HostLayout | |
| 158 | 190 | Location, Range uint | |
| 159 | 191 | } | |
| 160 | 192 | class_NSString := objc.GetClass("NSString") | |
| @@ -164,11 +196,11 @@ func ExampleSend() { | |||
| 164 | 196 | subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00") | |
| 165 | 197 | ||
| 166 | 198 | r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString) | |
| 167 | - | fmt.Println(r) | |
| 168 | - | // Output: {3 7} | |
| 199 | + | fmt.Println(r.Location, r.Range) | |
| 200 | + | // Output: 3 7 | |
| 169 | 201 | } | |
| 170 | 202 | ||
| 171 | - | func ExampleSendSuper() { | |
| 203 | + | func ExampleSendSuper2() { | |
| 172 | 204 | super, err := objc.RegisterClass( | |
| 173 | 205 | "SuperObject2", | |
| 174 | 206 | objc.GetClass("NSObject"), | |
| @@ -205,7 +237,7 @@ func ExampleSendSuper() { | |||
| 205 | 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 | 241 | fmt.Println(res) | |
| 210 | 242 | // 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 | 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 | 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 | 51 | switch { | |
| 16 | 52 | case outSize == 0: | |
| 17 | 53 | return reflect.New(outType).Elem() | |
| @@ -23,30 +59,36 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||
| 23 | 59 | // up to 8 bytes is returned in RAX | |
| 24 | 60 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() | |
| 25 | 61 | case outSize <= 16: | |
| 26 | - | var r1, r2 uintptr | |
| 27 | - | var intRegs, floatRegs int | |
| 28 | - | for i := 0; i < int((outSize+7)/8); i++ { | |
| 29 | - | class := classifyEightbyte(outType, uintptr(i)*8, uintptr(i)*8+8) | |
| 30 | - | var reg uintptr | |
| 31 | - | if class == _SSE { | |
| 32 | - | if floatRegs == 0 { | |
| 33 | - | reg = syscall.f1 | |
| 34 | - | } else { | |
| 35 | - | reg = syscall.f2 | |
| 36 | - | } | |
| 37 | - | floatRegs++ | |
| 38 | - | } else { | |
| 39 | - | if intRegs == 0 { | |
| 40 | - | reg = syscall.a1 | |
| 41 | - | } else { | |
| 42 | - | reg = syscall.a2 | |
| 62 | + | r1, r2 := syscall.a1, syscall.a2 | |
| 63 | + | if isAllFloats(outType) { | |
| 64 | + | r1 = syscall.f1 | |
| 65 | + | r2 = syscall.f2 | |
| 66 | + | } else { | |
| 67 | + | // check first 8 bytes if it's floats | |
| 68 | + | hasFirstFloat := false | |
| 69 | + | numFields := numABIFields(outType) | |
| 70 | + | f1 := abiField(outType, 0).Type | |
| 71 | + | if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && abiField(outType, 1).Type.Kind() == reflect.Float32 { | |
| 72 | + | r1 = syscall.f1 | |
| 73 | + | hasFirstFloat = true | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | // find index of the field that starts the second 8 bytes | |
| 77 | + | var i int | |
| 78 | + | 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 { | |
| 47 | - | r1 = reg | |
| 48 | - | } else { | |
| 49 | - | r2 = reg | |
| 83 | + | ||
| 84 | + | // check last 8 bytes if they are floats | |
| 85 | + | f1 = abiField(outType, i).Type | |
| 86 | + | 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 | 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 | 102 | func isAllFloats(ty reflect.Type) bool { | |
| 61 | - | for i := 0; i < ty.NumField(); i++ { | |
| 103 | + | for i := range ty.NumField() { | |
| 62 | 104 | f := ty.Field(i) | |
| 105 | + | if !isABIField(f) { | |
| 106 | + | continue | |
| 107 | + | } | |
| 63 | 108 | switch f.Type.Kind() { | |
| 64 | 109 | case reflect.Float64, reflect.Float32: | |
| 65 | 110 | default: | |
| @@ -82,6 +127,12 @@ const ( | |||
| 82 | 127 | ) | |
| 83 | 128 | ||
| 84 | 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 | 136 | if v.Type().Size() == 0 { | |
| 86 | 137 | return keepAlive | |
| 87 | 138 | } | |
| @@ -107,6 +158,27 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||
| 107 | 158 | 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 | 182 | func postMerger(t reflect.Type) (passInMemory bool) { | |
| 111 | 183 | // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other | |
| 112 | 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 | 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 | 227 | flushed = false | |
| 153 | 228 | var f reflect.Value | |
| 154 | 229 | if v.Kind() == reflect.Struct { | |
| @@ -294,7 +369,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 294 | 369 | // - If not enough registers for all eightbytes: entire struct goes on the stack | |
| 295 | 370 | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |
| 296 | 371 | switch runtime.GOOS { | |
| 297 | - | case "darwin", "freebsd", "linux", "netbsd": | |
| 372 | + | case "android", "darwin", "freebsd", "ios", "linux", "netbsd": | |
| 298 | 373 | default: | |
| 299 | 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 | 391 | // Count how many integer and SSE registers this struct needs. | |
| 317 | 392 | var needInts, needFloats int | |
| 318 | - | for i := 0; i < numEightbytes; i++ { | |
| 393 | + | for i := range numEightbytes { | |
| 319 | 394 | class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8) | |
| 320 | 395 | if class == _SSE { | |
| 321 | 396 | needFloats++ | |
| @@ -333,7 +408,7 @@ func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, | |||
| 333 | 408 | ||
| 334 | 409 | // Read each eightbyte from its appropriate register class. | |
| 335 | 410 | var r1, r2 uintptr | |
| 336 | - | for i := 0; i < numEightbytes; i++ { | |
| 411 | + | for i := range numEightbytes { | |
| 337 | 412 | class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8) | |
| 338 | 413 | if class == _SSE { | |
| 339 | 414 | if i == 0 { | |
| @@ -415,7 +490,7 @@ func doClassifyEightbyte(t reflect.Type, base, start, end uintptr) int { | |||
| 415 | 490 | switch t.Kind() { | |
| 416 | 491 | case reflect.Struct: | |
| 417 | 492 | class := _NO_CLASS | |
| 418 | - | for i := 0; i < t.NumField(); i++ { | |
| 493 | + | for i := range t.NumField() { | |
| 419 | 494 | f := t.Field(i) | |
| 420 | 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 | 498 | case reflect.Array: | |
| 424 | 499 | class := _NO_CLASS | |
| 425 | 500 | elemSize := t.Elem().Size() | |
| 426 | - | for i := 0; i < t.Len(); i++ { | |
| 501 | + | for i := range t.Len() { | |
| 427 | 502 | class |= doClassifyEightbyte(t.Elem(), base+uintptr(i)*elemSize, start, end) | |
| 428 | 503 | } | |
| 429 | 504 | 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 | 6 | import ( | |
| 7 | 7 | "reflect" | |
| 8 | - | "runtime" | |
| 9 | 8 | "unsafe" | |
| 10 | 9 | ) | |
| 11 | 10 | ||
| @@ -17,10 +16,8 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||
| 17 | 16 | ||
| 18 | 17 | // TODO: ARM EABI: small structs are passed in registers or on stack | |
| 19 | 18 | // For simplicity, pass by pointer for now | |
| 20 | - | ptr, live := stableValuePointer(v) | |
| 21 | - | if live != nil { | |
| 22 | - | keepAlive = append(keepAlive, live) | |
| 23 | - | } | |
| 19 | + | ptr := v.Addr().UnsafePointer() | |
| 20 | + | keepAlive = append(keepAlive, ptr) | |
| 24 | 21 | if *numInts < 4 { | |
| 25 | 22 | addInt(uintptr(ptr)) | |
| 26 | 23 | *numInts++ | |
| @@ -28,11 +25,17 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||
| 28 | 25 | addStack(uintptr(ptr)) | |
| 29 | 26 | *numStack++ | |
| 30 | 27 | } | |
| 31 | - | runtime.KeepAlive(live) | |
| 32 | 28 | 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 | 39 | outSize := outType.Size() | |
| 37 | 40 | if outSize == 0 { | |
| 38 | 41 | return reflect.New(outType).Elem() | |
| @@ -56,14 +59,13 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr | |||
| 56 | 59 | if size == 0 { | |
| 57 | 60 | return | |
| 58 | 61 | } | |
| 59 | - | ptr, live := stableValuePointer(v) | |
| 62 | + | ptr := unsafe.Pointer(v.UnsafeAddr()) | |
| 60 | 63 | if size <= 4 { | |
| 61 | 64 | addInt(*(*uintptr)(ptr)) | |
| 62 | 65 | } else if size <= 8 { | |
| 63 | 66 | addInt(*(*uintptr)(ptr)) | |
| 64 | 67 | addInt(*(*uintptr)(unsafe.Add(ptr, 4))) | |
| 65 | 68 | } | |
| 66 | - | runtime.KeepAlive(live) | |
| 67 | 69 | } | |
| 68 | 70 | ||
| 69 | 71 | // shouldBundleStackArgs always returns false on arm | |
M struct_arm64.go +65 -67
| @@ -14,7 +14,14 @@ import ( | |||
| 14 | 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 | 25 | outSize := outType.Size() | |
| 19 | 26 | switch { | |
| 20 | 27 | case outSize == 0: | |
| @@ -78,9 +85,9 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||
| 78 | 85 | if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 { | |
| 79 | 86 | // if this doesn't fit entirely in registers then | |
| 80 | 87 | // each element goes onto the stack | |
| 81 | - | if hfa && *numFloats+v.NumField() > numOfFloatRegisters() { | |
| 88 | + | if hfa && *numFloats+numABIFields(v.Type()) > numOfFloatRegisters() { | |
| 82 | 89 | *numFloats = numOfFloatRegisters() | |
| 83 | - | } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() { | |
| 90 | + | } else if hva && *numInts+numABIFields(v.Type()) > numOfIntegerRegisters() { | |
| 84 | 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 | 101 | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | |
| 95 | - | if runtime.GOOS == "darwin" { | |
| 102 | + | if isDarwin { | |
| 96 | 103 | placeRegistersDarwin(v, addFloat, addInt) | |
| 97 | 104 | return | |
| 98 | 105 | } | |
| @@ -112,7 +119,10 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui | |||
| 112 | 119 | } else { | |
| 113 | 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 | 126 | flushed = false | |
| 117 | 127 | var f reflect.Value | |
| 118 | 128 | if v.Kind() == reflect.Struct { | |
| @@ -229,52 +239,39 @@ func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |||
| 229 | 239 | // | |
| 230 | 240 | // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst | |
| 231 | 241 | func isHFA(t reflect.Type) bool { | |
| 242 | + | // round up struct size to nearest 8 see section B.4 | |
| 232 | 243 | structSize := roundUpTo8(t.Size()) | |
| 233 | - | if structSize == 0 { | |
| 244 | + | numFields := numABIFields(t) | |
| 245 | + | if structSize == 0 || numFields > 4 { | |
| 234 | 246 | return false | |
| 235 | 247 | } | |
| 236 | - | var kind reflect.Kind | |
| 237 | - | var members int | |
| 238 | - | var walk func(reflect.Type) bool | |
| 239 | - | walk = func(t reflect.Type) bool { | |
| 240 | - | for i := 0; i < t.NumField(); i++ { | |
| 241 | - | ft := t.Field(i).Type | |
| 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: | |
| 248 | + | first := abiField(t, 0) | |
| 249 | + | switch first.Type.Kind() { | |
| 250 | + | case reflect.Float32, reflect.Float64: | |
| 251 | + | firstKind := first.Type.Kind() | |
| 252 | + | for i := range numFields { | |
| 253 | + | if abiField(t, i).Type.Kind() != firstKind { | |
| 272 | 254 | return false | |
| 273 | 255 | } | |
| 274 | 256 | } | |
| 275 | 257 | 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 | 277 | // 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 | 287 | if structSize == 0 || (structSize != 8 && structSize != 16) { | |
| 291 | 288 | return false | |
| 292 | 289 | } | |
| 293 | - | first := t.Field(0) | |
| 290 | + | numFields := numABIFields(t) | |
| 291 | + | first := abiField(t, 0) | |
| 294 | 292 | switch first.Type.Kind() { | |
| 295 | 293 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: | |
| 296 | 294 | firstKind := first.Type.Kind() | |
| 297 | - | for i := 0; i < t.NumField(); i++ { | |
| 298 | - | if t.Field(i).Type.Kind() != firstKind { | |
| 295 | + | for i := range numFields { | |
| 296 | + | if abiField(t, i).Type.Kind() != firstKind { | |
| 299 | 297 | return false | |
| 300 | 298 | } | |
| 301 | 299 | } | |
| @@ -315,7 +313,7 @@ func isHVA(t reflect.Type) bool { | |||
| 315 | 313 | // copyStruct8ByteChunks copies struct memory in 8-byte chunks to the provided callback. | |
| 316 | 314 | // This is used for Darwin ARM64's byte-level packing of non-HFA/HVA structs. | |
| 317 | 315 | func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintptr)) { | |
| 318 | - | if runtime.GOOS != "darwin" { | |
| 316 | + | if !isDarwin { | |
| 319 | 317 | panic("purego: should only be called on darwin") | |
| 320 | 318 | } | |
| 321 | 319 | for offset := uintptr(0); offset < size; offset += 8 { | |
| @@ -325,7 +323,7 @@ func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintp | |||
| 325 | 323 | chunk = *(*uintptr)(unsafe.Add(ptr, offset)) | |
| 326 | 324 | } else { | |
| 327 | 325 | // Read byte-by-byte to avoid reading beyond allocation | |
| 328 | - | for i := uintptr(0); i < remaining; i++ { | |
| 326 | + | for i := range remaining { | |
| 329 | 327 | b := *(*byte)(unsafe.Add(ptr, offset+i)) | |
| 330 | 328 | chunk |= uintptr(b) << (i * 8) | |
| 331 | 329 | } | |
| @@ -342,7 +340,7 @@ func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintp | |||
| 342 | 340 | // For non-HFA/HVA structs, Darwin uses byte-level packing. We copy the struct memory in | |
| 343 | 341 | // 8-byte chunks, which works correctly for both register and stack placement. | |
| 344 | 342 | func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | |
| 345 | - | if runtime.GOOS != "darwin" { | |
| 343 | + | if !isDarwin { | |
| 346 | 344 | panic("purego: placeRegistersDarwin should only be called on darwin") | |
| 347 | 345 | } | |
| 348 | 346 | // Check if this is an HFA/HVA | |
| @@ -370,7 +368,7 @@ func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(u | |||
| 370 | 368 | // shouldBundleStackArgs determines if we need to start C-style packing for | |
| 371 | 369 | // Darwin ARM64 stack arguments. This happens when registers are exhausted. | |
| 372 | 370 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |
| 373 | - | if runtime.GOOS != "darwin" { | |
| 371 | + | if !isDarwin { | |
| 374 | 372 | return false | |
| 375 | 373 | } | |
| 376 | 374 | ||
| @@ -395,12 +393,12 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |||
| 395 | 393 | } | |
| 396 | 394 | ||
| 397 | 395 | if hfa { | |
| 398 | - | need := v.NumField() | |
| 396 | + | need := numABIFields(v.Type()) | |
| 399 | 397 | return numFloats+need > numOfFloatRegisters() | |
| 400 | 398 | } | |
| 401 | 399 | ||
| 402 | 400 | if hva { | |
| 403 | - | need := v.NumField() | |
| 401 | + | need := numABIFields(v.Type()) | |
| 404 | 402 | return numInts+need > numOfIntegerRegisters() | |
| 405 | 403 | } | |
| 406 | 404 | ||
| @@ -412,7 +410,7 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |||
| 412 | 410 | // registers, used during stack argument bundling to decide if a struct | |
| 413 | 411 | // should go through normal register allocation or be bundled with stack args. | |
| 414 | 412 | func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) { | |
| 415 | - | if runtime.GOOS != "darwin" { | |
| 413 | + | if !isDarwin { | |
| 416 | 414 | panic("purego: structFitsInRegisters should only be called on darwin") | |
| 417 | 415 | } | |
| 418 | 416 | hfa := isHFA(val.Type()) | |
| @@ -421,13 +419,13 @@ func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (b | |||
| 421 | 419 | ||
| 422 | 420 | if hfa { | |
| 423 | 421 | // HFA: check if elements fit in float registers | |
| 424 | - | if tempNumFloats+val.NumField() <= numOfFloatRegisters() { | |
| 425 | - | return true, tempNumInts, tempNumFloats + val.NumField() | |
| 422 | + | if numFields := numABIFields(val.Type()); tempNumFloats+numFields <= numOfFloatRegisters() { | |
| 423 | + | return true, tempNumInts, tempNumFloats + numFields | |
| 426 | 424 | } | |
| 427 | 425 | } else if hva { | |
| 428 | 426 | // HVA: check if elements fit in int registers | |
| 429 | - | if tempNumInts+val.NumField() <= numOfIntegerRegisters() { | |
| 430 | - | return true, tempNumInts + val.NumField(), tempNumFloats | |
| 427 | + | if numFields := numABIFields(val.Type()); tempNumInts+numFields <= numOfIntegerRegisters() { | |
| 428 | + | return true, tempNumInts + numFields, tempNumFloats | |
| 431 | 429 | } | |
| 432 | 430 | } else if size <= 16 { | |
| 433 | 431 | // 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 | 443 | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | |
| 446 | 444 | keepAlive []any, addInt, addFloat, addStack func(uintptr), | |
| 447 | 445 | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | |
| 448 | - | if runtime.GOOS != "darwin" { | |
| 446 | + | if !isDarwin { | |
| 449 | 447 | panic("purego: collectStackArgs should only be called on darwin") | |
| 450 | 448 | } | |
| 451 | 449 | ||
| @@ -502,7 +500,7 @@ const ( | |||
| 502 | 500 | // bundleStackArgs bundles remaining arguments for Darwin ARM64 C-style stack packing. | |
| 503 | 501 | // It creates a packed struct with proper alignment and copies it to the stack in 8-byte chunks. | |
| 504 | 502 | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |
| 505 | - | if runtime.GOOS != "darwin" { | |
| 503 | + | if !isDarwin { | |
| 506 | 504 | panic("purego: bundleStackArgs should only be called on darwin") | |
| 507 | 505 | } | |
| 508 | 506 | if len(stackArgs) == 0 { | |
| @@ -528,7 +526,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 528 | 526 | paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign)) | |
| 529 | 527 | fields = append(fields, reflect.StructField{ | |
| 530 | 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 | 531 | currentOffset += paddingNeeded | |
| 534 | 532 | fieldIndex++ | |
| @@ -548,7 +546,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 548 | 546 | ||
| 549 | 547 | // Set values (skip padding fields) | |
| 550 | 548 | argIndex := 0 | |
| 551 | - | for j := 0; j < structInstance.NumField(); j++ { | |
| 549 | + | for j := range structInstance.NumField() { | |
| 552 | 550 | fieldName := structType.Field(j).Name | |
| 553 | 551 | if stdstrings.HasPrefix(fieldName, paddingFieldPrefix) { | |
| 554 | 552 | continue | |
| @@ -576,7 +574,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 576 | 574 | // - Darwin ARM64: byte-level packing on the stack | |
| 577 | 575 | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |
| 578 | 576 | switch runtime.GOOS { | |
| 579 | - | case "darwin", "freebsd", "linux", "netbsd": | |
| 577 | + | case "android", "darwin", "freebsd", "ios", "linux", "netbsd": | |
| 580 | 578 | default: | |
| 581 | 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 | 623 | // Pointer on stack (rare: all integer registers exhausted). | |
| 626 | - | if runtime.GOOS == "darwin" { | |
| 627 | - | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeOf(uintptr(0))) | |
| 624 | + | if isDarwin { | |
| 625 | + | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeFor[uintptr]()) | |
| 628 | 626 | ptr := uintptr(ptrVal.Uint()) | |
| 629 | 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 | 638 | size := inType.Size() | |
| 641 | 639 | ||
| 642 | 640 | // Determine the element type | |
| 643 | - | root := inType.Field(0).Type | |
| 641 | + | root := abiField(inType, 0).Type | |
| 644 | 642 | for root.Kind() == reflect.Struct { | |
| 645 | - | root = root.Field(0).Type | |
| 643 | + | root = abiField(root, 0).Type | |
| 646 | 644 | } | |
| 647 | 645 | isFloat32 := root.Kind() == reflect.Float32 | |
| 648 | 646 | ||
| @@ -704,7 +702,7 @@ func readHFAFromRegisters(inType reflect.Type, f *[callbackMaxFrame]uintptr, flo | |||
| 704 | 702 | // callback frame. On Darwin ARM64, arguments are byte-packed on the stack. | |
| 705 | 703 | // On Linux ARM64, arguments are 8-byte aligned. | |
| 706 | 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 | 706 | return callbackArgFromStack(frame, *stackSlot, stackByteOffset, inType) | |
| 709 | 707 | } | |
| 710 | 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 | 4 | package purego | |
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | - | "math" | |
| 8 | 7 | "reflect" | |
| 9 | 8 | "unsafe" | |
| 10 | 9 | ) | |
| 11 | 10 | ||
| 12 | - | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |
| 13 | - | outSize := outType.Size() | |
| 14 | - | switch { | |
| 15 | - | case outSize == 0: | |
| 16 | - | return reflect.New(outType).Elem() | |
| 17 | - | case outSize <= 8: | |
| 18 | - | r1 := syscall.a1 | |
| 19 | - | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 20 | - | r1 = syscall.f1 | |
| 21 | - | if numFields == 2 { | |
| 22 | - | r1 = packFloat32Pair(syscall.f1, syscall.f2) | |
| 11 | + | // loong64Leaf is a scalar member of an aggregate after flattening nested structs | |
| 12 | + | // and arrays. offset is the byte offset of the member within the aggregate. | |
| 13 | + | type loong64Leaf struct { | |
| 14 | + | isFloat bool | |
| 15 | + | kind reflect.Kind | |
| 16 | + | offset uintptr | |
| 17 | + | size uintptr | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | // loong64Flatten appends the scalar leaves of t to leaves, offsetting each member | |
| 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() | |
| 26 | - | case outSize <= 16: | |
| 27 | - | r1, r2 := syscall.a1, syscall.a2 | |
| 28 | - | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 29 | - | switch numFields { | |
| 30 | - | case 4: | |
| 31 | - | r1 = packFloat32Pair(syscall.f1, syscall.f2) | |
| 32 | - | r2 = packFloat32Pair(syscall.f3, syscall.f4) | |
| 33 | - | case 3: | |
| 34 | - | r1 = packFloat32Pair(syscall.f1, syscall.f2) | |
| 35 | - | r2 = syscall.f3 | |
| 36 | - | case 2: | |
| 37 | - | r1 = syscall.f1 | |
| 38 | - | r2 = syscall.f2 | |
| 39 | - | default: | |
| 40 | - | panic("not reached") | |
| 41 | - | } | |
| 34 | + | case reflect.Array: | |
| 35 | + | elem := t.Elem() | |
| 36 | + | for i := range t.Len() { | |
| 37 | + | loong64Flatten(elem, base+uintptr(i)*elem.Size(), leaves) | |
| 38 | + | } | |
| 39 | + | default: | |
| 40 | + | k := t.Kind() | |
| 41 | + | *leaves = append(*leaves, loong64Leaf{ | |
| 42 | + | isFloat: k == reflect.Float32 || k == reflect.Float64, | |
| 43 | + | kind: k, | |
| 44 | + | offset: base, | |
| 45 | + | size: t.Size(), | |
| 46 | + | }) | |
| 47 | + | } | |
| 48 | + | } | |
| 49 | + | ||
| 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 | 70 | default: | |
| 45 | - | // create struct from the Go pointer created above | |
| 46 | - | // weird pointer dereference to circumvent go vet | |
| 71 | + | return leaves, false | |
| 72 | + | } | |
| 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 | 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 ( | |
| 52 | - | _NO_CLASS = 0b00 | |
| 53 | - | _FLOAT = 0b01 | |
| 54 | - | _INT = 0b11 | |
| 55 | - | ) | |
| 123 | + | func loong64StoreInt(dst unsafe.Pointer, size uintptr, r uintptr) { | |
| 124 | + | switch size { | |
| 125 | + | case 1: | |
| 126 | + | *(*uint8)(dst) = uint8(r) | |
| 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 | 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 | 139 | 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 { | |
| 63 | - | placeRegisters(v, addFloat, addInt) | |
| 145 | + | var ptr unsafe.Pointer | |
| 146 | + | if v.CanAddr() { | |
| 147 | + | ptr = v.Addr().UnsafePointer() | |
| 64 | 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)) { | |
| 71 | - | var val uint64 | |
| 72 | - | var shift byte | |
| 73 | - | var flushed bool | |
| 74 | - | class := _NO_CLASS | |
| 75 | - | var place func(v reflect.Value) | |
| 76 | - | place = func(v reflect.Value) { | |
| 77 | - | var numFields int | |
| 78 | - | if v.Kind() == reflect.Struct { | |
| 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) | |
| 155 | + | if leaves, useFP := loong64Classify(v.Type()); useFP { | |
| 156 | + | for _, l := range leaves { | |
| 157 | + | src := unsafe.Add(ptr, l.offset) | |
| 158 | + | switch { | |
| 159 | + | case l.isFloat && l.kind == reflect.Float32: | |
| 160 | + | // NaN-box the single-precision value in the 64-bit FP register. | |
| 161 | + | addFloat(uintptr(*(*uint32)(src)) | 0xFFFFFFFF_00000000) | |
| 162 | + | case l.isFloat: | |
| 163 | + | addFloat(uintptr(*(*uint64)(src))) | |
| 161 | 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) | |
| 167 | - | if !flushed { | |
| 168 | - | if class == _FLOAT { | |
| 169 | - | addFloat(uintptr(val)) | |
| 170 | - | } else { | |
| 171 | - | addInt(uintptr(val)) | |
| 172 | - | } | |
| 170 | + | ||
| 171 | + | // Integer calling convention: pass the raw aggregate in one or two GARs. | |
| 172 | + | var words [16]byte | |
| 173 | + | copy(words[:], unsafe.Slice((*byte)(ptr), size)) | |
| 174 | + | addInt(*(*uintptr)(unsafe.Pointer(&words[0]))) | |
| 175 | + | if size > 8 { | |
| 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 | 4 | package purego | |
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | + | "math" | |
| 7 | 8 | "reflect" | |
| 8 | 9 | "unsafe" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| 11 | - | func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | |
| 12 | - | outSize := outType.Size() | |
| 13 | - | ||
| 14 | - | switch { | |
| 15 | - | case outSize == 0: | |
| 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 | - | } | |
| 12 | + | // ppc64leLeaf is a scalar member of a flattened aggregate at byte offset offset. | |
| 13 | + | type ppc64leLeaf struct { | |
| 14 | + | kind reflect.Kind | |
| 15 | + | offset uintptr | |
| 16 | + | } | |
| 27 | 17 | ||
| 28 | - | // Homogeneous float aggregates override integer regs | |
| 29 | - | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 30 | - | if outType.Field(0).Type.Kind() == reflect.Float32 { | |
| 31 | - | // float32 values in FP regs | |
| 32 | - | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | |
| 33 | - | for i := 0; i < numFields; i++ { | |
| 34 | - | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | |
| 35 | - | } | |
| 36 | - | } else { | |
| 37 | - | // float64: whole register value is valid | |
| 38 | - | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.f1 | |
| 39 | - | if outSize > 8 { | |
| 40 | - | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.f2 | |
| 41 | - | } | |
| 18 | + | // ppc64leFlatten appends the scalar leaves of t to leaves, expanding nested | |
| 19 | + | // structs and arrays. | |
| 20 | + | func ppc64leFlatten(t reflect.Type, base uintptr, leaves *[]ppc64leLeaf) { | |
| 21 | + | switch t.Kind() { | |
| 22 | + | case reflect.Struct: | |
| 23 | + | for i := range t.NumField() { | |
| 24 | + | f := t.Field(i) | |
| 25 | + | if f.Name == "_" { | |
| 26 | + | // Blank fields are padding, not counted members. | |
| 27 | + | continue | |
| 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 | 36 | default: | |
| 48 | - | // Returned indirectly via pointer in a1 | |
| 49 | - | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)) | |
| 50 | - | return reflect.NewAt(outType, ptr).Elem() | |
| 37 | + | *leaves = append(*leaves, ppc64leLeaf{kind: t.Kind(), offset: base}) | |
| 51 | 38 | } | |
| 52 | 39 | } | |
| 53 | 40 | ||
| 54 | - | func addStruct( | |
| 55 | - | v reflect.Value, | |
| 56 | - | numInts, numFloats, numStack *int, | |
| 57 | - | addInt, addFloat, addStack func(uintptr), | |
| 58 | - | keepAlive []any, | |
| 59 | - | ) []any { | |
| 60 | - | size := v.Type().Size() | |
| 61 | - | if size == 0 { | |
| 62 | - | return keepAlive | |
| 41 | + | // ppc64leClassifyHFA reports whether t is a homogeneous floating-point | |
| 42 | + | // aggregate: one to eight members all of the same floating-point type. | |
| 43 | + | func ppc64leClassifyHFA(t reflect.Type) (leaves []ppc64leLeaf, isHFA bool) { | |
| 44 | + | ppc64leFlatten(t, 0, &leaves) | |
| 45 | + | if len(leaves) == 0 || len(leaves) > 8 { | |
| 46 | + | return leaves, false | |
| 63 | 47 | } | |
| 64 | - | ||
| 65 | - | if size <= 16 { | |
| 66 | - | return placeSmallAggregatePPC64LE(v, addFloat, addInt, keepAlive) | |
| 48 | + | first := leaves[0].kind | |
| 49 | + | if first != reflect.Float32 && first != reflect.Float64 { | |
| 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( | |
| 73 | - | v reflect.Value, | |
| 74 | - | addFloat, addInt func(uintptr), | |
| 75 | - | keepAlive []any, | |
| 76 | - | ) []any { | |
| 72 | + | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |
| 77 | 73 | size := v.Type().Size() | |
| 74 | + | if size == 0 { | |
| 75 | + | return keepAlive | |
| 76 | + | } | |
| 78 | 77 | ||
| 79 | 78 | var ptr unsafe.Pointer | |
| 80 | 79 | if v.CanAddr() { | |
| @@ -86,58 +85,88 @@ func placeSmallAggregatePPC64LE( | |||
| 86 | 85 | keepAlive = append(keepAlive, tmp.Interface()) | |
| 87 | 86 | } | |
| 88 | 87 | ||
| 89 | - | var buf [16]byte | |
| 90 | - | src := unsafe.Slice((*byte)(ptr), size) | |
| 91 | - | copy(buf[:], src) | |
| 92 | - | ||
| 93 | - | w0 := *(*uintptr)(unsafe.Pointer(&buf[0])) | |
| 94 | - | w1 := uintptr(0) | |
| 95 | - | if size > 8 { | |
| 96 | - | w1 = *(*uintptr)(unsafe.Pointer(&buf[8])) | |
| 88 | + | // An HFA passes each member in its own floating-point register. | |
| 89 | + | if leaves, isHFA := ppc64leClassifyHFA(v.Type()); isHFA { | |
| 90 | + | for _, l := range leaves { | |
| 91 | + | src := unsafe.Add(ptr, l.offset) | |
| 92 | + | if l.kind == reflect.Float32 { | |
| 93 | + | // Single precision occupies the register in double format. | |
| 94 | + | addFloat(uintptr(math.Float64bits(float64(*(*float32)(src))))) | |
| 95 | + | } else { | |
| 96 | + | addFloat(uintptr(*(*uint64)(src))) | |
| 97 | + | } | |
| 98 | + | } | |
| 97 | 99 | } | |
| 98 | 100 | ||
| 99 | - | if isFloats, _ := isAllSameFloat(v.Type()); isFloats { | |
| 100 | - | addFloat(w0) | |
| 101 | - | if size > 8 { | |
| 102 | - | addFloat(w1) | |
| 103 | - | } | |
| 104 | - | } else { | |
| 105 | - | addInt(w0) | |
| 106 | - | if size > 8 { | |
| 107 | - | addInt(w1) | |
| 101 | + | // Every aggregate also occupies its size rounded up to doublewords in the | |
| 102 | + | // GPRs r3-r10 / parameter save area: shadow (skipped) for an HFA, the value | |
| 103 | + | // otherwise. | |
| 104 | + | for off := uintptr(0); off < size; off += 8 { | |
| 105 | + | if remaining := size - off; remaining >= 8 { | |
| 106 | + | addInt(*(*uintptr)(unsafe.Add(ptr, off))) | |
| 107 | + | } else { | |
| 108 | + | var word [8]byte | |
| 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 | 114 | return keepAlive | |
| 112 | 115 | } | |
| 113 | 116 | ||
| 114 | - | // placeStack is a fallback for structs that are too large to fit in registers | |
| 115 | - | func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |
| 116 | - | if v.CanAddr() { | |
| 117 | - | addInt(v.Addr().Pointer()) | |
| 118 | - | return keepAlive | |
| 117 | + | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| 118 | + | if outType.Size() == 0 { | |
| 119 | + | return reflect.New(outType).Elem() | |
| 120 | + | } | |
| 121 | + | ||
| 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()) | |
| 121 | - | ptr.Elem().Set(v) | |
| 122 | - | addInt(ptr.Pointer()) | |
| 123 | - | return append(keepAlive, ptr.Interface()) | |
| 153 | + | return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem() | |
| 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 | 158 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |
| 127 | - | // PPC64LE does not bundle stack args | |
| 128 | 159 | return false | |
| 129 | 160 | } | |
| 130 | 161 | ||
| 131 | - | func collectStackArgs( | |
| 132 | - | args []reflect.Value, | |
| 133 | - | i, numInts, numFloats int, | |
| 134 | - | keepAlive []any, | |
| 135 | - | addInt, addFloat, addStack func(uintptr), | |
| 136 | - | numIntsPtr, numFloatsPtr, numStackPtr *int, | |
| 137 | - | ) ([]reflect.Value, []any) { | |
| 138 | - | return nil, keepAlive | |
| 162 | + | // collectStackArgs is not used on ppc64le. | |
| 163 | + | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | |
| 164 | + | keepAlive []any, addInt, addFloat, addStack func(uintptr), | |
| 165 | + | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | |
| 166 | + | panic("purego: collectStackArgs should not be called on ppc64le") | |
| 139 | 167 | } | |
| 140 | 168 | ||
| 169 | + | // bundleStackArgs is not used on ppc64le. | |
| 141 | 170 | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |
| 142 | 171 | panic("purego: bundleStackArgs should not be called on ppc64le") | |
| 143 | 172 | } | |
M struct_riscv64.go +10 -3
| @@ -8,7 +8,14 @@ import ( | |||
| 8 | 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 | 19 | outSize := outType.Size() | |
| 13 | 20 | ||
| 14 | 21 | switch { | |
| @@ -27,10 +34,10 @@ func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | |||
| 27 | 34 | ||
| 28 | 35 | // Homogeneous float aggregates override integer regs | |
| 29 | 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 | 38 | // float32 values are NaN-boxed in FP regs; use low 32 bits only | |
| 32 | 39 | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | |
| 33 | - | for i := 0; i < numFields; i++ { | |
| 40 | + | for i := range numFields { | |
| 34 | 41 | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | |
| 35 | 42 | } | |
| 36 | 43 | } else { | |
M struct_s390x.go +11 -4
| @@ -8,7 +8,14 @@ import ( | |||
| 8 | 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 | 19 | outSize := outType.Size() | |
| 13 | 20 | ||
| 14 | 21 | switch { | |
| @@ -27,11 +34,11 @@ func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | |||
| 27 | 34 | ||
| 28 | 35 | // Homogeneous float aggregates override integer regs | |
| 29 | 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 | 38 | // float32 values in FP regs | |
| 32 | 39 | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | |
| 33 | - | for i := 0; i < numFields; i++ { | |
| 34 | - | *(*uint32)(unsafe.Pointer(&buf[i*4])) = highFloat32Bits(f[i]) | |
| 40 | + | for i := range numFields { | |
| 41 | + | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | |
| 35 | 42 | } | |
| 36 | 43 | } else { | |
| 37 | 44 | // 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 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // 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 | 6 | package purego_test | |
| 7 | 7 | ||
| 8 | 8 | import ( | |
| 9 | + | "iter" | |
| 9 | 10 | "math" | |
| 10 | 11 | "os" | |
| 11 | 12 | "path/filepath" | |
| 12 | 13 | "reflect" | |
| 13 | 14 | "runtime" | |
| 15 | + | "slices" | |
| 16 | + | "structs" | |
| 14 | 17 | "testing" | |
| 15 | 18 | "unsafe" | |
| 16 | 19 | ||
| 17 | 20 | "github.com/bnema/purego" | |
| 21 | + | "github.com/bnema/purego/internal/load" | |
| 18 | 22 | ) | |
| 19 | 23 | ||
| 20 | 24 | func TestRegisterFunc_structArgs(t *testing.T) { | |
| 21 | 25 | libFileName := filepath.Join(t.TempDir(), "structtest.so") | |
| 22 | 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 | 29 | t.Fatal(err) | |
| 26 | 30 | } | |
| 27 | 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 | 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 | 43 | const ( | |
| 35 | 44 | expectedUnsigned = 0xdeadbeef | |
| @@ -41,8 +50,9 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 41 | 50 | ) | |
| 42 | 51 | ||
| 43 | 52 | implementations := []struct { | |
| 44 | - | name string | |
| 45 | - | register func(fptr any, handle uintptr, name string, goFn any) | |
| 53 | + | name string | |
| 54 | + | usesCallbacks bool | |
| 55 | + | register func(fptr any, handle uintptr, name string, goFn any) | |
| 46 | 56 | }{ | |
| 47 | 57 | { | |
| 48 | 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 | 66 | register: func(fptr any, handle uintptr, name string, goFn any) { | |
| 56 | 67 | fnType := reflect.TypeOf(fptr).Elem() | |
| 57 | 68 | if fnType.NumOut() > 0 { | |
| @@ -68,11 +79,20 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 68 | 79 | }, | |
| 69 | 80 | } | |
| 70 | 81 | for _, imp := range implementations { | |
| 71 | - | imp := imp | |
| 82 | + | 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 | 90 | t.Run(imp.name, func(t *testing.T) { | |
| 73 | 91 | register := imp.register | |
| 74 | 92 | { | |
| 75 | - | type Empty struct{} | |
| 93 | + | type Empty struct { | |
| 94 | + | _ structs.HostLayout | |
| 95 | + | } | |
| 76 | 96 | var NoStruct func(Empty) int64 | |
| 77 | 97 | register(&NoStruct, lib, "NoStruct", func(Empty) int64 { | |
| 78 | 98 | 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 | 108 | var EmptyEmptyFn func(EmptyEmpty) int64 | |
| 87 | 109 | register(&EmptyEmptyFn, lib, "EmptyEmpty", func(EmptyEmpty) int64 { | |
| 88 | 110 | return 0xdeadbeef | |
| @@ -100,6 +122,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 100 | 122 | } | |
| 101 | 123 | { | |
| 102 | 124 | type GreaterThan16Bytes struct { | |
| 125 | + | _ structs.HostLayout | |
| 103 | 126 | x, y, z *int64 | |
| 104 | 127 | } | |
| 105 | 128 | var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000 | |
| @@ -113,7 +136,9 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 113 | 136 | } | |
| 114 | 137 | { | |
| 115 | 138 | type GreaterThan16BytesStruct struct { | |
| 139 | + | _ structs.HostLayout | |
| 116 | 140 | a struct { | |
| 141 | + | _ structs.HostLayout | |
| 117 | 142 | x, y, z *int64 | |
| 118 | 143 | } | |
| 119 | 144 | } | |
| @@ -122,12 +147,16 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 122 | 147 | register(&GreaterThan16BytesStructFn, lib, "GreaterThan16BytesStruct", func(g GreaterThan16BytesStruct) int64 { | |
| 123 | 148 | 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 | 154 | t.Fatalf("GreaterThan16BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned) | |
| 127 | 155 | } | |
| 128 | 156 | } | |
| 129 | 157 | { | |
| 130 | 158 | type GreaterThan16Bytes struct { | |
| 159 | + | _ structs.HostLayout | |
| 131 | 160 | x, y, z *int64 | |
| 132 | 161 | } | |
| 133 | 162 | var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000 | |
| @@ -151,48 +180,52 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 151 | 180 | register(&BeforeRegisters, lib, "BeforeRegisters", func(bytes GreaterThan16Bytes, a, b int64) uint64 { | |
| 152 | 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 | 184 | t.Fatalf("BeforeRegisters returned %#x wanted %#x", ret, expectedUnsigned) | |
| 156 | 185 | } | |
| 157 | 186 | } | |
| 158 | 187 | { | |
| 159 | 188 | type IntLessThan16Bytes struct { | |
| 189 | + | _ structs.HostLayout | |
| 160 | 190 | x, y int64 | |
| 161 | 191 | } | |
| 162 | 192 | var IntLessThan16BytesFn func(bytes IntLessThan16Bytes) int64 | |
| 163 | 193 | register(&IntLessThan16BytesFn, lib, "IntLessThan16Bytes", func(l IntLessThan16Bytes) int64 { | |
| 164 | 194 | 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 | 197 | t.Fatalf("IntLessThan16BytesFn returned %#x wanted %#x", ret, expectedUnsigned) | |
| 168 | 198 | } | |
| 169 | 199 | } | |
| 170 | 200 | { | |
| 171 | 201 | type FloatLessThan16Bytes struct { | |
| 202 | + | _ structs.HostLayout | |
| 172 | 203 | x, y float32 | |
| 173 | 204 | } | |
| 174 | 205 | var FloatLessThan16BytesFn func(FloatLessThan16Bytes) float32 | |
| 175 | 206 | register(&FloatLessThan16BytesFn, lib, "FloatLessThan16Bytes", func(f FloatLessThan16Bytes) float32 { | |
| 176 | 207 | 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 | 210 | t.Fatalf("FloatLessThan16Bytes returned %f wanted %f", ret, expectedFloat) | |
| 180 | 211 | } | |
| 181 | 212 | } | |
| 182 | 213 | { | |
| 183 | 214 | type ThreeSmallFields struct { | |
| 215 | + | _ structs.HostLayout | |
| 184 | 216 | x, y, z float32 | |
| 185 | 217 | } | |
| 186 | 218 | var ThreeSmallFieldsFn func(ThreeSmallFields) float32 | |
| 187 | 219 | register(&ThreeSmallFieldsFn, lib, "ThreeSmallFields", func(f ThreeSmallFields) float32 { | |
| 188 | 220 | 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 | 223 | t.Fatalf("ThreeSmallFields returned %f wanted %f", ret, expectedFloat) | |
| 192 | 224 | } | |
| 193 | 225 | } | |
| 194 | 226 | { | |
| 195 | 227 | type FloatAndInt struct { | |
| 228 | + | _ structs.HostLayout | |
| 196 | 229 | x float32 | |
| 197 | 230 | y int32 | |
| 198 | 231 | } | |
| @@ -200,37 +233,41 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 200 | 233 | register(&FloatAndIntFn, lib, "FloatAndInt", func(f FloatAndInt) float32 { | |
| 201 | 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 | 237 | t.Fatalf("FloatAndIntFn returned %f wanted %f", ret, expectedFloat) | |
| 205 | 238 | } | |
| 206 | 239 | } | |
| 207 | 240 | { | |
| 208 | 241 | type DoubleStruct struct { | |
| 242 | + | _ structs.HostLayout | |
| 209 | 243 | x float64 | |
| 210 | 244 | } | |
| 211 | 245 | var DoubleStructFn func(DoubleStruct) float64 | |
| 212 | 246 | register(&DoubleStructFn, lib, "DoubleStruct", func(d DoubleStruct) float64 { | |
| 213 | 247 | return d.x | |
| 214 | 248 | }) | |
| 215 | - | if ret := DoubleStructFn(DoubleStruct{10}); ret != expectedDouble { | |
| 249 | + | if ret := DoubleStructFn(DoubleStruct{x: 10}); ret != expectedDouble { | |
| 216 | 250 | t.Fatalf("DoubleStruct returned %f wanted %f", ret, expectedDouble) | |
| 217 | 251 | } | |
| 218 | 252 | } | |
| 219 | 253 | { | |
| 220 | 254 | type TwoDoubleStruct struct { | |
| 255 | + | _ structs.HostLayout | |
| 221 | 256 | x, y float64 | |
| 222 | 257 | } | |
| 223 | 258 | var TwoDoubleStructFn func(TwoDoubleStruct) float64 | |
| 224 | 259 | register(&TwoDoubleStructFn, lib, "TwoDoubleStruct", func(d TwoDoubleStruct) float64 { | |
| 225 | 260 | 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 | 263 | t.Fatalf("TwoDoubleStruct returned %f wanted %f", ret, expectedDouble) | |
| 229 | 264 | } | |
| 230 | 265 | } | |
| 231 | 266 | { | |
| 232 | 267 | type TwoDoubleTwoStruct struct { | |
| 268 | + | _ structs.HostLayout | |
| 233 | 269 | x struct { | |
| 270 | + | _ structs.HostLayout | |
| 234 | 271 | x, y float64 | |
| 235 | 272 | } | |
| 236 | 273 | } | |
| @@ -238,69 +275,84 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 238 | 275 | register(&TwoDoubleTwoStructFn, lib, "TwoDoubleTwoStruct", func(d TwoDoubleTwoStruct) float64 { | |
| 239 | 276 | 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 | 282 | t.Fatalf("TwoDoubleTwoStruct returned %f wanted %f", ret, expectedDouble) | |
| 243 | 283 | } | |
| 244 | 284 | } | |
| 245 | 285 | { | |
| 246 | 286 | type ThreeDoubleStruct struct { | |
| 287 | + | _ structs.HostLayout | |
| 247 | 288 | x, y, z float64 | |
| 248 | 289 | } | |
| 249 | 290 | var ThreeDoubleStructFn func(ThreeDoubleStruct) float64 | |
| 250 | 291 | register(&ThreeDoubleStructFn, lib, "ThreeDoubleStruct", func(d ThreeDoubleStruct) float64 { | |
| 251 | 292 | 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 | 295 | t.Fatalf("ThreeDoubleStructFn returned %f wanted %f", ret, expectedDouble) | |
| 255 | 296 | } | |
| 256 | 297 | } | |
| 257 | 298 | { | |
| 258 | 299 | type LargeFloatStruct struct { | |
| 300 | + | _ structs.HostLayout | |
| 259 | 301 | a, b, c, d, e, f float64 | |
| 260 | 302 | } | |
| 261 | 303 | var LargeFloatStructFn func(LargeFloatStruct) float64 | |
| 262 | 304 | register(&LargeFloatStructFn, lib, "LargeFloatStruct", func(s LargeFloatStruct) float64 { | |
| 263 | 305 | 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 | 308 | t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat) | |
| 267 | 309 | } | |
| 268 | - | var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64 | |
| 269 | - | register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 { | |
| 270 | - | return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f | |
| 271 | - | }) | |
| 272 | - | if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble { | |
| 273 | - | t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat) | |
| 310 | + | if runtime.GOARCH != "ppc64le" { | |
| 311 | + | // Needs 9 float argument registers (3 doubles + a 6-double HFA); | |
| 312 | + | // ppc64le wires 8. | |
| 313 | + | var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64 | |
| 314 | + | register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 { | |
| 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 | 323 | type Rect struct { | |
| 324 | + | _ structs.HostLayout | |
| 278 | 325 | x, y, w, h float64 | |
| 279 | 326 | } | |
| 280 | - | var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64 | |
| 281 | - | register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 { | |
| 282 | - | return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h | |
| 283 | - | }) | |
| 284 | - | if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{1, 2, 3, -4}); ret != expectedDouble { | |
| 285 | - | t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble) | |
| 327 | + | if runtime.GOARCH != "ppc64le" { | |
| 328 | + | // Needs 9 float argument registers (5 doubles + a 4-double HFA); | |
| 329 | + | // ppc64le wires 8. | |
| 330 | + | var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64 | |
| 331 | + | register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 { | |
| 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 | 338 | var RectangleSubtract func(rect Rect) float64 | |
| 288 | 339 | register(&RectangleSubtract, lib, "RectangleSubtract", func(rect Rect) float64 { | |
| 289 | 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 | 343 | t.Fatalf("RectangleSubtract returned %f wanted %f", ret, expectedDouble) | |
| 293 | 344 | } | |
| 294 | 345 | var Rectangle func(rect Rect) float64 | |
| 295 | 346 | register(&Rectangle, lib, "Rectangle", func(rect Rect) float64 { | |
| 296 | 347 | 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 | 350 | t.Fatalf("Rectangle returned %f wanted %f", ret, expectedFloat) | |
| 300 | 351 | } | |
| 301 | 352 | } | |
| 302 | 353 | { | |
| 303 | 354 | type FloatArray struct { | |
| 355 | + | _ structs.HostLayout | |
| 304 | 356 | a [2]float64 | |
| 305 | 357 | } | |
| 306 | 358 | var FloatArrayFn func(rect FloatArray) float64 | |
| @@ -313,6 +365,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 313 | 365 | } | |
| 314 | 366 | { | |
| 315 | 367 | type UnsignedChar4Bytes struct { | |
| 368 | + | _ structs.HostLayout | |
| 316 | 369 | a, b, c, d byte | |
| 317 | 370 | } | |
| 318 | 371 | var UnsignedChar4BytesFn func(UnsignedChar4Bytes) uint32 | |
| @@ -325,16 +378,21 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 325 | 378 | } | |
| 326 | 379 | { | |
| 327 | 380 | type UnsignedChar4BytesStruct struct { | |
| 381 | + | _ structs.HostLayout | |
| 328 | 382 | x struct { | |
| 383 | + | _ structs.HostLayout | |
| 329 | 384 | a byte | |
| 330 | 385 | } | |
| 331 | 386 | y struct { | |
| 387 | + | _ structs.HostLayout | |
| 332 | 388 | b byte | |
| 333 | 389 | } | |
| 334 | 390 | z struct { | |
| 391 | + | _ structs.HostLayout | |
| 335 | 392 | c byte | |
| 336 | 393 | } | |
| 337 | 394 | w struct { | |
| 395 | + | _ structs.HostLayout | |
| 338 | 396 | d byte | |
| 339 | 397 | } | |
| 340 | 398 | } | |
| @@ -343,16 +401,29 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 343 | 401 | return uint32(b.x.a)<<24 | uint32(b.y.b)<<16 | uint32(b.z.c)<<8 | uint32(b.w.d) | |
| 344 | 402 | }) | |
| 345 | 403 | if ret := UnsignedChar4BytesStructFn(UnsignedChar4BytesStruct{ | |
| 346 | - | x: struct{ a byte }{a: 0xDE}, | |
| 347 | - | y: struct{ b byte }{b: 0xAD}, | |
| 348 | - | z: struct{ c byte }{c: 0xBE}, | |
| 349 | - | w: struct{ d byte }{d: 0xEF}, | |
| 404 | + | x: struct { | |
| 405 | + | _ structs.HostLayout | |
| 406 | + | a byte | |
| 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 | 420 | }); ret != expectedUnsigned { | |
| 351 | 421 | t.Fatalf("UnsignedChar4BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned) | |
| 352 | 422 | } | |
| 353 | 423 | } | |
| 354 | 424 | { | |
| 355 | 425 | type Short struct { | |
| 426 | + | _ structs.HostLayout | |
| 356 | 427 | a, b, c, d uint16 | |
| 357 | 428 | } | |
| 358 | 429 | var ShortFn func(Short) uint64 | |
| @@ -365,6 +436,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 365 | 436 | } | |
| 366 | 437 | { | |
| 367 | 438 | type Int struct { | |
| 439 | + | _ structs.HostLayout | |
| 368 | 440 | a, b uint32 | |
| 369 | 441 | } | |
| 370 | 442 | var IntFn func(Int) uint64 | |
| @@ -377,6 +449,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 377 | 449 | } | |
| 378 | 450 | { | |
| 379 | 451 | type Long struct { | |
| 452 | + | _ structs.HostLayout | |
| 380 | 453 | a uint64 | |
| 381 | 454 | } | |
| 382 | 455 | var LongFn func(Long) uint64 | |
| @@ -389,6 +462,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 389 | 462 | } | |
| 390 | 463 | { | |
| 391 | 464 | type Char8Bytes struct { | |
| 465 | + | _ structs.HostLayout | |
| 392 | 466 | a, b, c, d, e, f, g, h int8 | |
| 393 | 467 | } | |
| 394 | 468 | var Char8BytesFn func(Char8Bytes) int32 | |
| @@ -401,6 +475,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 401 | 475 | } | |
| 402 | 476 | { | |
| 403 | 477 | type Odd struct { | |
| 478 | + | _ structs.HostLayout | |
| 404 | 479 | a, b, c byte | |
| 405 | 480 | } | |
| 406 | 481 | var OddFn func(Odd) int32 | |
| @@ -413,6 +488,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 413 | 488 | } | |
| 414 | 489 | { | |
| 415 | 490 | type Char2Short1 struct { | |
| 491 | + | _ structs.HostLayout | |
| 416 | 492 | a, b byte | |
| 417 | 493 | c uint16 | |
| 418 | 494 | } | |
| @@ -426,6 +502,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 426 | 502 | } | |
| 427 | 503 | { | |
| 428 | 504 | type SignedChar2Short1 struct { | |
| 505 | + | _ structs.HostLayout | |
| 429 | 506 | a, b int8 | |
| 430 | 507 | c int16 | |
| 431 | 508 | } | |
| @@ -439,6 +516,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 439 | 516 | } | |
| 440 | 517 | { | |
| 441 | 518 | type Array4UnsignedChars struct { | |
| 519 | + | _ structs.HostLayout | |
| 442 | 520 | a [4]uint8 | |
| 443 | 521 | } | |
| 444 | 522 | var Array4UnsignedCharsFn func(chars Array4UnsignedChars) uint32 | |
| @@ -451,6 +529,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 451 | 529 | } | |
| 452 | 530 | { | |
| 453 | 531 | type Array3UnsignedChar struct { | |
| 532 | + | _ structs.HostLayout | |
| 454 | 533 | a [3]uint8 | |
| 455 | 534 | } | |
| 456 | 535 | var Array3UnsignedChars func(chars Array3UnsignedChar) uint32 | |
| @@ -463,6 +542,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 463 | 542 | } | |
| 464 | 543 | { | |
| 465 | 544 | type Array2UnsignedShort struct { | |
| 545 | + | _ structs.HostLayout | |
| 466 | 546 | a [2]uint16 | |
| 467 | 547 | } | |
| 468 | 548 | var Array2UnsignedShorts func(chars Array2UnsignedShort) uint32 | |
| @@ -475,6 +555,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 475 | 555 | } | |
| 476 | 556 | { | |
| 477 | 557 | type Array4Chars struct { | |
| 558 | + | _ structs.HostLayout | |
| 478 | 559 | a [4]int8 | |
| 479 | 560 | } | |
| 480 | 561 | var Array4CharsFn func(chars Array4Chars) int32 | |
| @@ -488,6 +569,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 488 | 569 | } | |
| 489 | 570 | { | |
| 490 | 571 | type Array2Short struct { | |
| 572 | + | _ structs.HostLayout | |
| 491 | 573 | a [2]int16 | |
| 492 | 574 | } | |
| 493 | 575 | var Array2Shorts func(chars Array2Short) int32 | |
| @@ -500,6 +582,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 500 | 582 | } | |
| 501 | 583 | { | |
| 502 | 584 | type Array3Short struct { | |
| 585 | + | _ structs.HostLayout | |
| 503 | 586 | a [3]int16 | |
| 504 | 587 | } | |
| 505 | 588 | var Array3Shorts func(chars Array3Short) int32 | |
| @@ -512,21 +595,23 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 512 | 595 | } | |
| 513 | 596 | { | |
| 514 | 597 | type BoolStruct struct { | |
| 598 | + | _ structs.HostLayout | |
| 515 | 599 | b bool | |
| 516 | 600 | } | |
| 517 | 601 | var BoolStructFn func(BoolStruct) bool | |
| 518 | 602 | register(&BoolStructFn, lib, "BoolStruct", func(b BoolStruct) bool { | |
| 519 | 603 | return b.b | |
| 520 | 604 | }) | |
| 521 | - | if ret := BoolStructFn(BoolStruct{true}); ret != true { | |
| 605 | + | if ret := BoolStructFn(BoolStruct{b: true}); ret != true { | |
| 522 | 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 | 609 | t.Fatalf("BoolStructFn returned %v wanted %v", ret, false) | |
| 526 | 610 | } | |
| 527 | 611 | } | |
| 528 | 612 | { | |
| 529 | 613 | type BoolFloat struct { | |
| 614 | + | _ structs.HostLayout | |
| 530 | 615 | b bool | |
| 531 | 616 | _ [3]byte // purego won't do padding for you so make sure it aligns properly with C struct | |
| 532 | 617 | 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 } | |
| 550 | - | type size struct{ width, height float64 } | |
| 634 | + | type point struct { | |
| 635 | + | _ structs.HostLayout | |
| 636 | + | x, y float64 | |
| 637 | + | } | |
| 638 | + | type size struct { | |
| 639 | + | _ structs.HostLayout | |
| 640 | + | width, height float64 | |
| 641 | + | } | |
| 551 | 642 | type Content struct { | |
| 643 | + | _ structs.HostLayout | |
| 552 | 644 | point point | |
| 553 | 645 | size size | |
| 554 | 646 | } | |
| @@ -564,13 +656,14 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 564 | 656 | }) | |
| 565 | 657 | if ret := InitWithContentRect(new(int), | |
| 566 | 658 | // 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 | 660 | 15, 4, true); ret != expectedUnsigned { | |
| 569 | 661 | t.Fatalf("InitWithContentRect returned %d wanted %#x", ret, expectedUnsigned) | |
| 570 | 662 | } | |
| 571 | 663 | } | |
| 572 | 664 | { | |
| 573 | 665 | type GoInt4 struct { | |
| 666 | + | _ structs.HostLayout | |
| 574 | 667 | A, B, C, D int | |
| 575 | 668 | } | |
| 576 | 669 | var GoInt4Fn func(GoInt4) int | |
| @@ -578,12 +671,13 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 578 | 671 | return g.A + g.B - g.C + g.D | |
| 579 | 672 | }) | |
| 580 | 673 | 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 | 675 | t.Fatalf("GoInt4Fn returned %d wanted %#x", ret, expected) | |
| 583 | 676 | } | |
| 584 | 677 | } | |
| 585 | 678 | { | |
| 586 | 679 | type GoUint4 struct { | |
| 680 | + | _ structs.HostLayout | |
| 587 | 681 | A, B, C, D uint | |
| 588 | 682 | } | |
| 589 | 683 | var GoUint4Fn func(GoUint4) uint | |
| @@ -591,23 +685,27 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 591 | 685 | return g.A + g.B + g.C + g.D | |
| 592 | 686 | }) | |
| 593 | 687 | 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 | 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 | 697 | var TakeGoUintAndReturn func(a OneLong) uint64 | |
| 601 | 698 | register(&TakeGoUintAndReturn, lib, "TakeGoUintAndReturn", func(a OneLong) uint64 { | |
| 602 | 699 | return uint64(a.a) | |
| 603 | 700 | }) | |
| 604 | 701 | expected := uint64(7) | |
| 605 | - | if ret := TakeGoUintAndReturn(OneLong{7}); ret != expected { | |
| 702 | + | if ret := TakeGoUintAndReturn(OneLong{a: 7}); ret != expected { | |
| 606 | 703 | t.Fatalf("TakeGoUintAndReturn returned %+v wanted %+v", ret, expected) | |
| 607 | 704 | } | |
| 608 | 705 | } | |
| 609 | 706 | { | |
| 610 | 707 | type FloatAndBool struct { | |
| 708 | + | _ structs.HostLayout | |
| 611 | 709 | x float32 | |
| 612 | 710 | y bool | |
| 613 | 711 | } | |
| @@ -627,13 +725,14 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 627 | 725 | } | |
| 628 | 726 | { | |
| 629 | 727 | type FourInt32s struct { | |
| 728 | + | _ structs.HostLayout | |
| 630 | 729 | f0, f1, f2, f3 int32 | |
| 631 | 730 | } | |
| 632 | 731 | var FourInt32sFn func(FourInt32s) int32 | |
| 633 | 732 | register(&FourInt32sFn, lib, "FourInt32s", func(s FourInt32s) int32 { | |
| 634 | 733 | 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 | 736 | const want = 100 - 127 + 4 - 100 | |
| 638 | 737 | if result != want { | |
| 639 | 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 | 742 | type PointerWrapper struct { | |
| 743 | + | _ structs.HostLayout | |
| 644 | 744 | ctx unsafe.Pointer | |
| 645 | 745 | } | |
| 646 | 746 | var ExtractPointer func(wrapper PointerWrapper) uintptr | |
| @@ -658,6 +758,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 658 | 758 | } | |
| 659 | 759 | { | |
| 660 | 760 | type TwoPointers struct { | |
| 761 | + | _ structs.HostLayout | |
| 661 | 762 | ptr1, ptr2 unsafe.Pointer | |
| 662 | 763 | } | |
| 663 | 764 | var AddPointers func(wrapper TwoPointers) uintptr | |
| @@ -669,7 +770,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 669 | 770 | ptr1 := unsafe.Pointer(&v1) | |
| 670 | 771 | ptr2 := unsafe.Pointer(&v2) | |
| 671 | 772 | expected := uintptr(ptr1) + uintptr(ptr2) | |
| 672 | - | result := AddPointers(TwoPointers{ptr1, ptr2}) | |
| 773 | + | result := AddPointers(TwoPointers{ptr1: ptr1, ptr2: ptr2}) | |
| 673 | 774 | if result != expected { | |
| 674 | 775 | t.Fatalf("AddPointers returned %#x wanted %#x", result, expected) | |
| 675 | 776 | } | |
| @@ -678,48 +779,60 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 678 | 779 | // Identity struct arg tests | |
| 679 | 780 | { | |
| 680 | 781 | // Single int64: one integer register. | |
| 681 | - | type OneInt64 struct{ A int64 } | |
| 782 | + | type OneInt64 struct { | |
| 783 | + | _ structs.HostLayout | |
| 784 | + | A int64 | |
| 785 | + | } | |
| 682 | 786 | var fn func(OneInt64) OneInt64 | |
| 683 | 787 | register(&fn, lib, "IdentityOneInt64", func(s OneInt64) OneInt64 { | |
| 684 | 788 | return s | |
| 685 | 789 | }) | |
| 686 | - | expected := OneInt64{0xDEADBEEF} | |
| 790 | + | expected := OneInt64{A: 0xDEADBEEF} | |
| 687 | 791 | if ret := fn(expected); ret != expected { | |
| 688 | 792 | t.Fatalf("IdentityOneInt64 returned %+v wanted %+v", ret, expected) | |
| 689 | 793 | } | |
| 690 | 794 | } | |
| 691 | 795 | { | |
| 692 | 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 | 801 | var fn func(IntLessThan16Bytes) IntLessThan16Bytes | |
| 695 | 802 | register(&fn, lib, "IdentityIntLessThan16Bytes", func(s IntLessThan16Bytes) IntLessThan16Bytes { | |
| 696 | 803 | return s | |
| 697 | 804 | }) | |
| 698 | - | expected := IntLessThan16Bytes{0xDEADBEEF, -42} | |
| 805 | + | expected := IntLessThan16Bytes{X: 0xDEADBEEF, Y: -42} | |
| 699 | 806 | if ret := fn(expected); ret != expected { | |
| 700 | 807 | t.Fatalf("IdentityIntLessThan16Bytes returned %+v wanted %+v", ret, expected) | |
| 701 | 808 | } | |
| 702 | 809 | } | |
| 703 | 810 | { | |
| 704 | 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 | 816 | var fn func(TwoDoubleStruct) TwoDoubleStruct | |
| 707 | 817 | register(&fn, lib, "IdentityTwoDoubleStruct", func(s TwoDoubleStruct) TwoDoubleStruct { | |
| 708 | 818 | return s | |
| 709 | 819 | }) | |
| 710 | - | expected := TwoDoubleStruct{3.14, -2.71} | |
| 820 | + | expected := TwoDoubleStruct{X: 3.14, Y: -2.71} | |
| 711 | 821 | if ret := fn(expected); ret != expected { | |
| 712 | 822 | t.Fatalf("IdentityTwoDoubleStruct returned %+v wanted %+v", ret, expected) | |
| 713 | 823 | } | |
| 714 | 824 | } | |
| 715 | 825 | { | |
| 716 | 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 | 831 | var fn func(FourFloat32) FourFloat32 | |
| 719 | 832 | register(&fn, lib, "IdentityFourFloat32", func(s FourFloat32) FourFloat32 { | |
| 720 | 833 | 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 | 836 | if ret := fn(expected); ret != expected { | |
| 724 | 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 | 841 | // Mixed float32 + int32: INTEGER class eightbyte on amd64, non-HFA int regs on arm64. | |
| 729 | 842 | type FloatAndInt struct { | |
| 843 | + | _ structs.HostLayout | |
| 730 | 844 | X float32 | |
| 731 | 845 | Y int32 | |
| 732 | 846 | } | |
| @@ -734,19 +848,22 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 734 | 848 | register(&fn, lib, "IdentityFloatAndInt", func(s FloatAndInt) FloatAndInt { | |
| 735 | 849 | return s | |
| 736 | 850 | }) | |
| 737 | - | expected := FloatAndInt{7.5, 42} | |
| 851 | + | expected := FloatAndInt{X: 7.5, Y: 42} | |
| 738 | 852 | if ret := fn(expected); ret != expected { | |
| 739 | 853 | t.Fatalf("IdentityFloatAndInt returned %+v wanted %+v", ret, expected) | |
| 740 | 854 | } | |
| 741 | 855 | } | |
| 742 | 856 | { | |
| 743 | 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 | 862 | var fn func(ThreeInt64) ThreeInt64 | |
| 746 | 863 | register(&fn, lib, "IdentityThreeInt64", func(s ThreeInt64) ThreeInt64 { | |
| 747 | 864 | return s | |
| 748 | 865 | }) | |
| 749 | - | expected := ThreeInt64{1, 2, 3} | |
| 866 | + | expected := ThreeInt64{A: 1, B: 2, C: 3} | |
| 750 | 867 | if ret := fn(expected); ret != expected { | |
| 751 | 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 | 872 | // Struct > 16 bytes with pointer fields. | |
| 756 | 873 | type PtrInt64Ptr struct { | |
| 874 | + | _ structs.HostLayout | |
| 757 | 875 | A *int64 | |
| 758 | 876 | B int64 | |
| 759 | 877 | C *int64 | |
| @@ -764,7 +882,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 764 | 882 | register(&fn, lib, "IdentityPtrInt64Ptr", func(s PtrInt64Ptr) PtrInt64Ptr { | |
| 765 | 883 | return s | |
| 766 | 884 | }) | |
| 767 | - | expected := PtrInt64Ptr{a, 200, c} | |
| 885 | + | expected := PtrInt64Ptr{A: a, B: 200, C: c} | |
| 768 | 886 | ret := fn(expected) | |
| 769 | 887 | if *ret.A != *expected.A || ret.B != expected.B || *ret.C != *expected.C { | |
| 770 | 888 | t.Fatalf("IdentityPtrInt64Ptr returned %+v wanted %+v", ret, expected) | |
| @@ -772,26 +890,34 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 772 | 890 | runtime.KeepAlive(a) | |
| 773 | 891 | runtime.KeepAlive(c) | |
| 774 | 892 | } | |
| 775 | - | { | |
| 893 | + | if runtime.GOARCH != "ppc64le" { | |
| 776 | 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 | 901 | var fn func(int64, float64, IntLessThan16Bytes) IntLessThan16Bytes | |
| 779 | 902 | register(&fn, lib, "IdentityTwoInt64AfterPrims", func(x int64, y float64, s IntLessThan16Bytes) IntLessThan16Bytes { | |
| 780 | 903 | return s | |
| 781 | 904 | }) | |
| 782 | - | expected := IntLessThan16Bytes{100, 200} | |
| 905 | + | expected := IntLessThan16Bytes{X: 100, Y: 200} | |
| 783 | 906 | if ret := fn(1, 2.0, expected); ret != expected { | |
| 784 | 907 | t.Fatalf("IdentityTwoInt64AfterPrims returned %+v wanted %+v", ret, expected) | |
| 785 | 908 | } | |
| 786 | 909 | } | |
| 787 | 910 | { | |
| 788 | 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 | 916 | var fn func(float64, float64, FloatLessThan16Bytes) FloatLessThan16Bytes | |
| 791 | 917 | register(&fn, lib, "IdentityTwoFloat32AfterFloats", func(x, y float64, s FloatLessThan16Bytes) FloatLessThan16Bytes { | |
| 792 | 918 | return s | |
| 793 | 919 | }) | |
| 794 | - | expected := FloatLessThan16Bytes{5.0, 6.0} | |
| 920 | + | expected := FloatLessThan16Bytes{X: 5.0, Y: 6.0} | |
| 795 | 921 | if ret := fn(1.0, 2.0, expected); ret != expected { | |
| 796 | 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 | 926 | // Mixed struct with pointer: pointer + int32 + float32 + int32. | |
| 801 | 927 | type Mixed5Args struct { | |
| 928 | + | _ structs.HostLayout | |
| 802 | 929 | A *int64 | |
| 803 | 930 | B int32 | |
| 804 | 931 | C float32 | |
| @@ -810,7 +937,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 810 | 937 | register(&fn, lib, "IdentityMixed5Args", func(s Mixed5Args) Mixed5Args { | |
| 811 | 938 | 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 | 941 | ret := fn(expected) | |
| 815 | 942 | if ret.A != expected.A || ret.B != expected.B || ret.C != expected.C || ret.D != expected.D { | |
| 816 | 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.23 | |
| 825 | - | func nextFieldFn(v reflect.Value) func() (reflect.Value, bool) { | |
| 826 | - | var fieldIndex int | |
| 827 | - | var tracker func() (reflect.Value, bool) | |
| 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++ | |
| 951 | + | func fields(v reflect.Value) iter.Seq[reflect.Value] { | |
| 952 | + | return func(yield func(reflect.Value) bool) { | |
| 953 | + | for i := range v.NumField() { | |
| 954 | + | if v.Type().Field(i).Name == "_" { | |
| 841 | 955 | continue | |
| 842 | 956 | } | |
| 843 | - | field := v.Field(fieldIndex) | |
| 844 | - | fieldIndex++ | |
| 957 | + | field := v.Field(i) | |
| 845 | 958 | if field.Kind() == reflect.Struct { | |
| 846 | - | tracker = nextFieldFn(field) | |
| 847 | - | if inner, ok := tracker(); ok { | |
| 848 | - | return inner, ok | |
| 959 | + | for inner := range fields(field) { | |
| 960 | + | if !yield(inner) { | |
| 961 | + | return | |
| 962 | + | } | |
| 849 | 963 | } | |
| 850 | - | tracker = nil | |
| 851 | - | } else { | |
| 852 | - | return field, true | |
| 964 | + | } else if !yield(field) { | |
| 965 | + | return | |
| 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 | 972 | libFileName := filepath.Join(t.TempDir(), "structreturntest.so") | |
| 861 | 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 | 976 | t.Fatal(err) | |
| 865 | 977 | } | |
| 866 | 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 | 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 | 989 | implementations := []struct { | |
| 873 | - | name string | |
| 874 | - | register func(fptr any, handle uintptr, name string) | |
| 990 | + | name string | |
| 991 | + | usesCallbacks bool | |
| 992 | + | register func(fptr any, handle uintptr, name string) | |
| 875 | 993 | }{ | |
| 876 | 994 | { | |
| 877 | 995 | name: "RegisterLibFunc", | |
| 878 | 996 | register: purego.RegisterLibFunc, | |
| 879 | 997 | }, | |
| 880 | 998 | { | |
| 881 | - | name: "GoCallbackFunc", | |
| 999 | + | name: "GoCallbackFunc", | |
| 1000 | + | usesCallbacks: true, | |
| 882 | 1001 | register: func(fptr any, _ uintptr, _ string) { | |
| 883 | 1002 | fn := reflect.MakeFunc(reflect.TypeOf(fptr).Elem(), func(args []reflect.Value) []reflect.Value { | |
| 884 | 1003 | retType := reflect.TypeOf(fptr).Elem().Out(0) | |
| 885 | 1004 | ret := reflect.New(retType).Elem() | |
| 886 | - | next := nextFieldFn(ret) | |
| 887 | - | for _, a := range args { | |
| 888 | - | field, ok := next() | |
| 889 | - | if !ok { | |
| 1005 | + | leaves := slices.Collect(fields(ret)) | |
| 1006 | + | for i, a := range args { | |
| 1007 | + | if i >= len(leaves) { | |
| 890 | 1008 | panic("purego: no more fields") | |
| 891 | 1009 | } | |
| 892 | - | field.Set(a) | |
| 1010 | + | leaves[i].Set(a) | |
| 893 | 1011 | } | |
| 894 | 1012 | return []reflect.Value{ret} | |
| 895 | 1013 | }) | |
| @@ -898,170 +1016,239 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 898 | 1016 | }, | |
| 899 | 1017 | } | |
| 900 | 1018 | for _, imp := range implementations { | |
| 901 | - | imp := imp | |
| 1019 | + | 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 | 1027 | t.Run(imp.name, func(t *testing.T) { | |
| 903 | 1028 | register := imp.register | |
| 904 | 1029 | { | |
| 905 | - | type Empty struct{} | |
| 1030 | + | type Empty struct { | |
| 1031 | + | _ structs.HostLayout | |
| 1032 | + | } | |
| 906 | 1033 | var ReturnEmpty func() Empty | |
| 907 | 1034 | register(&ReturnEmpty, lib, "ReturnEmpty") | |
| 908 | 1035 | ret := ReturnEmpty() | |
| 909 | 1036 | _ = ret | |
| 910 | 1037 | } | |
| 911 | 1038 | { | |
| 912 | - | type inner struct{ A int16 } | |
| 1039 | + | type inner struct { | |
| 1040 | + | _ structs.HostLayout | |
| 1041 | + | A int16 | |
| 1042 | + | } | |
| 913 | 1043 | type StructInStruct struct { | |
| 1044 | + | _ structs.HostLayout | |
| 914 | 1045 | A inner | |
| 915 | 1046 | B inner | |
| 916 | 1047 | C inner | |
| 917 | 1048 | } | |
| 918 | 1049 | var ReturnStructInStruct func(a, b, c int16) StructInStruct | |
| 919 | 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 | 1052 | if ret := ReturnStructInStruct(^int16(0), 2, 3); ret != expected { | |
| 922 | 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 | 1061 | var ReturnThreeShorts func(a, b, c int16) ThreeShorts | |
| 928 | 1062 | register(&ReturnThreeShorts, lib, "ReturnThreeShorts") | |
| 929 | - | expected := ThreeShorts{^int16(0), 2, 3} | |
| 1063 | + | expected := ThreeShorts{A: ^int16(0), B: 2, C: 3} | |
| 930 | 1064 | if ret := ReturnThreeShorts(^int16(0), 2, 3); ret != expected { | |
| 931 | 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 | 1073 | var ReturnFourShorts func(a, b, c, d int16) FourShorts | |
| 937 | 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 | 1076 | if ret := ReturnFourShorts(^int16(0), 2, 3, 4); ret != expected { | |
| 940 | 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 | 1085 | var ReturnOneLong func(a int64) OneLong | |
| 946 | 1086 | register(&ReturnOneLong, lib, "ReturnOneLong") | |
| 947 | - | expected := OneLong{5} | |
| 1087 | + | expected := OneLong{A: 5} | |
| 948 | 1088 | if ret := ReturnOneLong(5); ret != expected { | |
| 949 | 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 | 1097 | var ReturnTwoLongs func(a, b int64) TwoLongs | |
| 955 | 1098 | register(&ReturnTwoLongs, lib, "ReturnTwoLongs") | |
| 956 | - | expected := TwoLongs{1, 2} | |
| 1099 | + | expected := TwoLongs{A: 1, B: 2} | |
| 957 | 1100 | if ret := ReturnTwoLongs(1, 2); ret != expected { | |
| 958 | 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 | 1109 | var ReturnThreeLongs func(a, b, c int64) ThreeLongs | |
| 964 | 1110 | register(&ReturnThreeLongs, lib, "ReturnThreeLongs") | |
| 965 | - | expected := ThreeLongs{1, 2, 3} | |
| 1111 | + | expected := ThreeLongs{A: 1, B: 2, C: 3} | |
| 966 | 1112 | if ret := ReturnThreeLongs(1, 2, 3); ret != expected { | |
| 967 | 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 | 1121 | var ReturnOneFloat func(a float32) OneFloat | |
| 973 | 1122 | register(&ReturnOneFloat, lib, "ReturnOneFloat") | |
| 974 | - | expected := OneFloat{1} | |
| 1123 | + | expected := OneFloat{A: 1} | |
| 975 | 1124 | if ret := ReturnOneFloat(1); ret != expected { | |
| 976 | 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 | 1133 | var ReturnTwoFloats func(a, b float32) TwoFloats | |
| 982 | 1134 | register(&ReturnTwoFloats, lib, "ReturnTwoFloats") | |
| 983 | - | expected := TwoFloats{5, 2} | |
| 1135 | + | expected := TwoFloats{A: 5, B: 2} | |
| 984 | 1136 | if ret := ReturnTwoFloats(5, 2); ret != expected { | |
| 985 | 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 | 1145 | var ReturnThreeFloats func(a, b, c float32) ThreeFloats | |
| 991 | 1146 | register(&ReturnThreeFloats, lib, "ReturnThreeFloats") | |
| 992 | - | expected := ThreeFloats{1, 2, 3} | |
| 1147 | + | expected := ThreeFloats{A: 1, B: 2, C: 3} | |
| 993 | 1148 | if ret := ReturnThreeFloats(1, 2, 3); ret != expected { | |
| 994 | 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 | 1157 | var ReturnFourFloats func(a, b, c, d float32) FourFloats | |
| 1000 | 1158 | register(&ReturnFourFloats, lib, "ReturnFourFloats") | |
| 1001 | - | expected := FourFloats{1, 2, 3, 4} | |
| 1159 | + | expected := FourFloats{A: 1, B: 2, C: 3, D: 4} | |
| 1002 | 1160 | if ret := ReturnFourFloats(1, 2, 3, 4); ret != expected { | |
| 1003 | 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 | 1169 | var ReturnOneDouble func(a float64) OneDouble | |
| 1009 | 1170 | register(&ReturnOneDouble, lib, "ReturnOneDouble") | |
| 1010 | - | expected := OneDouble{1} | |
| 1171 | + | expected := OneDouble{A: 1} | |
| 1011 | 1172 | if ret := ReturnOneDouble(1); ret != expected { | |
| 1012 | 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 | 1181 | var ReturnTwoDoubles func(a, b float64) TwoDoubles | |
| 1018 | 1182 | register(&ReturnTwoDoubles, lib, "ReturnTwoDoubles") | |
| 1019 | - | expected := TwoDoubles{7, 3} | |
| 1183 | + | expected := TwoDoubles{A: 7, B: 3} | |
| 1020 | 1184 | if ret := ReturnTwoDoubles(7, 3); ret != expected { | |
| 1021 | 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 | 1193 | var ReturnThreeDoubles func(a, b, c float64) ThreeDoubles | |
| 1027 | 1194 | register(&ReturnThreeDoubles, lib, "ReturnThreeDoubles") | |
| 1028 | - | expected := ThreeDoubles{1, 2, 3} | |
| 1195 | + | expected := ThreeDoubles{A: 1, B: 2, C: 3} | |
| 1029 | 1196 | if ret := ReturnThreeDoubles(1, 2, 3); ret != expected { | |
| 1030 | 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 | 1205 | var ReturnFourDoubles func(a, b, c, d float64) FourDoubles | |
| 1036 | 1206 | register(&ReturnFourDoubles, lib, "ReturnFourDoubles") | |
| 1037 | - | expected := FourDoubles{1, 2, 3, 4} | |
| 1207 | + | expected := FourDoubles{A: 1, B: 2, C: 3, D: 4} | |
| 1038 | 1208 | if ret := ReturnFourDoubles(1, 2, 3, 4); ret != expected { | |
| 1039 | 1209 | t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected) | |
| 1040 | 1210 | } | |
| 1041 | 1211 | } | |
| 1042 | 1212 | { | |
| 1043 | 1213 | type FourDoublesInternal struct { | |
| 1044 | - | F struct{ A, B float64 } | |
| 1045 | - | G struct{ C, D float64 } | |
| 1214 | + | _ structs.HostLayout | |
| 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 | 1224 | var ReturnFourDoublesInternal func(a, b, c, d float64) FourDoublesInternal | |
| 1048 | 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 | 1233 | if ret := ReturnFourDoublesInternal(1, 2, 3, 4); ret != expected { | |
| 1051 | 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 | 1242 | var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles | |
| 1057 | 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 | 1245 | if ret := ReturnFiveDoubles(1, 2, 3, 4, 5); ret != expected { | |
| 1060 | 1246 | t.Fatalf("ReturnFiveDoubles returned %+v wanted %+v", ret, expected) | |
| 1061 | 1247 | } | |
| 1062 | 1248 | } | |
| 1063 | 1249 | { | |
| 1064 | 1250 | type OneFloatOneDouble struct { | |
| 1251 | + | _ structs.HostLayout | |
| 1065 | 1252 | A float32 | |
| 1066 | 1253 | _ float32 | |
| 1067 | 1254 | B float64 | |
| @@ -1075,18 +1262,20 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 1075 | 1262 | } | |
| 1076 | 1263 | { | |
| 1077 | 1264 | type OneDoubleOneFloat struct { | |
| 1265 | + | _ structs.HostLayout | |
| 1078 | 1266 | A float64 | |
| 1079 | 1267 | B float32 | |
| 1080 | 1268 | } | |
| 1081 | 1269 | var ReturnOneDoubleOneFloat func(a float64, b float32) OneDoubleOneFloat | |
| 1082 | 1270 | register(&ReturnOneDoubleOneFloat, lib, "ReturnOneDoubleOneFloat") | |
| 1083 | - | expected := OneDoubleOneFloat{1, 2} | |
| 1271 | + | expected := OneDoubleOneFloat{A: 1, B: 2} | |
| 1084 | 1272 | if ret := ReturnOneDoubleOneFloat(1, 2); ret != expected { | |
| 1085 | 1273 | t.Fatalf("ReturnOneDoubleOneFloat returned %+v wanted %+v", ret, expected) | |
| 1086 | 1274 | } | |
| 1087 | 1275 | } | |
| 1088 | 1276 | { | |
| 1089 | 1277 | type Unaligned1 struct { | |
| 1278 | + | _ structs.HostLayout | |
| 1090 | 1279 | A int8 | |
| 1091 | 1280 | _ [1]int8 | |
| 1092 | 1281 | B int16 | |
| @@ -1101,76 +1290,88 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 1101 | 1290 | } | |
| 1102 | 1291 | } | |
| 1103 | 1292 | ||
| 1104 | - | { | |
| 1105 | - | type Mixed1 struct { | |
| 1106 | - | A float32 | |
| 1107 | - | B int32 | |
| 1108 | - | } | |
| 1109 | - | var ReturnMixed1 func(a float32, b int32) Mixed1 | |
| 1110 | - | register(&ReturnMixed1, lib, "ReturnMixed1") | |
| 1111 | - | expected := Mixed1{1, 2} | |
| 1112 | - | if ret := ReturnMixed1(1, 2); ret != expected { | |
| 1113 | - | t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected) | |
| 1114 | - | } | |
| 1115 | - | } | |
| 1116 | - | { | |
| 1117 | - | type Mixed2 struct { | |
| 1118 | - | A float32 | |
| 1119 | - | B int32 | |
| 1120 | - | C float32 | |
| 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) | |
| 1293 | + | if runtime.GOARCH != "ppc64le" { | |
| 1294 | + | // ppc64le is skipped: a float argument shadows a GPR, which purego | |
| 1295 | + | // does not model for scalars, so a trailing integer arg is misplaced. | |
| 1296 | + | // TODO: Support mixed scalar floating-point and integer arguments on | |
| 1297 | + | // ppc64le by modeling the ELFv2 general-purpose register shadowing. | |
| 1298 | + | { | |
| 1299 | + | type Mixed1 struct { | |
| 1300 | + | _ structs.HostLayout | |
| 1301 | + | A float32 | |
| 1302 | + | B int32 | |
| 1303 | + | } | |
| 1304 | + | var ReturnMixed1 func(a float32, b int32) Mixed1 | |
| 1305 | + | register(&ReturnMixed1, lib, "ReturnMixed1") | |
| 1306 | + | expected := Mixed1{A: 1, B: 2} | |
| 1307 | + | if ret := ReturnMixed1(1, 2); ret != expected { | |
| 1308 | + | t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected) | |
| 1309 | + | } | |
| 1141 | 1310 | } | |
| 1142 | - | } | |
| 1143 | - | { | |
| 1144 | - | type Mixed4 struct { | |
| 1145 | - | A float64 | |
| 1146 | - | B uint32 | |
| 1147 | - | C float32 | |
| 1311 | + | { | |
| 1312 | + | type Mixed2 struct { | |
| 1313 | + | _ structs.HostLayout | |
| 1314 | + | A float32 | |
| 1315 | + | B int32 | |
| 1316 | + | 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) Mixed4 | |
| 1150 | - | register(&ReturnMixed4, lib, "ReturnMixed4") | |
| 1151 | - | expected := Mixed4{1, 2, 3} | |
| 1152 | - | if ret := ReturnMixed4(1, 2, 3); ret != expected { | |
| 1153 | - | t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected) | |
| 1326 | + | { | |
| 1327 | + | type Mixed3 struct { | |
| 1328 | + | _ structs.HostLayout | |
| 1329 | + | A float32 | |
| 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 | - | } | |
| 1156 | - | { | |
| 1157 | - | type Mixed5 struct { | |
| 1158 | - | A *int64 | |
| 1159 | - | B int32 | |
| 1160 | - | C float32 | |
| 1161 | - | D int32 | |
| 1340 | + | { | |
| 1341 | + | type Mixed4 struct { | |
| 1342 | + | _ structs.HostLayout | |
| 1343 | + | A float64 | |
| 1344 | + | B uint32 | |
| 1345 | + | C float32 | |
| 1346 | + | } | |
| 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) Mixed5 | |
| 1164 | - | register(&ReturnMixed5, lib, "ReturnMixed5") | |
| 1165 | - | ptr := new(int64) | |
| 1166 | - | expected := Mixed5{ptr, 1, 7.2, 9} | |
| 1167 | - | if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected { | |
| 1168 | - | t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected) | |
| 1354 | + | { | |
| 1355 | + | type Mixed5 struct { | |
| 1356 | + | _ structs.HostLayout | |
| 1357 | + | A *int64 | |
| 1358 | + | B int32 | |
| 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 | 1373 | type Mixed5 struct { | |
| 1374 | + | _ structs.HostLayout | |
| 1174 | 1375 | A *int64 | |
| 1175 | 1376 | B int32 | |
| 1176 | 1377 | C float32 | |
| @@ -1182,7 +1383,7 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 1182 | 1383 | // Struct callback args are tested in TestRegisterFunc_structArgs. | |
| 1183 | 1384 | purego.RegisterLibFunc(&IdentityMixed5, lib, "IdentityMixed5") | |
| 1184 | 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 | 1387 | if ret := IdentityMixed5(expected); ret != expected { | |
| 1187 | 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 | 1393 | type SmallBool struct { | |
| 1394 | + | _ structs.HostLayout | |
| 1193 | 1395 | A bool | |
| 1194 | 1396 | B int32 | |
| 1195 | 1397 | C int64 | |
| 1196 | 1398 | } | |
| 1197 | 1399 | var ReturnSmallBool func(a bool, b int32, c int64) SmallBool | |
| 1198 | 1400 | register(&ReturnSmallBool, lib, "ReturnSmallBool") | |
| 1199 | - | expected := SmallBool{true, 42, 123456789} | |
| 1401 | + | expected := SmallBool{A: true, B: 42, C: 123456789} | |
| 1200 | 1402 | if ret := ReturnSmallBool(true, 42, 123456789); ret != expected { | |
| 1201 | 1403 | t.Fatalf("ReturnSmallBool returned %+v wanted %+v", ret, expected) | |
| 1202 | 1404 | } | |
| 1203 | 1405 | } | |
| 1204 | 1406 | { | |
| 1205 | 1407 | type LargeBool struct { | |
| 1408 | + | _ structs.HostLayout | |
| 1206 | 1409 | A bool | |
| 1207 | 1410 | B int32 | |
| 1208 | 1411 | C int64 | |
| @@ -1210,20 +1413,21 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 1210 | 1413 | } | |
| 1211 | 1414 | var ReturnLargeBool func(a bool, b int32, c int64, d int64) LargeBool | |
| 1212 | 1415 | register(&ReturnLargeBool, lib, "ReturnLargeBool") | |
| 1213 | - | expected := LargeBool{false, -99, 987654321, 111222333444} | |
| 1416 | + | expected := LargeBool{A: false, B: -99, C: 987654321, D: 111222333444} | |
| 1214 | 1417 | if ret := ReturnLargeBool(false, -99, 987654321, 111222333444); ret != expected { | |
| 1215 | 1418 | t.Fatalf("ReturnLargeBool returned %+v wanted %+v", ret, expected) | |
| 1216 | 1419 | } | |
| 1217 | 1420 | } | |
| 1218 | 1421 | { | |
| 1219 | 1422 | type Ptr1 struct { | |
| 1423 | + | _ structs.HostLayout | |
| 1220 | 1424 | a *int64 | |
| 1221 | 1425 | b unsafe.Pointer | |
| 1222 | 1426 | } | |
| 1223 | 1427 | var ReturnPtr1 func(a *int64, b unsafe.Pointer) Ptr1 | |
| 1224 | 1428 | purego.RegisterLibFunc(&ReturnPtr1, lib, "ReturnPtr1") | |
| 1225 | 1429 | a, b := new(int64), new(struct{}) | |
| 1226 | - | expected := Ptr1{a, unsafe.Pointer(b)} | |
| 1430 | + | expected := Ptr1{a: a, b: unsafe.Pointer(b)} | |
| 1227 | 1431 | if ret := ReturnPtr1(a, unsafe.Pointer(b)); ret != expected { | |
| 1228 | 1432 | t.Fatalf("ReturnPtr1 returned %+v wanted %+v", ret, expected) | |
| 1229 | 1433 | } | |
M sys_386.s +44 -43
| @@ -10,8 +10,8 @@ | |||
| 10 | 10 | #define STACK_SIZE 160 | |
| 11 | 11 | #define PTR_ADDRESS (STACK_SIZE - 4) | |
| 12 | 12 | ||
| 13 | - | // syscall15X calls a function in libc on behalf of the syscall package. | |
| 14 | - | // syscall15X takes a pointer to a struct like: | |
| 13 | + | // syscallX calls a function in libc on behalf of the syscall package. | |
| 14 | + | // syscallX takes a pointer to a struct like: | |
| 15 | 15 | // struct { | |
| 16 | 16 | // fn uintptr | |
| 17 | 17 | // a1 uintptr | |
| @@ -22,18 +22,18 @@ | |||
| 22 | 22 | // f16 uintptr | |
| 23 | 23 | // arm64_r8 uintptr | |
| 24 | 24 | // } | |
| 25 | - | // syscall15X must be called on the g0 stack with the | |
| 25 | + | // syscallX must be called on the g0 stack with the | |
| 26 | 26 | // C calling convention (use libcCall). | |
| 27 | 27 | // | |
| 28 | 28 | // On i386 System V ABI, all arguments are passed on the stack. | |
| 29 | 29 | // Return value is in EAX (and EDX for 64-bit values). | |
| 30 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $4 | |
| 31 | - | DATA ·syscall15XABI0(SB)/4, $syscall15X(SB) | |
| 32 | - | TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |
| 30 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $4 | |
| 31 | + | DATA ·syscallXABI0(SB)/4, $syscallX(SB) | |
| 32 | + | TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 | |
| 33 | 33 | // Called via C calling convention: argument pointer at 4(SP) | |
| 34 | 34 | // NOT via Go calling convention | |
| 35 | 35 | // On i386, the first argument is at 4(SP) after CALL pushes return address | |
| 36 | - | MOVL 4(SP), AX // get pointer to syscall15Args | |
| 36 | + | MOVL 4(SP), AX // get pointer to syscallArgs | |
| 37 | 37 | ||
| 38 | 38 | // Save callee-saved registers | |
| 39 | 39 | PUSHL BP | |
| @@ -50,75 +50,75 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 50 | 50 | MOVL BX, PTR_ADDRESS(SP) // save args pointer | |
| 51 | 51 | ||
| 52 | 52 | // Load function pointer | |
| 53 | - | MOVL syscall15Args_fn(BX), AX | |
| 53 | + | MOVL syscallArgs_fn(BX), AX | |
| 54 | 54 | MOVL AX, (PTR_ADDRESS-4)(SP) // save fn pointer | |
| 55 | 55 | ||
| 56 | 56 | // Push all integer arguments onto the stack (a1-a32) | |
| 57 | 57 | // i386 SysV ABI: arguments pushed right-to-left, but we're | |
| 58 | 58 | // setting up the stack from low to high addresses | |
| 59 | - | MOVL syscall15Args_a1(BX), AX | |
| 59 | + | MOVL syscallArgs_a1(BX), AX | |
| 60 | 60 | MOVL AX, 0(SP) | |
| 61 | - | MOVL syscall15Args_a2(BX), AX | |
| 61 | + | MOVL syscallArgs_a2(BX), AX | |
| 62 | 62 | MOVL AX, 4(SP) | |
| 63 | - | MOVL syscall15Args_a3(BX), AX | |
| 63 | + | MOVL syscallArgs_a3(BX), AX | |
| 64 | 64 | MOVL AX, 8(SP) | |
| 65 | - | MOVL syscall15Args_a4(BX), AX | |
| 65 | + | MOVL syscallArgs_a4(BX), AX | |
| 66 | 66 | MOVL AX, 12(SP) | |
| 67 | - | MOVL syscall15Args_a5(BX), AX | |
| 67 | + | MOVL syscallArgs_a5(BX), AX | |
| 68 | 68 | MOVL AX, 16(SP) | |
| 69 | - | MOVL syscall15Args_a6(BX), AX | |
| 69 | + | MOVL syscallArgs_a6(BX), AX | |
| 70 | 70 | MOVL AX, 20(SP) | |
| 71 | - | MOVL syscall15Args_a7(BX), AX | |
| 71 | + | MOVL syscallArgs_a7(BX), AX | |
| 72 | 72 | MOVL AX, 24(SP) | |
| 73 | - | MOVL syscall15Args_a8(BX), AX | |
| 73 | + | MOVL syscallArgs_a8(BX), AX | |
| 74 | 74 | MOVL AX, 28(SP) | |
| 75 | - | MOVL syscall15Args_a9(BX), AX | |
| 75 | + | MOVL syscallArgs_a9(BX), AX | |
| 76 | 76 | MOVL AX, 32(SP) | |
| 77 | - | MOVL syscall15Args_a10(BX), AX | |
| 77 | + | MOVL syscallArgs_a10(BX), AX | |
| 78 | 78 | MOVL AX, 36(SP) | |
| 79 | - | MOVL syscall15Args_a11(BX), AX | |
| 79 | + | MOVL syscallArgs_a11(BX), AX | |
| 80 | 80 | MOVL AX, 40(SP) | |
| 81 | - | MOVL syscall15Args_a12(BX), AX | |
| 81 | + | MOVL syscallArgs_a12(BX), AX | |
| 82 | 82 | MOVL AX, 44(SP) | |
| 83 | - | MOVL syscall15Args_a13(BX), AX | |
| 83 | + | MOVL syscallArgs_a13(BX), AX | |
| 84 | 84 | MOVL AX, 48(SP) | |
| 85 | - | MOVL syscall15Args_a14(BX), AX | |
| 85 | + | MOVL syscallArgs_a14(BX), AX | |
| 86 | 86 | MOVL AX, 52(SP) | |
| 87 | - | MOVL syscall15Args_a15(BX), AX | |
| 87 | + | MOVL syscallArgs_a15(BX), AX | |
| 88 | 88 | MOVL AX, 56(SP) | |
| 89 | - | MOVL syscall15Args_a16(BX), AX | |
| 89 | + | MOVL syscallArgs_a16(BX), AX | |
| 90 | 90 | MOVL AX, 60(SP) | |
| 91 | - | MOVL syscall15Args_a17(BX), AX | |
| 91 | + | MOVL syscallArgs_a17(BX), AX | |
| 92 | 92 | MOVL AX, 64(SP) | |
| 93 | - | MOVL syscall15Args_a18(BX), AX | |
| 93 | + | MOVL syscallArgs_a18(BX), AX | |
| 94 | 94 | MOVL AX, 68(SP) | |
| 95 | - | MOVL syscall15Args_a19(BX), AX | |
| 95 | + | MOVL syscallArgs_a19(BX), AX | |
| 96 | 96 | MOVL AX, 72(SP) | |
| 97 | - | MOVL syscall15Args_a20(BX), AX | |
| 97 | + | MOVL syscallArgs_a20(BX), AX | |
| 98 | 98 | MOVL AX, 76(SP) | |
| 99 | - | MOVL syscall15Args_a21(BX), AX | |
| 99 | + | MOVL syscallArgs_a21(BX), AX | |
| 100 | 100 | MOVL AX, 80(SP) | |
| 101 | - | MOVL syscall15Args_a22(BX), AX | |
| 101 | + | MOVL syscallArgs_a22(BX), AX | |
| 102 | 102 | MOVL AX, 84(SP) | |
| 103 | - | MOVL syscall15Args_a23(BX), AX | |
| 103 | + | MOVL syscallArgs_a23(BX), AX | |
| 104 | 104 | MOVL AX, 88(SP) | |
| 105 | - | MOVL syscall15Args_a24(BX), AX | |
| 105 | + | MOVL syscallArgs_a24(BX), AX | |
| 106 | 106 | MOVL AX, 92(SP) | |
| 107 | - | MOVL syscall15Args_a25(BX), AX | |
| 107 | + | MOVL syscallArgs_a25(BX), AX | |
| 108 | 108 | MOVL AX, 96(SP) | |
| 109 | - | MOVL syscall15Args_a26(BX), AX | |
| 109 | + | MOVL syscallArgs_a26(BX), AX | |
| 110 | 110 | MOVL AX, 100(SP) | |
| 111 | - | MOVL syscall15Args_a27(BX), AX | |
| 111 | + | MOVL syscallArgs_a27(BX), AX | |
| 112 | 112 | MOVL AX, 104(SP) | |
| 113 | - | MOVL syscall15Args_a28(BX), AX | |
| 113 | + | MOVL syscallArgs_a28(BX), AX | |
| 114 | 114 | MOVL AX, 108(SP) | |
| 115 | - | MOVL syscall15Args_a29(BX), AX | |
| 115 | + | MOVL syscallArgs_a29(BX), AX | |
| 116 | 116 | MOVL AX, 112(SP) | |
| 117 | - | MOVL syscall15Args_a30(BX), AX | |
| 117 | + | MOVL syscallArgs_a30(BX), AX | |
| 118 | 118 | MOVL AX, 116(SP) | |
| 119 | - | MOVL syscall15Args_a31(BX), AX | |
| 119 | + | MOVL syscallArgs_a31(BX), AX | |
| 120 | 120 | MOVL AX, 120(SP) | |
| 121 | - | MOVL syscall15Args_a32(BX), AX | |
| 121 | + | MOVL syscallArgs_a32(BX), AX | |
| 122 | 122 | MOVL AX, 124(SP) | |
| 123 | 123 | ||
| 124 | 124 | // Call the C function | |
| @@ -127,13 +127,14 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 127 | 127 | ||
| 128 | 128 | // Get args pointer back and save results | |
| 129 | 129 | MOVL PTR_ADDRESS(SP), BX | |
| 130 | - | MOVL AX, syscall15Args_a1(BX) // return value r1 | |
| 131 | - | MOVL DX, syscall15Args_a2(BX) // return value r2 (for 64-bit returns) | |
| 130 | + | MOVL AX, syscallArgs_a1(BX) // return value r1 | |
| 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 | 134 | // Save x87 FPU return value (ST0) to f1 field | |
| 134 | 135 | // On i386 System V ABI, float/double returns are in ST(0) | |
| 135 | 136 | // We save as float64 (8 bytes) to preserve precision | |
| 136 | - | FMOVDP F0, syscall15Args_f1(BX) | |
| 137 | + | FMOVDP F0, syscallArgs_f1(BX) | |
| 137 | 138 | ||
| 138 | 139 | // Clean up stack | |
| 139 | 140 | ADDL $STACK_SIZE, SP | |
M sys_amd64.s +73 -37
| @@ -8,11 +8,11 @@ | |||
| 8 | 8 | #include "go_asm.h" | |
| 9 | 9 | #include "funcdata.h" | |
| 10 | 10 | ||
| 11 | - | #define STACK_SIZE 80 | |
| 11 | + | #define STACK_SIZE 224 | |
| 12 | 12 | #define PTR_ADDRESS (STACK_SIZE - 8) | |
| 13 | 13 | ||
| 14 | - | // syscall15X calls a function in libc on behalf of the syscall package. | |
| 15 | - | // syscall15X takes a pointer to a struct like: | |
| 14 | + | // syscallX calls a function in libc on behalf of the syscall package. | |
| 15 | + | // syscallX takes a pointer to a struct like: | |
| 16 | 16 | // struct { | |
| 17 | 17 | // fn uintptr | |
| 18 | 18 | // a1 uintptr | |
| @@ -34,65 +34,101 @@ | |||
| 34 | 34 | // r2 uintptr | |
| 35 | 35 | // err uintptr | |
| 36 | 36 | // } | |
| 37 | - | // syscall15X must be called on the g0 stack with the | |
| 37 | + | // syscallX must be called on the g0 stack with the | |
| 38 | 38 | // C calling convention (use libcCall). | |
| 39 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 40 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 41 | - | TEXT syscall15X(SB), NOSPLIT, $STACK_SIZE | |
| 39 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 | |
| 40 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) | |
| 41 | + | TEXT syscallX(SB), NOSPLIT, $STACK_SIZE | |
| 42 | 42 | MOVQ DI, PTR_ADDRESS(SP) // save the pointer | |
| 43 | 43 | MOVQ DI, R11 | |
| 44 | 44 | ||
| 45 | - | MOVQ syscall15Args_f1(R11), X0 // f1 | |
| 46 | - | MOVQ syscall15Args_f2(R11), X1 // f2 | |
| 47 | - | MOVQ syscall15Args_f3(R11), X2 // f3 | |
| 48 | - | MOVQ syscall15Args_f4(R11), X3 // f4 | |
| 49 | - | MOVQ syscall15Args_f5(R11), X4 // f5 | |
| 50 | - | MOVQ syscall15Args_f6(R11), X5 // f6 | |
| 51 | - | MOVQ syscall15Args_f7(R11), X6 // f7 | |
| 52 | - | MOVQ syscall15Args_f8(R11), X7 // f8 | |
| 53 | - | ||
| 54 | - | MOVQ syscall15Args_a1(R11), DI // a1 | |
| 55 | - | MOVQ syscall15Args_a2(R11), SI // a2 | |
| 56 | - | MOVQ syscall15Args_a3(R11), DX // a3 | |
| 57 | - | MOVQ syscall15Args_a4(R11), CX // a4 | |
| 58 | - | MOVQ syscall15Args_a5(R11), R8 // a5 | |
| 59 | - | MOVQ syscall15Args_a6(R11), R9 // a6 | |
| 45 | + | MOVQ syscallArgs_f1(R11), X0 // f1 | |
| 46 | + | MOVQ syscallArgs_f2(R11), X1 // f2 | |
| 47 | + | MOVQ syscallArgs_f3(R11), X2 // f3 | |
| 48 | + | MOVQ syscallArgs_f4(R11), X3 // f4 | |
| 49 | + | MOVQ syscallArgs_f5(R11), X4 // f5 | |
| 50 | + | MOVQ syscallArgs_f6(R11), X5 // f6 | |
| 51 | + | MOVQ syscallArgs_f7(R11), X6 // f7 | |
| 52 | + | MOVQ syscallArgs_f8(R11), X7 // f8 | |
| 53 | + | ||
| 54 | + | MOVQ syscallArgs_a1(R11), DI // a1 | |
| 55 | + | MOVQ syscallArgs_a2(R11), SI // a2 | |
| 56 | + | MOVQ syscallArgs_a3(R11), DX // a3 | |
| 57 | + | MOVQ syscallArgs_a4(R11), CX // a4 | |
| 58 | + | MOVQ syscallArgs_a5(R11), R8 // a5 | |
| 59 | + | MOVQ syscallArgs_a6(R11), R9 // a6 | |
| 60 | 60 | ||
| 61 | 61 | // push the remaining parameters onto the stack | |
| 62 | - | MOVQ syscall15Args_a7(R11), R12 | |
| 62 | + | MOVQ syscallArgs_a7(R11), R12 | |
| 63 | 63 | MOVQ R12, 0(SP) // push a7 | |
| 64 | - | MOVQ syscall15Args_a8(R11), R12 | |
| 64 | + | MOVQ syscallArgs_a8(R11), R12 | |
| 65 | 65 | MOVQ R12, 8(SP) // push a8 | |
| 66 | - | MOVQ syscall15Args_a9(R11), R12 | |
| 66 | + | MOVQ syscallArgs_a9(R11), R12 | |
| 67 | 67 | MOVQ R12, 16(SP) // push a9 | |
| 68 | - | MOVQ syscall15Args_a10(R11), R12 | |
| 68 | + | MOVQ syscallArgs_a10(R11), R12 | |
| 69 | 69 | MOVQ R12, 24(SP) // push a10 | |
| 70 | - | MOVQ syscall15Args_a11(R11), R12 | |
| 70 | + | MOVQ syscallArgs_a11(R11), R12 | |
| 71 | 71 | MOVQ R12, 32(SP) // push a11 | |
| 72 | - | MOVQ syscall15Args_a12(R11), R12 | |
| 72 | + | MOVQ syscallArgs_a12(R11), R12 | |
| 73 | 73 | MOVQ R12, 40(SP) // push a12 | |
| 74 | - | MOVQ syscall15Args_a13(R11), R12 | |
| 74 | + | MOVQ syscallArgs_a13(R11), R12 | |
| 75 | 75 | MOVQ R12, 48(SP) // push a13 | |
| 76 | - | MOVQ syscall15Args_a14(R11), R12 | |
| 76 | + | MOVQ syscallArgs_a14(R11), R12 | |
| 77 | 77 | MOVQ R12, 56(SP) // push a14 | |
| 78 | - | MOVQ syscall15Args_a15(R11), R12 | |
| 78 | + | MOVQ syscallArgs_a15(R11), R12 | |
| 79 | 79 | 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 | 114 | XORL AX, AX // vararg: say "no float args" | |
| 81 | 115 | ||
| 82 | - | MOVQ syscall15Args_fn(R11), R10 // fn | |
| 116 | + | MOVQ syscallArgs_fn(R11), R10 // fn | |
| 83 | 117 | CALL R10 | |
| 84 | 118 | ||
| 85 | 119 | MOVQ PTR_ADDRESS(SP), DI // get the pointer back | |
| 86 | - | MOVQ AX, syscall15Args_a1(DI) // r1 | |
| 87 | - | MOVQ DX, syscall15Args_a2(DI) // r2 | |
| 88 | - | MOVQ X0, syscall15Args_f1(DI) // f1 | |
| 89 | - | MOVQ X1, syscall15Args_f2(DI) // f2 | |
| 120 | + | MOVQ AX, syscallArgs_a1(DI) // r1 | |
| 121 | + | MOVQ DX, syscallArgs_a2(DI) // r2 | |
| 122 | + | MOVQ X0, syscallArgs_f1(DI) // f1 | |
| 123 | + | MOVQ X1, syscallArgs_f2(DI) // f2 | |
| 90 | 124 | ||
| 91 | 125 | #ifdef GOOS_darwin | |
| 92 | 126 | CALL purego_error(SB) | |
| 93 | 127 | MOVQ PTR_ADDRESS(SP), DI // reload (DI clobbered by call) | |
| 94 | 128 | MOVQ (AX), AX | |
| 95 | - | MOVQ AX, syscall15Args_a3(DI) // save errno | |
| 129 | + | MOVQ AX, syscallArgs_a3(DI) // save errno | |
| 130 | + | #else | |
| 131 | + | MOVQ $0, syscallArgs_a3(DI) // no errno here: clear the input argument | |
| 96 | 132 | #endif | |
| 97 | 133 | ||
| 98 | 134 | XORL AX, AX // no error (it's ignored anyway) | |
M sys_arm.s +65 -54
| @@ -10,8 +10,8 @@ | |||
| 10 | 10 | #define STACK_SIZE 128 | |
| 11 | 11 | #define PTR_ADDRESS (STACK_SIZE - 4) | |
| 12 | 12 | ||
| 13 | - | // syscall15X calls a function in libc on behalf of the syscall package. | |
| 14 | - | // syscall15X takes a pointer to a struct like: | |
| 13 | + | // syscallX calls a function in libc on behalf of the syscall package. | |
| 14 | + | // syscallX takes a pointer to a struct like: | |
| 15 | 15 | // struct { | |
| 16 | 16 | // fn uintptr | |
| 17 | 17 | // a1 uintptr | |
| @@ -22,12 +22,12 @@ | |||
| 22 | 22 | // f16 uintptr | |
| 23 | 23 | // arm64_r8 uintptr | |
| 24 | 24 | // } | |
| 25 | - | // syscall15X must be called on the g0 stack with the | |
| 25 | + | // syscallX must be called on the g0 stack with the | |
| 26 | 26 | // C calling convention (use libcCall). | |
| 27 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $4 | |
| 28 | - | DATA ·syscall15XABI0(SB)/4, $syscall15X(SB) | |
| 29 | - | TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |
| 30 | - | // Called via C calling convention: R0 = pointer to syscall15Args | |
| 27 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $4 | |
| 28 | + | DATA ·syscallXABI0(SB)/4, $syscallX(SB) | |
| 29 | + | TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 | |
| 30 | + | // Called via C calling convention: R0 = pointer to syscallArgs | |
| 31 | 31 | // NOT via Go calling convention | |
| 32 | 32 | // Save link register and callee-saved registers first | |
| 33 | 33 | MOVW.W R14, -4(R13) // save LR (decrement and store) | |
| @@ -38,82 +38,87 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 38 | 38 | MOVW R8, PTR_ADDRESS(R13) | |
| 39 | 39 | ||
| 40 | 40 | // Load function pointer first (before anything can corrupt R8) | |
| 41 | - | MOVW syscall15Args_fn(R8), R5 | |
| 41 | + | MOVW syscallArgs_fn(R8), R5 | |
| 42 | 42 | 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 | 48 | // Load floating point arguments | |
| 45 | 49 | // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 2 | |
| 46 | - | MOVD syscall15Args_f1(R8), F0 // f1+f2 -> D0 | |
| 47 | - | MOVD syscall15Args_f3(R8), F1 // f3+f4 -> D1 | |
| 48 | - | MOVD syscall15Args_f5(R8), F2 // f5+f6 -> D2 | |
| 49 | - | MOVD syscall15Args_f7(R8), F3 // f7+f8 -> D3 | |
| 50 | - | MOVD syscall15Args_f9(R8), F4 // f9+f10 -> D4 | |
| 51 | - | MOVD syscall15Args_f11(R8), F5 // f11+f12 -> D5 | |
| 52 | - | MOVD syscall15Args_f13(R8), F6 // f13+f14 -> D6 | |
| 53 | - | MOVD syscall15Args_f15(R8), F7 // f15+f16 -> D7 | |
| 50 | + | MOVD syscallArgs_f1(R8), F0 // f1+f2 -> D0 | |
| 51 | + | MOVD syscallArgs_f3(R8), F1 // f3+f4 -> D1 | |
| 52 | + | MOVD syscallArgs_f5(R8), F2 // f5+f6 -> D2 | |
| 53 | + | MOVD syscallArgs_f7(R8), F3 // f7+f8 -> D3 | |
| 54 | + | MOVD syscallArgs_f9(R8), F4 // f9+f10 -> D4 | |
| 55 | + | MOVD syscallArgs_f11(R8), F5 // f11+f12 -> D5 | |
| 56 | + | MOVD syscallArgs_f13(R8), F6 // f13+f14 -> D6 | |
| 57 | + | MOVD syscallArgs_f15(R8), F7 // f15+f16 -> D7 | |
| 54 | 58 | ||
| 59 | + | skipfpload: | |
| 55 | 60 | // Load integer arguments into registers (R0-R3 for ARM EABI) | |
| 56 | - | MOVW syscall15Args_a1(R8), R0 // a1 | |
| 57 | - | MOVW syscall15Args_a2(R8), R1 // a2 | |
| 58 | - | MOVW syscall15Args_a3(R8), R2 // a3 | |
| 59 | - | MOVW syscall15Args_a4(R8), R3 // a4 | |
| 61 | + | MOVW syscallArgs_a1(R8), R0 // a1 | |
| 62 | + | MOVW syscallArgs_a2(R8), R1 // a2 | |
| 63 | + | MOVW syscallArgs_a3(R8), R2 // a3 | |
| 64 | + | MOVW syscallArgs_a4(R8), R3 // a4 | |
| 60 | 65 | ||
| 61 | 66 | // push a5-a32 onto stack | |
| 62 | - | MOVW syscall15Args_a5(R8), R4 | |
| 67 | + | MOVW syscallArgs_a5(R8), R4 | |
| 63 | 68 | MOVW R4, 0(R13) | |
| 64 | - | MOVW syscall15Args_a6(R8), R4 | |
| 69 | + | MOVW syscallArgs_a6(R8), R4 | |
| 65 | 70 | MOVW R4, 4(R13) | |
| 66 | - | MOVW syscall15Args_a7(R8), R4 | |
| 71 | + | MOVW syscallArgs_a7(R8), R4 | |
| 67 | 72 | MOVW R4, 8(R13) | |
| 68 | - | MOVW syscall15Args_a8(R8), R4 | |
| 73 | + | MOVW syscallArgs_a8(R8), R4 | |
| 69 | 74 | MOVW R4, 12(R13) | |
| 70 | - | MOVW syscall15Args_a9(R8), R4 | |
| 75 | + | MOVW syscallArgs_a9(R8), R4 | |
| 71 | 76 | MOVW R4, 16(R13) | |
| 72 | - | MOVW syscall15Args_a10(R8), R4 | |
| 77 | + | MOVW syscallArgs_a10(R8), R4 | |
| 73 | 78 | MOVW R4, 20(R13) | |
| 74 | - | MOVW syscall15Args_a11(R8), R4 | |
| 79 | + | MOVW syscallArgs_a11(R8), R4 | |
| 75 | 80 | MOVW R4, 24(R13) | |
| 76 | - | MOVW syscall15Args_a12(R8), R4 | |
| 81 | + | MOVW syscallArgs_a12(R8), R4 | |
| 77 | 82 | MOVW R4, 28(R13) | |
| 78 | - | MOVW syscall15Args_a13(R8), R4 | |
| 83 | + | MOVW syscallArgs_a13(R8), R4 | |
| 79 | 84 | MOVW R4, 32(R13) | |
| 80 | - | MOVW syscall15Args_a14(R8), R4 | |
| 85 | + | MOVW syscallArgs_a14(R8), R4 | |
| 81 | 86 | MOVW R4, 36(R13) | |
| 82 | - | MOVW syscall15Args_a15(R8), R4 | |
| 87 | + | MOVW syscallArgs_a15(R8), R4 | |
| 83 | 88 | MOVW R4, 40(R13) | |
| 84 | - | MOVW syscall15Args_a16(R8), R4 | |
| 89 | + | MOVW syscallArgs_a16(R8), R4 | |
| 85 | 90 | MOVW R4, 44(R13) | |
| 86 | - | MOVW syscall15Args_a17(R8), R4 | |
| 91 | + | MOVW syscallArgs_a17(R8), R4 | |
| 87 | 92 | MOVW R4, 48(R13) | |
| 88 | - | MOVW syscall15Args_a18(R8), R4 | |
| 93 | + | MOVW syscallArgs_a18(R8), R4 | |
| 89 | 94 | MOVW R4, 52(R13) | |
| 90 | - | MOVW syscall15Args_a19(R8), R4 | |
| 95 | + | MOVW syscallArgs_a19(R8), R4 | |
| 91 | 96 | MOVW R4, 56(R13) | |
| 92 | - | MOVW syscall15Args_a20(R8), R4 | |
| 97 | + | MOVW syscallArgs_a20(R8), R4 | |
| 93 | 98 | MOVW R4, 60(R13) | |
| 94 | - | MOVW syscall15Args_a21(R8), R4 | |
| 99 | + | MOVW syscallArgs_a21(R8), R4 | |
| 95 | 100 | MOVW R4, 64(R13) | |
| 96 | - | MOVW syscall15Args_a22(R8), R4 | |
| 101 | + | MOVW syscallArgs_a22(R8), R4 | |
| 97 | 102 | MOVW R4, 68(R13) | |
| 98 | - | MOVW syscall15Args_a23(R8), R4 | |
| 103 | + | MOVW syscallArgs_a23(R8), R4 | |
| 99 | 104 | MOVW R4, 72(R13) | |
| 100 | - | MOVW syscall15Args_a24(R8), R4 | |
| 105 | + | MOVW syscallArgs_a24(R8), R4 | |
| 101 | 106 | MOVW R4, 76(R13) | |
| 102 | - | MOVW syscall15Args_a25(R8), R4 | |
| 107 | + | MOVW syscallArgs_a25(R8), R4 | |
| 103 | 108 | MOVW R4, 80(R13) | |
| 104 | - | MOVW syscall15Args_a26(R8), R4 | |
| 109 | + | MOVW syscallArgs_a26(R8), R4 | |
| 105 | 110 | MOVW R4, 84(R13) | |
| 106 | - | MOVW syscall15Args_a27(R8), R4 | |
| 111 | + | MOVW syscallArgs_a27(R8), R4 | |
| 107 | 112 | MOVW R4, 88(R13) | |
| 108 | - | MOVW syscall15Args_a28(R8), R4 | |
| 113 | + | MOVW syscallArgs_a28(R8), R4 | |
| 109 | 114 | MOVW R4, 92(R13) | |
| 110 | - | MOVW syscall15Args_a29(R8), R4 | |
| 115 | + | MOVW syscallArgs_a29(R8), R4 | |
| 111 | 116 | MOVW R4, 96(R13) | |
| 112 | - | MOVW syscall15Args_a30(R8), R4 | |
| 117 | + | MOVW syscallArgs_a30(R8), R4 | |
| 113 | 118 | MOVW R4, 100(R13) | |
| 114 | - | MOVW syscall15Args_a31(R8), R4 | |
| 119 | + | MOVW syscallArgs_a31(R8), R4 | |
| 115 | 120 | MOVW R4, 104(R13) | |
| 116 | - | MOVW syscall15Args_a32(R8), R4 | |
| 121 | + | MOVW syscallArgs_a32(R8), R4 | |
| 117 | 122 | MOVW R4, 108(R13) | |
| 118 | 123 | ||
| 119 | 124 | // Load saved function pointer and call | |
| @@ -128,15 +133,21 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 128 | 133 | ADD $STACK_SIZE, R13 | |
| 129 | 134 | ||
| 130 | 135 | // save R0, R1 | |
| 131 | - | MOVW R0, syscall15Args_a1(R8) | |
| 132 | - | MOVW R1, syscall15Args_a2(R8) | |
| 136 | + | MOVW R0, syscallArgs_a1(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 | 144 | // save f0-f3 (each float64 spans 2 uintptr slots on ARM32) | |
| 135 | - | MOVD F0, syscall15Args_f1(R8) | |
| 136 | - | MOVD F1, syscall15Args_f3(R8) | |
| 137 | - | MOVD F2, syscall15Args_f5(R8) | |
| 138 | - | MOVD F3, syscall15Args_f7(R8) | |
| 145 | + | MOVD F0, syscallArgs_f1(R8) | |
| 146 | + | MOVD F1, syscallArgs_f3(R8) | |
| 147 | + | MOVD F2, syscallArgs_f5(R8) | |
| 148 | + | MOVD F3, syscallArgs_f7(R8) | |
| 139 | 149 | ||
| 150 | + | skipfpsave: | |
| 140 | 151 | // Restore callee-saved registers and return | |
| 141 | 152 | MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11] | |
| 142 | 153 | MOVW.P 4(R13), R15 // pop LR into PC (return) | |
M sys_arm64.s +75 -40
| @@ -7,11 +7,11 @@ | |||
| 7 | 7 | #include "go_asm.h" | |
| 8 | 8 | #include "funcdata.h" | |
| 9 | 9 | ||
| 10 | - | #define STACK_SIZE 64 | |
| 10 | + | #define STACK_SIZE 208 | |
| 11 | 11 | #define PTR_ADDRESS (STACK_SIZE - 8) | |
| 12 | 12 | ||
| 13 | - | // syscall15X calls a function in libc on behalf of the syscall package. | |
| 14 | - | // syscall15X takes a pointer to a struct like: | |
| 13 | + | // syscallX calls a function in libc on behalf of the syscall package. | |
| 14 | + | // syscallX takes a pointer to a struct like: | |
| 15 | 15 | // struct { | |
| 16 | 16 | // fn uintptr | |
| 17 | 17 | // a1 uintptr | |
| @@ -33,66 +33,101 @@ | |||
| 33 | 33 | // r2 uintptr | |
| 34 | 34 | // err uintptr | |
| 35 | 35 | // } | |
| 36 | - | // syscall15X must be called on the g0 stack with the | |
| 36 | + | // syscallX must be called on the g0 stack with the | |
| 37 | 37 | // C calling convention (use libcCall). | |
| 38 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 39 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 40 | - | TEXT syscall15X(SB), NOSPLIT, $0 | |
| 38 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 | |
| 39 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) | |
| 40 | + | TEXT syscallX(SB), NOSPLIT, $0 | |
| 41 | 41 | SUB $STACK_SIZE, RSP // push structure pointer | |
| 42 | 42 | MOVD R0, PTR_ADDRESS(RSP) | |
| 43 | 43 | MOVD R0, R9 | |
| 44 | 44 | ||
| 45 | - | FMOVD syscall15Args_f1(R9), F0 // f1 | |
| 46 | - | FMOVD syscall15Args_f2(R9), F1 // f2 | |
| 47 | - | FMOVD syscall15Args_f3(R9), F2 // f3 | |
| 48 | - | FMOVD syscall15Args_f4(R9), F3 // f4 | |
| 49 | - | FMOVD syscall15Args_f5(R9), F4 // f5 | |
| 50 | - | FMOVD syscall15Args_f6(R9), F5 // f6 | |
| 51 | - | FMOVD syscall15Args_f7(R9), F6 // f7 | |
| 52 | - | FMOVD syscall15Args_f8(R9), F7 // f8 | |
| 45 | + | FMOVD syscallArgs_f1(R9), F0 // f1 | |
| 46 | + | FMOVD syscallArgs_f2(R9), F1 // f2 | |
| 47 | + | FMOVD syscallArgs_f3(R9), F2 // f3 | |
| 48 | + | FMOVD syscallArgs_f4(R9), F3 // f4 | |
| 49 | + | FMOVD syscallArgs_f5(R9), F4 // f5 | |
| 50 | + | FMOVD syscallArgs_f6(R9), F5 // f6 | |
| 51 | + | FMOVD syscallArgs_f7(R9), F6 // f7 | |
| 52 | + | FMOVD syscallArgs_f8(R9), F7 // f8 | |
| 53 | 53 | ||
| 54 | - | MOVD syscall15Args_a1(R9), R0 // a1 | |
| 55 | - | MOVD syscall15Args_a2(R9), R1 // a2 | |
| 56 | - | MOVD syscall15Args_a3(R9), R2 // a3 | |
| 57 | - | MOVD syscall15Args_a4(R9), R3 // a4 | |
| 58 | - | MOVD syscall15Args_a5(R9), R4 // a5 | |
| 59 | - | MOVD syscall15Args_a6(R9), R5 // a6 | |
| 60 | - | MOVD syscall15Args_a7(R9), R6 // a7 | |
| 61 | - | MOVD syscall15Args_a8(R9), R7 // a8 | |
| 62 | - | MOVD syscall15Args_arm64_r8(R9), R8 // r8 | |
| 54 | + | MOVD syscallArgs_a1(R9), R0 // a1 | |
| 55 | + | MOVD syscallArgs_a2(R9), R1 // a2 | |
| 56 | + | MOVD syscallArgs_a3(R9), R2 // a3 | |
| 57 | + | MOVD syscallArgs_a4(R9), R3 // a4 | |
| 58 | + | MOVD syscallArgs_a5(R9), R4 // a5 | |
| 59 | + | MOVD syscallArgs_a6(R9), R5 // a6 | |
| 60 | + | MOVD syscallArgs_a7(R9), R6 // a7 | |
| 61 | + | MOVD syscallArgs_a8(R9), R7 // a8 | |
| 62 | + | MOVD syscallArgs_arm64_r8(R9), R8 // r8 | |
| 63 | 63 | ||
| 64 | - | MOVD syscall15Args_a9(R9), R10 | |
| 64 | + | MOVD syscallArgs_a9(R9), R10 | |
| 65 | 65 | MOVD R10, 0(RSP) // push a9 onto stack | |
| 66 | - | MOVD syscall15Args_a10(R9), R10 | |
| 66 | + | MOVD syscallArgs_a10(R9), R10 | |
| 67 | 67 | MOVD R10, 8(RSP) // push a10 onto stack | |
| 68 | - | MOVD syscall15Args_a11(R9), R10 | |
| 68 | + | MOVD syscallArgs_a11(R9), R10 | |
| 69 | 69 | MOVD R10, 16(RSP) // push a11 onto stack | |
| 70 | - | MOVD syscall15Args_a12(R9), R10 | |
| 70 | + | MOVD syscallArgs_a12(R9), R10 | |
| 71 | 71 | MOVD R10, 24(RSP) // push a12 onto stack | |
| 72 | - | MOVD syscall15Args_a13(R9), R10 | |
| 72 | + | MOVD syscallArgs_a13(R9), R10 | |
| 73 | 73 | MOVD R10, 32(RSP) // push a13 onto stack | |
| 74 | - | MOVD syscall15Args_a14(R9), R10 | |
| 74 | + | MOVD syscallArgs_a14(R9), R10 | |
| 75 | 75 | MOVD R10, 40(RSP) // push a14 onto stack | |
| 76 | - | MOVD syscall15Args_a15(R9), R10 | |
| 76 | + | MOVD syscallArgs_a15(R9), R10 | |
| 77 | 77 | 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 // fn | |
| 113 | + | MOVD syscallArgs_fn(R9), R10 // fn | |
| 80 | 114 | BL (R10) | |
| 81 | 115 | ||
| 82 | 116 | MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer | |
| 83 | 117 | ADD $STACK_SIZE, RSP | |
| 84 | 118 | ||
| 85 | - | MOVD R0, syscall15Args_a1(R2) // save r1 | |
| 86 | - | MOVD R1, syscall15Args_a2(R2) // save r3 | |
| 87 | - | FMOVD F0, syscall15Args_f1(R2) // save f0 | |
| 88 | - | FMOVD F1, syscall15Args_f2(R2) // save f1 | |
| 89 | - | FMOVD F2, syscall15Args_f3(R2) // save f2 | |
| 90 | - | FMOVD F3, syscall15Args_f4(R2) // save f3 | |
| 119 | + | MOVD R0, syscallArgs_a1(R2) // save r1 | |
| 120 | + | MOVD R1, syscallArgs_a2(R2) // save r3 | |
| 121 | + | FMOVD F0, syscallArgs_f1(R2) // save f0 | |
| 122 | + | FMOVD F1, syscallArgs_f2(R2) // save f1 | |
| 123 | + | FMOVD F2, syscallArgs_f3(R2) // save f2 | |
| 124 | + | FMOVD F3, syscallArgs_f4(R2) // save f3 | |
| 91 | 125 | ||
| 92 | 126 | #ifdef GOOS_darwin | |
| 93 | 127 | BL purego_error(SB) | |
| 94 | 128 | MOVD (R0), R0 | |
| 95 | - | MOVD R0, syscall15Args_a3(R2) // save errno | |
| 96 | - | ||
| 129 | + | MOVD R0, syscallArgs_a3(R2) // save errno | |
| 130 | + | #else | |
| 131 | + | MOVD $0, syscallArgs_a3(R2) // no errno here: clear the input argument | |
| 97 | 132 | #endif | |
| 98 | 133 | RET | |
M sys_loong64.s +72 -37
| @@ -7,11 +7,11 @@ | |||
| 7 | 7 | #include "go_asm.h" | |
| 8 | 8 | #include "funcdata.h" | |
| 9 | 9 | ||
| 10 | - | #define STACK_SIZE 64 | |
| 10 | + | #define STACK_SIZE 208 | |
| 11 | 11 | #define PTR_ADDRESS (STACK_SIZE - 8) | |
| 12 | 12 | ||
| 13 | - | // syscall15X calls a function in libc on behalf of the syscall package. | |
| 14 | - | // syscall15X takes a pointer to a struct like: | |
| 13 | + | // syscallX calls a function in libc on behalf of the syscall package. | |
| 14 | + | // syscallX takes a pointer to a struct like: | |
| 15 | 15 | // struct { | |
| 16 | 16 | // fn uintptr | |
| 17 | 17 | // a1 uintptr | |
| @@ -33,51 +33,85 @@ | |||
| 33 | 33 | // r2 uintptr | |
| 34 | 34 | // err uintptr | |
| 35 | 35 | // } | |
| 36 | - | // syscall15X must be called on the g0 stack with the | |
| 36 | + | // syscallX must be called on the g0 stack with the | |
| 37 | 37 | // C calling convention (use libcCall). | |
| 38 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 39 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 40 | - | TEXT syscall15X(SB), NOSPLIT, $0 | |
| 38 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 | |
| 39 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) | |
| 40 | + | TEXT syscallX(SB), NOSPLIT, $0 | |
| 41 | 41 | // push structure pointer | |
| 42 | 42 | SUBV $STACK_SIZE, R3 | |
| 43 | 43 | MOVV R4, PTR_ADDRESS(R3) | |
| 44 | 44 | MOVV R4, R13 | |
| 45 | 45 | ||
| 46 | - | MOVD syscall15Args_f1(R13), F0 // f1 | |
| 47 | - | MOVD syscall15Args_f2(R13), F1 // f2 | |
| 48 | - | MOVD syscall15Args_f3(R13), F2 // f3 | |
| 49 | - | MOVD syscall15Args_f4(R13), F3 // f4 | |
| 50 | - | MOVD syscall15Args_f5(R13), F4 // f5 | |
| 51 | - | MOVD syscall15Args_f6(R13), F5 // f6 | |
| 52 | - | MOVD syscall15Args_f7(R13), F6 // f7 | |
| 53 | - | MOVD syscall15Args_f8(R13), F7 // f8 | |
| 46 | + | MOVD syscallArgs_f1(R13), F0 // f1 | |
| 47 | + | MOVD syscallArgs_f2(R13), F1 // f2 | |
| 48 | + | MOVD syscallArgs_f3(R13), F2 // f3 | |
| 49 | + | MOVD syscallArgs_f4(R13), F3 // f4 | |
| 50 | + | MOVD syscallArgs_f5(R13), F4 // f5 | |
| 51 | + | MOVD syscallArgs_f6(R13), F5 // f6 | |
| 52 | + | MOVD syscallArgs_f7(R13), F6 // f7 | |
| 53 | + | MOVD syscallArgs_f8(R13), F7 // f8 | |
| 54 | 54 | ||
| 55 | - | MOVV syscall15Args_a1(R13), R4 // a1 | |
| 56 | - | MOVV syscall15Args_a2(R13), R5 // a2 | |
| 57 | - | MOVV syscall15Args_a3(R13), R6 // a3 | |
| 58 | - | MOVV syscall15Args_a4(R13), R7 // a4 | |
| 59 | - | MOVV syscall15Args_a5(R13), R8 // a5 | |
| 60 | - | MOVV syscall15Args_a6(R13), R9 // a6 | |
| 61 | - | MOVV syscall15Args_a7(R13), R10 // a7 | |
| 62 | - | MOVV syscall15Args_a8(R13), R11 // a8 | |
| 55 | + | MOVV syscallArgs_a1(R13), R4 // a1 | |
| 56 | + | MOVV syscallArgs_a2(R13), R5 // a2 | |
| 57 | + | MOVV syscallArgs_a3(R13), R6 // a3 | |
| 58 | + | MOVV syscallArgs_a4(R13), R7 // a4 | |
| 59 | + | MOVV syscallArgs_a5(R13), R8 // a5 | |
| 60 | + | MOVV syscallArgs_a6(R13), R9 // a6 | |
| 61 | + | MOVV syscallArgs_a7(R13), R10 // a7 | |
| 62 | + | MOVV syscallArgs_a8(R13), R11 // a8 | |
| 63 | 63 | ||
| 64 | 64 | // push a9-a15 onto stack | |
| 65 | - | MOVV syscall15Args_a9(R13), R12 | |
| 65 | + | MOVV syscallArgs_a9(R13), R12 | |
| 66 | 66 | MOVV R12, 0(R3) | |
| 67 | - | MOVV syscall15Args_a10(R13), R12 | |
| 67 | + | MOVV syscallArgs_a10(R13), R12 | |
| 68 | 68 | MOVV R12, 8(R3) | |
| 69 | - | MOVV syscall15Args_a11(R13), R12 | |
| 69 | + | MOVV syscallArgs_a11(R13), R12 | |
| 70 | 70 | MOVV R12, 16(R3) | |
| 71 | - | MOVV syscall15Args_a12(R13), R12 | |
| 71 | + | MOVV syscallArgs_a12(R13), R12 | |
| 72 | 72 | MOVV R12, 24(R3) | |
| 73 | - | MOVV syscall15Args_a13(R13), R12 | |
| 73 | + | MOVV syscallArgs_a13(R13), R12 | |
| 74 | 74 | MOVV R12, 32(R3) | |
| 75 | - | MOVV syscall15Args_a14(R13), R12 | |
| 75 | + | MOVV syscallArgs_a14(R13), R12 | |
| 76 | 76 | MOVV R12, 40(R3) | |
| 77 | - | MOVV syscall15Args_a15(R13), R12 | |
| 77 | + | MOVV syscallArgs_a15(R13), R12 | |
| 78 | 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), R12 | |
| 114 | + | MOVV syscallArgs_fn(R13), R12 | |
| 81 | 115 | JAL (R12) | |
| 82 | 116 | ||
| 83 | 117 | // pop structure pointer | |
| @@ -85,12 +119,13 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 85 | 119 | ADDV $STACK_SIZE, R3 | |
| 86 | 120 | ||
| 87 | 121 | // save R4, R5 | |
| 88 | - | MOVV R4, syscall15Args_a1(R13) | |
| 89 | - | MOVV R5, syscall15Args_a2(R13) | |
| 122 | + | MOVV R4, syscallArgs_a1(R13) | |
| 123 | + | MOVV R5, syscallArgs_a2(R13) | |
| 124 | + | MOVV R0, syscallArgs_a3(R13) // no errno here: clear the input argument | |
| 90 | 125 | ||
| 91 | 126 | // save f0-f3 | |
| 92 | - | MOVD F0, syscall15Args_f1(R13) | |
| 93 | - | MOVD F1, syscall15Args_f2(R13) | |
| 94 | - | MOVD F2, syscall15Args_f3(R13) | |
| 95 | - | MOVD F3, syscall15Args_f4(R13) | |
| 127 | + | MOVD F0, syscallArgs_f1(R13) | |
| 128 | + | MOVD F1, syscallArgs_f2(R13) | |
| 129 | + | MOVD F2, syscallArgs_f3(R13) | |
| 130 | + | MOVD F3, syscallArgs_f4(R13) | |
| 96 | 131 | RET | |
M sys_ppc64le.s +42 -36
| @@ -35,10 +35,10 @@ | |||
| 35 | 35 | #define TOC_SAVE 160 | |
| 36 | 36 | #define ARGP_SAVE 168 | |
| 37 | 37 | ||
| 38 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 39 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 38 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 | |
| 39 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) | |
| 40 | 40 | ||
| 41 | - | TEXT syscall15X(SB), NOSPLIT, $0 | |
| 41 | + | TEXT syscallX(SB), NOSPLIT, $0 | |
| 42 | 42 | // Prologue: create stack frame | |
| 43 | 43 | // R3 contains the args pointer on entry | |
| 44 | 44 | MOVD R1, R12 // save old SP | |
| @@ -51,49 +51,49 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 51 | 51 | // Save args pointer (in R3) | |
| 52 | 52 | MOVD R3, ARGP_SAVE(R1) | |
| 53 | 53 | ||
| 54 | - | // R11 := args pointer (syscall15Args*) | |
| 54 | + | // R11 := args pointer (syscallArgs*) | |
| 55 | 55 | MOVD R3, R11 | |
| 56 | 56 | ||
| 57 | 57 | // Load float args into F1-F8 | |
| 58 | - | FMOVD syscall15Args_f1(R11), F1 | |
| 59 | - | FMOVD syscall15Args_f2(R11), F2 | |
| 60 | - | FMOVD syscall15Args_f3(R11), F3 | |
| 61 | - | FMOVD syscall15Args_f4(R11), F4 | |
| 62 | - | FMOVD syscall15Args_f5(R11), F5 | |
| 63 | - | FMOVD syscall15Args_f6(R11), F6 | |
| 64 | - | FMOVD syscall15Args_f7(R11), F7 | |
| 65 | - | FMOVD syscall15Args_f8(R11), F8 | |
| 58 | + | FMOVD syscallArgs_f1(R11), F1 | |
| 59 | + | FMOVD syscallArgs_f2(R11), F2 | |
| 60 | + | FMOVD syscallArgs_f3(R11), F3 | |
| 61 | + | FMOVD syscallArgs_f4(R11), F4 | |
| 62 | + | FMOVD syscallArgs_f5(R11), F5 | |
| 63 | + | FMOVD syscallArgs_f6(R11), F6 | |
| 64 | + | FMOVD syscallArgs_f7(R11), F7 | |
| 65 | + | FMOVD syscallArgs_f8(R11), F8 | |
| 66 | 66 | ||
| 67 | 67 | // Load integer args into R3-R10 | |
| 68 | - | MOVD syscall15Args_a1(R11), R3 | |
| 69 | - | MOVD syscall15Args_a2(R11), R4 | |
| 70 | - | MOVD syscall15Args_a3(R11), R5 | |
| 71 | - | MOVD syscall15Args_a4(R11), R6 | |
| 72 | - | MOVD syscall15Args_a5(R11), R7 | |
| 73 | - | MOVD syscall15Args_a6(R11), R8 | |
| 74 | - | MOVD syscall15Args_a7(R11), R9 | |
| 75 | - | MOVD syscall15Args_a8(R11), R10 | |
| 68 | + | MOVD syscallArgs_a1(R11), R3 | |
| 69 | + | MOVD syscallArgs_a2(R11), R4 | |
| 70 | + | MOVD syscallArgs_a3(R11), R5 | |
| 71 | + | MOVD syscallArgs_a4(R11), R6 | |
| 72 | + | MOVD syscallArgs_a5(R11), R7 | |
| 73 | + | MOVD syscallArgs_a6(R11), R8 | |
| 74 | + | MOVD syscallArgs_a7(R11), R9 | |
| 75 | + | MOVD syscallArgs_a8(R11), R10 | |
| 76 | 76 | ||
| 77 | 77 | // Spill a9-a15 onto the stack (stack parameters start at 96(R1)) | |
| 78 | 78 | // Per ELFv2: parameter save area is 32-95, stack args start at 96 | |
| 79 | 79 | MOVD ARGP_SAVE(R1), R11 // reload args pointer | |
| 80 | - | MOVD syscall15Args_a9(R11), R12 | |
| 80 | + | MOVD syscallArgs_a9(R11), R12 | |
| 81 | 81 | MOVD R12, 96(R1) // a9 at 96(R1) | |
| 82 | - | MOVD syscall15Args_a10(R11), R12 | |
| 82 | + | MOVD syscallArgs_a10(R11), R12 | |
| 83 | 83 | MOVD R12, 104(R1) // a10 at 104(R1) | |
| 84 | - | MOVD syscall15Args_a11(R11), R12 | |
| 84 | + | MOVD syscallArgs_a11(R11), R12 | |
| 85 | 85 | MOVD R12, 112(R1) // a11 at 112(R1) | |
| 86 | - | MOVD syscall15Args_a12(R11), R12 | |
| 86 | + | MOVD syscallArgs_a12(R11), R12 | |
| 87 | 87 | MOVD R12, 120(R1) // a12 at 120(R1) | |
| 88 | - | MOVD syscall15Args_a13(R11), R12 | |
| 88 | + | MOVD syscallArgs_a13(R11), R12 | |
| 89 | 89 | MOVD R12, 128(R1) // a13 at 128(R1) | |
| 90 | - | MOVD syscall15Args_a14(R11), R12 | |
| 90 | + | MOVD syscallArgs_a14(R11), R12 | |
| 91 | 91 | MOVD R12, 136(R1) // a14 at 136(R1) | |
| 92 | - | MOVD syscall15Args_a15(R11), R12 | |
| 92 | + | MOVD syscallArgs_a15(R11), R12 | |
| 93 | 93 | MOVD R12, 144(R1) // a15 at 144(R1) | |
| 94 | 94 | ||
| 95 | 95 | // Call function: load fn and call | |
| 96 | - | MOVD syscall15Args_fn(R11), R12 | |
| 96 | + | MOVD syscallArgs_fn(R11), R12 | |
| 97 | 97 | MOVD R12, CTR | |
| 98 | 98 | BL (CTR) | |
| 99 | 99 | ||
| @@ -104,14 +104,20 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 104 | 104 | MOVD ARGP_SAVE(R1), R11 | |
| 105 | 105 | ||
| 106 | 106 | // Store integer results back (R3, R4) | |
| 107 | - | MOVD R3, syscall15Args_a1(R11) | |
| 108 | - | MOVD R4, syscall15Args_a2(R11) | |
| 109 | - | ||
| 110 | - | // Store float return values (F1-F4) | |
| 111 | - | FMOVD F1, syscall15Args_f1(R11) | |
| 112 | - | FMOVD F2, syscall15Args_f2(R11) | |
| 113 | - | FMOVD F3, syscall15Args_f3(R11) | |
| 114 | - | FMOVD F4, syscall15Args_f4(R11) | |
| 107 | + | MOVD R3, syscallArgs_a1(R11) | |
| 108 | + | MOVD R4, syscallArgs_a2(R11) | |
| 109 | + | MOVD $0, R3 | |
| 110 | + | MOVD R3, syscallArgs_a3(R11) // no errno here: clear the input argument | |
| 111 | + | ||
| 112 | + | // Store float return values F1-F8 (an HFA may span up to eight). | |
| 113 | + | FMOVD F1, syscallArgs_f1(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 | 122 | // Epilogue: restore and return | |
| 117 | 123 | MOVD LR_SAVE(R1), R12 | |
M sys_riscv64.s +84 -50
| @@ -8,22 +8,21 @@ | |||
| 8 | 8 | #include "funcdata.h" | |
| 9 | 9 | ||
| 10 | 10 | // Stack usage: | |
| 11 | - | // 0(SP) - 56(SP): stack args a9-a15 (7 * 8 bytes = 56) | |
| 12 | - | // 56(SP) - 64(SP): saved RA (x1) | |
| 13 | - | // 64(SP) - 72(SP): saved X9 (s1) | |
| 14 | - | // 72(SP) - 80(SP): saved X18 (s2) | |
| 15 | - | // 80(SP) - 88(SP): saved args pointer (original X10) | |
| 16 | - | // 88(SP) - 96(SP): padding | |
| 17 | - | #define STACK_SIZE 96 | |
| 18 | - | #define SAVE_RA 56 | |
| 19 | - | #define SAVE_X9 64 | |
| 20 | - | #define SAVE_X18 72 | |
| 21 | - | #define SAVE_ARGP 80 | |
| 22 | - | ||
| 23 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 24 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 25 | - | ||
| 26 | - | TEXT syscall15X(SB), NOSPLIT, $0 | |
| 11 | + | // 0(SP) - 192(SP): stack args a9-a32 (24 * 8 bytes) | |
| 12 | + | // 192(SP) - 200(SP): saved RA (x1) | |
| 13 | + | // 200(SP) - 208(SP): saved X9 (s1) | |
| 14 | + | // 208(SP) - 216(SP): saved X18 (s2) | |
| 15 | + | // 216(SP) - 224(SP): saved args pointer (original X10) | |
| 16 | + | #define STACK_SIZE 224 | |
| 17 | + | #define SAVE_RA 192 | |
| 18 | + | #define SAVE_X9 200 | |
| 19 | + | #define SAVE_X18 208 | |
| 20 | + | #define SAVE_ARGP 216 | |
| 21 | + | ||
| 22 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 | |
| 23 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) | |
| 24 | + | ||
| 25 | + | TEXT syscallX(SB), NOSPLIT, $0 | |
| 27 | 26 | // Allocate stack frame (keeps 16-byte alignment) | |
| 28 | 27 | SUB $STACK_SIZE, SP | |
| 29 | 28 | ||
| @@ -35,62 +34,97 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 35 | 34 | // Save original args pointer (in a0/X10) | |
| 36 | 35 | MOV X10, SAVE_ARGP(SP) | |
| 37 | 36 | ||
| 38 | - | // X9 := args pointer (syscall15Args*) | |
| 37 | + | // X9 := args pointer (syscallArgs*) | |
| 39 | 38 | MOV X10, X9 | |
| 40 | 39 | ||
| 41 | 40 | // Load float args into fa0-fa7 (F10-F17) | |
| 42 | - | MOVD syscall15Args_f1(X9), F10 | |
| 43 | - | MOVD syscall15Args_f2(X9), F11 | |
| 44 | - | MOVD syscall15Args_f3(X9), F12 | |
| 45 | - | MOVD syscall15Args_f4(X9), F13 | |
| 46 | - | MOVD syscall15Args_f5(X9), F14 | |
| 47 | - | MOVD syscall15Args_f6(X9), F15 | |
| 48 | - | MOVD syscall15Args_f7(X9), F16 | |
| 49 | - | MOVD syscall15Args_f8(X9), F17 | |
| 41 | + | MOVD syscallArgs_f1(X9), F10 | |
| 42 | + | MOVD syscallArgs_f2(X9), F11 | |
| 43 | + | MOVD syscallArgs_f3(X9), F12 | |
| 44 | + | MOVD syscallArgs_f4(X9), F13 | |
| 45 | + | MOVD syscallArgs_f5(X9), F14 | |
| 46 | + | MOVD syscallArgs_f6(X9), F15 | |
| 47 | + | MOVD syscallArgs_f7(X9), F16 | |
| 48 | + | MOVD syscallArgs_f8(X9), F17 | |
| 50 | 49 | ||
| 51 | 50 | // Load integer args into a0-a7 (X10-X17) | |
| 52 | - | MOV syscall15Args_a1(X9), X10 | |
| 53 | - | MOV syscall15Args_a2(X9), X11 | |
| 54 | - | MOV syscall15Args_a3(X9), X12 | |
| 55 | - | MOV syscall15Args_a4(X9), X13 | |
| 56 | - | MOV syscall15Args_a5(X9), X14 | |
| 57 | - | MOV syscall15Args_a6(X9), X15 | |
| 58 | - | MOV syscall15Args_a7(X9), X16 | |
| 59 | - | MOV syscall15Args_a8(X9), X17 | |
| 60 | - | ||
| 61 | - | // Spill a9-a15 onto the stack (C ABI) | |
| 62 | - | MOV syscall15Args_a9(X9), X18 | |
| 51 | + | MOV syscallArgs_a1(X9), X10 | |
| 52 | + | MOV syscallArgs_a2(X9), X11 | |
| 53 | + | MOV syscallArgs_a3(X9), X12 | |
| 54 | + | MOV syscallArgs_a4(X9), X13 | |
| 55 | + | MOV syscallArgs_a5(X9), X14 | |
| 56 | + | MOV syscallArgs_a6(X9), X15 | |
| 57 | + | MOV syscallArgs_a7(X9), X16 | |
| 58 | + | MOV syscallArgs_a8(X9), X17 | |
| 59 | + | ||
| 60 | + | // Spill a9-a32 onto the stack (C ABI) | |
| 61 | + | MOV syscallArgs_a9(X9), X18 | |
| 63 | 62 | MOV X18, 0(SP) | |
| 64 | - | MOV syscall15Args_a10(X9), X18 | |
| 63 | + | MOV syscallArgs_a10(X9), X18 | |
| 65 | 64 | MOV X18, 8(SP) | |
| 66 | - | MOV syscall15Args_a11(X9), X18 | |
| 65 | + | MOV syscallArgs_a11(X9), X18 | |
| 67 | 66 | MOV X18, 16(SP) | |
| 68 | - | MOV syscall15Args_a12(X9), X18 | |
| 67 | + | MOV syscallArgs_a12(X9), X18 | |
| 69 | 68 | MOV X18, 24(SP) | |
| 70 | - | MOV syscall15Args_a13(X9), X18 | |
| 69 | + | MOV syscallArgs_a13(X9), X18 | |
| 71 | 70 | MOV X18, 32(SP) | |
| 72 | - | MOV syscall15Args_a14(X9), X18 | |
| 71 | + | MOV syscallArgs_a14(X9), X18 | |
| 73 | 72 | MOV X18, 40(SP) | |
| 74 | - | MOV syscall15Args_a15(X9), X18 | |
| 73 | + | MOV syscallArgs_a15(X9), X18 | |
| 75 | 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 | 110 | // Call fn | |
| 78 | 111 | // IMPORTANT: preserve RA across this call (we saved it above) | |
| 79 | - | MOV syscall15Args_fn(X9), X18 | |
| 112 | + | MOV syscallArgs_fn(X9), X18 | |
| 80 | 113 | CALL X18 | |
| 81 | 114 | ||
| 82 | - | // Restore args pointer (syscall15Args*) for storing results | |
| 115 | + | // Restore args pointer (syscallArgs*) for storing results | |
| 83 | 116 | MOV SAVE_ARGP(SP), X9 | |
| 84 | 117 | ||
| 85 | 118 | // Store results back | |
| 86 | - | MOV X10, syscall15Args_a1(X9) | |
| 87 | - | MOV X11, syscall15Args_a2(X9) | |
| 119 | + | MOV X10, syscallArgs_a1(X9) | |
| 120 | + | MOV X11, syscallArgs_a2(X9) | |
| 121 | + | MOV X0, syscallArgs_a3(X9) // no errno here: clear the input argument | |
| 88 | 122 | ||
| 89 | 123 | // Store back float return regs if used by your ABI contract | |
| 90 | - | MOVD F10, syscall15Args_f1(X9) | |
| 91 | - | MOVD F11, syscall15Args_f2(X9) | |
| 92 | - | MOVD F12, syscall15Args_f3(X9) | |
| 93 | - | MOVD F13, syscall15Args_f4(X9) | |
| 124 | + | MOVD F10, syscallArgs_f1(X9) | |
| 125 | + | MOVD F11, syscallArgs_f2(X9) | |
| 126 | + | MOVD F12, syscallArgs_f3(X9) | |
| 127 | + | MOVD F13, syscallArgs_f4(X9) | |
| 94 | 128 | ||
| 95 | 129 | // Restore callee-saved regs and return address | |
| 96 | 130 | MOV SAVE_X18(SP), X18 | |
M sys_s390x.s +70 -36
| @@ -24,19 +24,18 @@ | |||
| 24 | 24 | // | |
| 25 | 25 | // We need space for: | |
| 26 | 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 | 28 | // - Saved args pointer (8 bytes) | |
| 29 | - | // - Padding for alignment | |
| 30 | - | // Total: 264 bytes (rounded to 8-byte alignment) | |
| 29 | + | // Total: 384 bytes | |
| 31 | 30 | ||
| 32 | - | #define STACK_SIZE 264 | |
| 31 | + | #define STACK_SIZE 384 | |
| 33 | 32 | #define STACK_ARGS 160 | |
| 34 | - | #define ARGP_SAVE 248 | |
| 33 | + | #define ARGP_SAVE 376 | |
| 35 | 34 | ||
| 36 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 37 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 35 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 | |
| 36 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) | |
| 38 | 37 | ||
| 39 | - | TEXT syscall15X(SB), NOSPLIT, $0 | |
| 38 | + | TEXT syscallX(SB), NOSPLIT, $0 | |
| 40 | 39 | // On entry, R2 contains the args pointer | |
| 41 | 40 | // Save callee-saved registers in caller's frame (per ABI) | |
| 42 | 41 | STMG R6, R15, 48(R15) | |
| @@ -49,61 +48,96 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 49 | 48 | // Save args pointer | |
| 50 | 49 | MOVD R2, ARGP_SAVE(R15) | |
| 51 | 50 | ||
| 52 | - | // R9 := args pointer (syscall15Args*) | |
| 51 | + | // R9 := args pointer (syscallArgs*) | |
| 53 | 52 | MOVD R2, R9 | |
| 54 | 53 | ||
| 55 | 54 | // Load float args into F0, F2, F4, F6 (s390x uses even-numbered FPRs) | |
| 56 | - | FMOVD syscall15Args_f1(R9), F0 | |
| 57 | - | FMOVD syscall15Args_f2(R9), F2 | |
| 58 | - | FMOVD syscall15Args_f3(R9), F4 | |
| 59 | - | FMOVD syscall15Args_f4(R9), F6 | |
| 55 | + | FMOVD syscallArgs_f1(R9), F0 | |
| 56 | + | FMOVD syscallArgs_f2(R9), F2 | |
| 57 | + | FMOVD syscallArgs_f3(R9), F4 | |
| 58 | + | FMOVD syscallArgs_f4(R9), F6 | |
| 60 | 59 | ||
| 61 | 60 | // Load integer args into R2-R6 (5 registers) | |
| 62 | - | MOVD syscall15Args_a1(R9), R2 | |
| 63 | - | MOVD syscall15Args_a2(R9), R3 | |
| 64 | - | MOVD syscall15Args_a3(R9), R4 | |
| 65 | - | MOVD syscall15Args_a4(R9), R5 | |
| 66 | - | MOVD syscall15Args_a5(R9), R6 | |
| 61 | + | MOVD syscallArgs_a1(R9), R2 | |
| 62 | + | MOVD syscallArgs_a2(R9), R3 | |
| 63 | + | MOVD syscallArgs_a3(R9), R4 | |
| 64 | + | MOVD syscallArgs_a4(R9), R5 | |
| 65 | + | 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 | 68 | MOVD ARGP_SAVE(R15), R9 // reload args pointer | |
| 70 | - | MOVD syscall15Args_a6(R9), R1 | |
| 69 | + | MOVD syscallArgs_a6(R9), R1 | |
| 71 | 70 | MOVD R1, (STACK_ARGS+0*8)(R15) | |
| 72 | - | MOVD syscall15Args_a7(R9), R1 | |
| 71 | + | MOVD syscallArgs_a7(R9), R1 | |
| 73 | 72 | MOVD R1, (STACK_ARGS+1*8)(R15) | |
| 74 | - | MOVD syscall15Args_a8(R9), R1 | |
| 73 | + | MOVD syscallArgs_a8(R9), R1 | |
| 75 | 74 | MOVD R1, (STACK_ARGS+2*8)(R15) | |
| 76 | - | MOVD syscall15Args_a9(R9), R1 | |
| 75 | + | MOVD syscallArgs_a9(R9), R1 | |
| 77 | 76 | MOVD R1, (STACK_ARGS+3*8)(R15) | |
| 78 | - | MOVD syscall15Args_a10(R9), R1 | |
| 77 | + | MOVD syscallArgs_a10(R9), R1 | |
| 79 | 78 | MOVD R1, (STACK_ARGS+4*8)(R15) | |
| 80 | - | MOVD syscall15Args_a11(R9), R1 | |
| 79 | + | MOVD syscallArgs_a11(R9), R1 | |
| 81 | 80 | MOVD R1, (STACK_ARGS+5*8)(R15) | |
| 82 | - | MOVD syscall15Args_a12(R9), R1 | |
| 81 | + | MOVD syscallArgs_a12(R9), R1 | |
| 83 | 82 | MOVD R1, (STACK_ARGS+6*8)(R15) | |
| 84 | - | MOVD syscall15Args_a13(R9), R1 | |
| 83 | + | MOVD syscallArgs_a13(R9), R1 | |
| 85 | 84 | MOVD R1, (STACK_ARGS+7*8)(R15) | |
| 86 | - | MOVD syscall15Args_a14(R9), R1 | |
| 85 | + | MOVD syscallArgs_a14(R9), R1 | |
| 87 | 86 | MOVD R1, (STACK_ARGS+8*8)(R15) | |
| 88 | - | MOVD syscall15Args_a15(R9), R1 | |
| 87 | + | MOVD syscallArgs_a15(R9), R1 | |
| 89 | 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 | 124 | // Call function | |
| 92 | - | MOVD syscall15Args_fn(R9), R1 | |
| 125 | + | MOVD syscallArgs_fn(R9), R1 | |
| 93 | 126 | BL (R1) | |
| 94 | 127 | ||
| 95 | 128 | // Restore args pointer for storing results | |
| 96 | 129 | MOVD ARGP_SAVE(R15), R9 | |
| 97 | 130 | ||
| 98 | 131 | // Store integer results back (R2, R3) | |
| 99 | - | MOVD R2, syscall15Args_a1(R9) | |
| 100 | - | MOVD R3, syscall15Args_a2(R9) | |
| 132 | + | MOVD R2, syscallArgs_a1(R9) | |
| 133 | + | MOVD R3, syscallArgs_a2(R9) | |
| 134 | + | MOVD $0, syscallArgs_a3(R9) // no errno here: clear the input argument | |
| 101 | 135 | ||
| 102 | 136 | // Store float return values (F0, F2, F4, F6) | |
| 103 | - | FMOVD F0, syscall15Args_f1(R9) | |
| 104 | - | FMOVD F2, syscall15Args_f2(R9) | |
| 105 | - | FMOVD F4, syscall15Args_f3(R9) | |
| 106 | - | FMOVD F6, syscall15Args_f4(R9) | |
| 137 | + | FMOVD F0, syscallArgs_f1(R9) | |
| 138 | + | FMOVD F2, syscallArgs_f2(R9) | |
| 139 | + | FMOVD F4, syscallArgs_f3(R9) | |
| 140 | + | FMOVD F6, syscallArgs_f4(R9) | |
| 107 | 141 | ||
| 108 | 142 | // Deallocate stack frame | |
| 109 | 143 | ADD $STACK_SIZE, R15 | |
M sys_unix_386.s +82 -74
| @@ -29,9 +29,10 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 29 | 29 | // 0-15: saved callee-saved registers (BX, SI, DI, BP) | |
| 30 | 30 | // 16-19: saved callback index | |
| 31 | 31 | // 20-23: saved return address | |
| 32 | - | // 24-35: callbackArgs struct (12 bytes) | |
| 33 | - | // 36-291: copy of C arguments (256 bytes for 64 args, matching callbackMaxFrame) | |
| 34 | - | // Total: 292 bytes, round up to 304 for alignment | |
| 32 | + | // 24-47: callbackArgs struct (24 bytes: index, args, result[4], | |
| 33 | + | // matching Go's callbackArgs with room for 64-bit returns) | |
| 34 | + | // 48-303: copy of C arguments (256 bytes for 64 args, matching callbackMaxFrame) | |
| 35 | + | // Total: 304 bytes, already 16-byte aligned | |
| 35 | 36 | SUBL $304, SP | |
| 36 | 37 | ||
| 37 | 38 | // Save callee-saved registers | |
| @@ -46,148 +47,152 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 46 | 47 | ||
| 47 | 48 | // Copy C arguments from original stack location to our frame | |
| 48 | 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 | 51 | // Copy 64 arguments (256 bytes, matching callbackMaxFrame = 64 * ptrSize) | |
| 51 | 52 | MOVL 308(SP), AX | |
| 52 | - | MOVL AX, 36(SP) | |
| 53 | + | MOVL AX, 48(SP) | |
| 53 | 54 | MOVL 312(SP), AX | |
| 54 | - | MOVL AX, 40(SP) | |
| 55 | + | MOVL AX, 52(SP) | |
| 55 | 56 | MOVL 316(SP), AX | |
| 56 | - | MOVL AX, 44(SP) | |
| 57 | + | MOVL AX, 56(SP) | |
| 57 | 58 | MOVL 320(SP), AX | |
| 58 | - | MOVL AX, 48(SP) | |
| 59 | + | MOVL AX, 60(SP) | |
| 59 | 60 | MOVL 324(SP), AX | |
| 60 | - | MOVL AX, 52(SP) | |
| 61 | + | MOVL AX, 64(SP) | |
| 61 | 62 | MOVL 328(SP), AX | |
| 62 | - | MOVL AX, 56(SP) | |
| 63 | + | MOVL AX, 68(SP) | |
| 63 | 64 | MOVL 332(SP), AX | |
| 64 | - | MOVL AX, 60(SP) | |
| 65 | + | MOVL AX, 72(SP) | |
| 65 | 66 | MOVL 336(SP), AX | |
| 66 | - | MOVL AX, 64(SP) | |
| 67 | + | MOVL AX, 76(SP) | |
| 67 | 68 | MOVL 340(SP), AX | |
| 68 | - | MOVL AX, 68(SP) | |
| 69 | + | MOVL AX, 80(SP) | |
| 69 | 70 | MOVL 344(SP), AX | |
| 70 | - | MOVL AX, 72(SP) | |
| 71 | + | MOVL AX, 84(SP) | |
| 71 | 72 | MOVL 348(SP), AX | |
| 72 | - | MOVL AX, 76(SP) | |
| 73 | + | MOVL AX, 88(SP) | |
| 73 | 74 | MOVL 352(SP), AX | |
| 74 | - | MOVL AX, 80(SP) | |
| 75 | + | MOVL AX, 92(SP) | |
| 75 | 76 | MOVL 356(SP), AX | |
| 76 | - | MOVL AX, 84(SP) | |
| 77 | + | MOVL AX, 96(SP) | |
| 77 | 78 | MOVL 360(SP), AX | |
| 78 | - | MOVL AX, 88(SP) | |
| 79 | + | MOVL AX, 100(SP) | |
| 79 | 80 | MOVL 364(SP), AX | |
| 80 | - | MOVL AX, 92(SP) | |
| 81 | + | MOVL AX, 104(SP) | |
| 81 | 82 | MOVL 368(SP), AX | |
| 82 | - | MOVL AX, 96(SP) | |
| 83 | + | MOVL AX, 108(SP) | |
| 83 | 84 | MOVL 372(SP), AX | |
| 84 | - | MOVL AX, 100(SP) | |
| 85 | + | MOVL AX, 112(SP) | |
| 85 | 86 | MOVL 376(SP), AX | |
| 86 | - | MOVL AX, 104(SP) | |
| 87 | + | MOVL AX, 116(SP) | |
| 87 | 88 | MOVL 380(SP), AX | |
| 88 | - | MOVL AX, 108(SP) | |
| 89 | + | MOVL AX, 120(SP) | |
| 89 | 90 | MOVL 384(SP), AX | |
| 90 | - | MOVL AX, 112(SP) | |
| 91 | + | MOVL AX, 124(SP) | |
| 91 | 92 | MOVL 388(SP), AX | |
| 92 | - | MOVL AX, 116(SP) | |
| 93 | + | MOVL AX, 128(SP) | |
| 93 | 94 | MOVL 392(SP), AX | |
| 94 | - | MOVL AX, 120(SP) | |
| 95 | + | MOVL AX, 132(SP) | |
| 95 | 96 | MOVL 396(SP), AX | |
| 96 | - | MOVL AX, 124(SP) | |
| 97 | + | MOVL AX, 136(SP) | |
| 97 | 98 | MOVL 400(SP), AX | |
| 98 | - | MOVL AX, 128(SP) | |
| 99 | + | MOVL AX, 140(SP) | |
| 99 | 100 | MOVL 404(SP), AX | |
| 100 | - | MOVL AX, 132(SP) | |
| 101 | + | MOVL AX, 144(SP) | |
| 101 | 102 | MOVL 408(SP), AX | |
| 102 | - | MOVL AX, 136(SP) | |
| 103 | + | MOVL AX, 148(SP) | |
| 103 | 104 | MOVL 412(SP), AX | |
| 104 | - | MOVL AX, 140(SP) | |
| 105 | + | MOVL AX, 152(SP) | |
| 105 | 106 | MOVL 416(SP), AX | |
| 106 | - | MOVL AX, 144(SP) | |
| 107 | + | MOVL AX, 156(SP) | |
| 107 | 108 | MOVL 420(SP), AX | |
| 108 | - | MOVL AX, 148(SP) | |
| 109 | + | MOVL AX, 160(SP) | |
| 109 | 110 | MOVL 424(SP), AX | |
| 110 | - | MOVL AX, 152(SP) | |
| 111 | + | MOVL AX, 164(SP) | |
| 111 | 112 | MOVL 428(SP), AX | |
| 112 | - | MOVL AX, 156(SP) | |
| 113 | + | MOVL AX, 168(SP) | |
| 113 | 114 | MOVL 432(SP), AX | |
| 114 | - | MOVL AX, 160(SP) | |
| 115 | + | MOVL AX, 172(SP) | |
| 115 | 116 | MOVL 436(SP), AX | |
| 116 | - | MOVL AX, 164(SP) | |
| 117 | + | MOVL AX, 176(SP) | |
| 117 | 118 | MOVL 440(SP), AX | |
| 118 | - | MOVL AX, 168(SP) | |
| 119 | + | MOVL AX, 180(SP) | |
| 119 | 120 | MOVL 444(SP), AX | |
| 120 | - | MOVL AX, 172(SP) | |
| 121 | + | MOVL AX, 184(SP) | |
| 121 | 122 | MOVL 448(SP), AX | |
| 122 | - | MOVL AX, 176(SP) | |
| 123 | + | MOVL AX, 188(SP) | |
| 123 | 124 | MOVL 452(SP), AX | |
| 124 | - | MOVL AX, 180(SP) | |
| 125 | + | MOVL AX, 192(SP) | |
| 125 | 126 | MOVL 456(SP), AX | |
| 126 | - | MOVL AX, 184(SP) | |
| 127 | + | MOVL AX, 196(SP) | |
| 127 | 128 | MOVL 460(SP), AX | |
| 128 | - | MOVL AX, 188(SP) | |
| 129 | + | MOVL AX, 200(SP) | |
| 129 | 130 | MOVL 464(SP), AX | |
| 130 | - | MOVL AX, 192(SP) | |
| 131 | + | MOVL AX, 204(SP) | |
| 131 | 132 | MOVL 468(SP), AX | |
| 132 | - | MOVL AX, 196(SP) | |
| 133 | + | MOVL AX, 208(SP) | |
| 133 | 134 | MOVL 472(SP), AX | |
| 134 | - | MOVL AX, 200(SP) | |
| 135 | + | MOVL AX, 212(SP) | |
| 135 | 136 | MOVL 476(SP), AX | |
| 136 | - | MOVL AX, 204(SP) | |
| 137 | + | MOVL AX, 216(SP) | |
| 137 | 138 | MOVL 480(SP), AX | |
| 138 | - | MOVL AX, 208(SP) | |
| 139 | + | MOVL AX, 220(SP) | |
| 139 | 140 | MOVL 484(SP), AX | |
| 140 | - | MOVL AX, 212(SP) | |
| 141 | + | MOVL AX, 224(SP) | |
| 141 | 142 | MOVL 488(SP), AX | |
| 142 | - | MOVL AX, 216(SP) | |
| 143 | + | MOVL AX, 228(SP) | |
| 143 | 144 | MOVL 492(SP), AX | |
| 144 | - | MOVL AX, 220(SP) | |
| 145 | + | MOVL AX, 232(SP) | |
| 145 | 146 | MOVL 496(SP), AX | |
| 146 | - | MOVL AX, 224(SP) | |
| 147 | + | MOVL AX, 236(SP) | |
| 147 | 148 | MOVL 500(SP), AX | |
| 148 | - | MOVL AX, 228(SP) | |
| 149 | + | MOVL AX, 240(SP) | |
| 149 | 150 | MOVL 504(SP), AX | |
| 150 | - | MOVL AX, 232(SP) | |
| 151 | + | MOVL AX, 244(SP) | |
| 151 | 152 | MOVL 508(SP), AX | |
| 152 | - | MOVL AX, 236(SP) | |
| 153 | + | MOVL AX, 248(SP) | |
| 153 | 154 | MOVL 512(SP), AX | |
| 154 | - | MOVL AX, 240(SP) | |
| 155 | + | MOVL AX, 252(SP) | |
| 155 | 156 | MOVL 516(SP), AX | |
| 156 | - | MOVL AX, 244(SP) | |
| 157 | + | MOVL AX, 256(SP) | |
| 157 | 158 | MOVL 520(SP), AX | |
| 158 | - | MOVL AX, 248(SP) | |
| 159 | + | MOVL AX, 260(SP) | |
| 159 | 160 | MOVL 524(SP), AX | |
| 160 | - | MOVL AX, 252(SP) | |
| 161 | + | MOVL AX, 264(SP) | |
| 161 | 162 | MOVL 528(SP), AX | |
| 162 | - | MOVL AX, 256(SP) | |
| 163 | + | MOVL AX, 268(SP) | |
| 163 | 164 | MOVL 532(SP), AX | |
| 164 | - | MOVL AX, 260(SP) | |
| 165 | + | MOVL AX, 272(SP) | |
| 165 | 166 | MOVL 536(SP), AX | |
| 166 | - | MOVL AX, 264(SP) | |
| 167 | + | MOVL AX, 276(SP) | |
| 167 | 168 | MOVL 540(SP), AX | |
| 168 | - | MOVL AX, 268(SP) | |
| 169 | + | MOVL AX, 280(SP) | |
| 169 | 170 | MOVL 544(SP), AX | |
| 170 | - | MOVL AX, 272(SP) | |
| 171 | + | MOVL AX, 284(SP) | |
| 171 | 172 | MOVL 548(SP), AX | |
| 172 | - | MOVL AX, 276(SP) | |
| 173 | + | MOVL AX, 288(SP) | |
| 173 | 174 | MOVL 552(SP), AX | |
| 174 | - | MOVL AX, 280(SP) | |
| 175 | + | MOVL AX, 292(SP) | |
| 175 | 176 | MOVL 556(SP), AX | |
| 176 | - | MOVL AX, 284(SP) | |
| 177 | + | MOVL AX, 296(SP) | |
| 177 | 178 | MOVL 560(SP), AX | |
| 178 | - | MOVL AX, 288(SP) | |
| 179 | + | MOVL AX, 300(SP) | |
| 179 | 180 | ||
| 180 | 181 | // Set up callbackArgs struct at 24(SP) | |
| 181 | 182 | // struct callbackArgs { | |
| 182 | - | // index uintptr // offset 0 | |
| 183 | - | // args *byte // offset 4 | |
| 184 | - | // result uintptr // offset 8 | |
| 183 | + | // index uintptr // offset 0 | |
| 184 | + | // args unsafe.Pointer // offset 4 | |
| 185 | + | // result [4]uintptr // offset 8: result[0] and result[1] hold | |
| 186 | + | // a 64-bit return, result[2:] are unused | |
| 185 | 187 | // } | |
| 186 | 188 | MOVL 16(SP), AX // callback index | |
| 187 | 189 | MOVL AX, 24(SP) // callbackArgs.index | |
| 188 | - | LEAL 36(SP), AX // pointer to copied arguments | |
| 190 | + | LEAL 48(SP), AX // pointer to copied arguments | |
| 189 | 191 | MOVL AX, 28(SP) // callbackArgs.args | |
| 190 | - | MOVL $0, 32(SP) // callbackArgs.result = 0 | |
| 192 | + | 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 | 197 | // Call crosscall2(fn, frame, 0, ctxt) | |
| 193 | 198 | // crosscall2 expects arguments on stack: | |
| @@ -209,8 +214,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 209 | 214 | ||
| 210 | 215 | ADDL $16, SP | |
| 211 | 216 | ||
| 212 | - | // Get result from callbackArgs.result | |
| 217 | + | // 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 | 220 | MOVL 32(SP), AX | |
| 221 | + | MOVL 36(SP), DX | |
| 214 | 222 | ||
| 215 | 223 | // Restore callee-saved registers | |
| 216 | 224 | MOVL 0(SP), BX | |
M sys_unix_arm.s +11 -1
| @@ -33,8 +33,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 33 | 33 | MOVW R2, 136(R13) | |
| 34 | 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 | 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 | 41 | MOVD F0, 64(R13) | |
| 39 | 42 | MOVD F1, 72(R13) | |
| 40 | 43 | MOVD F2, 80(R13) | |
| @@ -44,6 +47,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 44 | 47 | MOVD F6, 112(R13) | |
| 45 | 48 | MOVD F7, 120(R13) | |
| 46 | 49 | ||
| 50 | + | skipfpsave: | |
| 47 | 51 | // Set up callbackArgs at SP+48 | |
| 48 | 52 | MOVW 36(R13), R4 | |
| 49 | 53 | MOVW R4, 48(R13) | |
| @@ -51,6 +55,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 51 | 55 | MOVW R4, 52(R13) | |
| 52 | 56 | MOVW $0, R4 | |
| 53 | 57 | MOVW R4, 56(R13) | |
| 58 | + | MOVW R4, 60(R13) // high word of a 64-bit return | |
| 54 | 59 | ||
| 55 | 60 | // Call crosscall2(fn, frame, 0, ctxt) | |
| 56 | 61 | MOVW ·callbackWrap_call(SB), R0 | |
| @@ -63,7 +68,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 63 | 68 | ||
| 64 | 69 | // Get result | |
| 65 | 70 | 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 | 76 | // Restore float registers | |
| 68 | 77 | MOVD 64(R13), F0 | |
| 69 | 78 | MOVD 72(R13), F1 | |
| @@ -74,6 +83,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 74 | 83 | MOVD 112(R13), F6 | |
| 75 | 84 | MOVD 120(R13), F7 | |
| 76 | 85 | ||
| 86 | + | skipfprest: | |
| 77 | 87 | // Restore callee-saved registers | |
| 78 | 88 | MOVW 0(R13), R4 | |
| 79 | 89 | MOVW 4(R13), R5 | |
M sys_unix_s390x.s +10 -8
| @@ -18,22 +18,24 @@ | |||
| 18 | 18 | // | |
| 19 | 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 | 24 | // 0(R15) - back chain | |
| 23 | 25 | // 48(R15) - saved R6-R15 (done by STMG) | |
| 24 | - | // 160(R15) - callbackArgs struct (32 bytes: index, args, result, stackArgs) | |
| 25 | - | // 192(R15) - args array start | |
| 26 | + | // 128(R15) - args array start | |
| 26 | 27 | // | |
| 27 | 28 | // Args array layout: | |
| 28 | 29 | // - floats F0,F2,F4,F6 (32 bytes) | |
| 29 | 30 | // - ints R2-R6 (40 bytes) | |
| 30 | - | // Total args array: 72 bytes, ends at 264 | |
| 31 | + | // 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 | 34 | // Stack args in caller's frame start at old_R15+160 | |
| 33 | 35 | ||
| 34 | - | #define FRAME_SIZE 264 | |
| 35 | - | #define CB_ARGS 160 | |
| 36 | - | #define ARGS_ARRAY 192 | |
| 36 | + | #define FRAME_SIZE 200 | |
| 37 | + | #define ARGS_ARRAY 128 | |
| 38 | + | #define CB_ARGS (FRAME_SIZE+16) | |
| 37 | 39 | #define FLOAT_OFF 0 | |
| 38 | 40 | #define INT_OFF 32 | |
| 39 | 41 | ||
| @@ -71,7 +73,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 71 | 73 | MOVD R5, (ARGS_ARRAY+INT_OFF+3*8)(R15) | |
| 72 | 74 | ||
| 73 | 75 | // 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 | 77 | MOVD (48+FRAME_SIZE)(R15), R1 | |
| 76 | 78 | MOVD R1, (ARGS_ARRAY+INT_OFF+4*8)(R15) | |
| 77 | 79 | ||
M syscall.go +56 -65
| @@ -1,66 +1,65 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // 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 | 6 | package purego | |
| 7 | 7 | ||
| 8 | - | // CDecl marks a function as being called using the __cdecl calling convention as defined in | |
| 9 | - | // the [MSDocs] when passed to NewCallback. It must be the first argument to the function. | |
| 10 | - | // This is only useful on 386 Windows, but it is safe to use on other platforms. | |
| 11 | - | // | |
| 12 | - | // [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 | |
| 13 | - | type CDecl struct{} | |
| 8 | + | import ( | |
| 9 | + | "runtime" | |
| 10 | + | "structs" | |
| 11 | + | "unsafe" | |
| 12 | + | ) | |
| 14 | 13 | ||
| 15 | 14 | const ( | |
| 16 | - | maxArgs = 15 | |
| 15 | + | maxArgs = 32 | |
| 17 | 16 | ) | |
| 18 | 17 | ||
| 19 | - | type syscall15Args struct { | |
| 20 | - | _ hostLayout | |
| 21 | - | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr | |
| 22 | - | f1, f2, f3, f4, f5, f6, f7, f8 uintptr | |
| 23 | - | arm64_r8 uintptr | |
| 18 | + | // syscallArgs is the argument block handed to the syscall15 trampoline. On | |
| 19 | + | // 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 | + | ||
| 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) { | |
| 27 | - | s.fn = fn | |
| 28 | - | s.a1 = ints[0] | |
| 29 | - | s.a2 = ints[1] | |
| 30 | - | s.a3 = ints[2] | |
| 31 | - | s.a4 = ints[3] | |
| 32 | - | s.a5 = ints[4] | |
| 33 | - | s.a6 = ints[5] | |
| 34 | - | s.a7 = ints[6] | |
| 35 | - | s.a8 = ints[7] | |
| 36 | - | s.a9 = ints[8] | |
| 37 | - | s.a10 = ints[9] | |
| 38 | - | s.a11 = ints[10] | |
| 39 | - | s.a12 = ints[11] | |
| 40 | - | s.a13 = ints[12] | |
| 41 | - | s.a14 = ints[13] | |
| 42 | - | s.a15 = ints[14] | |
| 43 | - | s.f1 = floats[0] | |
| 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 | |
| 34 | + | func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs { | |
| 35 | + | s := thePool.Get().(*syscallArgs) | |
| 36 | + | *s = syscallArgs{ | |
| 37 | + | fn: fn, | |
| 38 | + | a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3], | |
| 39 | + | a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7], | |
| 40 | + | a9: sysargs[8], a10: sysargs[9], a11: sysargs[10], a12: sysargs[11], | |
| 41 | + | a13: sysargs[12], a14: sysargs[13], a15: sysargs[14], a16: sysargs[15], | |
| 42 | + | a17: sysargs[16], a18: sysargs[17], a19: sysargs[18], a20: sysargs[19], | |
| 43 | + | a21: sysargs[20], a22: sysargs[21], a23: sysargs[22], a24: sysargs[23], | |
| 44 | + | a25: sysargs[24], a26: sysargs[25], a27: sysargs[26], a28: sysargs[27], | |
| 45 | + | a29: sysargs[28], a30: sysargs[29], a31: sysargs[30], a32: sysargs[31], | |
| 46 | + | f1: floats[0], f2: floats[1], f3: floats[2], f4: floats[3], | |
| 47 | + | f5: floats[4], f6: floats[5], f7: floats[6], f8: floats[7], | |
| 48 | + | arm64_r8: r8, | |
| 49 | + | } | |
| 50 | + | runtime_cgocall(syscallXABI0, unsafe.Pointer(s)) | |
| 51 | + | return s | |
| 52 | 52 | } | |
| 53 | 53 | ||
| 54 | 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. | |
| 56 | - | // On 386 and arm, SyscallN supports at most 15 uintptr arguments. | |
| 57 | - | // It panics when the maximum is exceeded. It returns the result and the libc error code if there is one. | |
| 55 | + | // There is an internal maximum number of arguments that SyscallN can take. It panics | |
| 56 | + | // when the maximum is exceeded. It returns the result and GetLastError on Windows or the errno on | |
| 57 | + | // platforms that capture it, otherwise 0. | |
| 58 | 58 | // | |
| 59 | 59 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | |
| 60 | 60 | // especially point 4. | |
| 61 | 61 | // | |
| 62 | 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 | 63 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | |
| 65 | 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 | 78 | if len(args) > maxArgs { | |
| 80 | 79 | panic("purego: too many arguments to SyscallN") | |
| 81 | 80 | } | |
| 82 | - | // add padding so there is no out-of-bounds slicing | |
| 81 | + | ||
| 82 | + | // Add padding so there is no out-of-bounds slicing. | |
| 83 | 83 | var tmp [maxArgs]uintptr | |
| 84 | - | 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]) | |
| 84 | + | n := copy(tmp[:], args) | |
| 85 | + | return syscallPadded(fn, &tmp, n) | |
| 86 | 86 | } | |
| 87 | 87 | ||
| 88 | - | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | |
| 89 | - | // It is designed for method-like calls where the first argument is always a receiver pointer. | |
| 90 | - | // It shares SyscallN's argument-placement limits and mixed integer/float restriction. | |
| 91 | - | // On 386 and arm, the total count including self is limited to 15 uintptr arguments. | |
| 92 | - | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | |
| 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") | |
| 88 | + | // syscallPadded calls fn with the first n values of tmp as arguments. | |
| 89 | + | func syscallPadded(fn uintptr, tmp *[maxArgs]uintptr, n int) (r1, r2, err uintptr) { | |
| 90 | + | // Windows uses syscall.SyscallN in syscall_windows.go. | |
| 91 | + | if runtime.GOOS == "windows" { | |
| 92 | + | return syscall_syscallN(fn, tmp[:n]...) | |
| 101 | 93 | } | |
| 102 | - | // Use a stack-allocated array to prepend self without heap allocation. | |
| 103 | - | // self goes in tmp[0], remaining args follow. | |
| 104 | - | var tmp [maxArgs]uintptr | |
| 105 | - | tmp[0] = self | |
| 106 | - | copy(tmp[1:], args) | |
| 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]) | |
| 94 | + | var floats [maxArgs]uintptr | |
| 95 | + | copy(floats[:], tmp[:]) | |
| 96 | + | s := syscall_SyscallN(fn, tmp[:], floats[:], 0) | |
| 97 | + | defer thePool.Put(s) | |
| 98 | + | return s.a1, s.a2, s.a3 | |
| 108 | 99 | } | |
M syscall_32bit.go +53 -67
| @@ -5,69 +5,63 @@ | |||
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| 8 | - | // CDecl marks a function as being called using the __cdecl calling convention as defined in | |
| 9 | - | // the [MSDocs] when passed to NewCallback. It must be the first argument to the function. | |
| 10 | - | // This is only useful on 386 Windows, but it is safe to use on other platforms. | |
| 11 | - | // | |
| 12 | - | // [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 | |
| 13 | - | type CDecl struct{} | |
| 8 | + | import ( | |
| 9 | + | "runtime" | |
| 10 | + | "structs" | |
| 11 | + | "unsafe" | |
| 12 | + | ) | |
| 14 | 13 | ||
| 15 | 14 | const ( | |
| 16 | - | maxArgs = 15 | |
| 15 | + | maxArgs = 32 | |
| 17 | 16 | ) | |
| 18 | 17 | ||
| 19 | - | type syscall15Args struct { | |
| 20 | - | _ hostLayout | |
| 18 | + | // syscallArgs is the argument block handed to the syscall15 trampoline. On | |
| 19 | + | // 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 | 28 | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr | |
| 22 | 29 | a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32 uintptr | |
| 23 | 30 | f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16 uintptr | |
| 24 | 31 | arm64_r8 uintptr | |
| 25 | 32 | } | |
| 26 | 33 | ||
| 27 | - | func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uintptr) { | |
| 28 | - | s.fn = fn | |
| 29 | - | s.a1 = ints[0] | |
| 30 | - | s.a2 = ints[1] | |
| 31 | - | s.a3 = ints[2] | |
| 32 | - | s.a4 = ints[3] | |
| 33 | - | s.a5 = ints[4] | |
| 34 | - | s.a6 = ints[5] | |
| 35 | - | s.a7 = ints[6] | |
| 36 | - | s.a8 = ints[7] | |
| 37 | - | s.a9 = ints[8] | |
| 38 | - | s.a10 = ints[9] | |
| 39 | - | s.a11 = ints[10] | |
| 40 | - | s.a12 = ints[11] | |
| 41 | - | s.a13 = ints[12] | |
| 42 | - | s.a14 = ints[13] | |
| 43 | - | s.a15 = ints[14] | |
| 44 | - | s.f1 = floats[0] | |
| 45 | - | s.f2 = floats[1] | |
| 46 | - | s.f3 = floats[2] | |
| 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 | |
| 34 | + | func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs { | |
| 35 | + | s := thePool.Get().(*syscallArgs) | |
| 36 | + | *s = syscallArgs{ | |
| 37 | + | fn: fn, | |
| 38 | + | a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3], | |
| 39 | + | a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7], | |
| 40 | + | a9: sysargs[8], a10: sysargs[9], a11: sysargs[10], a12: sysargs[11], | |
| 41 | + | a13: sysargs[12], a14: sysargs[13], a15: sysargs[14], a16: sysargs[15], | |
| 42 | + | a17: sysargs[16], a18: sysargs[17], a19: sysargs[18], a20: sysargs[19], | |
| 43 | + | a21: sysargs[20], a22: sysargs[21], a23: sysargs[22], a24: sysargs[23], | |
| 44 | + | a25: sysargs[24], a26: sysargs[25], a27: sysargs[26], a28: sysargs[27], | |
| 45 | + | a29: sysargs[28], a30: sysargs[29], a31: sysargs[30], a32: sysargs[31], | |
| 46 | + | f1: floats[0], f2: floats[1], f3: floats[2], f4: floats[3], | |
| 47 | + | f5: floats[4], f6: floats[5], f7: floats[6], f8: floats[7], | |
| 48 | + | f9: floats[8], f10: floats[9], f11: floats[10], f12: floats[11], | |
| 49 | + | f13: floats[12], f14: floats[13], f15: floats[14], f16: floats[15], | |
| 50 | + | arm64_r8: r8, | |
| 51 | + | } | |
| 52 | + | runtime_cgocall(syscallXABI0, unsafe.Pointer(s)) | |
| 53 | + | return s | |
| 60 | 54 | } | |
| 61 | 55 | ||
| 62 | 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. | |
| 64 | - | // It panics when the maximum is exceeded. It returns the result and the libc error code if there is one. | |
| 57 | + | // There is an internal maximum number of arguments that SyscallN can take. It panics | |
| 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 | 61 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | |
| 67 | 62 | // especially point 4. | |
| 68 | 63 | // | |
| 69 | 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 | 65 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | |
| 72 | 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 | 80 | if len(args) > maxArgs { | |
| 87 | 81 | panic("purego: too many arguments to SyscallN") | |
| 88 | 82 | } | |
| 89 | - | // add padding so there is no out-of-bounds slicing | |
| 83 | + | ||
| 84 | + | // Add padding so there is no out-of-bounds slicing. | |
| 90 | 85 | var tmp [maxArgs]uintptr | |
| 91 | - | 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]) | |
| 86 | + | n := copy(tmp[:], args) | |
| 87 | + | return syscallPadded(fn, &tmp, n) | |
| 93 | 88 | } | |
| 94 | 89 | ||
| 95 | - | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | |
| 96 | - | // It is designed for method-like calls where the first argument is always a receiver pointer. | |
| 97 | - | // It shares SyscallN's argument-placement limits and mixed integer/float restriction. | |
| 98 | - | // On 386 and arm, the total count including self is limited to 15 uintptr arguments. | |
| 99 | - | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | |
| 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") | |
| 90 | + | // syscallPadded calls fn with the first n values of tmp as arguments. | |
| 91 | + | func syscallPadded(fn uintptr, tmp *[maxArgs]uintptr, n int) (r1, r2, err uintptr) { | |
| 92 | + | // Windows uses syscall.SyscallN in syscall_windows.go. | |
| 93 | + | if runtime.GOOS == "windows" { | |
| 94 | + | return syscall_syscallN(fn, tmp[:n]...) | |
| 108 | 95 | } | |
| 109 | - | // Use a stack-allocated array to prepend self without heap allocation. | |
| 110 | - | // self goes in tmp[0], remaining args follow. | |
| 111 | - | var tmp [maxArgs]uintptr | |
| 112 | - | tmp[0] = self | |
| 113 | - | copy(tmp[1:], args) | |
| 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]) | |
| 96 | + | var floats [16]uintptr | |
| 97 | + | copy(floats[:], tmp[:16]) | |
| 98 | + | s := syscall_SyscallN(fn, tmp[:], floats[:], 0) | |
| 99 | + | defer thePool.Put(s) | |
| 100 | + | return s.a1, s.a2, s.a3 | |
| 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 | 38 | // Build C library for benchmarking | |
| 39 | 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 | 41 | b.Fatalf("Failed to build C library: %v", err) | |
| 42 | 42 | } | |
| 43 | 43 | b.Cleanup(func() { | |
| @@ -116,7 +116,7 @@ func BenchmarkCallingMethods(b *testing.B) { | |||
| 116 | 116 | args := int64sToUintptrs(tc.args) | |
| 117 | 117 | var result uintptr | |
| 118 | 118 | b.ResetTimer() | |
| 119 | - | for i := 0; i < b.N; i++ { | |
| 119 | + | for range b.N { | |
| 120 | 120 | result, _, _ = purego.SyscallN(tc.goFnPtr, args...) | |
| 121 | 121 | } | |
| 122 | 122 | b.StopTimer() | |
| @@ -135,7 +135,7 @@ func BenchmarkCallingMethods(b *testing.B) { | |||
| 135 | 135 | args := int64sToUintptrs(tc.args) | |
| 136 | 136 | var result uintptr | |
| 137 | 137 | b.ResetTimer() | |
| 138 | - | for i := 0; i < b.N; i++ { | |
| 138 | + | for range b.N { | |
| 139 | 139 | result, _, _ = purego.SyscallN(tc.cFnPtr, args...) | |
| 140 | 140 | } | |
| 141 | 141 | b.StopTimer() | |
| @@ -165,7 +165,7 @@ func BenchmarkCallingMethods(b *testing.B) { | |||
| 165 | 165 | ||
| 166 | 166 | var result uintptr | |
| 167 | 167 | b.ResetTimer() | |
| 168 | - | for i := 0; i < b.N; i++ { | |
| 168 | + | for range b.N { | |
| 169 | 169 | result, _, _ = purego.SyscallN(tc.cCallbackPtr, callbackArgs...) | |
| 170 | 170 | } | |
| 171 | 171 | b.StopTimer() | |
| @@ -205,40 +205,40 @@ func callRegisterFunc(registerFn any, n int, args []int64, iterations int) int64 | |||
| 205 | 205 | switch n { | |
| 206 | 206 | case 1: | |
| 207 | 207 | f := registerFn.(*func(int64) int64) | |
| 208 | - | for i := 0; i < iterations; i++ { | |
| 208 | + | for range iterations { | |
| 209 | 209 | result = (*f)(args[0]) | |
| 210 | 210 | } | |
| 211 | 211 | case 2: | |
| 212 | 212 | f := registerFn.(*func(int64, int64) int64) | |
| 213 | - | for i := 0; i < iterations; i++ { | |
| 213 | + | for range iterations { | |
| 214 | 214 | result = (*f)(args[0], args[1]) | |
| 215 | 215 | } | |
| 216 | 216 | case 3: | |
| 217 | 217 | f := registerFn.(*func(int64, int64, int64) int64) | |
| 218 | - | for i := 0; i < iterations; i++ { | |
| 218 | + | for range iterations { | |
| 219 | 219 | result = (*f)(args[0], args[1], args[2]) | |
| 220 | 220 | } | |
| 221 | 221 | case 5: | |
| 222 | 222 | f := registerFn.(*func(int64, int64, int64, int64, int64) int64) | |
| 223 | - | for i := 0; i < iterations; i++ { | |
| 223 | + | for range iterations { | |
| 224 | 224 | result = (*f)(args[0], args[1], args[2], args[3], args[4]) | |
| 225 | 225 | } | |
| 226 | 226 | case 10: | |
| 227 | 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 | 229 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | |
| 230 | 230 | args[5], args[6], args[7], args[8], args[9]) | |
| 231 | 231 | } | |
| 232 | 232 | case 14: | |
| 233 | 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 | 235 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | |
| 236 | 236 | args[5], args[6], args[7], args[8], args[9], | |
| 237 | 237 | args[10], args[11], args[12], args[13]) | |
| 238 | 238 | } | |
| 239 | 239 | case 15: | |
| 240 | 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 | 242 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | |
| 243 | 243 | args[5], args[6], args[7], args[8], args[9], | |
| 244 | 244 | args[10], args[11], args[12], args[13], args[14]) | |
M syscall_cgo_linux.go +5 -6
| @@ -9,13 +9,12 @@ import ( | |||
| 9 | 9 | "github.com/bnema/purego/internal/cgo" | |
| 10 | 10 | ) | |
| 11 | 11 | ||
| 12 | - | var syscall15XABI0 = uintptr(cgo.Syscall15XABI0) | |
| 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 | - | } | |
| 12 | + | var syscallXABI0 = uintptr(cgo.SyscallXABI0) | |
| 18 | 13 | ||
| 19 | 14 | func NewCallback(_ any) uintptr { | |
| 20 | 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 | 10 | "os" | |
| 11 | 11 | "path/filepath" | |
| 12 | 12 | "runtime" | |
| 13 | - | "sort" | |
| 13 | + | "slices" | |
| 14 | 14 | "strings" | |
| 15 | 15 | "syscall" | |
| 16 | 16 | "testing" | |
| @@ -119,8 +119,8 @@ func compareStatus(filter, expect string) error { | |||
| 119 | 119 | // "... : bad file descriptor. | |
| 120 | 120 | continue | |
| 121 | 121 | } | |
| 122 | - | lines := strings.Split(string(d), "\n") | |
| 123 | - | for _, line := range lines { | |
| 122 | + | lines := strings.SplitSeq(string(d), "\n") | |
| 123 | + | for line := range lines { | |
| 124 | 124 | // Different kernel vintages pad differently. | |
| 125 | 125 | line = strings.TrimSpace(line) | |
| 126 | 126 | if strings.HasPrefix(line, "Pid:\t") { | |
| @@ -149,7 +149,7 @@ func compareStatus(filter, expect string) error { | |||
| 149 | 149 | // https://github.com/golang/go/issues/46145 | |
| 150 | 150 | // Containers don't reliably output this line in sorted order so manually sort and compare that. | |
| 151 | 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 | 153 | got := strings.Join(a, " ") | |
| 154 | 154 | if got == expected[8:] { | |
| 155 | 155 | foundAThread = true | |
| @@ -173,7 +173,7 @@ func compareStatus(filter, expect string) error { | |||
| 173 | 173 | func TestDlopenThenAllThreadsSyscall(t *testing.T) { | |
| 174 | 174 | // Step 1: Build and load a shared C library that calls back into Go. | |
| 175 | 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 | 177 | t.Fatal(err) | |
| 178 | 178 | } | |
| 179 | 179 | defer os.Remove(libFileName) | |
| @@ -192,12 +192,12 @@ func TestDlopenThenAllThreadsSyscall(t *testing.T) { | |||
| 192 | 192 | } | |
| 193 | 193 | ||
| 194 | 194 | cb := purego.NewCallback(goFunc) | |
| 195 | - | for i := 0; i < 10; i++ { | |
| 195 | + | for range 10 { | |
| 196 | 196 | callCallback(cb, "hello") | |
| 197 | 197 | } | |
| 198 | 198 | ||
| 199 | 199 | // Step 2: Generate thread churn + AllThreadsSyscall (via Setuid). | |
| 200 | - | for i := 0; i < 20; i++ { | |
| 200 | + | for range 20 { | |
| 201 | 201 | c := make(chan struct{}) | |
| 202 | 202 | go func() { | |
| 203 | 203 | runtime.LockOSThread() | |
M syscall_notstackargs.go +18 -1
| @@ -17,7 +17,6 @@ import "unsafe" | |||
| 17 | 17 | // the runtime's own callback mechanism, so this type is compiled but unused, | |
| 18 | 18 | // serving only as a stub to satisfy cross-platform compilation. | |
| 19 | 19 | type callbackArgs struct { | |
| 20 | - | _ hostLayout | |
| 21 | 20 | index uintptr | |
| 22 | 21 | // args points to the argument block. | |
| 23 | 22 | // | |
| @@ -36,3 +35,21 @@ type callbackArgs struct { | |||
| 36 | 35 | func (c *callbackArgs) stackFrame() unsafe.Pointer { | |
| 37 | 36 | 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 | 4 | package purego_test | |
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | - | "fmt" | |
| 8 | 7 | "os" | |
| 8 | + | "path/filepath" | |
| 9 | 9 | "runtime" | |
| 10 | 10 | "testing" | |
| 11 | - | "unsafe" | |
| 12 | 11 | ||
| 13 | 12 | "github.com/bnema/purego" | |
| 14 | 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 | 43 | func TestErrno(t *testing.T) { | |
| 30 | - | if runtime.GOOS != "darwin" { | |
| 44 | + | if !errnoIsCaptured() { | |
| 31 | 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 | 55 | if err != nil { | |
| 36 | 56 | t.Fatal(err) | |
| 37 | 57 | } | |
| 38 | 58 | ||
| 39 | - | openSym, err := load.OpenSymbol(libc, "open") | |
| 59 | + | setErrno, err := load.OpenSymbol(lib, "setErrno") | |
| 40 | 60 | if err != nil { | |
| 41 | 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 | 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 | 78 | var strerror func(int32) string | |
| @@ -55,79 +84,3 @@ func TestErrno(t *testing.T) { | |||
| 55 | 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 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | // TODO: remove s390x cgo dependency once golang/go#77449 is resolved | |
| 5 | - | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (cgo && s390x))) || netbsd | |
| 4 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (s390x && (cgo || go1.27)))) || netbsd | |
| 6 | 5 | ||
| 7 | 6 | package purego | |
| 8 | 7 | ||
| @@ -10,6 +9,7 @@ import ( | |||
| 10 | 9 | "encoding/json" | |
| 11 | 10 | "errors" | |
| 12 | 11 | "fmt" | |
| 12 | + | "math" | |
| 13 | 13 | "os" | |
| 14 | 14 | "reflect" | |
| 15 | 15 | "runtime" | |
| @@ -22,31 +22,31 @@ import ( | |||
| 22 | 22 | internalstrings "github.com/bnema/purego/internal/strings" | |
| 23 | 23 | ) | |
| 24 | 24 | ||
| 25 | - | var syscall15XABI0 uintptr | |
| 25 | + | 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) { | |
| 28 | - | args := &syscall15Args{ | |
| 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 | |
| 27 | + | func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |
| 28 | + | panic("purego: syscall_syscallN is only supported on windows") | |
| 37 | 29 | } | |
| 38 | 30 | ||
| 39 | 31 | // NewCallback converts a Go function to a function pointer conforming to the C calling convention. | |
| 40 | 32 | // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a | |
| 41 | 33 | // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size | |
| 42 | 34 | // 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 function | |
| 35 | + | // for these callbacks is never released. At least 1024 callbacks can always be created. Although this function | |
| 44 | 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 | 42 | func NewCallback(fn any) uintptr { | |
| 46 | 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 | 48 | in := ty.In(i) | |
| 49 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | |
| 49 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 50 | 50 | continue | |
| 51 | 51 | } | |
| 52 | 52 | if i != 0 { | |
| @@ -101,7 +101,6 @@ func UnrefCallback(cb uintptr) error { | |||
| 101 | 101 | delete(cbs.knownIdx, cb) | |
| 102 | 102 | cbs.holes[idx] = struct{}{} | |
| 103 | 103 | cbs.funcs[idx] = reflect.Value{} | |
| 104 | - | cbs.argPools[idx] = nil | |
| 105 | 104 | recordCallbackLedger("release", idx, cb, key, val, false, "") | |
| 106 | 105 | return nil | |
| 107 | 106 | } | |
| @@ -134,7 +133,6 @@ func UnrefCallbackFnPtr(fnPtr any) error { | |||
| 134 | 133 | cbs.fnPtrKeys[idx] = 0 | |
| 135 | 134 | cbs.holes[idx] = struct{}{} | |
| 136 | 135 | cbs.funcs[idx] = reflect.Value{} | |
| 137 | - | cbs.argPools[idx] = nil | |
| 138 | 136 | recordCallbackLedger("release-fnptr", idx, addr, val.Pointer(), callbackVal, false, "") | |
| 139 | 137 | return nil | |
| 140 | 138 | } | |
| @@ -148,7 +146,6 @@ var cbs = struct { | |||
| 148 | 146 | numFn int // the highest allocated callback index + 1 | |
| 149 | 147 | holes map[int]struct{} // reusable callback slots | |
| 150 | 148 | funcs [maxCB]reflect.Value // the saved callbacks | |
| 151 | - | argPools [maxCB]*sync.Pool // pre-allocated argument buffers per callback | |
| 152 | 149 | knownIdx map[uintptr]int // callback address -> slot index | |
| 153 | 150 | knownFnPtr map[uintptr]uintptr // function pointer variable address -> callback address | |
| 154 | 151 | fnPtrKeys [maxCB]uintptr // slot index -> function pointer variable address | |
| @@ -342,10 +339,6 @@ func traceCallbackAllocation(event string, val reflect.Value, remaining int) { | |||
| 342 | 339 | if traceFile == "" { | |
| 343 | 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 | 342 | if f, err := os.OpenFile(traceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); err == nil { | |
| 350 | 343 | _, _ = f.WriteString(message) | |
| 351 | 344 | _ = f.Close() | |
| @@ -361,14 +354,14 @@ func compileCallback(fn any) uintptr { | |||
| 361 | 354 | panic("purego: function must not be nil") | |
| 362 | 355 | } | |
| 363 | 356 | ty := val.Type() | |
| 364 | - | for i := 0; i < ty.NumIn(); i++ { | |
| 357 | + | for i := range ty.NumIn() { | |
| 365 | 358 | in := ty.In(i) | |
| 366 | 359 | switch in.Kind() { | |
| 367 | 360 | case reflect.Struct: | |
| 368 | - | if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) { | |
| 361 | + | if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 369 | 362 | continue | |
| 370 | 363 | } | |
| 371 | - | ensureStructSupported() | |
| 364 | + | ensureCallbackStructSupported() | |
| 372 | 365 | checkStructFieldsSupported(in) | |
| 373 | 366 | continue | |
| 374 | 367 | case reflect.Interface, reflect.Func, reflect.Slice, | |
| @@ -381,9 +374,13 @@ output: | |||
| 381 | 374 | switch { | |
| 382 | 375 | case ty.NumOut() == 1: | |
| 383 | 376 | switch ty.Out(0).Kind() { | |
| 377 | + | case reflect.Struct: | |
| 378 | + | ensureCallbackStructSupported() | |
| 379 | + | checkStructFieldsSupported(ty.Out(0)) | |
| 380 | + | break output | |
| 384 | 381 | case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | |
| 385 | 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 | 384 | break output | |
| 388 | 385 | } | |
| 389 | 386 | panic("purego: unsupported return type: " + ty.String()) | |
| @@ -414,12 +411,6 @@ output: | |||
| 414 | 411 | cbs.numFn++ | |
| 415 | 412 | } | |
| 416 | 413 | 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 | 414 | addr := callbackasmAddr(index) | |
| 424 | 415 | cbs.knownIdx[addr] = index | |
| 425 | 416 | if reused { | |
| @@ -449,26 +440,23 @@ var callbackWrap_call = callbackWrap | |||
| 449 | 440 | // callbackWrap is called by assembly code which determines which Go function to call. | |
| 450 | 441 | // This function takes the arguments and passes them to the Go function and returns the result. | |
| 451 | 442 | func callbackWrap(a *callbackArgs) { | |
| 452 | - | cbs.lock.RLock() | |
| 443 | + | cbs.lock.Lock() | |
| 453 | 444 | fn := cbs.funcs[a.index] | |
| 454 | - | pool := cbs.argPools[a.index] | |
| 455 | - | cbs.lock.RUnlock() | |
| 445 | + | cbs.lock.Unlock() | |
| 456 | 446 | fnType := fn.Type() | |
| 457 | - | args := pool.Get().([]reflect.Value) | |
| 458 | - | defer func() { | |
| 459 | - | for i := range args { | |
| 460 | - | args[i] = reflect.Value{} | |
| 461 | - | } | |
| 462 | - | pool.Put(args) | |
| 463 | - | }() | |
| 447 | + | args := make([]reflect.Value, fnType.NumIn()) | |
| 464 | 448 | frame := (*[callbackMaxFrame]uintptr)(a.args) | |
| 465 | 449 | // stackFrame points to stack-passed arguments. On most architectures this is | |
| 466 | 450 | // contiguous with frame (after register args), but on ppc64le it's separate. | |
| 467 | 451 | var stackFrame *[callbackMaxFrame]uintptr | |
| 452 | + | var intFrame *[callbackMaxFrame]uintptr | |
| 468 | 453 | if sf := a.stackFrame(); sf != nil { | |
| 469 | 454 | // Only ppc64le uses separate stackArgs pointer due to NOSPLIT constraints | |
| 470 | 455 | stackFrame = (*[callbackMaxFrame]uintptr)(sf) | |
| 471 | 456 | } | |
| 457 | + | if intf := a.intFrame(); intf != nil { | |
| 458 | + | intFrame = (*[callbackMaxFrame]uintptr)(intf) | |
| 459 | + | } | |
| 472 | 460 | // floatsN and intsN track the number of register slots used, not argument count. | |
| 473 | 461 | // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers). | |
| 474 | 462 | var floatsN int | |
| @@ -476,7 +464,7 @@ func callbackWrap(a *callbackArgs) { | |||
| 476 | 464 | // On amd64/loong64/ppc64le/riscv64/s390x, when returning a struct larger than | |
| 477 | 465 | // maxRegAllocStructSize, the caller passes a hidden pointer in the first integer | |
| 478 | 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 | 468 | fnType.NumOut() == 1 && fnType.Out(0).Kind() == reflect.Struct && | |
| 481 | 469 | fnType.Out(0).Size() > maxRegAllocStructSize { | |
| 482 | 470 | intsN = 1 | |
| @@ -494,33 +482,62 @@ func callbackWrap(a *callbackArgs) { | |||
| 494 | 482 | stackByteOffset := uintptr(0) | |
| 495 | 483 | for i := range args { | |
| 496 | 484 | // slots is the number of pointer-sized slots the argument takes | |
| 497 | - | var slots int | |
| 498 | 485 | inType := fnType.In(i) | |
| 486 | + | slots := int((inType.Size() + ptrSize - 1) / ptrSize) | |
| 499 | 487 | switch inType.Kind() { | |
| 500 | 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 | 509 | if floatsN+slots > numOfFloatRegisters() { | |
| 503 | - | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | |
| 510 | + | if isDarwin && runtime.GOARCH == "arm64" { | |
| 504 | 511 | // Darwin ARM64: read from packed stack with proper alignment | |
| 505 | 512 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| 506 | 513 | } else if stackFrame != nil { | |
| 507 | 514 | // 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 | 519 | // s390x big-endian: sub-8-byte values are right-justified | |
| 510 | 520 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | |
| 511 | - | } else { | |
| 521 | + | default: | |
| 512 | 522 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |
| 513 | 523 | } | |
| 514 | 524 | 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 | 529 | } else { | |
| 516 | 530 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |
| 517 | 531 | stackSlot += slots | |
| 518 | 532 | } | |
| 519 | 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 | 538 | // s390x big-endian: float32 is right-justified in 8-byte FPR slot | |
| 522 | 539 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType) | |
| 523 | - | } else { | |
| 540 | + | default: | |
| 524 | 541 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | |
| 525 | 542 | } | |
| 526 | 543 | } | |
| @@ -550,7 +567,7 @@ func callbackWrap(a *callbackArgs) { | |||
| 550 | 567 | args[i] = reflect.ValueOf(internalstrings.GoString(ptr)) | |
| 551 | 568 | continue | |
| 552 | 569 | case reflect.Struct: | |
| 553 | - | if i == 0 && inType.AssignableTo(reflect.TypeOf(CDecl{})) { | |
| 570 | + | if i == 0 && inType.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 554 | 571 | args[i] = reflect.Zero(inType) | |
| 555 | 572 | continue | |
| 556 | 573 | } | |
| @@ -561,9 +578,11 @@ func callbackWrap(a *callbackArgs) { | |||
| 561 | 578 | args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset) | |
| 562 | 579 | continue | |
| 563 | 580 | default: | |
| 564 | - | slots = int((inType.Size() + ptrSize - 1) / ptrSize) | |
| 581 | + | if isARMPaddingNeeded(inType, -1, intsN) { | |
| 582 | + | intsN++ | |
| 583 | + | } | |
| 565 | 584 | if intsN+slots > numOfIntegerRegisters() { | |
| 566 | - | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | |
| 585 | + | if isDarwin && runtime.GOARCH == "arm64" { | |
| 567 | 586 | // Darwin ARM64: read from packed stack with proper alignment | |
| 568 | 587 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| 569 | 588 | } else if stackFrame != nil { | |
| @@ -575,18 +594,26 @@ func callbackWrap(a *callbackArgs) { | |||
| 575 | 594 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |
| 576 | 595 | } | |
| 577 | 596 | 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 | 601 | } else { | |
| 579 | 602 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |
| 580 | 603 | stackSlot += slots | |
| 581 | 604 | } | |
| 582 | 605 | } else { | |
| 583 | - | // the integers begin after the floats in frame | |
| 584 | - | pos := intsN + numOfFloatRegisters() | |
| 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) | |
| 606 | + | if intFrame != nil { | |
| 607 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&intFrame[intsN])).Elem() | |
| 588 | 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 | 619 | intsN += slots | |
| @@ -595,9 +622,13 @@ func callbackWrap(a *callbackArgs) { | |||
| 595 | 622 | ret := fn.Call(args) | |
| 596 | 623 | if len(ret) > 0 { | |
| 597 | 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 | 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 | 632 | a.result[0] = uintptr(ret[0].Int()) | |
| 602 | 633 | case reflect.Bool: | |
| 603 | 634 | if ret[0].Bool() { | |
| @@ -640,6 +671,18 @@ func callbackArgFromStack(argsBase unsafe.Pointer, stackSlot int, stackByteOffse | |||
| 640 | 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 | 686 | // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures. | |
| 644 | 687 | // On s390x: | |
| 645 | 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 | 11 | "unsafe" | |
| 12 | 12 | ) | |
| 13 | 13 | ||
| 14 | - | var syscall15XABI0 uintptr | |
| 14 | + | 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) { | |
| 17 | - | r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) | |
| 16 | + | func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |
| 17 | + | r1, r2, errno := syscall.SyscallN(fn, args...) | |
| 18 | 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 | 23 | // function with one uintptr-sized result. The function must not have arguments with size larger than the | |
| 24 | 24 | // size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory | |
| 25 | 25 | // 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 act | |
| 26 | + | // callbacks can always be created. Although this function is similar to the darwin version it may act | |
| 27 | 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 | 33 | func NewCallback(fn any) uintptr { | |
| 29 | 34 | isCDecl := false | |
| 30 | 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 | 40 | in := ty.In(i) | |
| 33 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | |
| 41 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 34 | 42 | continue | |
| 35 | 43 | } | |
| 36 | 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 | 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 | 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 | 5 | #include <stdio.h> | |
| 6 | 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 | 8 | // Empty is empty | |
| 14 | 9 | struct Empty {}; | |
| 15 | 10 | ||
| 16 | 11 | // 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 | 13 | return 0xdeadbeef; | |
| 19 | 14 | } | |
| 20 | 15 | ||
| @@ -22,28 +17,28 @@ struct EmptyEmpty { | |||
| 22 | 17 | struct Empty x; | |
| 23 | 18 | }; | |
| 24 | 19 | ||
| 25 | - | unsigned long EmptyEmpty(struct EmptyEmpty e) { | |
| 20 | + | uint64_t EmptyEmpty(struct EmptyEmpty e) { | |
| 26 | 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 | 25 | return (x << 16) | y; | |
| 31 | 26 | } | |
| 32 | 27 | ||
| 33 | 28 | // GreaterThan16Bytes is 24 bytes on 64 bit systems | |
| 34 | 29 | struct GreaterThan16Bytes { | |
| 35 | - | long *x, *y, *z; | |
| 30 | + | int64_t *x, *y, *z; | |
| 36 | 31 | }; | |
| 37 | 32 | ||
| 38 | 33 | // 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 | 35 | return *g.x + *g.y + *g.z; | |
| 41 | 36 | } | |
| 42 | 37 | ||
| 43 | 38 | // 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) { | |
| 45 | - | long registers = a + b + c + d + e + f + g + h; | |
| 46 | - | long stack = *bytes.x + *bytes.y + *bytes.z; | |
| 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) { | |
| 40 | + | intptr_t registers = a + b + c + d + e + f + g + h; | |
| 41 | + | int64_t stack = *bytes.x + *bytes.y + *bytes.z; | |
| 47 | 42 | if (registers != stack) { | |
| 48 | 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 | 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 | 52 | return *bytes.x + *bytes.y + *bytes.z + a + b; | |
| 58 | 53 | } | |
| 59 | 54 | ||
| 60 | 55 | struct GreaterThan16BytesStruct { | |
| 61 | 56 | struct { | |
| 62 | - | long *x, *y, *z; | |
| 57 | + | int64_t *x, *y, *z; | |
| 63 | 58 | } a ; | |
| 64 | 59 | }; | |
| 65 | 60 | ||
| 66 | - | unsigned long GreaterThan16BytesStruct(struct GreaterThan16BytesStruct g) { | |
| 61 | + | uint64_t GreaterThan16BytesStruct(struct GreaterThan16BytesStruct g) { | |
| 67 | 62 | return *(g.a.x) + *(g.a.y) + *(g.a.z); | |
| 68 | 63 | } | |
| 69 | 64 | ||
| 70 | 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 | 70 | return l.x + l.y; | |
| 76 | 71 | } | |
| 77 | 72 | ||
| @@ -201,23 +196,23 @@ struct Short { | |||
| 201 | 196 | unsigned short a, b, c, d; | |
| 202 | 197 | }; | |
| 203 | 198 | ||
| 204 | - | unsigned long Short(struct Short s) { | |
| 205 | - | return (long)s.a << 48 | (long)s.b << 32 | (long)s.c << 16 | (long)s.d << 0; | |
| 199 | + | uint64_t Short(struct Short s) { | |
| 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 | 203 | struct Int { | |
| 209 | 204 | unsigned int a, b; | |
| 210 | 205 | }; | |
| 211 | 206 | ||
| 212 | - | unsigned long Int(struct Int i) { | |
| 213 | - | return (long)i.a << 32 | (long)i.b << 0; | |
| 207 | + | uint64_t Int(struct Int i) { | |
| 208 | + | return (uint64_t)i.a << 32 | (uint64_t)i.b << 0; | |
| 214 | 209 | } | |
| 215 | 210 | ||
| 216 | 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 | 216 | return l.a; | |
| 222 | 217 | } | |
| 223 | 218 | ||
| @@ -327,31 +322,31 @@ struct Content { | |||
| 327 | 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 | 326 | if (win == 0) | |
| 332 | 327 | return 0xBAD; | |
| 333 | 328 | if (!flag) | |
| 334 | 329 | 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 | 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 | 338 | return g.a + g.b - g.c + g.d; | |
| 344 | 339 | } | |
| 345 | 340 | ||
| 346 | 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 | 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 | 350 | return a; | |
| 356 | 351 | } | |
| 357 | 352 | ||
| @@ -395,7 +390,7 @@ uintptr_t AddPointers(struct TwoPointers wrapper) { | |||
| 395 | 390 | // Identity functions for round-trip testing of struct arguments | |
| 396 | 391 | ||
| 397 | 392 | struct OneInt64 { | |
| 398 | - | long long a; | |
| 393 | + | int64_t a; | |
| 399 | 394 | }; | |
| 400 | 395 | ||
| 401 | 396 | struct OneInt64 IdentityOneInt64(struct OneInt64 s) { | |
| @@ -423,7 +418,7 @@ struct FloatAndInt IdentityFloatAndInt(struct FloatAndInt s) { | |||
| 423 | 418 | } | |
| 424 | 419 | ||
| 425 | 420 | struct ThreeInt64 { | |
| 426 | - | long long a, b, c; | |
| 421 | + | int64_t a, b, c; | |
| 427 | 422 | }; | |
| 428 | 423 | ||
| 429 | 424 | struct ThreeInt64 IdentityThreeInt64(struct ThreeInt64 s) { | |
| @@ -431,16 +426,16 @@ struct ThreeInt64 IdentityThreeInt64(struct ThreeInt64 s) { | |||
| 431 | 426 | } | |
| 432 | 427 | ||
| 433 | 428 | struct PtrInt64Ptr { | |
| 434 | - | long long *a; | |
| 435 | - | long long b; | |
| 436 | - | long long *c; | |
| 429 | + | int64_t *a; | |
| 430 | + | int64_t b; | |
| 431 | + | int64_t *c; | |
| 437 | 432 | }; | |
| 438 | 433 | ||
| 439 | 434 | struct PtrInt64Ptr IdentityPtrInt64Ptr(struct PtrInt64Ptr s) { | |
| 440 | 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 | 439 | return s; | |
| 445 | 440 | } | |
| 446 | 441 | ||
| @@ -449,7 +444,7 @@ struct FloatLessThan16Bytes IdentityTwoFloat32AfterFloats(double x, double y, st | |||
| 449 | 444 | } | |
| 450 | 445 | ||
| 451 | 446 | struct Mixed5Args { | |
| 452 | - | long long *a; | |
| 447 | + | int64_t *a; | |
| 453 | 448 | int32_t b; | |
| 454 | 449 | float c; | |
| 455 | 450 | int32_t d; | |
M wincallback.go +8 -8
| @@ -35,7 +35,7 @@ func genasm386() { | |||
| 35 | 35 | ||
| 36 | 36 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 37 | 37 | `) | |
| 38 | - | for i := 0; i < maxCallback; i++ { | |
| 38 | + | for i := range maxCallback { | |
| 39 | 39 | fmt.Fprintf(&buf, "\tMOVL $%d, CX\n", i) | |
| 40 | 40 | buf.WriteString("\tJMP callbackasm1(SB)\n") | |
| 41 | 41 | } | |
| @@ -63,7 +63,7 @@ func genasmAmd64() { | |||
| 63 | 63 | ||
| 64 | 64 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 65 | 65 | `) | |
| 66 | - | for i := 0; i < maxCallback; i++ { | |
| 66 | + | for range maxCallback { | |
| 67 | 67 | buf.WriteString("\tCALL callbackasm1(SB)\n") | |
| 68 | 68 | } | |
| 69 | 69 | if err := os.WriteFile("zcallback_amd64.s", buf.Bytes(), 0644); err != nil { | |
| @@ -90,7 +90,7 @@ func genasmArm() { | |||
| 90 | 90 | ||
| 91 | 91 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 92 | 92 | `) | |
| 93 | - | for i := 0; i < maxCallback; i++ { | |
| 93 | + | for i := range maxCallback { | |
| 94 | 94 | fmt.Fprintf(&buf, "\tMOVW $%d, R12\n", i) | |
| 95 | 95 | buf.WriteString("\tB callbackasm1(SB)\n") | |
| 96 | 96 | } | |
| @@ -118,7 +118,7 @@ func genasmArm64() { | |||
| 118 | 118 | ||
| 119 | 119 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 120 | 120 | `) | |
| 121 | - | for i := 0; i < maxCallback; i++ { | |
| 121 | + | for i := range maxCallback { | |
| 122 | 122 | fmt.Fprintf(&buf, "\tMOVD $%d, R12\n", i) | |
| 123 | 123 | buf.WriteString("\tB callbackasm1(SB)\n") | |
| 124 | 124 | } | |
| @@ -146,7 +146,7 @@ func genasmLoong64() { | |||
| 146 | 146 | ||
| 147 | 147 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 148 | 148 | `) | |
| 149 | - | for i := 0; i < maxCallback; i++ { | |
| 149 | + | for i := range maxCallback { | |
| 150 | 150 | fmt.Fprintf(&buf, "\tMOVV $%d, R12\n", i) | |
| 151 | 151 | buf.WriteString("\tJMP callbackasm1(SB)\n") | |
| 152 | 152 | } | |
| @@ -174,7 +174,7 @@ func genasmPpc64le() { | |||
| 174 | 174 | ||
| 175 | 175 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 176 | 176 | `) | |
| 177 | - | for i := 0; i < maxCallback; i++ { | |
| 177 | + | for i := range maxCallback { | |
| 178 | 178 | fmt.Fprintf(&buf, "\tMOVD $%d, R11\n", i) | |
| 179 | 179 | buf.WriteString("\tBR callbackasm1(SB)\n") | |
| 180 | 180 | } | |
| @@ -206,7 +206,7 @@ func genasmRiscv64() { | |||
| 206 | 206 | ||
| 207 | 207 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 208 | 208 | `) | |
| 209 | - | for i := 0; i < maxCallback; i++ { | |
| 209 | + | for i := range maxCallback { | |
| 210 | 210 | space := " " | |
| 211 | 211 | if i <= 32 { | |
| 212 | 212 | fmt.Fprintf(&buf, "\tPCALIGN $8\n") | |
| @@ -240,7 +240,7 @@ func genasmS390x() { | |||
| 240 | 240 | ||
| 241 | 241 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 242 | 242 | `) | |
| 243 | - | for i := 0; i < maxCallback; i++ { | |
| 243 | + | for i := range maxCallback { | |
| 244 | 244 | fmt.Fprintf(&buf, "\tMOVD $%d, R0\n", i) | |
| 245 | 245 | buf.WriteString("\tBR callbackasm1(SB)\n") | |
| 246 | 246 | } | |