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 | contact_links: | 2 | contact_links: | ||
| 3 | - name: Questions | 3 | - name: Questions | ||
| 4 | about: Please use one of the forums for questions or general discussions | 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 | on: [push, pull_request] | 3 | on: [push, pull_request] | ||
| 4 | 4 | ||||
| 5 | env: | 5 | env: | ||
| 6 | - | QEMU_VERSION: "11.0.1+ds-1" | 6 | + | QEMU_MAJOR_VERSION: "10" |
| 7 | 7 | ||||
| 8 | jobs: | 8 | jobs: | ||
| 9 | test: | 9 | test: | ||
| 10 | strategy: | 10 | strategy: | ||
| 11 | fail-fast: false | 11 | fail-fast: false | ||
| 12 | matrix: | 12 | matrix: | ||
| 13 | - | os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest] | 13 | + | os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest, windows-11-arm] |
| 14 | - | go: ['1.20.x', '1.21.x', '1.22.x', '1.23.x', '1.24.x', '1.25.x', '1.26.x'] | 14 | + | go: ['1.25.x', '1.26.x', '1.27.x'] |
| 15 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | 15 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | ||
| 16 | runs-on: ${{ matrix.os }} | 16 | runs-on: ${{ matrix.os }} | ||
| 17 | defaults: | 17 | defaults: | ||
| @@ -32,8 +32,8 @@ jobs: | |||||
| 32 | with: | 32 | with: | ||
| 33 | go-version: ${{ matrix.go }} | 33 | go-version: ${{ matrix.go }} | ||
| 34 | 34 | ||||
| 35 | - | - name: Set up the prerequisites | 35 | + | - name: Set up Msys2 |
| 36 | - | if: runner.os == 'Windows' | 36 | + | if: runner.os == 'Windows' && runner.arch != 'ARM64' |
| 37 | uses: msys2/setup-msys2@v2 | 37 | uses: msys2/setup-msys2@v2 | ||
| 38 | 38 | ||||
| 39 | - name: go vet | 39 | - name: go vet | ||
| @@ -76,13 +76,21 @@ jobs: | |||||
| 76 | 76 | ||||
| 77 | - name: go build (Linux minor architectures) | 77 | - name: go build (Linux minor architectures) | ||
| 78 | # Test them only on the latest Go to reduce CI time. | 78 | # Test them only on the latest Go to reduce CI time. | ||
| 79 | - | # s390x cannot be tested here as it requires Cgo. | 79 | + | if: startsWith(matrix.go, '1.27.') |
| 80 | - | if: startsWith(matrix.go, '1.26.') | |||
| 81 | run: | | 80 | run: | | ||
| 82 | # Check cross-compiling Linux binaries for minor architectures. | 81 | # Check cross-compiling Linux binaries for minor architectures. | ||
| 83 | env GOOS=linux GOARCH=loong64 go build -v ./... | 82 | env GOOS=linux GOARCH=loong64 go build -v ./... | ||
| 84 | env GOOS=linux GOARCH=ppc64le go build -v ./... | 83 | env GOOS=linux GOARCH=ppc64le go build -v ./... | ||
| 85 | env GOOS=linux GOARCH=riscv64 go build -v ./... | 84 | env GOOS=linux GOARCH=riscv64 go build -v ./... | ||
| 85 | + | env GOOS=linux GOARCH=s390x go build -v ./... | |||
| 86 | + | ||||
| 87 | + | - name: go build (Linux mips, Cgo) | |||
| 88 | + | # mips builds only via the Cgo fallback (CGO_ENABLED=1). See #460. | |||
| 89 | + | if: runner.os == 'Linux' | |||
| 90 | + | run: | | |||
| 91 | + | sudo apt-get update | |||
| 92 | + | sudo apt-get install -y gcc-mips-linux-gnu | |||
| 93 | + | env GOOS=linux GOARCH=mips CGO_ENABLED=1 CC=mips-linux-gnu-gcc go build -v ./... | |||
| 86 | 94 | ||||
| 87 | - name: go build (plugin) | 95 | - name: go build (plugin) | ||
| 88 | if: runner.os == 'Linux' || runner.os == 'macOS' | 96 | if: runner.os == 'Linux' || runner.os == 'macOS' | ||
| @@ -120,32 +128,68 @@ jobs: | |||||
| 120 | if: runner.os == 'Windows' | 128 | if: runner.os == 'Windows' | ||
| 121 | run: | | 129 | run: | | ||
| 122 | env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./... | 130 | env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./... | ||
| 131 | + | ||||
| 132 | + | - name: go test (Windows 386, Cgo) | |||
| 133 | + | if: runner.os == 'Windows' && runner.arch != 'ARM64' | |||
| 134 | + | run: | | |||
| 123 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | 135 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | ||
| 124 | 136 | ||||
| 137 | + | - name: go test (Linux 386) | |||
| 138 | + | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' }} | |||
| 139 | + | run: | | |||
| 140 | + | sudo apt-get update | |||
| 141 | + | sudo apt-get install gcc-multilib | |||
| 142 | + | sudo apt-get install g++-multilib | |||
| 143 | + | env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./... | |||
| 144 | + | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | |||
| 145 | + | ||||
| 125 | - name: go test (Linux arm) | 146 | - name: go test (Linux arm) | ||
| 126 | if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} | 147 | if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} | ||
| 127 | run: | | 148 | run: | | ||
| 128 | - | sudo apt-get update | 149 | + | sudo apt-get update |
| 129 | - | sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross | 150 | + | sudo apt-get install -y \ |
| 130 | - | 151 | + | gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross \ | |
| 131 | - | # Install QEMU 10.x from Debian trixie. Ubuntu 24.04 ships QEMU 8.2 | 152 | + | gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libc6-armel-cross |
| 132 | - | # which has a bug where inter-thread signal delivery (tgkill) hangs, | 153 | + | |
| 133 | - | # causing TestSetuidEtc to deadlock. | 154 | + | # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2 |
| 134 | - | wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user_${QEMU_VERSION}_arm64.deb -O /tmp/qemu-user.deb | 155 | + | # which has a bug where inter-thread signal delivery (tgkill) hangs, |
| 135 | - | wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_${QEMU_VERSION}_arm64.deb -O /tmp/qemu-user-binfmt.deb | 156 | + | # causing TestSetuidEtc to deadlock. |
| 136 | - | sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.deb | 157 | + | arch=$(dpkg --print-architecture) |
| 137 | - | sudo apt-get -f install -y | 158 | + | qemu_user_deb=$( |
| 138 | - | 159 | + | curl -fsSL https://ftp.debian.org/debian/pool/main/q/qemu/ | | |
| 139 | - | # Create symlink for the dynamic linker that QEMU expects | 160 | + | grep -Eo "qemu-user_${QEMU_MAJOR_VERSION}\.[0-9.]+\+ds-[0-9]+([+~][A-Za-z0-9.+:~-]+)*_${arch}\.deb" | |
| 140 | - | sudo ln -sf /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3 | 161 | + | sort -V | |
| 141 | - | go env -w CC=arm-linux-gnueabihf-gcc | 162 | + | tail -n 1 |
| 142 | - | go env -w CXX=arm-linux-gnueabihf-g++ | 163 | + | ) |
| 143 | - | env GOOS=linux GOARCH=arm CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | 164 | + | if [ -z "${qemu_user_deb}" ]; then |
| 144 | - | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | 165 | + | echo "missing qemu-user ${QEMU_MAJOR_VERSION}.x package for ${arch}" >&2 |
| 145 | - | env GOOS=linux GOARCH=arm CGO_ENABLED=1 go test -c -o=purego-test-cgo . | 166 | + | exit 1 |
| 146 | - | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | 167 | + | fi |
| 147 | - | go env -u CC | 168 | + | wget -q "https://ftp.debian.org/debian/pool/main/q/qemu/${qemu_user_deb}" -O /tmp/qemu-user.deb |
| 148 | - | go env -u CXX | 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 | - name: go test race (no Cgo) | 194 | - name: go test race (no Cgo) | ||
| 151 | if: runner.os == 'macOS' | 195 | if: runner.os == 'macOS' | ||
| @@ -154,6 +198,7 @@ jobs: | |||||
| 154 | env CGO_ENABLED=0 go test -race -shuffle=on -v -count=10 ./... | 198 | env CGO_ENABLED=0 go test -race -shuffle=on -v -count=10 ./... | ||
| 155 | 199 | ||||
| 156 | - name: go test race (Cgo) | 200 | - name: go test race (Cgo) | ||
| 201 | + | if: ${{ !(runner.os == 'Windows' && runner.arch == 'ARM64') }} | |||
| 157 | run: | | 202 | run: | | ||
| 158 | env CGO_ENABLED=1 go test -race -shuffle=on -v -count=10 ./... | 203 | env CGO_ENABLED=1 go test -race -shuffle=on -v -count=10 ./... | ||
| 159 | 204 | ||||
| @@ -161,9 +206,7 @@ jobs: | |||||
| 161 | strategy: | 206 | strategy: | ||
| 162 | fail-fast: false | 207 | fail-fast: false | ||
| 163 | matrix: | 208 | matrix: | ||
| 164 | - | # Test only the latest two Go versions to save CI time. | 209 | + | go: ['1.25.x', '1.26.x', '1.27.x'] |
| 165 | - | # See https://go.dev/doc/devel/release#policy | |||
| 166 | - | go: ['1.25.x', '1.26.x'] | |||
| 167 | name: Test with Go ${{ matrix.go }} on Linux minor architectures | 210 | name: Test with Go ${{ matrix.go }} on Linux minor architectures | ||
| 168 | runs-on: ubuntu-latest | 211 | runs-on: ubuntu-latest | ||
| 169 | defaults: | 212 | defaults: | ||
| @@ -187,12 +230,22 @@ jobs: | |||||
| 187 | gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \ | 230 | gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \ | ||
| 188 | gcc-s390x-linux-gnu g++-s390x-linux-gnu | 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 | # which has a bug where inter-thread signal delivery (tgkill) hangs, | 234 | # which has a bug where inter-thread signal delivery (tgkill) hangs, | ||
| 192 | # causing TestSetuidEtc to deadlock. | 235 | # causing TestSetuidEtc to deadlock. | ||
| 193 | - | wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user_${QEMU_VERSION}_amd64.deb -O /tmp/qemu-user.deb | 236 | + | arch=$(dpkg --print-architecture) |
| 194 | - | wget -q https://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_${QEMU_VERSION}_amd64.deb -O /tmp/qemu-user-binfmt.deb | 237 | + | qemu_user_deb=$( |
| 195 | - | sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.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 | sudo apt-get -f install -y | 249 | sudo apt-get -f install -y | ||
| 197 | 250 | ||||
| 198 | # Verify that the expected QEMU binaries are installed and usable. | 251 | # Verify that the expected QEMU binaries are installed and usable. | ||
| @@ -240,6 +293,11 @@ jobs: | |||||
| 240 | run: | | 293 | run: | | ||
| 241 | go env -w CC=s390x-linux-gnu-gcc | 294 | go env -w CC=s390x-linux-gnu-gcc | ||
| 242 | go env -w CXX=s390x-linux-gnu-g++ | 295 | go env -w CXX=s390x-linux-gnu-g++ | ||
| 296 | + | # CGO_ENABLED=0 is only supported on Go 1.27+ for s390x. | |||
| 297 | + | if [[ "${{ matrix.go }}" != 1.25.* && "${{ matrix.go }}" != 1.26.* ]]; then | |||
| 298 | + | env GOOS=linux GOARCH=s390x CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |||
| 299 | + | env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |||
| 300 | + | fi | |||
| 243 | env GOOS=linux GOARCH=s390x CGO_ENABLED=1 go test -c -o=purego-test-cgo . | 301 | env GOOS=linux GOARCH=s390x CGO_ENABLED=1 go test -c -o=purego-test-cgo . | ||
| 244 | env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | 302 | env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | ||
| 245 | go env -u CC | 303 | go env -u CC | ||
| @@ -250,8 +308,6 @@ jobs: | |||||
| 250 | fail-fast: false | 308 | fail-fast: false | ||
| 251 | matrix: | 309 | matrix: | ||
| 252 | os: ['FreeBSD', 'NetBSD'] | 310 | os: ['FreeBSD', 'NetBSD'] | ||
| 253 | - | # Test only the latest two Go versions to save CI time. | |||
| 254 | - | # See https://go.dev/doc/devel/release#policy | |||
| 255 | go: ['1.25.8', '1.26.1'] | 311 | go: ['1.25.8', '1.26.1'] | ||
| 256 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | 312 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | ||
| 257 | runs-on: ubuntu-22.04 | 313 | runs-on: ubuntu-22.04 | ||
| @@ -281,3 +337,78 @@ jobs: | |||||
| 281 | rm -fr /usr/local/go && tar -C /usr/local -xf go${{matrix.go}}.netbsd-amd64.tar.gz | 337 | rm -fr /usr/local/go && tar -C /usr/local -xf go${{matrix.go}}.netbsd-amd64.tar.gz | ||
| 282 | chmod +x $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh | 338 | chmod +x $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh | ||
| 283 | run: $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh | 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 | A library for calling C functions from Go without Cgo. | 4 | A library for calling C functions from Go without Cgo. | ||
| 5 | 5 | ||||
| 6 | - | > Fork notice: this repository is a maintained fork of `github.com/ebitengine/purego` with additional ABI, callback, and generator-facing fixes needed by the PureGoTK stack. | |||
| 7 | - | ||||
| 8 | > This is beta software so expect bugs and potentially API breaking changes | 6 | > This is beta software so expect bugs and potentially API breaking changes | ||
| 9 | > but each release will be tagged to avoid breaking people's code. | 7 | > but each release will be tagged to avoid breaking people's code. | ||
| 10 | > Bug reports are encouraged. | 8 | > Bug reports are encouraged. | ||
| @@ -36,24 +34,27 @@ Tier 1 platforms are the primary targets officially supported by PureGo. When a | |||||
| 36 | - **iOS**: amd64<sup>1</sup>, arm64<sup>1</sup> | 34 | - **iOS**: amd64<sup>1</sup>, arm64<sup>1</sup> | ||
| 37 | - **Linux**: amd64, arm64 | 35 | - **Linux**: amd64, arm64 | ||
| 38 | - **macOS**: amd64, arm64 | 36 | - **macOS**: amd64, arm64 | ||
| 39 | - | - **Windows**: amd64, arm64 | 37 | + | - **Windows**: amd64<sup>2</sup>, arm64<sup>2</sup> |
| 40 | 38 | ||||
| 41 | ### Tier 2 | 39 | ### Tier 2 | ||
| 42 | 40 | ||||
| 43 | Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs on Tier 2 platforms do not block new PureGo releases. However, fixes contributed by external contributors are very welcome and encouraged. | 41 | Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs on Tier 2 platforms do not block new PureGo releases. However, fixes contributed by external contributors are very welcome and encouraged. | ||
| 44 | 42 | ||||
| 45 | - | - **Android**: 386<sup>1</sup>, arm<sup>1</sup> | 43 | + | - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup> |
| 46 | - | - **FreeBSD**: amd64<sup>2</sup>, arm64<sup>2</sup> | 44 | + | - **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> |
| 47 | - | - **Linux**: 386, arm, loong64, ppc64le, riscv64, s390x<sup>1</sup> | 45 | + | - **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>2</sup>, ppc64le<sup>2</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup> |
| 48 | - | - **NetBSD**: amd64<sup>2</sup>, arm64<sup>2</sup> | 46 | + | - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> |
| 49 | - | - **Windows**: 386<sup>3</sup>, arm<sup>3,4</sup> | 47 | + | - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup> |
| 50 | 48 | ||||
| 51 | #### Support Notes | 49 | #### Support Notes | ||
| 52 | 50 | ||||
| 53 | 1. These architectures require CGO_ENABLED=1 to compile | 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 | 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` |
| 55 | - | 3. These architectures only support `SyscallN` and `NewCallback` | 53 | + | 3. These architectures do not support passing structs by value as arguments or return values |
| 56 | - | 4. These architectures are no longer supported as of Go 1.26 | 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 | ## Example | 59 | ## Example | ||
| 59 | 60 | ||||
| @@ -101,21 +102,9 @@ exhaustion on Unix-like platforms, set `PUREGO_CALLBACK_LEDGER=1` to append JSON | |||||
| 101 | callback lifecycle events to `/tmp/purego-callback-ledger-<pid>.jsonl`, or set | 102 | callback lifecycle events to `/tmp/purego-callback-ledger-<pid>.jsonl`, or set | ||
| 102 | `PUREGO_CALLBACK_LEDGER_FILE=/path/to/file.jsonl` to choose the output file. Stack | 103 | `PUREGO_CALLBACK_LEDGER_FILE=/path/to/file.jsonl` to choose the output file. Stack | ||
| 103 | capture is also disabled by default; set `PUREGO_CALLBACK_LEDGER_STACK=1` when call | 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 | + | site attribution is needed. For ad-hoc stderr/file traces near callback exhaustion, |
| 105 | - | reserved for active debugging sessions. For ad-hoc stderr/file traces near callback | 106 | + | set `PUREGO_CALLBACK_TRACE=1`; `PUREGO_CALLBACK_TRACE_FILE` overrides the default |
| 106 | - | exhaustion, set `PUREGO_CALLBACK_TRACE=1`; `PUREGO_CALLBACK_TRACE_FILE` overrides | 107 | + | `/tmp/purego-callback-trace-<pid>.log` path. |
| 107 | - | the default `/tmp/purego-callback-trace-<pid>.log` path. | |||
| 108 | - | ||||
| 109 | - | Diagnostic files are created with `0o600` permissions. They are append-only and | |||
| 110 | - | unbounded, so long-running processes can exhaust disk space if diagnostics stay | |||
| 111 | - | enabled. A ledger line is a JSON object such as: | |||
| 112 | - | ||||
| 113 | - | ```json | |||
| 114 | - | {"time":"2026-06-09T14:03:00Z","pid":1234,"seq":42,"event":"alloc","index":7,"addr":"0x1234","stack":"..."} | |||
| 115 | - | ``` | |||
| 116 | - | ||||
| 117 | - | Use `time` as the timestamp, `index` as the callback ID, and the `pid`, `seq`, | |||
| 118 | - | `event`, `addr`, and optional `stack` fields when parsing the ledger output. | |||
| 119 | 108 | ||||
| 120 | ## Questions | 109 | ## Questions | ||
| 121 | 110 | ||||
| @@ -130,7 +119,6 @@ This is a list of the copied files: | |||||
| 130 | 119 | ||||
| 131 | * `abi_*.h` from package `runtime/cgo` | 120 | * `abi_*.h` from package `runtime/cgo` | ||
| 132 | * `wincallback.go` from package `runtime` | 121 | * `wincallback.go` from package `runtime` | ||
| 133 | - | * `zcallback_darwin_*.s` from package `runtime` | |||
| 134 | * `internal/fakecgo/abi_*.h` from package `runtime/cgo` | 122 | * `internal/fakecgo/abi_*.h` from package `runtime/cgo` | ||
| 135 | * `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo` | 123 | * `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo` | ||
| 136 | * `internal/fakecgo/callbacks.go` from package `runtime/cgo` | 124 | * `internal/fakecgo/callbacks.go` from package `runtime/cgo` | ||
M callback_test.go +102 -9
| @@ -22,7 +22,7 @@ func TestCallGoFromSharedLib(t *testing.T) { | |||||
| 22 | libFileName := filepath.Join(t.TempDir(), "libcbtest.so") | 22 | libFileName := filepath.Join(t.TempDir(), "libcbtest.so") | ||
| 23 | t.Logf("Build %v", libFileName) | 23 | t.Logf("Build %v", libFileName) | ||
| 24 | 24 | ||||
| 25 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil { | 25 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil { |
| 26 | t.Fatal(err) | 26 | t.Fatal(err) | ||
| 27 | } | 27 | } | ||
| 28 | defer os.Remove(libFileName) | 28 | defer os.Remove(libFileName) | ||
| @@ -43,7 +43,7 @@ func TestCallGoFromSharedLib(t *testing.T) { | |||||
| 43 | 43 | ||||
| 44 | const want = 10101 | 44 | const want = 10101 | ||
| 45 | cb := purego.NewCallback(goFunc) | 45 | cb := purego.NewCallback(goFunc) | ||
| 46 | - | for i := 0; i < 10; i++ { | 46 | + | for i := range 10 { |
| 47 | got := callCallback(cb, "a test string") | 47 | got := callCallback(cb, "a test string") | ||
| 48 | if got != want { | 48 | if got != want { | ||
| 49 | t.Fatalf("%d: callCallback() got %v want %v", i, got, want) | 49 | t.Fatalf("%d: callCallback() got %v want %v", i, got, want) | ||
| @@ -57,7 +57,7 @@ func TestCallbackStringConversion(t *testing.T) { | |||||
| 57 | libFileName := filepath.Join(t.TempDir(), "libcbtest.so") | 57 | libFileName := filepath.Join(t.TempDir(), "libcbtest.so") | ||
| 58 | t.Logf("Build %v", libFileName) | 58 | t.Logf("Build %v", libFileName) | ||
| 59 | 59 | ||||
| 60 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil { | 60 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil { |
| 61 | t.Fatal(err) | 61 | t.Fatal(err) | ||
| 62 | } | 62 | } | ||
| 63 | defer os.Remove(libFileName) | 63 | defer os.Remove(libFileName) | ||
| @@ -191,6 +191,53 @@ func TestNewCallbackFloat32AndFloat64(t *testing.T) { | |||||
| 191 | } | 191 | } | ||
| 192 | } | 192 | } | ||
| 193 | 193 | ||||
| 194 | + | func TestNewCallbackInt64Result(t *testing.T) { | |||
| 195 | + | // A 64-bit result needs two words: EDX:EAX on 386 and R1:R0 on arm. | |||
| 196 | + | // The trampoline must forward both halves of the value. | |||
| 197 | + | const mask = int64(0x0123456789abcdef) | |||
| 198 | + | imp := purego.NewCallback(func(v int64) int64 { return v ^ mask }) | |||
| 199 | + | var fn func(v int64) int64 | |||
| 200 | + | purego.RegisterFunc(&fn, imp) | |||
| 201 | + | for _, v := range []int64{ | |||
| 202 | + | 0, | |||
| 203 | + | -1, | |||
| 204 | + | 1 << 32, | |||
| 205 | + | -0x7edcba9876543210, | |||
| 206 | + | } { | |||
| 207 | + | if got, want := fn(v), v^mask; got != want { | |||
| 208 | + | t.Errorf("callback(%#x) = %#x, want %#x", uint64(v), uint64(got), uint64(want)) | |||
| 209 | + | } | |||
| 210 | + | } | |||
| 211 | + | } | |||
| 212 | + | ||||
| 213 | + | func TestNewCallbackUint64Result(t *testing.T) { | |||
| 214 | + | // The upper half of a uint64 result must not be dropped by the trampoline. | |||
| 215 | + | imp := purego.NewCallback(func(lo, hi uint32) uint64 { return uint64(hi)<<32 | uint64(lo) }) | |||
| 216 | + | var fn func(lo, hi uint32) uint64 | |||
| 217 | + | purego.RegisterFunc(&fn, imp) | |||
| 218 | + | const want = uint64(0x0123456789abcdef) | |||
| 219 | + | if got := fn(0x89abcdef, 0x01234567); got != want { | |||
| 220 | + | t.Errorf("callback() = %#x, want %#x", got, want) | |||
| 221 | + | } | |||
| 222 | + | } | |||
| 223 | + | ||||
| 224 | + | func TestNewCallbackInt64ResultWithStackArgs(t *testing.T) { | |||
| 225 | + | // On 386 all the arguments are passed on the stack, so this also checks | |||
| 226 | + | // that the copied arguments do not overlap the result of the callback. | |||
| 227 | + | // The number of arguments is kept small enough for ppc64le, which only | |||
| 228 | + | // has maxArgs(15) slots in total, 8 of them in integer registers. | |||
| 229 | + | const wantSum = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 | |||
| 230 | + | imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 int) int64 { | |||
| 231 | + | return int64(a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12)<<32 | 0x0badf00d | |||
| 232 | + | }) | |||
| 233 | + | var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 int) int64 | |||
| 234 | + | purego.RegisterFunc(&fn, imp) | |||
| 235 | + | const want = int64(wantSum)<<32 | 0x0badf00d | |||
| 236 | + | if got := fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); got != want { | |||
| 237 | + | t.Errorf("callback() = %#x, want %#x", uint64(got), uint64(want)) | |||
| 238 | + | } | |||
| 239 | + | } | |||
| 240 | + | ||||
| 194 | func ExampleNewCallback() { | 241 | func ExampleNewCallback() { | ||
| 195 | cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int) int { | 242 | cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int) int { | ||
| 196 | fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) | 243 | fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) | ||
| @@ -223,7 +270,7 @@ func TestCallbackInt32Packing(t *testing.T) { | |||||
| 223 | } | 270 | } | ||
| 224 | 271 | ||||
| 225 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | 272 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | ||
| 226 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { | 273 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { |
| 227 | t.Fatal(err) | 274 | t.Fatal(err) | ||
| 228 | } | 275 | } | ||
| 229 | defer os.Remove(libFileName) | 276 | defer os.Remove(libFileName) | ||
| @@ -255,7 +302,7 @@ func TestCallbackMixedStackPacking(t *testing.T) { | |||||
| 255 | } | 302 | } | ||
| 256 | 303 | ||||
| 257 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | 304 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | ||
| 258 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { | 305 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { |
| 259 | t.Fatal(err) | 306 | t.Fatal(err) | ||
| 260 | } | 307 | } | ||
| 261 | defer os.Remove(libFileName) | 308 | defer os.Remove(libFileName) | ||
| @@ -288,7 +335,7 @@ func TestCallbackSmallTypesPacking(t *testing.T) { | |||||
| 288 | } | 335 | } | ||
| 289 | 336 | ||||
| 290 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | 337 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | ||
| 291 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { | 338 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { |
| 292 | t.Fatal(err) | 339 | t.Fatal(err) | ||
| 293 | } | 340 | } | ||
| 294 | defer os.Remove(libFileName) | 341 | defer os.Remove(libFileName) | ||
| @@ -356,7 +403,7 @@ func TestCallback10Int32Packing(t *testing.T) { | |||||
| 356 | } | 403 | } | ||
| 357 | 404 | ||||
| 358 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | 405 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | ||
| 359 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { | 406 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { |
| 360 | t.Fatal(err) | 407 | t.Fatal(err) | ||
| 361 | } | 408 | } | ||
| 362 | defer os.Remove(libFileName) | 409 | defer os.Remove(libFileName) | ||
| @@ -387,7 +434,7 @@ func TestCallbackFloat64StackPacking(t *testing.T) { | |||||
| 387 | } | 434 | } | ||
| 388 | 435 | ||||
| 389 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | 436 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | ||
| 390 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { | 437 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { |
| 391 | t.Fatal(err) | 438 | t.Fatal(err) | ||
| 392 | } | 439 | } | ||
| 393 | defer os.Remove(libFileName) | 440 | defer os.Remove(libFileName) | ||
| @@ -421,7 +468,7 @@ func TestCallbackFloat32StackPacking(t *testing.T) { | |||||
| 421 | } | 468 | } | ||
| 422 | 469 | ||||
| 423 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | 470 | libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so") | ||
| 424 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { | 471 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil { |
| 425 | t.Fatal(err) | 472 | t.Fatal(err) | ||
| 426 | } | 473 | } | ||
| 427 | defer os.Remove(libFileName) | 474 | defer os.Remove(libFileName) | ||
| @@ -448,3 +495,49 @@ func TestCallbackFloat32StackPacking(t *testing.T) { | |||||
| 448 | t.Errorf("callCallback12Float32() = %d, want %d", got, want) | 495 | t.Errorf("callCallback12Float32() = %d, want %d", got, want) | ||
| 449 | } | 496 | } | ||
| 450 | } | 497 | } | ||
| 498 | + | ||||
| 499 | + | func TestRegisterFuncExpandFinalAnyArgument(t *testing.T) { | |||
| 500 | + | // The last argument is expanded into the C arguments held in it, | |||
| 501 | + | // whether it is declared as ...any or as []any (#506). | |||
| 502 | + | cb := purego.NewCallback(func(a, b, c uintptr) uintptr { | |||
| 503 | + | return a*100 + b*10 + c | |||
| 504 | + | }) | |||
| 505 | + | const want = uintptr(123) | |||
| 506 | + | t.Run("Variadic", func(t *testing.T) { | |||
| 507 | + | var fn func(a uintptr, args ...any) uintptr | |||
| 508 | + | purego.RegisterFunc(&fn, cb) | |||
| 509 | + | if got := fn(1, uintptr(2), uintptr(3)); got != want { | |||
| 510 | + | t.Errorf("func(uintptr, ...any) = %d, want %d", got, want) | |||
| 511 | + | } | |||
| 512 | + | }) | |||
| 513 | + | t.Run("Slice", func(t *testing.T) { | |||
| 514 | + | var fn func(a uintptr, args []any) uintptr | |||
| 515 | + | purego.RegisterFunc(&fn, cb) | |||
| 516 | + | if got := fn(1, []any{uintptr(2), uintptr(3)}); got != want { | |||
| 517 | + | t.Errorf("func(uintptr, []any) = %d, want %d", got, want) | |||
| 518 | + | } | |||
| 519 | + | }) | |||
| 520 | + | } | |||
| 521 | + | ||||
| 522 | + | func TestRegisterFuncKeepNonFinalAnyArgument(t *testing.T) { | |||
| 523 | + | // A []any that isn't the last argument is a regular argument: | |||
| 524 | + | // it is passed as one value and must not panic (#506). | |||
| 525 | + | var first any | |||
| 526 | + | var second uintptr | |||
| 527 | + | cb := purego.NewCallback(func(args *any, b uintptr) uintptr { | |||
| 528 | + | first = *args | |||
| 529 | + | second = b | |||
| 530 | + | return 0 | |||
| 531 | + | }) | |||
| 532 | + | var fn func(args []any, b uintptr) uintptr | |||
| 533 | + | purego.RegisterFunc(&fn, cb) | |||
| 534 | + | s := []any{uintptr(2), uintptr(3)} | |||
| 535 | + | fn(s, 7) | |||
| 536 | + | runtime.KeepAlive(s) | |||
| 537 | + | if want := any(uintptr(2)); first != want { | |||
| 538 | + | t.Errorf("the first C argument = %v, want %v", first, want) | |||
| 539 | + | } | |||
| 540 | + | if second != 7 { | |||
| 541 | + | t.Errorf("the second C argument = %d, want 7", second) | |||
| 542 | + | } | |||
| 543 | + | } | |||
A cdecl.go +11 -0
| @@ -0,0 +1,11 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package purego | |||
| 5 | + | ||||
| 6 | + | // CDecl marks a function as being called using the __cdecl calling convention as defined in | |||
| 7 | + | // the [MSDocs] when passed to NewCallback. It must be the first argument to the function. | |||
| 8 | + | // This is only useful on 386 Windows, but it is safe to use on other platforms. | |||
| 9 | + | // | |||
| 10 | + | // [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 | |||
| 11 | + | type CDecl struct{} | |||
A cstrings/nsstrings_darwin.go +44 -0
| @@ -0,0 +1,44 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package cstrings | |||
| 5 | + | ||||
| 6 | + | import ( | |||
| 7 | + | "fmt" | |||
| 8 | + | ||||
| 9 | + | "github.com/bnema/purego" | |||
| 10 | + | "github.com/bnema/purego/internal/strings" | |||
| 11 | + | "github.com/bnema/purego/objc" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | var ( | |||
| 15 | + | sel_isKindOf objc.SEL | |||
| 16 | + | sel_UTF8String objc.SEL | |||
| 17 | + | ||||
| 18 | + | class_NSString objc.Class | |||
| 19 | + | ) | |||
| 20 | + | ||||
| 21 | + | func init() { | |||
| 22 | + | // Must pull in Foundation to get the NSString class. | |||
| 23 | + | _, err := purego.Dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", purego.RTLD_GLOBAL|purego.RTLD_NOW) | |||
| 24 | + | if err != nil { | |||
| 25 | + | panic(fmt.Errorf("cstrings: %w", err)) | |||
| 26 | + | } | |||
| 27 | + | sel_isKindOf = objc.RegisterName("isKindOfClass:") | |||
| 28 | + | sel_UTF8String = objc.RegisterName("UTF8String") | |||
| 29 | + | class_NSString = objc.GetClass("NSString") | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | // NSStringToString returns a copy of the NSString contents as a Go string. | |||
| 33 | + | // If the ID is 0 then an empty string is returned. | |||
| 34 | + | // If the ID is not an NSString class then the function panics. | |||
| 35 | + | // This function is only available on darwin. | |||
| 36 | + | func NSStringToString(str objc.ID) string { | |||
| 37 | + | if str == 0 { | |||
| 38 | + | return "" | |||
| 39 | + | } | |||
| 40 | + | if str.Send(sel_isKindOf, class_NSString) == 0 { | |||
| 41 | + | panic("cstrings: provided ID is not an NSString") | |||
| 42 | + | } | |||
| 43 | + | return strings.GoString(uintptr(str.Send(sel_UTF8String))) | |||
| 44 | + | } | |||
A cstrings/nsstrings_darwin_test.go +54 -0
| @@ -0,0 +1,54 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package cstrings_test | |||
| 5 | + | ||||
| 6 | + | import ( | |||
| 7 | + | "testing" | |||
| 8 | + | ||||
| 9 | + | "github.com/bnema/purego/cstrings" | |||
| 10 | + | "github.com/bnema/purego/objc" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | func TestNSStringToString(t *testing.T) { | |||
| 14 | + | t.Run("nil ID returns empty string", func(t *testing.T) { | |||
| 15 | + | result := cstrings.NSStringToString(0) | |||
| 16 | + | if result != "" { | |||
| 17 | + | t.Errorf("expected empty string for nil ID, got %q", result) | |||
| 18 | + | } | |||
| 19 | + | }) | |||
| 20 | + | ||||
| 21 | + | t.Run("valid NSString returns correct string", func(t *testing.T) { | |||
| 22 | + | sel := objc.RegisterName("stringWithUTF8String:") | |||
| 23 | + | nsString := objc.ID(objc.GetClass("NSString")).Send(sel, "Hello, World!\x00") | |||
| 24 | + | ||||
| 25 | + | result := cstrings.NSStringToString(nsString) | |||
| 26 | + | if result != "Hello, World!" { | |||
| 27 | + | t.Errorf("expected %q, got %q", "Hello, World!", result) | |||
| 28 | + | } | |||
| 29 | + | }) | |||
| 30 | + | ||||
| 31 | + | t.Run("empty NSString returns empty string", func(t *testing.T) { | |||
| 32 | + | sel := objc.RegisterName("stringWithUTF8String:") | |||
| 33 | + | nsString := objc.ID(objc.GetClass("NSString")).Send(sel, "\x00") | |||
| 34 | + | ||||
| 35 | + | result := cstrings.NSStringToString(nsString) | |||
| 36 | + | if result != "" { | |||
| 37 | + | t.Errorf("expected empty string, got %q", result) | |||
| 38 | + | } | |||
| 39 | + | }) | |||
| 40 | + | ||||
| 41 | + | t.Run("non-NSString panics", func(t *testing.T) { | |||
| 42 | + | defer func() { | |||
| 43 | + | if r := recover(); r == nil { | |||
| 44 | + | t.Error("expected panic for non-NSString ID") | |||
| 45 | + | } | |||
| 46 | + | }() | |||
| 47 | + | ||||
| 48 | + | classNSNumber := objc.GetClass("NSNumber") | |||
| 49 | + | sel := objc.RegisterName("numberWithInt:") | |||
| 50 | + | nsNumber := objc.ID(classNSNumber).Send(sel, 42) | |||
| 51 | + | ||||
| 52 | + | cstrings.NSStringToString(nsNumber) | |||
| 53 | + | }) | |||
| 54 | + | } | |||
M dlfcn_test.go +31 -1
| @@ -8,6 +8,7 @@ package purego_test | |||||
| 8 | import ( | 8 | import ( | ||
| 9 | "os" | 9 | "os" | ||
| 10 | "path/filepath" | 10 | "path/filepath" | ||
| 11 | + | "syscall" | |||
| 11 | "testing" | 12 | "testing" | ||
| 12 | "unsafe" | 13 | "unsafe" | ||
| 13 | 14 | ||||
| @@ -24,7 +25,7 @@ func TestNestedDlopenCall(t *testing.T) { | |||||
| 24 | libFileName := filepath.Join(t.TempDir(), "libdlnested.so") | 25 | libFileName := filepath.Join(t.TempDir(), "libdlnested.so") | ||
| 25 | t.Logf("Build %v", libFileName) | 26 | t.Logf("Build %v", libFileName) | ||
| 26 | 27 | ||||
| 27 | - | if err := buildSharedLib("CXX", libFileName, filepath.Join("testdata", "libdlnested", "nested_test.cpp")); err != nil { | 28 | + | if err := buildSharedLib(t, "CXX", libFileName, filepath.Join("testdata", "libdlnested", "nested_test.cpp")); err != nil { |
| 28 | t.Fatal(err) | 29 | t.Fatal(err) | ||
| 29 | } | 30 | } | ||
| 30 | defer os.Remove(libFileName) | 31 | defer os.Remove(libFileName) | ||
| @@ -48,3 +49,32 @@ func TestSyscallN(t *testing.T) { | |||||
| 48 | t.Fatalf("SyscallN didn't return the same result as purego.Dlsym: %d", err2) | 49 | t.Fatalf("SyscallN didn't return the same result as purego.Dlsym: %d", err2) | ||
| 49 | } | 50 | } | ||
| 50 | } | 51 | } | ||
| 52 | + | ||||
| 53 | + | func TestSyscallNErrnoIsNotAnInputArgument(t *testing.T) { | |||
| 54 | + | if errnoIsCaptured() { | |||
| 55 | + | t.Skip("this platform saves errno; see TestErrno") | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | openSym, err := purego.Dlsym(purego.RTLD_DEFAULT, "open") | |||
| 59 | + | if err != nil { | |||
| 60 | + | t.Fatal(err) | |||
| 61 | + | } | |||
| 62 | + | ||||
| 63 | + | path, err := syscall.BytePtrFromString("_file_that_does_not_exist_") | |||
| 64 | + | if err != nil { | |||
| 65 | + | t.Fatal(err) | |||
| 66 | + | } | |||
| 67 | + | ||||
| 68 | + | // The third argument is non-zero so that echoing it back as errno is visible. | |||
| 69 | + | const third = uintptr(0o600) | |||
| 70 | + | r1, _, errno := purego.SyscallN(openSym, | |||
| 71 | + | uintptr(unsafe.Pointer(path)), | |||
| 72 | + | uintptr(os.O_RDWR), | |||
| 73 | + | third) | |||
| 74 | + | if int32(r1) != -1 { | |||
| 75 | + | t.Fatalf("open returned %d, wanted -1", r1) | |||
| 76 | + | } | |||
| 77 | + | if errno != 0 { | |||
| 78 | + | t.Errorf("SyscallN returned %d as errno where errno is not captured, wanted 0", errno) | |||
| 79 | + | } | |||
| 80 | + | } | |||
M examples/objc/main_darwin.go +2 -2
| @@ -18,7 +18,7 @@ var ( | |||||
| 18 | ) | 18 | ) | ||
| 19 | 19 | ||||
| 20 | func BarInit(id objc.ID, cmd objc.SEL) objc.ID { | 20 | func BarInit(id objc.ID, cmd objc.SEL) objc.ID { | ||
| 21 | - | return id.SendSuper(cmd) | 21 | + | return id.SendSuper2(objc.GetClass("BarObject"), cmd) |
| 22 | } | 22 | } | ||
| 23 | 23 | ||||
| 24 | func main() { | 24 | func main() { | ||
| @@ -36,7 +36,7 @@ func main() { | |||||
| 36 | []objc.FieldDef{ | 36 | []objc.FieldDef{ | ||
| 37 | { | 37 | { | ||
| 38 | Name: "bar", | 38 | Name: "bar", | ||
| 39 | - | Type: reflect.TypeOf(int(0)), | 39 | + | Type: reflect.TypeFor[int](), |
| 40 | Attribute: objc.ReadWrite, | 40 | Attribute: objc.ReadWrite, | ||
| 41 | }, | 41 | }, | ||
| 42 | }, | 42 | }, | ||
M examples/window/main_darwin.go +5 -1
| @@ -5,6 +5,7 @@ package main | |||||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | "runtime" | 7 | "runtime" | ||
| 8 | + | "structs" | |||
| 8 | 9 | ||||
| 9 | "github.com/bnema/purego" | 10 | "github.com/bnema/purego" | ||
| 10 | "github.com/bnema/purego/objc" | 11 | "github.com/bnema/purego/objc" | ||
| @@ -17,14 +18,17 @@ const ( | |||||
| 17 | ) | 18 | ) | ||
| 18 | 19 | ||||
| 19 | type NSPoint struct { | 20 | type NSPoint struct { | ||
| 21 | + | _ structs.HostLayout | |||
| 20 | X, Y float64 | 22 | X, Y float64 | ||
| 21 | } | 23 | } | ||
| 22 | 24 | ||||
| 23 | type NSSize struct { | 25 | type NSSize struct { | ||
| 26 | + | _ structs.HostLayout | |||
| 24 | Width, Height float64 | 27 | Width, Height float64 | ||
| 25 | } | 28 | } | ||
| 26 | 29 | ||||
| 27 | type NSRect struct { | 30 | type NSRect struct { | ||
| 31 | + | _ structs.HostLayout | |||
| 28 | Origin NSPoint | 32 | Origin NSPoint | ||
| 29 | Size NSSize | 33 | Size NSSize | ||
| 30 | } | 34 | } | ||
| @@ -56,5 +60,5 @@ func main() { | |||||
| 56 | } | 60 | } | ||
| 57 | 61 | ||||
| 58 | func NSMakeRect(x, y, width, height float64) NSRect { | 62 | func NSMakeRect(x, y, width, height float64) NSRect { | ||
| 59 | - | return NSRect{Origin: NSPoint{x, y}, Size: NSSize{width, height}} | 63 | + | return NSRect{Origin: NSPoint{X: x, Y: y}, Size: NSSize{Width: width, Height: height}} |
| 60 | } | 64 | } | ||
M examples/window/main_windows.go +5 -0
| @@ -5,6 +5,7 @@ package main | |||||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | "runtime" | 7 | "runtime" | ||
| 8 | + | "structs" | |||
| 8 | "syscall" | 9 | "syscall" | ||
| 9 | "unsafe" | 10 | "unsafe" | ||
| 10 | 11 | ||||
| @@ -30,6 +31,7 @@ type ( | |||||
| 30 | ) | 31 | ) | ||
| 31 | 32 | ||||
| 32 | type WNDCLASSEX struct { | 33 | type WNDCLASSEX struct { | ||
| 34 | + | _ structs.HostLayout | |||
| 33 | Size uint32 | 35 | Size uint32 | ||
| 34 | Style uint32 | 36 | Style uint32 | ||
| 35 | WndProc uintptr | 37 | WndProc uintptr | ||
| @@ -45,14 +47,17 @@ type WNDCLASSEX struct { | |||||
| 45 | } | 47 | } | ||
| 46 | 48 | ||||
| 47 | type RECT struct { | 49 | type RECT struct { | ||
| 50 | + | _ structs.HostLayout | |||
| 48 | Left, Top, Right, Bottom int32 | 51 | Left, Top, Right, Bottom int32 | ||
| 49 | } | 52 | } | ||
| 50 | 53 | ||||
| 51 | type POINT struct { | 54 | type POINT struct { | ||
| 55 | + | _ structs.HostLayout | |||
| 52 | X, Y int32 | 56 | X, Y int32 | ||
| 53 | } | 57 | } | ||
| 54 | 58 | ||||
| 55 | type MSG struct { | 59 | type MSG struct { | ||
| 60 | + | _ structs.HostLayout | |||
| 56 | Hwnd HWND | 61 | Hwnd HWND | ||
| 57 | Message uint32 | 62 | Message uint32 | ||
| 58 | WParam uintptr | 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 | "math" | 10 | "math" | ||
| 11 | "reflect" | 11 | "reflect" | ||
| 12 | "runtime" | 12 | "runtime" | ||
| 13 | + | "structs" | |||
| 14 | + | "sync" | |||
| 13 | "unsafe" | 15 | "unsafe" | ||
| 14 | 16 | ||||
| 15 | "github.com/bnema/purego/internal/strings" | 17 | "github.com/bnema/purego/internal/strings" | ||
| 16 | - | "github.com/bnema/purego/internal/xreflect" | |||
| 17 | ) | 18 | ) | ||
| 18 | 19 | ||||
| 19 | const ( | 20 | const ( | ||
| @@ -21,6 +22,14 @@ const ( | |||||
| 21 | align8ByteSize = 8 // 8-byte alignment boundary | 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 | // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). | 33 | // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). | ||
| 25 | // It panics if it can't find the name symbol. | 34 | // It panics if it can't find the name symbol. | ||
| 26 | func RegisterLibFunc(fptr any, handle uintptr, name string) { | 35 | func RegisterLibFunc(fptr any, handle uintptr, name string) { | ||
| @@ -59,15 +68,17 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||||
| 59 | // int64 <=> int64_t | 68 | // int64 <=> int64_t | ||
| 60 | // float32 <=> float | 69 | // float32 <=> float | ||
| 61 | // float64 <=> double | 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 | // func <=> C function | 72 | // func <=> C function | ||
| 64 | // unsafe.Pointer, *T <=> void* | 73 | // unsafe.Pointer, *T <=> void* | ||
| 65 | // []T => void* | 74 | // []T => void* | ||
| 66 | // | 75 | // | ||
| 67 | - | // There is a special case when the last argument of fptr is a variadic interface (or []interface} | 76 | + | // There is a special case when the last argument of fptr is a variadic interface (or []any): |
| 68 | // it will be expanded into a call to the C function as if it had the arguments in that slice. | 77 | // it will be expanded into a call to the C function as if it had the arguments in that slice. | ||
| 69 | // This means that using arg ...any is like a cast to the function with the arguments inside arg. | 78 | // This means that using arg ...any is like a cast to the function with the arguments inside arg. | ||
| 70 | // This is not the same as C variadic. | 79 | // This is not the same as C variadic. | ||
| 80 | + | // Only the last argument is expanded this way. A []any in any other position is an ordinary | |||
| 81 | + | // argument and is passed as a single value like any other slice. | |||
| 71 | // | 82 | // | ||
| 72 | // # Memory | 83 | // # Memory | ||
| 73 | // | 84 | // | ||
| @@ -94,8 +105,11 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||||
| 94 | // it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure | 105 | // it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure | ||
| 95 | // that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example. | 106 | // that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example. | ||
| 96 | // | 107 | // | ||
| 97 | - | // On Darwin ARM64, purego handles proper alignment of struct arguments when passing them on the stack, | 108 | + | // On Apple ARM64 platforms (macOS and iOS), purego handles proper alignment of struct arguments |
| 98 | - | // following the C ABI's byte-level packing rules. | 109 | + | // when passing them on the stack, following the C ABI's byte-level packing rules. |
| 110 | + | // | |||
| 111 | + | // On Windows, struct arguments and returns are supported on amd64 and arm64 when calling C functions. | |||
| 112 | + | // Passing or returning structs in callbacks created with [NewCallback] is not supported on Windows. | |||
| 99 | // | 113 | // | ||
| 100 | // # Example | 114 | // # Example | ||
| 101 | // | 115 | // | ||
| @@ -116,7 +130,11 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||||
| 116 | // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C | 130 | // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C | ||
| 117 | func RegisterFunc(fptr any, cfn uintptr) { | 131 | func RegisterFunc(fptr any, cfn uintptr) { | ||
| 118 | const is32bit = unsafe.Sizeof(uintptr(0)) == 4 | 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 | ty := fn.Type() | 138 | ty := fn.Type() | ||
| 121 | if ty.Kind() != reflect.Func { | 139 | if ty.Kind() != reflect.Func { | ||
| 122 | panic("purego: fptr must be a function pointer") | 140 | panic("purego: fptr must be a function pointer") | ||
| @@ -136,40 +154,71 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 136 | // to avoid crashing with too many arguments | 154 | // to avoid crashing with too many arguments | ||
| 137 | var ints int | 155 | var ints int | ||
| 138 | var floats int | 156 | var floats int | ||
| 157 | + | floatArgRegs := numOfFloatRegisters() | |||
| 158 | + | ptrSize := unsafe.Sizeof(uintptr(0)) | |||
| 139 | var stack int | 159 | var stack int | ||
| 140 | - | for i := 0; i < ty.NumIn(); i++ { | 160 | + | for i := range ty.NumIn() { |
| 141 | arg := ty.In(i) | 161 | arg := ty.In(i) | ||
| 142 | switch arg.Kind() { | 162 | switch arg.Kind() { | ||
| 143 | case reflect.Func: | 163 | case reflect.Func: | ||
| 144 | // This only does preliminary testing to ensure the CDecl argument | 164 | // This only does preliminary testing to ensure the CDecl argument | ||
| 145 | // is the first argument. Full testing is done when the callback is actually | 165 | // is the first argument. Full testing is done when the callback is actually | ||
| 146 | // created in NewCallback. | 166 | // created in NewCallback. | ||
| 147 | - | for j := 0; j < arg.NumIn(); j++ { | 167 | + | for j := range arg.NumIn() { |
| 148 | in := arg.In(j) | 168 | in := arg.In(j) | ||
| 149 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | 169 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { |
| 150 | continue | 170 | continue | ||
| 151 | } | 171 | } | ||
| 152 | if j != 0 { | 172 | if j != 0 { | ||
| 153 | panic("purego: CDecl must be the first argument") | 173 | panic("purego: CDecl must be the first argument") | ||
| 154 | } | 174 | } | ||
| 155 | } | 175 | } | ||
| 156 | - | case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | 176 | + | case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, |
| 157 | - | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer, | 177 | + | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Pointer, reflect.UnsafePointer, |
| 158 | reflect.Slice, reflect.Array, reflect.Bool: | 178 | reflect.Slice, reflect.Array, reflect.Bool: | ||
| 159 | if ints < numOfIntegerRegisters() { | 179 | if ints < numOfIntegerRegisters() { | ||
| 160 | ints++ | 180 | ints++ | ||
| 161 | } else { | 181 | } else { | ||
| 162 | stack++ | 182 | stack++ | ||
| 163 | } | 183 | } | ||
| 184 | + | case reflect.Uint64, reflect.Int64: | |||
| 185 | + | usesSlots := max(1, int(arg.Size()/ptrSize)) | |||
| 186 | + | if isARMPaddingNeeded(arg, ints, stack) { | |||
| 187 | + | usesSlots++ | |||
| 188 | + | } | |||
| 189 | + | ||||
| 190 | + | if ints < numOfIntegerRegisters() { | |||
| 191 | + | ints += usesSlots | |||
| 192 | + | } else { | |||
| 193 | + | stack += usesSlots | |||
| 194 | + | } | |||
| 164 | case reflect.Float32, reflect.Float64: | 195 | case reflect.Float32, reflect.Float64: | ||
| 165 | - | if floats < numOfFloatRegisters() { | 196 | + | usesSlots := max(1, int(arg.Size()/ptrSize)) |
| 197 | + | if isARMSoftFloat() { | |||
| 198 | + | // float64 for arm with softfloat uses same rules as int64 | |||
| 199 | + | if isARMPaddingNeeded(arg, ints, stack) { | |||
| 200 | + | usesSlots++ | |||
| 201 | + | } | |||
| 202 | + | if ints < numOfIntegerRegisters() { | |||
| 203 | + | ints += usesSlots | |||
| 204 | + | } else { | |||
| 205 | + | stack += usesSlots | |||
| 206 | + | } | |||
| 207 | + | continue | |||
| 208 | + | } | |||
| 209 | + | ||||
| 210 | + | if isARMFloatPaddingNeeded(arg, floats, stack) { | |||
| 211 | + | usesSlots++ | |||
| 212 | + | } | |||
| 213 | + | if floats < floatArgRegs { | |||
| 166 | floats++ | 214 | floats++ | ||
| 167 | } else { | 215 | } else { | ||
| 168 | - | stack++ | 216 | + | stack += usesSlots |
| 169 | } | 217 | } | ||
| 170 | case reflect.Struct: | 218 | case reflect.Struct: | ||
| 171 | ensureStructSupported() | 219 | ensureStructSupported() | ||
| 172 | - | if arg.Size() == 0 { | 220 | + | if arg.Size() == 0 && runtime.GOOS != "windows" { |
| 221 | + | // On Windows an empty struct still consumes one argument slot. | |||
| 173 | continue | 222 | continue | ||
| 174 | } | 223 | } | ||
| 175 | addInt := func(u uintptr) { | 224 | addInt := func(u uintptr) { | ||
| @@ -190,17 +239,28 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 190 | ensureStructSupported() | 239 | ensureStructSupported() | ||
| 191 | outType := ty.Out(0) | 240 | outType := ty.Out(0) | ||
| 192 | checkStructFieldsSupported(outType) | 241 | checkStructFieldsSupported(outType) | ||
| 193 | - | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | 242 | + | if structReturnInMemory(outType) { |
| 194 | - | // on amd64 if struct is bigger than 16 bytes allocate the return struct | 243 | + | // A struct returned in memory is allocated by the caller and its |
| 195 | - | // and pass it in as a hidden first argument. | 244 | + | // pointer is passed as a hidden first integer argument. When the |
| 196 | - | ints++ | 245 | + | // integer registers are already full, prepending it spills a |
| 246 | + | // regular argument onto the stack. | |||
| 247 | + | if ints < numOfIntegerRegisters() { | |||
| 248 | + | ints++ | |||
| 249 | + | } else { | |||
| 250 | + | stack++ | |||
| 251 | + | } | |||
| 197 | } | 252 | } | ||
| 198 | } | 253 | } | ||
| 199 | 254 | ||||
| 200 | - | sizeOfStack := maxArgs - numOfIntegerRegisters() | 255 | + | argsLimit := maxArgs |
| 201 | - | // On Darwin ARM64, use byte-based validation since arguments pack efficiently. | 256 | + | sizeOfStack := argsLimit - numOfIntegerRegisters() |
| 202 | - | // See https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms | 257 | + | if runtime.GOOS == "windows" { |
| 203 | - | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | 258 | + | if ints+floats+stack > argsLimit { |
| 259 | + | panic("purego: too many stack arguments") | |||
| 260 | + | } | |||
| 261 | + | } else if isDarwin && runtime.GOARCH == "arm64" { | |||
| 262 | + | // On Darwin ARM64, use byte-based validation since arguments pack efficiently. | |||
| 263 | + | // See https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms | |||
| 204 | stackBytes := estimateStackBytes(ty) | 264 | stackBytes := estimateStackBytes(ty) | ||
| 205 | maxStackBytes := sizeOfStack * 8 | 265 | maxStackBytes := sizeOfStack * 8 | ||
| 206 | if stackBytes > maxStackBytes { | 266 | if stackBytes > maxStackBytes { | ||
| @@ -213,35 +273,13 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 213 | } | 273 | } | ||
| 214 | } | 274 | } | ||
| 215 | 275 | ||||
| 216 | - | // Task 3: Analyze types at RegisterFunc time to determine if keepAlive is needed. | |||
| 217 | - | // keepAlive is only used for: string args (CString alloc), struct args (addStruct), | |||
| 218 | - | // struct returns > maxRegAllocStructSize (hidden pointer), array args (addressable copy), | |||
| 219 | - | // and variadic []any args (unknown types at registration time). | |||
| 220 | - | needsKeepAlive := false | |||
| 221 | - | for i := 0; i < ty.NumIn(); i++ { | |||
| 222 | - | inKind := ty.In(i).Kind() | |||
| 223 | - | switch inKind { | |||
| 224 | - | case reflect.String, reflect.Struct, reflect.Array, reflect.Slice: | |||
| 225 | - | // Slice covers []string (CString allocs) and variadic ...any ([]any) | |||
| 226 | - | needsKeepAlive = true | |||
| 227 | - | case reflect.Interface: | |||
| 228 | - | needsKeepAlive = true | |||
| 229 | - | } | |||
| 230 | - | } | |||
| 231 | - | if ty.NumOut() == 1 { | |||
| 232 | - | switch ty.Out(0).Kind() { | |||
| 233 | - | case reflect.Struct: | |||
| 234 | - | // struct returns may need hidden pointer allocation stored in keepAlive | |||
| 235 | - | needsKeepAlive = true | |||
| 236 | - | } | |||
| 237 | - | } | |||
| 238 | - | ||||
| 239 | v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | 276 | v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | ||
| 240 | var sysargs [maxArgs]uintptr | 277 | var sysargs [maxArgs]uintptr | ||
| 241 | // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free, | 278 | // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free, | ||
| 242 | // since numOfFloatRegisters() is a function call, not a constant. | 279 | // since numOfFloatRegisters() is a function call, not a constant. | ||
| 243 | // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe. | 280 | // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe. | ||
| 244 | var floats [maxArgs]uintptr | 281 | var floats [maxArgs]uintptr | ||
| 282 | + | floatArgRegs := numOfFloatRegisters() | |||
| 245 | var numInts int | 283 | var numInts int | ||
| 246 | var numFloats int | 284 | var numFloats int | ||
| 247 | var numStack int | 285 | var numStack int | ||
| @@ -261,7 +299,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 261 | } | 299 | } | ||
| 262 | } | 300 | } | ||
| 263 | addFloat = func(x uintptr) { | 301 | addFloat = func(x uintptr) { | ||
| 264 | - | if numFloats < numOfFloatRegisters() { | 302 | + | if numFloats < floatArgRegs { |
| 265 | floats[numFloats] = x | 303 | floats[numFloats] = x | ||
| 266 | numFloats++ | 304 | numFloats++ | ||
| 267 | } else { | 305 | } else { | ||
| @@ -275,6 +313,9 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 275 | // This is in contrast to how macOS and Linux pass arguments which | 313 | // This is in contrast to how macOS and Linux pass arguments which | ||
| 276 | // tries to use as many registers as possible in the calling convention. | 314 | // tries to use as many registers as possible in the calling convention. | ||
| 277 | addStack = func(x uintptr) { | 315 | addStack = func(x uintptr) { | ||
| 316 | + | if numStack >= maxArgs { | |||
| 317 | + | panic("purego: too many stack arguments") | |||
| 318 | + | } | |||
| 278 | sysargs[numStack] = x | 319 | sysargs[numStack] = x | ||
| 279 | numStack++ | 320 | numStack++ | ||
| 280 | } | 321 | } | ||
| @@ -282,189 +323,169 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 282 | addFloat = addStack | 323 | addFloat = addStack | ||
| 283 | } | 324 | } | ||
| 284 | 325 | ||||
| 285 | - | if needsKeepAlive { | 326 | + | var keepAlive []any |
| 286 | - | // Slow path: allocate keepAlive slice and defer cleanup. | 327 | + | defer func() { |
| 287 | - | // Used when args include strings, structs, arrays, or variadic []any. | 328 | + | runtime.KeepAlive(keepAlive) |
| 288 | - | var keepAlive []any | 329 | + | runtime.KeepAlive(args) |
| 289 | - | defer func() { | 330 | + | }() |
| 290 | - | runtime.KeepAlive(keepAlive) | |||
| 291 | - | runtime.KeepAlive(args) | |||
| 292 | - | }() | |||
| 293 | 331 | ||||
| 294 | - | var arm64_r8 uintptr | 332 | + | var arm64_r8 uintptr |
| 295 | - | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 333 | + | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { |
| 296 | - | outType := ty.Out(0) | 334 | + | outType := ty.Out(0) |
| 297 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize { | 335 | + | if structReturnInMemory(outType) { |
| 336 | + | // The caller allocates the return value and passes its pointer | |||
| 337 | + | // as a hidden first integer argument. | |||
| 338 | + | val := reflect.New(outType) | |||
| 339 | + | keepAlive = append(keepAlive, val) | |||
| 340 | + | addInt(val.Pointer()) | |||
| 341 | + | } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { | |||
| 342 | + | isAllFloats, numFields := isAllSameFloat(outType) | |||
| 343 | + | if !isAllFloats || numFields > 4 { | |||
| 298 | val := reflect.New(outType) | 344 | val := reflect.New(outType) | ||
| 299 | keepAlive = append(keepAlive, val) | 345 | keepAlive = append(keepAlive, val) | ||
| 300 | - | addInt(val.Pointer()) | 346 | + | arm64_r8 = val.Pointer() |
| 301 | - | } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { | |||
| 302 | - | isAllFloats, numFields := isAllSameFloat(outType) | |||
| 303 | - | if !isAllFloats || numFields > 4 { | |||
| 304 | - | val := reflect.New(outType) | |||
| 305 | - | keepAlive = append(keepAlive, val) | |||
| 306 | - | arm64_r8 = val.Pointer() | |||
| 307 | - | } | |||
| 308 | } | 347 | } | ||
| 309 | } | 348 | } | ||
| 310 | - | for i, v := range args { | 349 | + | } |
| 311 | - | if variadic, ok := xreflect.TypeAssert[[]any](v); ok { | 350 | + | for i, v := range args { |
| 312 | - | if i != len(args)-1 { | 351 | + | // Only the last argument is expanded into several C arguments. |
| 313 | - | panic("purego: can only expand last parameter") | 352 | + | // Both a declared ...any parameter and a final []any parameter |
| 314 | - | } | 353 | + | // arrive here as a []any holding those arguments. A []any in any |
| 354 | + | // other position is an ordinary argument that is passed as one value. | |||
| 355 | + | if i == len(args)-1 { | |||
| 356 | + | if variadic, ok := reflect.TypeAssert[[]any](args[i]); ok { | |||
| 315 | for _, x := range variadic { | 357 | for _, x := range variadic { | ||
| 316 | keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | 358 | keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | ||
| 317 | } | 359 | } | ||
| 318 | continue | 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 | // Check if we need to start Darwin ARM64 C-style stack packing | 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) { | 364 | + | if runtime.GOARCH == "arm64" && isDarwin && shouldBundleStackArgs(v, numInts, numFloats) { |
| 352 | - | stackArgs, _ := collectStackArgs(args, i, numInts, numFloats, | 365 | + | // Collect and separate remaining args into register vs stack |
| 353 | - | nil, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | 366 | + | stackArgs, newKeepAlive := collectStackArgs(args, i, numInts, numFloats, |
| 367 | + | keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) | |||
| 368 | + | keepAlive = newKeepAlive | |||
| 369 | + | ||||
| 370 | + | // Bundle stack arguments with C-style packing | |||
| 354 | bundleStackArgs(stackArgs, addStack) | 371 | bundleStackArgs(stackArgs, addStack) | ||
| 355 | break | 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) | 377 | + | var syscall *syscallArgs |
| 361 | - | }) | 378 | + | if runtime.GOOS == "windows" && runtime.GOARCH != "arm64" { |
| 362 | - | fn.Set(v) | 379 | + | // Windows amd64, 386, and arm use syscall.SyscallN. |
| 363 | - | } | 380 | + | syscall = thePool.Get().(*syscallArgs) |
| 364 | - | 381 | + | syscall.a1, syscall.a2, _ = syscall_syscallN(cfn, sysargs[:numStack]...) | |
| 365 | - | // callAndReturn performs the actual C function call and constructs the return value. | 382 | + | syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support |
| 366 | - | // It is shared between the fast path (no keepAlive) and slow path (with keepAlive). | |||
| 367 | - | func callAndReturn(ty reflect.Type, is32bit bool, cfn uintptr, sysargs *[maxArgs]uintptr, floats *[maxArgs]uintptr, arm64_r8 uintptr, args []reflect.Value) []reflect.Value { | |||
| 368 | - | syscall := &syscall15Args{} | |||
| 369 | - | ||||
| 370 | - | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { | |||
| 371 | - | syscall.Set(cfn, sysargs[:], floats[:], 0) | |||
| 372 | - | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |||
| 373 | - | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | |||
| 374 | - | // Use the normal arm64 calling convention even on Windows | |||
| 375 | - | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) | |||
| 376 | - | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |||
| 377 | - | } else { | |||
| 378 | - | // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats | |||
| 379 | - | syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], | |||
| 380 | - | sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |||
| 381 | - | sysargs[12], sysargs[13], sysargs[14]) | |||
| 382 | - | syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support | |||
| 383 | - | } | |||
| 384 | - | if ty.NumOut() == 0 { | |||
| 385 | - | return nil | |||
| 386 | - | } | |||
| 387 | - | outType := ty.Out(0) | |||
| 388 | - | v := reflect.New(outType).Elem() | |||
| 389 | - | switch outType.Kind() { | |||
| 390 | - | case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||
| 391 | - | v.SetUint(uint64(syscall.a1)) | |||
| 392 | - | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||
| 393 | - | v.SetInt(int64(syscall.a1)) | |||
| 394 | - | case reflect.Bool: | |||
| 395 | - | v.SetBool(byte(syscall.a1) != 0) | |||
| 396 | - | case reflect.UnsafePointer: | |||
| 397 | - | // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer | |||
| 398 | - | v = reflect.New(outType).Elem() | |||
| 399 | - | v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) | |||
| 400 | - | case reflect.Pointer: | |||
| 401 | - | v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem() | |||
| 402 | - | case reflect.Func: | |||
| 403 | - | // wrap this C function in a nicely typed Go function | |||
| 404 | - | v = reflect.New(outType) | |||
| 405 | - | RegisterFunc(v.Interface(), syscall.a1) | |||
| 406 | - | case reflect.String: | |||
| 407 | - | v = reflect.ValueOf(strings.GoString(syscall.a1)) | |||
| 408 | - | case reflect.Float32: | |||
| 409 | - | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |||
| 410 | - | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |||
| 411 | - | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | |||
| 412 | - | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | |||
| 413 | - | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | |||
| 414 | - | v = reflect.New(outType).Elem() | |||
| 415 | - | switch runtime.GOARCH { | |||
| 416 | - | case "386": | |||
| 417 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |||
| 418 | - | case "ppc64le": | |||
| 419 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |||
| 420 | - | case "s390x": | |||
| 421 | - | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | |||
| 422 | - | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | |||
| 423 | - | default: | |||
| 424 | - | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |||
| 425 | - | } | |||
| 426 | - | case reflect.Float64: | |||
| 427 | - | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |||
| 428 | - | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |||
| 429 | - | v = reflect.New(outType).Elem() | |||
| 430 | - | if is32bit { | |||
| 431 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |||
| 432 | } else { | 383 | } else { | ||
| 433 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | 384 | + | syscall = syscall_SyscallN(cfn, sysargs[:], floats[:], arm64_r8) |
| 434 | } | 385 | } | ||
| 435 | - | case reflect.Struct: | 386 | + | defer thePool.Put(syscall) |
| 436 | - | v = getStruct(outType, *syscall) | 387 | + | if ty.NumOut() == 0 { |
| 437 | - | case reflect.Slice: | 388 | + | return nil |
| 438 | - | if outType.Elem().Kind() == reflect.String { | 389 | + | } |
| 439 | - | // C returns char** (NULL-terminated array of strings) | 390 | + | outType := ty.Out(0) |
| 440 | - | if syscall.a1 == 0 { | 391 | + | v := reflect.New(outType).Elem() |
| 441 | - | v = reflect.Zero(outType) | 392 | + | switch outType.Kind() { |
| 393 | + | case reflect.Uint64: | |||
| 394 | + | if is32bit { | |||
| 395 | + | // high-word is recorded at a2 for 32-bit platforms and 64-bit returns | |||
| 396 | + | v.SetUint(uint64(syscall.a1) | (uint64(syscall.a2) << 32)) | |||
| 397 | + | } else { | |||
| 398 | + | v.SetUint(uint64(syscall.a1)) | |||
| 399 | + | } | |||
| 400 | + | case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: | |||
| 401 | + | v.SetUint(uint64(syscall.a1)) | |||
| 402 | + | case reflect.Int64: | |||
| 403 | + | if is32bit { | |||
| 404 | + | v.SetInt(int64(syscall.a1) | (int64(syscall.a2) << 32)) | |||
| 405 | + | } else { | |||
| 406 | + | v.SetInt(int64(syscall.a1)) | |||
| 407 | + | } | |||
| 408 | + | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: | |||
| 409 | + | v.SetInt(int64(syscall.a1)) | |||
| 410 | + | case reflect.Bool: | |||
| 411 | + | v.SetBool(byte(syscall.a1) != 0) | |||
| 412 | + | case reflect.UnsafePointer: | |||
| 413 | + | // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer | |||
| 414 | + | v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) | |||
| 415 | + | case reflect.Pointer: | |||
| 416 | + | // Copy syscall.a1 into a local variable to prevent v | |||
| 417 | + | // from holding a pointer to the pooled syscallArgs field. | |||
| 418 | + | a1 := syscall.a1 | |||
| 419 | + | v = reflect.NewAt(outType, unsafe.Pointer(&a1)).Elem() | |||
| 420 | + | case reflect.Func: | |||
| 421 | + | // wrap this C function in a nicely typed Go function | |||
| 422 | + | if syscall.a1 != 0 { | |||
| 423 | + | RegisterFunc(v.Addr().Interface(), syscall.a1) | |||
| 424 | + | } | |||
| 425 | + | case reflect.String: | |||
| 426 | + | v.SetString(strings.GoString(syscall.a1)) | |||
| 427 | + | case reflect.Float32: | |||
| 428 | + | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |||
| 429 | + | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |||
| 430 | + | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | |||
| 431 | + | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | |||
| 432 | + | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | |||
| 433 | + | // On 32bit ARM with softfloat float32 returned as integer | |||
| 434 | + | switch runtime.GOARCH { | |||
| 435 | + | case "386": | |||
| 436 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |||
| 437 | + | case "ppc64le": | |||
| 438 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |||
| 439 | + | case "s390x": | |||
| 440 | + | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | |||
| 441 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | |||
| 442 | + | case "arm": | |||
| 443 | + | if isARMSoftFloat() { | |||
| 444 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.a1)))) | |||
| 445 | + | } else { | |||
| 446 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |||
| 447 | + | } | |||
| 448 | + | default: | |||
| 449 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |||
| 450 | + | } | |||
| 451 | + | case reflect.Float64: | |||
| 452 | + | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |||
| 453 | + | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |||
| 454 | + | if isARMSoftFloat() { | |||
| 455 | + | // a1,a2 are populated in this case | |||
| 456 | + | v.SetFloat(math.Float64frombits(uint64(syscall.a1) | (uint64(syscall.a2) << 32))) | |||
| 457 | + | } else if is32bit { | |||
| 458 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |||
| 442 | } else { | 459 | } else { | ||
| 460 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |||
| 461 | + | } | |||
| 462 | + | case reflect.Struct: | |||
| 463 | + | v = getStruct(outType, *syscall) | |||
| 464 | + | case reflect.Slice: | |||
| 465 | + | if outType.Elem().Kind() != reflect.String { | |||
| 466 | + | panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String()) | |||
| 467 | + | } | |||
| 468 | + | // C returns a NULL-terminated char** array. | |||
| 469 | + | if syscall.a1 != 0 { | |||
| 443 | var result []string | 470 | var result []string | ||
| 444 | p := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)) | 471 | p := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)) | ||
| 445 | - | for { | 472 | + | for ; *(*uintptr)(p) != 0; p = unsafe.Add(p, unsafe.Sizeof(uintptr(0))) { |
| 446 | - | strPtr := *(*uintptr)(p) | 473 | + | result = append(result, strings.GoString(*(*uintptr)(p))) |
| 447 | - | if strPtr == 0 { | |||
| 448 | - | break | |||
| 449 | - | } | |||
| 450 | - | result = append(result, strings.GoString(strPtr)) | |||
| 451 | - | p = unsafe.Add(p, unsafe.Sizeof(uintptr(0))) | |||
| 452 | } | 474 | } | ||
| 453 | - | v = reflect.ValueOf(result) | 475 | + | v = reflect.ValueOf(result).Convert(outType) |
| 454 | } | 476 | } | ||
| 477 | + | default: | |||
| 478 | + | panic("purego: unsupported return kind: " + outType.Kind().String()) | |||
| 479 | + | } | |||
| 480 | + | if len(args) > 0 { | |||
| 481 | + | // reuse args slice instead of allocating one when possible | |||
| 482 | + | args[0] = v | |||
| 483 | + | return args[:1] | |||
| 455 | } else { | 484 | } else { | ||
| 456 | - | panic("purego: unsupported return slice element kind: " + outType.Elem().Kind().String()) | 485 | + | return []reflect.Value{v} |
| 457 | } | 486 | } | ||
| 458 | - | default: | 487 | + | }) |
| 459 | - | panic("purego: unsupported return kind: " + outType.Kind().String()) | 488 | + | fn.Set(v) |
| 460 | - | } | |||
| 461 | - | if len(args) > 0 { | |||
| 462 | - | // reuse args slice instead of allocating one when possible | |||
| 463 | - | args[0] = v | |||
| 464 | - | return args[:1] | |||
| 465 | - | } else { | |||
| 466 | - | return []reflect.Value{v} | |||
| 467 | - | } | |||
| 468 | } | 489 | } | ||
| 469 | 490 | ||||
| 470 | func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any { | 491 | func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any { | ||
| @@ -474,33 +495,52 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 474 | ptr := strings.CString(v.String()) | 495 | ptr := strings.CString(v.String()) | ||
| 475 | keepAlive = append(keepAlive, ptr) | 496 | keepAlive = append(keepAlive, ptr) | ||
| 476 | addInt(uintptr(unsafe.Pointer(ptr))) | 497 | addInt(uintptr(unsafe.Pointer(ptr))) | ||
| 477 | - | case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | 498 | + | case reflect.Uint64: |
| 499 | + | if isARMPaddingNeeded(v.Type(), *numInts, *numStack) { | |||
| 500 | + | addInt(0) | |||
| 501 | + | } | |||
| 478 | addInt(uintptr(v.Uint())) | 502 | addInt(uintptr(v.Uint())) | ||
| 479 | - | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | 503 | + | if is32bit { |
| 504 | + | addInt(uintptr(v.Uint() >> 32)) // on 32bit we must add high word too | |||
| 505 | + | } | |||
| 506 | + | case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: | |||
| 507 | + | addInt(uintptr(v.Uint())) | |||
| 508 | + | case reflect.Int64: | |||
| 509 | + | if isARMPaddingNeeded(v.Type(), *numInts, *numStack) { | |||
| 510 | + | addInt(0) | |||
| 511 | + | } | |||
| 512 | + | addInt(uintptr(v.Int())) | |||
| 513 | + | if is32bit { | |||
| 514 | + | addInt(uintptr(v.Int() >> 32)) | |||
| 515 | + | } | |||
| 516 | + | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: | |||
| 480 | addInt(uintptr(v.Int())) | 517 | addInt(uintptr(v.Int())) | ||
| 481 | - | case reflect.Pointer, reflect.UnsafePointer: | |||
| 482 | - | // There is no need to keepAlive this pointer separately because it is kept alive in the args variable | |||
| 483 | - | addInt(v.Pointer()) | |||
| 484 | case reflect.Slice: | 518 | case reflect.Slice: | ||
| 485 | - | if v.Type().Elem().Kind() == reflect.String { | 519 | + | if v.Type().Elem().Kind() != reflect.String { |
| 486 | - | ss := v.Interface().([]string) | 520 | + | // There is no need to keepAlive this pointer separately because it is kept alive in the args variable |
| 487 | - | if ss == nil { | |||
| 488 | - | addInt(0) | |||
| 489 | - | } else { | |||
| 490 | - | res := make([]*byte, len(ss)+1) | |||
| 491 | - | for i, s := range ss { | |||
| 492 | - | ptr := strings.CString(s) | |||
| 493 | - | keepAlive = append(keepAlive, ptr) | |||
| 494 | - | res[i] = ptr | |||
| 495 | - | } | |||
| 496 | - | // NULL-terminated for C | |||
| 497 | - | res[len(ss)] = nil | |||
| 498 | - | keepAlive = append(keepAlive, res) | |||
| 499 | - | addInt(uintptr(unsafe.Pointer(&res[0]))) | |||
| 500 | - | } | |||
| 501 | - | } else { | |||
| 502 | addInt(v.Pointer()) | 521 | addInt(v.Pointer()) | ||
| 522 | + | break | |||
| 523 | + | } | |||
| 524 | + | if v.IsNil() { | |||
| 525 | + | addInt(0) | |||
| 526 | + | break | |||
| 503 | } | 527 | } | ||
| 528 | + | // Pass []string as a NULL-terminated char** array. | |||
| 529 | + | res := make([]*byte, v.Len()+1) | |||
| 530 | + | for i := range v.Len() { | |||
| 531 | + | res[i] = strings.CString(v.Index(i).String()) | |||
| 532 | + | } | |||
| 533 | + | keepAlive = append(keepAlive, res) | |||
| 534 | + | addInt(uintptr(unsafe.Pointer(&res[0]))) | |||
| 535 | + | case reflect.Array: | |||
| 536 | + | // C array parameters decay to pointers, so pass a pointer to a copy. | |||
| 537 | + | tmp := reflect.New(v.Type()) | |||
| 538 | + | tmp.Elem().Set(v) | |||
| 539 | + | keepAlive = append(keepAlive, tmp) | |||
| 540 | + | addInt(tmp.Pointer()) | |||
| 541 | + | case reflect.Pointer, reflect.UnsafePointer: | |||
| 542 | + | // There is no need to keepAlive this pointer separately because it is kept alive in the args variable | |||
| 543 | + | addInt(v.Pointer()) | |||
| 504 | case reflect.Func: | 544 | case reflect.Func: | ||
| 505 | addInt(NewCallback(v.Interface())) | 545 | addInt(NewCallback(v.Interface())) | ||
| 506 | case reflect.Bool: | 546 | case reflect.Bool: | ||
| @@ -510,30 +550,41 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 510 | addInt(0) | 550 | addInt(0) | ||
| 511 | } | 551 | } | ||
| 512 | case reflect.Float32: | 552 | case reflect.Float32: | ||
| 513 | - | // On S390X big-endian, float32 goes in upper 32 bits of 64-bit FP register | 553 | + | switch runtime.GOARCH { |
| 514 | - | if runtime.GOARCH == "s390x" { | 554 | + | case "ppc64le": |
| 555 | + | // A single-precision argument occupies a floating-point register in double format on Power. | |||
| 556 | + | addFloat(uintptr(math.Float64bits(v.Float()))) | |||
| 557 | + | case "s390x": | |||
| 558 | + | // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register. | |||
| 515 | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | 559 | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | ||
| 516 | - | } else { | 560 | + | case "arm": |
| 561 | + | if isARMSoftFloat() { | |||
| 562 | + | // 32-bit ARM with softfloat: float32 goes as integer | |||
| 563 | + | addInt(uintptr(math.Float32bits(float32(v.Float())))) | |||
| 564 | + | } else { | |||
| 565 | + | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |||
| 566 | + | } | |||
| 567 | + | default: | |||
| 517 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | 568 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | ||
| 518 | } | 569 | } | ||
| 519 | case reflect.Float64: | 570 | case reflect.Float64: | ||
| 520 | - | if is32bit { | 571 | + | bits := math.Float64bits(v.Float()) |
| 521 | - | bits := math.Float64bits(v.Float()) | 572 | + | if isARMFloatPaddingNeeded(v.Type(), *numFloats, *numStack) { |
| 573 | + | // if floats are spilled onto stack on ARM than we must follow AAPCS C.7 | |||
| 574 | + | addFloat(0) | |||
| 575 | + | } | |||
| 576 | + | if isARMSoftFloat() { | |||
| 577 | + | // add as uint64 | |||
| 578 | + | if isARMPaddingNeeded(v.Type(), *numInts, *numStack) { | |||
| 579 | + | addInt(0) | |||
| 580 | + | } | |||
| 581 | + | addInt(uintptr(bits)) | |||
| 582 | + | addInt(uintptr(bits >> 32)) | |||
| 583 | + | } else if is32bit { | |||
| 522 | addFloat(uintptr(bits)) | 584 | addFloat(uintptr(bits)) | ||
| 523 | addFloat(uintptr(bits >> 32)) | 585 | addFloat(uintptr(bits >> 32)) | ||
| 524 | } else { | 586 | } else { | ||
| 525 | - | addFloat(uintptr(math.Float64bits(v.Float()))) | 587 | + | addFloat(uintptr(bits)) |
| 526 | - | } | |||
| 527 | - | case reflect.Array: | |||
| 528 | - | // Task 7: Support fixed-size array arguments by passing a pointer to the array data. | |||
| 529 | - | if v.CanAddr() { | |||
| 530 | - | addInt(v.Addr().Pointer()) | |||
| 531 | - | } else { | |||
| 532 | - | // Array is not addressable (e.g. passed by value); make an addressable copy. | |||
| 533 | - | tmp := reflect.New(v.Type()).Elem() | |||
| 534 | - | tmp.Set(v) | |||
| 535 | - | keepAlive = append(keepAlive, tmp) | |||
| 536 | - | addInt(tmp.Addr().Pointer()) | |||
| 537 | } | 588 | } | ||
| 538 | case reflect.Struct: | 589 | case reflect.Struct: | ||
| 539 | keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) | 590 | keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) | ||
| @@ -550,17 +601,61 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 550 | // If you change this make sure to update it in objc_runtime_darwin.go | 601 | // If you change this make sure to update it in objc_runtime_darwin.go | ||
| 551 | const maxRegAllocStructSize = 16 | 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 | func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { | 639 | func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { | ||
| 554 | allFloats = true | 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 | for root.Kind() == reflect.Struct { | 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 | first := root.Kind() | 651 | first := root.Kind() | ||
| 560 | if first != reflect.Float32 && first != reflect.Float64 { | 652 | if first != reflect.Float32 && first != reflect.Float64 { | ||
| 561 | allFloats = false | 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 | f := ty.Field(i).Type | 659 | f := ty.Field(i).Type | ||
| 565 | if f.Kind() == reflect.Struct { | 660 | if f.Kind() == reflect.Struct { | ||
| 566 | var structNumFields int | 661 | var structNumFields int | ||
| @@ -577,7 +672,10 @@ func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { | |||||
| 577 | } | 672 | } | ||
| 578 | 673 | ||||
| 579 | func checkStructFieldsSupported(ty reflect.Type) { | 674 | func checkStructFieldsSupported(ty reflect.Type) { | ||
| 580 | - | for i := 0; i < ty.NumField(); i++ { | 675 | + | for i := range ty.NumField() { |
| 676 | + | if !isABIField(ty.Field(i)) { | |||
| 677 | + | continue | |||
| 678 | + | } | |||
| 581 | f := ty.Field(i).Type | 679 | f := ty.Field(i).Type | ||
| 582 | if f.Kind() == reflect.Array { | 680 | if f.Kind() == reflect.Array { | ||
| 583 | f = f.Elem() | 681 | f = f.Elem() | ||
| @@ -596,15 +694,39 @@ func checkStructFieldsSupported(ty reflect.Type) { | |||||
| 596 | } | 694 | } | ||
| 597 | } | 695 | } | ||
| 598 | 696 | ||||
| 697 | + | // ensureStructSupported panics if passing or returning structs through a call to | |||
| 698 | + | // a C function is unsupported on the current platform. | |||
| 599 | func ensureStructSupported() { | 699 | func ensureStructSupported() { | ||
| 700 | + | switch runtime.GOARCH { | |||
| 701 | + | case "amd64", "arm64", "loong64", "ppc64le": | |||
| 702 | + | default: | |||
| 703 | + | panic("purego: struct arguments/returns are only supported on amd64, arm64, loong64, and ppc64le") | |||
| 704 | + | } | |||
| 705 | + | switch runtime.GOOS { | |||
| 706 | + | case "android", "darwin", "ios", "linux", "windows": | |||
| 707 | + | default: | |||
| 708 | + | panic("purego: struct arguments/returns are only supported on android, darwin, ios, linux, and windows") | |||
| 709 | + | } | |||
| 710 | + | } | |||
| 711 | + | ||||
| 712 | + | // ensureCallbackStructSupported panics if passing or returning structs through a | |||
| 713 | + | // callback is unsupported on the current platform. Callbacks support structs on | |||
| 714 | + | // fewer architectures than a direct call to a C function. | |||
| 715 | + | func ensureCallbackStructSupported() { | |||
| 600 | if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" { | 716 | if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" { | ||
| 601 | - | panic("purego: struct arguments/returns are only supported on amd64 and arm64") | 717 | + | panic("purego: struct arguments/returns in callbacks are only supported on amd64 and arm64") |
| 602 | } | 718 | } | ||
| 603 | - | if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { | 719 | + | switch runtime.GOOS { |
| 604 | - | panic("purego: struct arguments/returns are only supported on darwin and linux") | 720 | + | case "android", "darwin", "ios", "linux", "windows": |
| 721 | + | default: | |||
| 722 | + | panic("purego: struct arguments/returns in callbacks are only supported on android, darwin, ios, linux, and windows") | |||
| 605 | } | 723 | } | ||
| 606 | } | 724 | } | ||
| 607 | 725 | ||||
| 726 | + | // isDarwin is true on platforms that use Apple's calling convention. | |||
| 727 | + | // iOS (GOOS=ios) shares it with macOS (GOOS=darwin). | |||
| 728 | + | const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios" | |||
| 729 | + | ||||
| 608 | func roundUpTo8(val uintptr) uintptr { | 730 | func roundUpTo8(val uintptr) uintptr { | ||
| 609 | return (val + align8ByteMask) &^ align8ByteMask | 731 | return (val + align8ByteMask) &^ align8ByteMask | ||
| 610 | } | 732 | } | ||
| @@ -616,6 +738,7 @@ func numOfFloatRegisters() int { | |||||
| 616 | case "s390x": | 738 | case "s390x": | ||
| 617 | return 4 | 739 | return 4 | ||
| 618 | case "arm": | 740 | case "arm": | ||
| 741 | + | // 8 doubles (16 words) are always reserved by asm trampolines, even if softfloat is used | |||
| 619 | return 16 | 742 | return 16 | ||
| 620 | case "386": | 743 | case "386": | ||
| 621 | // i386 SysV ABI passes all arguments on the stack, including floats | 744 | // i386 SysV ABI passes all arguments on the stack, including floats | ||
| @@ -654,7 +777,7 @@ func estimateStackBytes(ty reflect.Type) int { | |||||
| 654 | var numInts, numFloats int | 777 | var numInts, numFloats int | ||
| 655 | var stackBytes int | 778 | var stackBytes int | ||
| 656 | 779 | ||||
| 657 | - | for i := 0; i < ty.NumIn(); i++ { | 780 | + | for i := range ty.NumIn() { |
| 658 | arg := ty.In(i) | 781 | arg := ty.In(i) | ||
| 659 | size := int(arg.Size()) | 782 | size := int(arg.Size()) | ||
| 660 | 783 | ||||
| @@ -665,7 +788,6 @@ func estimateStackBytes(ty reflect.Type) int { | |||||
| 665 | } else if !usesInt && numFloats < numOfFloatRegisters() { | 788 | } else if !usesInt && numFloats < numOfFloatRegisters() { | ||
| 666 | numFloats++ | 789 | numFloats++ | ||
| 667 | } else { | 790 | } else { | ||
| 668 | - | // Goes to stack - accumulate total bytes | |||
| 669 | stackBytes += size | 791 | stackBytes += size | ||
| 670 | } | 792 | } | ||
| 671 | } | 793 | } | ||
| @@ -675,3 +797,29 @@ func estimateStackBytes(ty reflect.Type) int { | |||||
| 675 | } | 797 | } | ||
| 676 | return stackBytes | 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 | "bytes" | 7 | "bytes" | ||
| 8 | "errors" | 8 | "errors" | ||
| 9 | "fmt" | 9 | "fmt" | ||
| 10 | + | "math" | |||
| 11 | + | "os" | |||
| 10 | "os/exec" | 12 | "os/exec" | ||
| 11 | "path/filepath" | 13 | "path/filepath" | ||
| 14 | + | "reflect" | |||
| 12 | "runtime" | 15 | "runtime" | ||
| 16 | + | "strconv" | |||
| 13 | "strings" | 17 | "strings" | ||
| 18 | + | "structs" | |||
| 14 | "sync" | 19 | "sync" | ||
| 15 | "testing" | 20 | "testing" | ||
| 16 | "unsafe" | 21 | "unsafe" | ||
| @@ -21,7 +26,9 @@ import ( | |||||
| 21 | 26 | ||||
| 22 | func getSystemLibrary() (string, error) { | 27 | func getSystemLibrary() (string, error) { | ||
| 23 | switch runtime.GOOS { | 28 | switch runtime.GOOS { | ||
| 24 | - | case "darwin": | 29 | + | case "android": |
| 30 | + | return "libc.so", nil | |||
| 31 | + | case "darwin", "ios": | |||
| 25 | return "/usr/lib/libSystem.B.dylib", nil | 32 | return "/usr/lib/libSystem.B.dylib", nil | ||
| 26 | case "freebsd": | 33 | case "freebsd": | ||
| 27 | return "libc.so.7", nil | 34 | return "libc.so.7", nil | ||
| @@ -52,19 +59,21 @@ func TestRegisterFunc_ConcurrentPointerReturn(t *testing.T) { | |||||
| 52 | purego.RegisterLibFunc(&free, libc, "free") | 59 | purego.RegisterLibFunc(&free, libc, "free") | ||
| 53 | 60 | ||||
| 54 | var wg sync.WaitGroup | 61 | var wg sync.WaitGroup | ||
| 55 | - | for i := 0; i < runtime.NumCPU(); i++ { | 62 | + | |
| 63 | + | for i := range runtime.NumCPU() { | |||
| 56 | wg.Add(1) | 64 | wg.Add(1) | ||
| 57 | - | go func() { | 65 | + | go func(id int) { |
| 58 | defer wg.Done() | 66 | defer wg.Done() | ||
| 59 | - | for j := 0; j < 400_000; j++ { | 67 | + | for range 400_000 { |
| 60 | ptr := alloc(5) | 68 | ptr := alloc(5) | ||
| 61 | if ptr == nil { | 69 | if ptr == nil { | ||
| 62 | continue | 70 | continue | ||
| 63 | } | 71 | } | ||
| 64 | free(ptr) | 72 | free(ptr) | ||
| 65 | } | 73 | } | ||
| 66 | - | }() | 74 | + | }(i) |
| 67 | } | 75 | } | ||
| 76 | + | ||||
| 68 | wg.Wait() | 77 | wg.Wait() | ||
| 69 | } | 78 | } | ||
| 70 | 79 | ||||
| @@ -172,14 +181,65 @@ func TestRegisterLibFunc_Bool(t *testing.T) { | |||||
| 172 | } | 181 | } | ||
| 173 | } | 182 | } | ||
| 174 | 183 | ||||
| 175 | - | func TestABI(t *testing.T) { | 184 | + | func TestNewCallback_NotAFunction(t *testing.T) { |
| 176 | - | if runtime.GOOS == "windows" && runtime.GOARCH == "386" { | 185 | + | for _, tc := range []struct { |
| 177 | - | t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test | 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 | libFileName := filepath.Join(t.TempDir(), "abitest.so") | 239 | libFileName := filepath.Join(t.TempDir(), "abitest.so") | ||
| 180 | t.Logf("Build %v", libFileName) | 240 | t.Logf("Build %v", libFileName) | ||
| 181 | 241 | ||||
| 182 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil { | 242 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil { |
| 183 | t.Fatal(err) | 243 | t.Fatal(err) | ||
| 184 | } | 244 | } | ||
| 185 | 245 | ||||
| @@ -234,14 +294,29 @@ func TestABI(t *testing.T) { | |||||
| 234 | t.Fatalf("%s: got %q, want %q", cName, res, want) | 294 | t.Fatalf("%s: got %q, want %q", cName, res, want) | ||
| 235 | } | 295 | } | ||
| 236 | } | 296 | } | ||
| 297 | + | { | |||
| 298 | + | const cName = "return_func_ptr" | |||
| 299 | + | var fn func() func(a, b int32) int32 | |||
| 300 | + | purego.RegisterLibFunc(&fn, lib, cName) | |||
| 301 | + | add := fn() | |||
| 302 | + | const expect = 5 | |||
| 303 | + | if res := add(2, 3); res != expect { | |||
| 304 | + | t.Fatalf("%s: got %d, want %d", cName, res, expect) | |||
| 305 | + | } | |||
| 306 | + | } | |||
| 307 | + | { | |||
| 308 | + | const cName = "return_null_func_ptr" | |||
| 309 | + | var fn func() func(a, b int32) int32 | |||
| 310 | + | purego.RegisterLibFunc(&fn, lib, cName) | |||
| 311 | + | if fn() != nil { | |||
| 312 | + | t.Fatalf("%s: got a non-nil func, want nil", cName) | |||
| 313 | + | } | |||
| 314 | + | } | |||
| 237 | } | 315 | } | ||
| 238 | 316 | ||||
| 239 | func TestABI_ArgumentPassing(t *testing.T) { | 317 | func TestABI_ArgumentPassing(t *testing.T) { | ||
| 240 | - | if runtime.GOOS == "windows" && runtime.GOARCH == "386" { | |||
| 241 | - | t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test | |||
| 242 | - | } | |||
| 243 | libFileName := filepath.Join(t.TempDir(), "abitest.so") | 318 | libFileName := filepath.Join(t.TempDir(), "abitest.so") | ||
| 244 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil { | 319 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil { |
| 245 | t.Fatal(err) | 320 | t.Fatal(err) | ||
| 246 | } | 321 | } | ||
| 247 | lib, err := load.OpenLibrary(libFileName) | 322 | lib, err := load.OpenLibrary(libFileName) | ||
| @@ -373,11 +448,20 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||||
| 373 | }, | 448 | }, | ||
| 374 | { | 449 | { | ||
| 375 | name: "8int_hfa4_stack", | 450 | name: "8int_hfa4_stack", | ||
| 376 | - | fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y, z, w float32 })), | 451 | + | fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct { |
| 377 | - | cFn: "stack_8int_hfa4_stack", | 452 | + | _ structs.HostLayout |
| 453 | + | x, y, z, w float32 | |||
| 454 | + | })), | |||
| 455 | + | cFn: "stack_8int_hfa4_stack", | |||
| 378 | call: func(f any) string { | 456 | call: func(f any) string { | ||
| 379 | buf := make([]byte, 256) | 457 | buf := make([]byte, 256) | ||
| 380 | - | (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct{ x, y, z, w float32 })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct{ x, y, z, w float32 }{10.0, 20.0, 30.0, 40.0}) | 458 | + | (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct { |
| 459 | + | _ structs.HostLayout | |||
| 460 | + | x, y, z, w float32 | |||
| 461 | + | })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct { | |||
| 462 | + | _ structs.HostLayout | |||
| 463 | + | x, y, z, w float32 | |||
| 464 | + | }{x: 10.0, y: 20.0, z: 30.0, w: 40.0}) | |||
| 381 | return string(buf[:bytes.IndexByte(buf, 0)]) | 465 | return string(buf[:bytes.IndexByte(buf, 0)]) | ||
| 382 | }, | 466 | }, | ||
| 383 | want: "1:2:3:4:5:6:7:8:10.0:20.0:30.0:40.0", | 467 | want: "1:2:3:4:5:6:7:8:10.0:20.0:30.0:40.0", | ||
| @@ -385,6 +469,7 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||||
| 385 | { | 469 | { | ||
| 386 | name: "8int_mixed_struct", | 470 | name: "8int_mixed_struct", | ||
| 387 | fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct { | 471 | fn: new(func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct { | ||
| 472 | + | _ structs.HostLayout | |||
| 388 | a int32 | 473 | a int32 | ||
| 389 | b float32 | 474 | b float32 | ||
| 390 | })), | 475 | })), | ||
| @@ -392,24 +477,108 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||||
| 392 | call: func(f any) string { | 477 | call: func(f any) string { | ||
| 393 | buf := make([]byte, 256) | 478 | buf := make([]byte, 256) | ||
| 394 | (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct { | 479 | (*f.(*func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, struct { | ||
| 480 | + | _ structs.HostLayout | |||
| 395 | a int32 | 481 | a int32 | ||
| 396 | b float32 | 482 | b float32 | ||
| 397 | })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct { | 483 | })))(&buf[0], 256, 1, 2, 3, 4, 5, 6, 7, 8, struct { | ||
| 484 | + | _ structs.HostLayout | |||
| 398 | a int32 | 485 | a int32 | ||
| 399 | b float32 | 486 | b float32 | ||
| 400 | - | }{9, 10.0}) | 487 | + | }{a: 9, b: 10.0}) |
| 401 | return string(buf[:bytes.IndexByte(buf, 0)]) | 488 | return string(buf[:bytes.IndexByte(buf, 0)]) | ||
| 402 | }, | 489 | }, | ||
| 403 | want: "1:2:3:4:5:6:7:8:9:10.0", | 490 | want: "1:2:3:4:5:6:7:8:9:10.0", | ||
| 404 | }, | 491 | }, | ||
| 492 | + | { | |||
| 493 | + | // check if unaligned 64bit argument and 64bit returned value is properly passed via registers | |||
| 494 | + | // arm-specific but must work everywhere | |||
| 495 | + | name: "arm_int64_unaligned_in_registers", | |||
| 496 | + | fn: new(func(uintptr, int64) int64), | |||
| 497 | + | cFn: "arm_int64_unaligned_in_registers", | |||
| 498 | + | call: func(f any) string { | |||
| 499 | + | fn := *(f).(*func(x uintptr, y int64) int64) | |||
| 500 | + | return strconv.FormatInt(fn(456, math.MaxInt32+1500), 10) | |||
| 501 | + | }, | |||
| 502 | + | want: strconv.FormatInt(456*123+math.MaxInt32+1500, 10), | |||
| 503 | + | }, | |||
| 504 | + | { | |||
| 505 | + | // check if unaligned 64bit argument and 64bit returned value is properly passed via stack | |||
| 506 | + | // arm-specific but must work everywhere | |||
| 507 | + | name: "arm_int64_unaligned_on_stack", | |||
| 508 | + | fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, int64) int64), | |||
| 509 | + | cFn: "arm_int64_unaligned_on_stack", | |||
| 510 | + | call: func(f any) string { | |||
| 511 | + | fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 int64) int64) | |||
| 512 | + | return strconv.FormatInt(fn(12, 34, 56, 78, 90, math.MaxInt32+1500), 10) | |||
| 513 | + | }, | |||
| 514 | + | want: strconv.FormatInt(12*1+34*2+56*3+78*4+90*5+math.MaxInt32+1500, 10), | |||
| 515 | + | }, | |||
| 516 | + | { | |||
| 517 | + | // check if unaligned 64bit argument and 64bit returned value is properly passed via stack when it's occupied by floats | |||
| 518 | + | // arm-specific but must work everywhere | |||
| 519 | + | name: "arm_int64_unaligned_on_stack_after_floats", | |||
| 520 | + | fn: new(func( | |||
| 521 | + | uintptr, uintptr, uintptr, uintptr, | |||
| 522 | + | float32, float32, float32, float32, | |||
| 523 | + | float32, float32, float32, float32, | |||
| 524 | + | float32, float32, float32, float32, | |||
| 525 | + | float32, float32, float32, float32, | |||
| 526 | + | float32, int64, | |||
| 527 | + | ) int64), | |||
| 528 | + | cFn: "arm_int64_unaligned_on_stack_after_floats", | |||
| 529 | + | call: func(f any) string { | |||
| 530 | + | fn := *(f).(*func( | |||
| 531 | + | a1, a2, a3, a4 uintptr, | |||
| 532 | + | f1, f2, f3, f4 float32, | |||
| 533 | + | f5, f6, f7, f8 float32, | |||
| 534 | + | f9, f10, f11, f12 float32, | |||
| 535 | + | f13, f14, f15, f16 float32, | |||
| 536 | + | f17 float32, a5 int64, | |||
| 537 | + | ) int64) | |||
| 538 | + | return strconv.FormatInt(fn( | |||
| 539 | + | 12, 34, 56, 78, | |||
| 540 | + | 0, 0, 0, 0, | |||
| 541 | + | 0, 0, 0, 0, | |||
| 542 | + | 0, 0, 0, 0, | |||
| 543 | + | 0, 0, 0, 0, | |||
| 544 | + | 0, math.MaxInt32+1500, | |||
| 545 | + | ), 10) | |||
| 546 | + | }, | |||
| 547 | + | want: strconv.FormatInt(12*1+34*2+56*3+78*4+math.MaxInt32+1500, 10), | |||
| 548 | + | }, | |||
| 549 | + | { | |||
| 550 | + | // check if unaligned float 64bit argument and float 64bit returned value is properly passed via registers | |||
| 551 | + | // arm-softfloat-specific but must work everywhere | |||
| 552 | + | name: "arm_float64_unaligned_in_registers", | |||
| 553 | + | fn: new(func(uintptr, float64) float64), | |||
| 554 | + | cFn: "arm_float64_unaligned_in_registers", | |||
| 555 | + | call: func(f any) string { | |||
| 556 | + | fn := *(f).(*func(x uintptr, y float64) float64) | |||
| 557 | + | return strconv.FormatFloat(fn(456, math.MaxFloat32+1500), 'b', 10, 64) | |||
| 558 | + | }, | |||
| 559 | + | want: strconv.FormatFloat(456*123.5+math.MaxFloat32+1500, 'b', 10, 64), | |||
| 560 | + | }, | |||
| 561 | + | { | |||
| 562 | + | // check if unaligned float 64bit argument and float 64bit returned value is properly passed via stack | |||
| 563 | + | // arm-softfloat-specific but must work everywhere | |||
| 564 | + | name: "arm_float64_unaligned_on_stack", | |||
| 565 | + | fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, float64) float64), | |||
| 566 | + | cFn: "arm_float64_unaligned_on_stack", | |||
| 567 | + | call: func(f any) string { | |||
| 568 | + | fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 float64) float64) | |||
| 569 | + | return strconv.FormatFloat(fn(12, 34, 56, 78, 90, math.MaxFloat32+1500), 'b', 10, 64) | |||
| 570 | + | }, | |||
| 571 | + | want: strconv.FormatFloat(12*1+34*2+56*3+78*4+90*5+math.MaxFloat32+1500, 'b', 10, 64), | |||
| 572 | + | }, | |||
| 405 | } | 573 | } | ||
| 406 | 574 | ||||
| 407 | for _, tt := range tests { | 575 | for _, tt := range tests { | ||
| 408 | t.Run(tt.name, func(t *testing.T) { | 576 | t.Run(tt.name, func(t *testing.T) { | ||
| 409 | - | if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") { | 577 | + | if tt.name == "20_int32" && runtime.GOARCH == "ppc64le" { |
| 410 | - | t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking") | 578 | + | t.Skip("ppc64le retains the 15-argument limit") |
| 411 | } | 579 | } | ||
| 412 | - | if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") { | 580 | + | if (tt.name == "10_float32" || tt.name == "arm_int64_unaligned_on_stack_after_floats") && |
| 581 | + | (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") { | |||
| 413 | t.Skip("float32 stack arguments not yet supported on this platform") | 582 | t.Skip("float32 stack arguments not yet supported on this platform") | ||
| 414 | } | 583 | } | ||
| 415 | // Struct tests require Darwin ARM64 or AMD64 | 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) { | 600 | + | t.Run("20_uintptr", func(t *testing.T) { |
| 433 | - | if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" { | 601 | + | if runtime.GOARCH == "ppc64le" { |
| 434 | - | t.Skip("This test is specific to Darwin ARM64") | 602 | + | t.Skip("ppc64le retains the 15-argument limit") |
| 435 | - | } | 603 | + | } |
| 604 | + | var fn func(uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr) uintptr | |||
| 605 | + | purego.RegisterLibFunc(&fn, lib, "stack_20_uintptr") | |||
| 606 | + | got := fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) | |||
| 607 | + | const want = uintptr(210) | |||
| 608 | + | if got != want { | |||
| 609 | + | t.Fatalf("stack_20_uintptr: got %d, want %d", got, want) | |||
| 610 | + | } | |||
| 611 | + | }) | |||
| 436 | 612 | ||||
| 437 | - | libFileName := filepath.Join(t.TempDir(), "abitest.so") | 613 | + | t.Run("32_uintptr", func(t *testing.T) { |
| 438 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil { | 614 | + | if runtime.GOARCH == "ppc64le" { |
| 439 | - | t.Fatal(err) | 615 | + | t.Skip("ppc64le retains the 15-argument limit") |
| 440 | - | } | 616 | + | } |
| 441 | - | lib, err := load.OpenLibrary(libFileName) | 617 | + | var fn func( |
| 442 | - | if err != nil { | 618 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, |
| 443 | - | t.Fatalf("Failed to open library %q: %v", libFileName, err) | 619 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, |
| 444 | - | } | 620 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, |
| 445 | - | t.Cleanup(func() { | 621 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, |
| 446 | - | if err := load.CloseLibrary(lib); err != nil { | 622 | + | ) uintptr |
| 447 | - | t.Errorf("Failed to close library: %v", err) | 623 | + | purego.RegisterLibFunc(&fn, lib, "stack_32_uintptr") |
| 624 | + | got := fn( | |||
| 625 | + | 1, 2, 3, 4, 5, 6, 7, 8, | |||
| 626 | + | 9, 10, 11, 12, 13, 14, 15, 16, | |||
| 627 | + | 17, 18, 19, 20, 21, 22, 23, 24, | |||
| 628 | + | 25, 26, 27, 28, 29, 30, 31, 32, | |||
| 629 | + | ) | |||
| 630 | + | const want = uintptr(528) | |||
| 631 | + | if got != want { | |||
| 632 | + | t.Fatalf("stack_32_uintptr: got %d, want %d", got, want) | |||
| 633 | + | } | |||
| 634 | + | }) | |||
| 635 | + | ||||
| 636 | + | t.Run("syscalln_20_uintptr", func(t *testing.T) { | |||
| 637 | + | if runtime.GOARCH == "ppc64le" { | |||
| 638 | + | t.Skip("ppc64le retains the 15-argument limit") | |||
| 639 | + | } | |||
| 640 | + | fn, err := load.OpenSymbol(lib, "stack_20_uintptr") | |||
| 641 | + | if err != nil { | |||
| 642 | + | t.Fatalf("OpenSymbol(stack_20_uintptr) failed: %v", err) | |||
| 643 | + | } | |||
| 644 | + | got, _, _ := purego.SyscallN(fn, | |||
| 645 | + | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, | |||
| 646 | + | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, | |||
| 647 | + | ) | |||
| 648 | + | const want = uintptr(210) | |||
| 649 | + | if got != want { | |||
| 650 | + | t.Fatalf("stack_20_uintptr SyscallN: got %d, want %d", got, want) | |||
| 448 | } | 651 | } | ||
| 449 | }) | 652 | }) | ||
| 450 | 653 | ||||
| 451 | - | // Test that 35 int64 arguments (27 slots needed) exceeds the limit | 654 | + | t.Run("syscalln_32_uintptr", func(t *testing.T) { |
| 452 | - | t.Run("35_int64_exceeds_limit", func(t *testing.T) { | 655 | + | if runtime.GOARCH == "ppc64le" { |
| 656 | + | t.Skip("ppc64le retains the 15-argument limit") | |||
| 657 | + | } | |||
| 658 | + | fn, err := load.OpenSymbol(lib, "stack_32_uintptr") | |||
| 659 | + | if err != nil { | |||
| 660 | + | t.Fatalf("OpenSymbol(stack_32_uintptr) failed: %v", err) | |||
| 661 | + | } | |||
| 662 | + | got, _, _ := purego.SyscallN(fn, | |||
| 663 | + | 1, 2, 3, 4, 5, 6, 7, 8, | |||
| 664 | + | 9, 10, 11, 12, 13, 14, 15, 16, | |||
| 665 | + | 17, 18, 19, 20, 21, 22, 23, 24, | |||
| 666 | + | 25, 26, 27, 28, 29, 30, 31, 32, | |||
| 667 | + | ) | |||
| 668 | + | const want = uintptr(528) | |||
| 669 | + | if got != want { | |||
| 670 | + | t.Fatalf("stack_32_uintptr SyscallN: got %d, want %d", got, want) | |||
| 671 | + | } | |||
| 672 | + | }) | |||
| 673 | + | ||||
| 674 | + | t.Run("32_mixed_int_float", func(t *testing.T) { | |||
| 675 | + | if unsafe.Sizeof(uintptr(0)) == 4 { | |||
| 676 | + | t.Skip("requires 64-bit uintptr slots") | |||
| 677 | + | } | |||
| 678 | + | if runtime.GOARCH == "ppc64le" { | |||
| 679 | + | t.Skip("mixed int/float stack arguments are not yet supported on ppc64le") | |||
| 680 | + | } | |||
| 681 | + | ||||
| 682 | + | var fn func( | |||
| 683 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, | |||
| 684 | + | uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, | |||
| 685 | + | float64, float64, float64, float64, float64, float64, float64, float64, | |||
| 686 | + | float64, float64, float64, float64, float64, float64, float64, float64, | |||
| 687 | + | ) float64 | |||
| 688 | + | purego.RegisterLibFunc(&fn, lib, "stack_32_mixed_int_float") | |||
| 689 | + | got := fn( | |||
| 690 | + | 1, 2, 3, 4, 5, 6, 7, 8, | |||
| 691 | + | 9, 10, 11, 12, 13, 14, 15, 16, | |||
| 692 | + | 1, 2, 3, 4, 5, 6, 7, 8, | |||
| 693 | + | 9, 10, 11, 12, 13, 14, 15, 16, | |||
| 694 | + | ) | |||
| 695 | + | const want = 5168.0 | |||
| 696 | + | if got != want { | |||
| 697 | + | t.Fatalf("stack_32_mixed_int_float: got %f, want %f", got, want) | |||
| 698 | + | } | |||
| 699 | + | }) | |||
| 700 | + | } | |||
| 701 | + | ||||
| 702 | + | func TestABI_TooManyArguments(t *testing.T) { | |||
| 703 | + | mustPanic := func(t *testing.T, want string, f func()) { | |||
| 704 | + | t.Helper() | |||
| 453 | defer func() { | 705 | defer func() { | ||
| 454 | - | if r := recover(); r != nil { | 706 | + | r := recover() |
| 455 | - | t.Logf("Got expected panic: %v", r) | 707 | + | if r == nil { |
| 456 | - | } else { | 708 | + | t.Fatalf("expected panic %q, got none", want) |
| 457 | - | t.Errorf("Expected panic but didn't get one") | 709 | + | } |
| 710 | + | got := fmt.Sprint(r) | |||
| 711 | + | if got != want { | |||
| 712 | + | t.Fatalf("panic mismatch:\n got: %q\n want: %q", got, want) | |||
| 458 | } | 713 | } | ||
| 459 | }() | 714 | }() | ||
| 715 | + | f() | |||
| 716 | + | } | |||
| 460 | 717 | ||||
| 461 | - | var fn func(*byte, uintptr, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) | 718 | + | // 33 int64 parameters exceeds maxArgs=32. |
| 462 | - | purego.RegisterLibFunc(&fn, lib, "stack_35_int64_exceeds") | 719 | + | t.Run("registerfunc_33_int64_exceeds_limit", func(t *testing.T) { |
| 720 | + | mustPanic(t, "purego: too many stack arguments", func() { | |||
| 721 | + | var fn func( | |||
| 722 | + | int64, int64, int64, int64, int64, int64, int64, int64, | |||
| 723 | + | int64, int64, int64, int64, int64, int64, int64, int64, | |||
| 724 | + | int64, int64, int64, int64, int64, int64, int64, int64, | |||
| 725 | + | int64, int64, int64, int64, int64, int64, int64, int64, | |||
| 726 | + | int64, | |||
| 727 | + | ) | |||
| 728 | + | purego.RegisterFunc(&fn, 1) | |||
| 729 | + | }) | |||
| 730 | + | }) | |||
| 731 | + | ||||
| 732 | + | t.Run("registerfunc_16_int64_exceeds_ppc64le_limit", func(t *testing.T) { | |||
| 733 | + | if runtime.GOARCH != "ppc64le" { | |||
| 734 | + | t.Skip("ppc64le retains the 15-argument limit") | |||
| 735 | + | } | |||
| 736 | + | mustPanic(t, "purego: too many stack arguments", func() { | |||
| 737 | + | var fn func( | |||
| 738 | + | int64, int64, int64, int64, int64, int64, int64, int64, | |||
| 739 | + | int64, int64, int64, int64, int64, int64, int64, int64, | |||
| 740 | + | ) | |||
| 741 | + | purego.RegisterFunc(&fn, 1) | |||
| 742 | + | }) | |||
| 743 | + | }) | |||
| 744 | + | ||||
| 745 | + | t.Run("syscalln_33_uintptr_exceeds_limit", func(t *testing.T) { | |||
| 746 | + | mustPanic(t, "purego: too many arguments to SyscallN", func() { | |||
| 747 | + | purego.SyscallN(1, | |||
| 748 | + | 1, 2, 3, 4, 5, 6, 7, 8, | |||
| 749 | + | 9, 10, 11, 12, 13, 14, 15, 16, | |||
| 750 | + | 17, 18, 19, 20, 21, 22, 23, 24, | |||
| 751 | + | 25, 26, 27, 28, 29, 30, 31, 32, | |||
| 752 | + | 33, | |||
| 753 | + | ) | |||
| 754 | + | }) | |||
| 755 | + | }) | |||
| 756 | + | ||||
| 757 | + | t.Run("syscalln_16_uintptr_exceeds_ppc64le_limit", func(t *testing.T) { | |||
| 758 | + | if runtime.GOARCH != "ppc64le" { | |||
| 759 | + | t.Skip("ppc64le retains the 15-argument limit") | |||
| 760 | + | } | |||
| 761 | + | mustPanic(t, "purego: too many arguments to SyscallN", func() { | |||
| 762 | + | purego.SyscallN(1, | |||
| 763 | + | 1, 2, 3, 4, 5, 6, 7, 8, | |||
| 764 | + | 9, 10, 11, 12, 13, 14, 15, 16, | |||
| 765 | + | ) | |||
| 766 | + | }) | |||
| 463 | }) | 767 | }) | ||
| 464 | } | 768 | } | ||
| 465 | 769 | ||||
| 466 | - | func buildSharedLib(compilerEnv, libFile string, sources ...string) error { | 770 | + | func TestABI_StructReturnHiddenPointer(t *testing.T) { |
| 771 | + | // A struct returned in memory is passed a hidden pointer as the first | |||
| 772 | + | // integer argument, so maxArgs integer arguments plus it need one more slot | |||
| 773 | + | // than sysargs holds and RegisterFunc must reject the registration. | |||
| 774 | + | type bigStruct struct{ A, B, C uint64 } // larger than two eightbytes | |||
| 775 | + | ||||
| 776 | + | if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]()) { | |||
| 777 | + | t.Skipf("GOARCH=%s does not return large structs via a hidden integer argument", runtime.GOARCH) | |||
| 778 | + | } | |||
| 779 | + | ||||
| 780 | + | in := make([]reflect.Type, purego.MaxArgs) | |||
| 781 | + | for i := range in { | |||
| 782 | + | in[i] = reflect.TypeFor[uintptr]() | |||
| 783 | + | } | |||
| 784 | + | fnType := reflect.FuncOf(in, []reflect.Type{reflect.TypeFor[bigStruct]()}, false) | |||
| 785 | + | fptr := reflect.New(fnType) | |||
| 786 | + | ||||
| 787 | + | defer func() { | |||
| 788 | + | switch r := recover(); { | |||
| 789 | + | case r == nil: | |||
| 790 | + | t.Fatal("RegisterFunc accepted the call; the hidden struct-return pointer was not counted against the stack limit") | |||
| 791 | + | case strings.Contains(fmt.Sprint(r), "too many stack arguments"): | |||
| 792 | + | // Expected: the guard fired. | |||
| 793 | + | case strings.Contains(fmt.Sprint(r), "only supported on"): | |||
| 794 | + | t.Skipf("struct returns are unsupported on this platform: %v", r) | |||
| 795 | + | default: | |||
| 796 | + | t.Fatalf("unexpected panic: %v", r) | |||
| 797 | + | } | |||
| 798 | + | }() | |||
| 799 | + | ||||
| 800 | + | // A non-zero cfn passes the nil check; the panic fires during the preflight | |||
| 801 | + | // argument count, before the function is ever called. | |||
| 802 | + | purego.RegisterFunc(fptr.Interface(), uintptr(1)) | |||
| 803 | + | } | |||
| 804 | + | ||||
| 805 | + | func buildSharedLib(tb testing.TB, compilerEnv, libFile string, sources ...string) error { | |||
| 806 | + | tb.Helper() | |||
| 807 | + | // When PUREGO_TEST_PREBUILT_LIBDIR is set, the shared library has been | |||
| 808 | + | // cross-compiled ahead of time and placed in that directory under the | |||
| 809 | + | // base name of libFile. This allows running the tests on a target that | |||
| 810 | + | // has no C toolchain, such as an Android emulator. | |||
| 811 | + | if dir := os.Getenv("PUREGO_TEST_PREBUILT_LIBDIR"); dir != "" { | |||
| 812 | + | data, err := os.ReadFile(filepath.Join(dir, filepath.Base(libFile))) | |||
| 813 | + | if err != nil { | |||
| 814 | + | return fmt.Errorf("prebuilt lib: %w", err) | |||
| 815 | + | } | |||
| 816 | + | if err := os.WriteFile(libFile, data, 0o755); err != nil { | |||
| 817 | + | return fmt.Errorf("prebuilt lib: %w", err) | |||
| 818 | + | } | |||
| 819 | + | return nil | |||
| 820 | + | } | |||
| 821 | + | ||||
| 822 | + | // Compiling the library needs a C toolchain targeting GOARCH. CI has none | |||
| 823 | + | // for Windows on 386 or arm64, so skip those (the prebuilt path above | |||
| 824 | + | // avoids the toolchain). | |||
| 825 | + | if runtime.GOOS == "windows" { | |||
| 826 | + | switch runtime.GOARCH { | |||
| 827 | + | case "386": | |||
| 828 | + | tb.Skip("need a 386 C toolchain to run this test") // TODO: find a 386 C toolchain for test | |||
| 829 | + | case "arm64": | |||
| 830 | + | tb.Skip("need an arm64 C toolchain to run this test") | |||
| 831 | + | } | |||
| 832 | + | } | |||
| 833 | + | ||||
| 467 | out, err := exec.Command("go", "env", compilerEnv).Output() | 834 | out, err := exec.Command("go", "env", compilerEnv).Output() | ||
| 468 | if err != nil { | 835 | if err != nil { | ||
| 469 | return fmt.Errorf("go env %s error: %w", compilerEnv, err) | 836 | return fmt.Errorf("go env %s error: %w", compilerEnv, err) | ||
M go.mod +1 -1
| @@ -1,3 +1,3 @@ | |||||
| 1 | module github.com/bnema/purego | 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 | //go:linkname runtime_cgocall runtime.cgocall | 12 | //go:linkname runtime_cgocall runtime.cgocall | ||
| 13 | func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go | 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 | #include <errno.h> | 16 | #include <errno.h> | ||
| 17 | #include <assert.h> | 17 | #include <assert.h> | ||
| 18 | 18 | ||||
| 19 | - | typedef struct syscall15Args { | 19 | + | typedef struct syscallArgs { |
| 20 | uintptr_t fn; | 20 | uintptr_t fn; | ||
| 21 | uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15; | 21 | uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15; | ||
| 22 | + | uintptr_t a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32; | |||
| 22 | uintptr_t f1, f2, f3, f4, f5, f6, f7, f8; | 23 | uintptr_t f1, f2, f3, f4, f5, f6, f7, f8; | ||
| 23 | - | uintptr_t err; | 24 | + | uintptr_t arm64_r8; |
| 24 | - | } syscall15Args; | 25 | + | } syscallArgs; |
| 25 | 26 | ||||
| 26 | - | void syscall15(struct syscall15Args *args) { | 27 | + | void syscall15(struct syscallArgs *args) { |
| 27 | assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0); | 28 | assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0); | ||
| 28 | uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, | 29 | uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, | ||
| 29 | uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, | 30 | uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, | ||
| 30 | - | uintptr_t a13, uintptr_t a14, uintptr_t a15); | 31 | + | uintptr_t a13, uintptr_t a14, uintptr_t a15, uintptr_t a16, uintptr_t a17, uintptr_t a18, |
| 32 | + | uintptr_t a19, uintptr_t a20, uintptr_t a21, uintptr_t a22, uintptr_t a23, uintptr_t a24, | |||
| 33 | + | uintptr_t a25, uintptr_t a26, uintptr_t a27, uintptr_t a28, uintptr_t a29, uintptr_t a30, | |||
| 34 | + | uintptr_t a31, uintptr_t a32); | |||
| 31 | *(void**)(&func_name) = (void*)(args->fn); | 35 | *(void**)(&func_name) = (void*)(args->fn); | ||
| 32 | - | uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9, | 36 | + | uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9, |
| 33 | - | args->a10,args->a11,args->a12,args->a13,args->a14,args->a15); | 37 | + | args->a10,args->a11,args->a12,args->a13,args->a14,args->a15,args->a16,args->a17,args->a18, |
| 38 | + | args->a19,args->a20,args->a21,args->a22,args->a23,args->a24,args->a25,args->a26,args->a27, | |||
| 39 | + | args->a28,args->a29,args->a30,args->a31,args->a32); | |||
| 34 | args->a1 = r1; | 40 | args->a1 = r1; | ||
| 35 | - | args->err = errno; | 41 | + | args->a3 = errno; |
| 36 | } | 42 | } | ||
| 37 | 43 | ||||
| 38 | */ | 44 | */ | ||
| 39 | import "C" | 45 | import "C" | ||
| 40 | import "unsafe" | 46 | import "unsafe" | ||
| 41 | 47 | ||||
| 42 | - | // assign purego.syscall15XABI0 to the C version of this function. | 48 | + | // assign purego.syscallXABI0 to the C version of this function. |
| 43 | - | var Syscall15XABI0 = unsafe.Pointer(C.syscall15) | 49 | + | var SyscallXABI0 = unsafe.Pointer(C.syscall15) |
| 44 | - | ||||
| 45 | - | //go:nosplit | |||
| 46 | - | func Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { | |||
| 47 | - | args := C.syscall15Args{ | |||
| 48 | - | C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3), | |||
| 49 | - | C.uintptr_t(a4), C.uintptr_t(a5), C.uintptr_t(a6), | |||
| 50 | - | C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), C.uintptr_t(a10), C.uintptr_t(a11), C.uintptr_t(a12), | |||
| 51 | - | C.uintptr_t(a13), C.uintptr_t(a14), C.uintptr_t(a15), 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||
| 52 | - | } | |||
| 53 | - | C.syscall15(&args) | |||
| 54 | - | return uintptr(args.a1), 0, uintptr(args.err) | |||
| 55 | - | } | |||
M internal/fakecgo/asm_arm.s +9 -3
| @@ -21,8 +21,11 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||||
| 21 | // starting at 4(R13). | 21 | // starting at 4(R13). | ||
| 22 | MOVW.W R14, -4(R13) | 22 | MOVW.W R14, -4(R13) | ||
| 23 | 23 | ||||
| 24 | + | // Skip floating point registers if runtime.goarmsoftfp!=0. | |||
| 25 | + | MOVB runtime·goarmsoftfp(SB), R11 | |||
| 26 | + | CMP $0, R11 | |||
| 27 | + | BNE skipfpsave | |||
| 24 | // Save VFP callee-saved registers D8-D15 (same as S16-S31). | 28 | // Save VFP callee-saved registers D8-D15 (same as S16-S31). | ||
| 25 | - | // Note: We always save these since we target hard-float ABI. | |||
| 26 | MOVD F8, (13*4+8*1)(R13) | 29 | MOVD F8, (13*4+8*1)(R13) | ||
| 27 | MOVD F9, (13*4+8*2)(R13) | 30 | MOVD F9, (13*4+8*2)(R13) | ||
| 28 | MOVD F10, (13*4+8*3)(R13) | 31 | MOVD F10, (13*4+8*3)(R13) | ||
| @@ -32,11 +35,13 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||||
| 32 | MOVD F14, (13*4+8*7)(R13) | 35 | MOVD F14, (13*4+8*7)(R13) | ||
| 33 | MOVD F15, (13*4+8*8)(R13) | 36 | MOVD F15, (13*4+8*8)(R13) | ||
| 34 | 37 | ||||
| 35 | - | BL runtime·load_g(SB) | 38 | + | skipfpsave: |
| 36 | - | ||||
| 37 | // We set up the arguments to cgocallback when saving registers above. | 39 | // We set up the arguments to cgocallback when saving registers above. | ||
| 38 | BL runtime·cgocallback(SB) | 40 | BL runtime·cgocallback(SB) | ||
| 39 | 41 | ||||
| 42 | + | MOVB runtime·goarmsoftfp(SB), R11 | |||
| 43 | + | CMP $0, R11 | |||
| 44 | + | BNE skipfprest | |||
| 40 | MOVD (13*4+8*1)(R13), F8 | 45 | MOVD (13*4+8*1)(R13), F8 | ||
| 41 | MOVD (13*4+8*2)(R13), F9 | 46 | MOVD (13*4+8*2)(R13), F9 | ||
| 42 | MOVD (13*4+8*3)(R13), F10 | 47 | MOVD (13*4+8*3)(R13), F10 | ||
| @@ -46,6 +51,7 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||||
| 46 | MOVD (13*4+8*7)(R13), F14 | 51 | MOVD (13*4+8*7)(R13), F14 | ||
| 47 | MOVD (13*4+8*8)(R13), F15 | 52 | MOVD (13*4+8*8)(R13), F15 | ||
| 48 | 53 | ||||
| 54 | + | skipfprest: | |||
| 49 | MOVW.P 4(R13), R14 | 55 | MOVW.P 4(R13), R14 | ||
| 50 | MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12] | 56 | MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12] | ||
| 51 | ADD $(8*9), R13 | 57 | ADD $(8*9), R13 | ||
M internal/fakecgo/asm_arm64.s +0 -1
| @@ -25,7 +25,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||||
| 25 | STP (R29, R30), (8*22)(RSP) | 25 | STP (R29, R30), (8*22)(RSP) | ||
| 26 | 26 | ||||
| 27 | // Initialize Go ABI environment | 27 | // Initialize Go ABI environment | ||
| 28 | - | BL runtime·load_g(SB) | |||
| 29 | BL runtime·cgocallback(SB) | 28 | BL runtime·cgocallback(SB) | ||
| 30 | 29 | ||||
| 31 | RESTORE_R19_TO_R28(8*4) | 30 | RESTORE_R19_TO_R28(8*4) | ||
M internal/fakecgo/asm_loong64.s +0 -2
| @@ -27,8 +27,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||||
| 27 | MOVV R1, (22*8)(R3) | 27 | MOVV R1, (22*8)(R3) | ||
| 28 | 28 | ||||
| 29 | // Initialize Go ABI environment | 29 | // Initialize Go ABI environment | ||
| 30 | - | JAL runtime·load_g(SB) | |||
| 31 | - | ||||
| 32 | JAL runtime·cgocallback(SB) | 30 | JAL runtime·cgocallback(SB) | ||
| 33 | 31 | ||||
| 34 | RESTORE_R22_TO_R31((4*8)) | 32 | RESTORE_R22_TO_R31((4*8)) | ||
M internal/fakecgo/asm_ppc64le.s +0 -3
| @@ -50,9 +50,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||||
| 50 | // Initialize R0 to 0 as expected by Go | 50 | // Initialize R0 to 0 as expected by Go | ||
| 51 | MOVD $0, R0 | 51 | MOVD $0, R0 | ||
| 52 | 52 | ||||
| 53 | - | // Load the current g. | |||
| 54 | - | BL runtime·load_g(SB) | |||
| 55 | - | ||||
| 56 | // Set up arguments for cgocallback | 53 | // Set up arguments for cgocallback | ||
| 57 | MOVD R3, FIXED_FRAME+0(R1) // fn unsafe.Pointer | 54 | MOVD R3, FIXED_FRAME+0(R1) // fn unsafe.Pointer | ||
| 58 | MOVD R4, FIXED_FRAME+8(R1) // a unsafe.Pointer | 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 | SAVE_FPR((8*17)) | 25 | SAVE_FPR((8*17)) | ||
| 26 | 26 | ||||
| 27 | // Initialize Go ABI environment | 27 | // Initialize Go ABI environment | ||
| 28 | - | CALL runtime·load_g(SB) | |||
| 29 | CALL runtime·cgocallback(SB) | 28 | CALL runtime·cgocallback(SB) | ||
| 30 | 29 | ||||
| 31 | RESTORE_GPR((8*4)) | 30 | RESTORE_GPR((8*4)) | ||
M internal/fakecgo/asm_s390x.s +0 -3
| @@ -27,9 +27,6 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||||
| 27 | FMOVD F14, 80(R15) | 27 | FMOVD F14, 80(R15) | ||
| 28 | FMOVD F15, 88(R15) | 28 | FMOVD F15, 88(R15) | ||
| 29 | 29 | ||||
| 30 | - | // Initialize Go ABI environment. | |||
| 31 | - | BL runtime·load_g(SB) | |||
| 32 | - | ||||
| 33 | MOVD R2, 8(R15) // fn unsafe.Pointer | 30 | MOVD R2, 8(R15) // fn unsafe.Pointer | ||
| 34 | MOVD R3, 16(R15) // a unsafe.Pointer | 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 | pthreadSO = "/usr/lib/libSystem.B.dylib" | 268 | pthreadSO = "/usr/lib/libSystem.B.dylib" | ||
| 269 | case "freebsd": | 269 | case "freebsd": | ||
| 270 | libcSO = "libc.so.7" | 270 | libcSO = "libc.so.7" | ||
| 271 | - | pthreadSO = "libpthread.so" | 271 | + | pthreadSO = "libthr.so.3" |
| 272 | case "linux": | 272 | case "linux": | ||
| 273 | libcSO = "libc.so.6" | 273 | libcSO = "libc.so.6" | ||
| 274 | pthreadSO = "libpthread.so.0" | 274 | pthreadSO = "libpthread.so.0" | ||
M internal/fakecgo/go_darwin.go +1 -3
| @@ -55,9 +55,7 @@ func threadentry(v unsafe.Pointer) unsafe.Pointer { | |||||
| 55 | //#endif | 55 | //#endif | ||
| 56 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | 56 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | ||
| 57 | 57 | ||||
| 58 | - | // faking funcs in go is a bit a... involved - but the following works :) | 58 | + | callThreadEntryFn(ts.fn) |
| 59 | - | fn := uintptr(unsafe.Pointer(&ts.fn)) | |||
| 60 | - | (*(*func())(unsafe.Pointer(&fn)))() | |||
| 61 | 59 | ||||
| 62 | return nil | 60 | return nil | ||
| 63 | } | 61 | } | ||
M internal/fakecgo/go_freebsd.go +2 -4
| @@ -2,7 +2,7 @@ | |||||
| 2 | // Use of this source code is governed by a BSD-style | 2 | // Use of this source code is governed by a BSD-style | ||
| 3 | // license that can be found in the LICENSE file. | 3 | // license that can be found in the LICENSE file. | ||
| 4 | 4 | ||||
| 5 | - | //go:build !cgo | 5 | + | //go:build !cgo && (amd64 || arm64) |
| 6 | 6 | ||||
| 7 | package fakecgo | 7 | package fakecgo | ||
| 8 | 8 | ||||
| @@ -49,9 +49,7 @@ func threadentry(v unsafe.Pointer) unsafe.Pointer { | |||||
| 49 | 49 | ||||
| 50 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | 50 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | ||
| 51 | 51 | ||||
| 52 | - | // faking funcs in go is a bit a... involved - but the following works :) | 52 | + | callThreadEntryFn(ts.fn) |
| 53 | - | fn := uintptr(unsafe.Pointer(&ts.fn)) | |||
| 54 | - | (*(*func())(unsafe.Pointer(&fn)))() | |||
| 55 | 53 | ||||
| 56 | return nil | 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 | tries := ts.Nsec | 52 | tries := ts.Nsec | ||
| 53 | var err int | 53 | var err int | ||
| 54 | 54 | ||||
| 55 | - | for tries = 0; tries < 20; tries++ { | 55 | + | for tries = range 20 { |
| 56 | // inlined this call because it ran out of stack when inlining was disabled | 56 | // inlined this call because it ran out of stack when inlining was disabled | ||
| 57 | err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0)) | 57 | err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0)) | ||
| 58 | if err == 0 { | 58 | if err == 0 { | ||
M internal/fakecgo/go_linux.go +1 -3
| @@ -49,9 +49,7 @@ func threadentry(v unsafe.Pointer) unsafe.Pointer { | |||||
| 49 | 49 | ||||
| 50 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | 50 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | ||
| 51 | 51 | ||||
| 52 | - | // faking funcs in go is a bit a... involved - but the following works :) | 52 | + | callThreadEntryFn(ts.fn) |
| 53 | - | fn := uintptr(unsafe.Pointer(&ts.fn)) | |||
| 54 | - | (*(*func())(unsafe.Pointer(&fn)))() | |||
| 55 | 53 | ||||
| 56 | return nil | 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 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | 61 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | ||
| 62 | 62 | ||||
| 63 | - | // faking funcs in go is a bit a... involved - but the following works :) | 63 | + | callThreadEntryFn(ts.fn) |
| 64 | - | fn := uintptr(unsafe.Pointer(&ts.fn)) | |||
| 65 | - | (*(*func())(unsafe.Pointer(&fn)))() | |||
| 66 | 64 | ||||
| 67 | return nil | 65 | return nil | ||
| 68 | } | 66 | } | ||
M internal/fakecgo/libcgo.go +4 -0
| @@ -5,6 +5,8 @@ | |||||
| 5 | 5 | ||||
| 6 | package fakecgo | 6 | package fakecgo | ||
| 7 | 7 | ||||
| 8 | + | import "structs" | |||
| 9 | + | ||||
| 8 | type ( | 10 | type ( | ||
| 9 | size_t uintptr | 11 | size_t uintptr | ||
| 10 | // Sources: | 12 | // Sources: | ||
| @@ -28,11 +30,13 @@ const ( | |||||
| 28 | ) | 30 | ) | ||
| 29 | 31 | ||||
| 30 | type G struct { | 32 | type G struct { | ||
| 33 | + | _ structs.HostLayout | |||
| 31 | stacklo uintptr | 34 | stacklo uintptr | ||
| 32 | stackhi uintptr | 35 | stackhi uintptr | ||
| 33 | } | 36 | } | ||
| 34 | 37 | ||||
| 35 | type ThreadStart struct { | 38 | type ThreadStart struct { | ||
| 39 | + | _ structs.HostLayout | |||
| 36 | g *G | 40 | g *G | ||
| 37 | tls *uintptr | 41 | tls *uintptr | ||
| 38 | fn uintptr | 42 | fn uintptr | ||
M internal/fakecgo/libcgo_darwin.go +4 -0
| @@ -5,12 +5,16 @@ | |||||
| 5 | 5 | ||||
| 6 | package fakecgo | 6 | package fakecgo | ||
| 7 | 7 | ||||
| 8 | + | import "structs" | |||
| 9 | + | ||||
| 8 | type ( | 10 | type ( | ||
| 9 | pthread_mutex_t struct { | 11 | pthread_mutex_t struct { | ||
| 12 | + | _ structs.HostLayout | |||
| 10 | sig int64 | 13 | sig int64 | ||
| 11 | opaque [56]byte | 14 | opaque [56]byte | ||
| 12 | } | 15 | } | ||
| 13 | pthread_cond_t struct { | 16 | pthread_cond_t struct { | ||
| 17 | + | _ structs.HostLayout | |||
| 14 | sig int64 | 18 | sig int64 | ||
| 15 | opaque [40]byte | 19 | opaque [40]byte | ||
| 16 | } | 20 | } | ||
M internal/fakecgo/libcgo_netbsd.go +3 -0
| @@ -5,6 +5,8 @@ | |||||
| 5 | 5 | ||||
| 6 | package fakecgo | 6 | package fakecgo | ||
| 7 | 7 | ||||
| 8 | + | import "structs" | |||
| 9 | + | ||||
| 8 | type ( | 10 | type ( | ||
| 9 | pthread_cond_t uintptr | 11 | pthread_cond_t uintptr | ||
| 10 | pthread_mutex_t uintptr | 12 | pthread_mutex_t uintptr | ||
| @@ -17,6 +19,7 @@ var ( | |||||
| 17 | 19 | ||||
| 18 | // Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/sys/signal.h#L225 | 20 | // Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/sys/signal.h#L225 | ||
| 19 | type stack_t struct { | 21 | type stack_t struct { | ||
| 22 | + | _ structs.HostLayout | |||
| 20 | ss_sp uintptr | 23 | ss_sp uintptr | ||
| 21 | ss_size uintptr | 24 | ss_size uintptr | ||
| 22 | ss_flags int32 | 25 | ss_flags int32 | ||
M internal/fakecgo/linux.go +7 -2
| @@ -6,10 +6,15 @@ | |||||
| 6 | 6 | ||||
| 7 | package fakecgo | 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 | type argset struct { | 16 | type argset struct { | ||
| 17 | + | _ structs.HostLayout | |||
| 13 | args *uintptr | 18 | args *uintptr | ||
| 14 | retval uintptr | 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 | POP_REGS_HOST_TO_ABI0() | 83 | POP_REGS_HOST_TO_ABI0() | ||
| 84 | RET | 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 | TEXT ·call5(SB), NOSPLIT, $0-56 | 103 | TEXT ·call5(SB), NOSPLIT, $0-56 | ||
| 87 | MOVQ fn+0(FP), R11 | 104 | MOVQ fn+0(FP), R11 | ||
| 88 | MOVQ a1+8(FP), DI | 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 | MOVW g, 32(R13) // R10 | 70 | MOVW g, 32(R13) // R10 | ||
| 71 | MOVW R11, 36(R13) | 71 | MOVW R11, 36(R13) | ||
| 72 | 72 | ||||
| 73 | + | // Skip floating point registers if runtime.goarmsoftfp!=0. | |||
| 74 | + | MOVB runtime·goarmsoftfp(SB), R11 | |||
| 75 | + | CMP $0, R11 | |||
| 76 | + | BNE skipfpsave | |||
| 73 | MOVD F8, 40(R13) | 77 | MOVD F8, 40(R13) | ||
| 74 | MOVD F9, 48(R13) | 78 | MOVD F9, 48(R13) | ||
| 75 | MOVD F10, 56(R13) | 79 | MOVD F10, 56(R13) | ||
| @@ -79,10 +83,14 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 | |||||
| 79 | MOVD F14, 88(R13) | 83 | MOVD F14, 88(R13) | ||
| 80 | MOVD F15, 96(R13) | 84 | MOVD F15, 96(R13) | ||
| 81 | 85 | ||||
| 86 | + | skipfpsave: | |||
| 82 | MOVW ·threadentry_call(SB), R12 | 87 | MOVW ·threadentry_call(SB), R12 | ||
| 83 | MOVW (R12), R12 | 88 | MOVW (R12), R12 | ||
| 84 | CALL (R12) | 89 | CALL (R12) | ||
| 85 | 90 | ||||
| 91 | + | MOVB runtime·goarmsoftfp(SB), R11 | |||
| 92 | + | CMP $0, R11 | |||
| 93 | + | BNE skipfprest | |||
| 86 | MOVD 40(R13), F8 | 94 | MOVD 40(R13), F8 | ||
| 87 | MOVD 48(R13), F9 | 95 | MOVD 48(R13), F9 | ||
| 88 | MOVD 56(R13), F10 | 96 | MOVD 56(R13), F10 | ||
| @@ -92,6 +100,7 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 | |||||
| 92 | MOVD 88(R13), F14 | 100 | MOVD 88(R13), F14 | ||
| 93 | MOVD 96(R13), F15 | 101 | MOVD 96(R13), F15 | ||
| 94 | 102 | ||||
| 103 | + | skipfprest: | |||
| 95 | MOVW 8(R13), R4 | 104 | MOVW 8(R13), R4 | ||
| 96 | MOVW 12(R13), R5 | 105 | MOVW 12(R13), R5 | ||
| 97 | MOVW 16(R13), R6 | 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 | // func setg_trampoline(setg uintptr, g uintptr) | 48 | // func setg_trampoline(setg uintptr, g uintptr) | ||
| 49 | TEXT ·setg_trampoline(SB), NOSPLIT, $16-16 | 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 | MOVD setg+0(FP), R12 | 52 | MOVD setg+0(FP), R12 | ||
| 53 | MOVD newg+8(FP), R3 | 53 | MOVD newg+8(FP), R3 | ||
| 54 | 54 | ||||
| 55 | + | MOVD R3, 16(R1) // save newg before call | |||
| 56 | + | ||||
| 55 | MOVD R12, CTR | 57 | MOVD R12, CTR | ||
| 56 | CALL CTR | 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 | MOVD 8(R1), R31 | 64 | MOVD 8(R1), R31 | ||
| 61 | XOR R0, R0, R0 | 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 | //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7" | 16 | //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7" | ||
| 17 | //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7" | 17 | //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7" | ||
| 18 | //go:cgo_import_dynamic purego_abort abort "libc.so.7" | 18 | //go:cgo_import_dynamic purego_abort abort "libc.so.7" | ||
| 19 | - | //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so" | 19 | + | //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libthr.so.3" |
| 20 | - | //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so" | 20 | + | //go:cgo_import_dynamic purego_pthread_create pthread_create "libthr.so.3" |
| 21 | - | //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so" | 21 | + | //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libthr.so.3" |
| 22 | - | //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so" | 22 | + | //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libthr.so.3" |
| 23 | - | //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so" | 23 | + | //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libthr.so.3" |
| 24 | - | //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so" | 24 | + | //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libthr.so.3" |
| 25 | - | //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so" | 25 | + | //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libthr.so.3" |
| 26 | - | //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so" | 26 | + | //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libthr.so.3" |
| 27 | - | //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so" | 27 | + | //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libthr.so.3" |
| 28 | - | //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so" | 28 | + | //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libthr.so.3" |
| 29 | 29 | ||||
| 30 | //go:nosplit | 30 | //go:nosplit | ||
| 31 | //go:norace | 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 | import ( | 6 | import ( | ||
| 7 | "fmt" | 7 | "fmt" | ||
| 8 | "reflect" | 8 | "reflect" | ||
| 9 | + | "structs" | |||
| 9 | "sync" | 10 | "sync" | ||
| 10 | "unsafe" | 11 | "unsafe" | ||
| 11 | 12 | ||||
| 12 | "github.com/bnema/purego" | 13 | "github.com/bnema/purego" | ||
| 13 | - | "github.com/bnema/purego/internal/xreflect" | |||
| 14 | ) | 14 | ) | ||
| 15 | 15 | ||||
| 16 | const ( | 16 | const ( | ||
| @@ -19,7 +19,10 @@ const ( | |||||
| 19 | // when the reference count drops to zero, so the associated function is also unreferenced. | 19 | // when the reference count drops to zero, so the associated function is also unreferenced. | ||
| 20 | 20 | ||||
| 21 | // blockBaseClass is the name of the class that block objects will be initialized with. | 21 | // blockBaseClass is the name of the class that block objects will be initialized with. | ||
| 22 | - | blockBaseClass = "__NSMallocBlock__" | 22 | + | // A new block is a template that Block_copy always relocates to the |
| 23 | + | // Objective-C heap, so the stack-block class is the accurate name here, | |||
| 24 | + | // as the isa of the copied block becomes __NSMallocBlock__ anyway. | |||
| 25 | + | blockBaseClass = "__NSStackBlock__" | |||
| 23 | // blockFlags is the set of flags that block objects will be initialized with. | 26 | // blockFlags is the set of flags that block objects will be initialized with. | ||
| 24 | blockFlags = blockHasCopyDispose | blockHasSignature | 27 | blockFlags = blockHasCopyDispose | blockHasSignature | ||
| 25 | 28 | ||||
| @@ -35,6 +38,7 @@ const ( | |||||
| 35 | // | 38 | // | ||
| 36 | // The layout of this struct matches Block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level | 39 | // The layout of this struct matches Block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level | ||
| 37 | type blockDescriptor struct { | 40 | type blockDescriptor struct { | ||
| 41 | + | _ structs.HostLayout | |||
| 38 | _ uintptr | 42 | _ uintptr | ||
| 39 | size uintptr | 43 | size uintptr | ||
| 40 | _ uintptr | 44 | _ uintptr | ||
| @@ -48,6 +52,7 @@ type blockDescriptor struct { | |||||
| 48 | // | 52 | // | ||
| 49 | // The layout of this struct matches __block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level | 53 | // The layout of this struct matches __block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level | ||
| 50 | type blockLayout struct { | 54 | type blockLayout struct { | ||
| 55 | + | _ structs.HostLayout | |||
| 51 | isa Class | 56 | isa Class | ||
| 52 | flags uint32 | 57 | flags uint32 | ||
| 53 | _ uint32 | 58 | _ uint32 | ||
| @@ -125,7 +130,7 @@ func (*blockCache) encode(typ reflect.Type) *uint8 { | |||||
| 125 | encoding = returnType | 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 | panic(fmt.Sprintf("objc: A Block implementation must take a Block as its first argument; got %v", typ.String())) | 134 | panic(fmt.Sprintf("objc: A Block implementation must take a Block as its first argument; got %v", typ.String())) | ||
| 130 | } | 135 | } | ||
| 131 | 136 | ||||
| @@ -168,7 +173,7 @@ func (b *blockCache) getLayout(typ reflect.Type) blockLayout { | |||||
| 168 | reflect.MakeFunc( | 173 | reflect.MakeFunc( | ||
| 169 | typ, | 174 | typ, | ||
| 170 | func(args []reflect.Value) (results []reflect.Value) { | 175 | func(args []reflect.Value) (results []reflect.Value) { | ||
| 171 | - | block, ok := xreflect.TypeAssert[Block](args[0]) | 176 | + | block, ok := reflect.TypeAssert[Block](args[0]) |
| 172 | if !ok { | 177 | if !ok { | ||
| 173 | panic(fmt.Sprintf("objc: block argument is not a block but %s", args[0].Type().String())) | 178 | panic(fmt.Sprintf("objc: block argument is not a block but %s", args[0].Type().String())) | ||
| 174 | } | 179 | } | ||
| @@ -263,7 +268,7 @@ func InvokeBlock[T any](block Block, args ...any) (result T, err error) { | |||||
| 263 | callResult := fn.Call(reflectedArgs) | 268 | callResult := fn.Call(reflectedArgs) | ||
| 264 | 269 | ||||
| 265 | var ok bool | 270 | var ok bool | ||
| 266 | - | result, ok = xreflect.TypeAssert[T](callResult[0]) | 271 | + | result, ok = reflect.TypeAssert[T](callResult[0]) |
| 267 | if !ok { | 272 | if !ok { | ||
| 268 | return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result) | 273 | return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result) | ||
| 269 | } | 274 | } | ||
M objc/objc_block_darwin_test.go +2 -0
| @@ -5,6 +5,7 @@ package objc_test | |||||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | "fmt" | 7 | "fmt" | ||
| 8 | + | "structs" | |||
| 8 | "testing" | 9 | "testing" | ||
| 9 | 10 | ||||
| 10 | "github.com/bnema/purego" | 11 | "github.com/bnema/purego" | ||
| @@ -39,6 +40,7 @@ func ExampleNewBlock() { | |||||
| 39 | 40 | ||||
| 40 | func ExampleInvokeBlock() { | 41 | func ExampleInvokeBlock() { | ||
| 41 | type vector struct { | 42 | type vector struct { | ||
| 43 | + | _ structs.HostLayout | |||
| 42 | X, Y, Z float64 | 44 | X, Y, Z float64 | ||
| 43 | } | 45 | } | ||
| 44 | 46 | ||||
M objc/objc_runtime_darwin.go +58 -43
| @@ -12,15 +12,18 @@ import ( | |||||
| 12 | "reflect" | 12 | "reflect" | ||
| 13 | "regexp" | 13 | "regexp" | ||
| 14 | "runtime" | 14 | "runtime" | ||
| 15 | + | "slices" | |||
| 15 | stdstrings "strings" | 16 | stdstrings "strings" | ||
| 17 | + | "structs" | |||
| 16 | "unicode" | 18 | "unicode" | ||
| 17 | "unsafe" | 19 | "unsafe" | ||
| 18 | 20 | ||||
| 19 | "github.com/bnema/purego" | 21 | "github.com/bnema/purego" | ||
| 20 | "github.com/bnema/purego/internal/strings" | 22 | "github.com/bnema/purego/internal/strings" | ||
| 21 | - | "github.com/bnema/purego/internal/xreflect" | |||
| 22 | ) | 23 | ) | ||
| 23 | 24 | ||||
| 25 | + | var hostLayoutType = reflect.TypeFor[structs.HostLayout]() | |||
| 26 | + | ||||
| 24 | // TODO: support try/catch? | 27 | // TODO: support try/catch? | ||
| 25 | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | 28 | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | ||
| 26 | var ( | 29 | var ( | ||
| @@ -168,32 +171,44 @@ func Send[T any](id ID, sel SEL, args ...any) T { | |||||
| 168 | return fn(id, sel, args...) | 171 | return fn(id, sel, args...) | ||
| 169 | } | 172 | } | ||
| 170 | 173 | ||||
| 171 | - | // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword | 174 | + | // objc_super supplies the receiver and defining class to objc_msgSendSuper2, |
| 172 | - | // as the receiver of a message. It specifies the class definition of the particular superclass that should | 175 | + | // which begins method lookup in that class's superclass. |
| 173 | - | // be messaged. | |||
| 174 | type objc_super struct { | 176 | type objc_super struct { | ||
| 177 | + | _ structs.HostLayout | |||
| 175 | receiver ID | 178 | receiver ID | ||
| 176 | superClass Class | 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 | 182 | + | // SendSuper sends a message starting lookup in the receiver's runtime superclass. |
| 180 | - | // instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result | 183 | + | // |
| 181 | - | // of RegisterName. | 184 | + | // Deprecated: Use [ID.SendSuper2] with the class defining the executing method. |
| 182 | func (id ID) SendSuper(sel SEL, args ...any) ID { | 185 | func (id ID) SendSuper(sel SEL, args ...any) ID { | ||
| 186 | + | return id.SendSuper2(id.Class(), sel, args...) | |||
| 187 | + | } | |||
| 188 | + | ||||
| 189 | + | // SendSuper2 sends a message starting lookup in the superclass of class. | |||
| 190 | + | // For a super call, class must be the class defining the executing method. | |||
| 191 | + | func (id ID) SendSuper2(class Class, sel SEL, args ...any) ID { | |||
| 183 | super := &objc_super{ | 192 | super := &objc_super{ | ||
| 184 | receiver: id, | 193 | receiver: id, | ||
| 185 | - | superClass: id.Class(), | 194 | + | superClass: class, |
| 186 | } | 195 | } | ||
| 187 | return objc_msgSendSuper2(super, sel, args...) | 196 | return objc_msgSendSuper2(super, sel, args...) | ||
| 188 | } | 197 | } | ||
| 189 | 198 | ||||
| 190 | - | // SendSuper is a convenience method for sending message to object's super that can return any type. | 199 | + | // SendSuper sends a message starting lookup in the receiver's runtime superclass. |
| 191 | - | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | 200 | + | // |
| 192 | - | // It is best to cache the result of RegisterName. | 201 | + | // Deprecated: Use [SendSuper2] with the class defining the executing method. |
| 193 | func SendSuper[T any](id ID, sel SEL, args ...any) T { | 202 | func SendSuper[T any](id ID, sel SEL, args ...any) T { | ||
| 203 | + | return SendSuper2[T](id, id.Class(), sel, args...) | |||
| 204 | + | } | |||
| 205 | + | ||||
| 206 | + | // SendSuper2 sends a message starting lookup in the superclass of class and returns T. | |||
| 207 | + | // For a super call, class must be the class defining the executing method. | |||
| 208 | + | func SendSuper2[T any](id ID, class Class, sel SEL, args ...any) T { | |||
| 194 | super := &objc_super{ | 209 | super := &objc_super{ | ||
| 195 | receiver: id, | 210 | receiver: id, | ||
| 196 | - | superClass: id.Class(), | 211 | + | superClass: class, |
| 197 | } | 212 | } | ||
| 198 | var fn func(objcSuper *objc_super, sel SEL, args ...any) T | 213 | var fn func(objcSuper *objc_super, sel SEL, args ...any) T | ||
| 199 | var zero T | 214 | var zero T | ||
| @@ -265,7 +280,7 @@ type FieldDef struct { | |||||
| 265 | } | 280 | } | ||
| 266 | 281 | ||||
| 267 | // ivarRegex checks to make sure the Ivar is correctly formatted | 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 | // RegisterClass takes the name of the class to create, the superclass, a list of protocols this class | 285 | // RegisterClass takes the name of the class to create, the superclass, a list of protocols this class | ||
| 271 | // implements, a list of fields this class has and a list of methods. It returns the created class or an error | 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 | case ReadWrite: | 337 | case ReadWrite: | ||
| 323 | ty := reflect.FuncOf( | 338 | ty := reflect.FuncOf( | ||
| 324 | []reflect.Type{ | 339 | []reflect.Type{ | ||
| 325 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), ivar.Type, | 340 | + | reflect.TypeFor[ID](), reflect.TypeFor[SEL](), ivar.Type, |
| 326 | }, | 341 | }, | ||
| 327 | nil, false, | 342 | nil, false, | ||
| 328 | ) | 343 | ) | ||
| @@ -343,7 +358,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||||
| 343 | // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 | 358 | // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 | ||
| 344 | // | 359 | // | ||
| 345 | // However, since the type of the variable is unknown reflection is used to actually assign the value | 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 | if !ok { | 362 | if !ok { | ||
| 348 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | 363 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | ||
| 349 | } | 364 | } | ||
| @@ -358,7 +373,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||||
| 358 | case ReadOnly: | 373 | case ReadOnly: | ||
| 359 | ty := reflect.FuncOf( | 374 | ty := reflect.FuncOf( | ||
| 360 | []reflect.Type{ | 375 | []reflect.Type{ | ||
| 361 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), | 376 | + | reflect.TypeFor[ID](), reflect.TypeFor[SEL](), |
| 362 | }, | 377 | }, | ||
| 363 | []reflect.Type{ivar.Type}, false, | 378 | []reflect.Type{ivar.Type}, false, | ||
| 364 | ) | 379 | ) | ||
| @@ -371,7 +386,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||||
| 371 | if len(args) != 2 { | 386 | if len(args) != 2 { | ||
| 372 | panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) | 387 | panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) | ||
| 373 | } | 388 | } | ||
| 374 | - | id, ok := xreflect.TypeAssert[ID](args[0]) | 389 | + | id, ok := reflect.TypeAssert[ID](args[0]) |
| 375 | if !ok { | 390 | if !ok { | ||
| 376 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | 391 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | ||
| 377 | } | 392 | } | ||
| @@ -402,8 +417,8 @@ const ( | |||||
| 402 | encUShort = "S" | 417 | encUShort = "S" | ||
| 403 | encInt = "i" | 418 | encInt = "i" | ||
| 404 | encUInt = "I" | 419 | encUInt = "I" | ||
| 405 | - | encLong = "l" | 420 | + | encLongLong = "q" |
| 406 | - | encULong = "L" | 421 | + | encULongLong = "Q" |
| 407 | encFloat = "f" | 422 | encFloat = "f" | ||
| 408 | encDouble = "d" | 423 | encDouble = "d" | ||
| 409 | encBool = "B" | 424 | encBool = "B" | ||
| @@ -419,11 +434,11 @@ const ( | |||||
| 419 | // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 | 434 | // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 | ||
| 420 | func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | 435 | func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | ||
| 421 | switch typ { | 436 | switch typ { | ||
| 422 | - | case reflect.TypeOf(Class(0)): | 437 | + | case reflect.TypeFor[Class](): |
| 423 | return encClass, nil | 438 | return encClass, nil | ||
| 424 | - | case reflect.TypeOf(ID(0)), reflect.TypeOf(Block(0)): | 439 | + | case reflect.TypeFor[ID](), reflect.TypeFor[Block](): |
| 425 | return encId, nil | 440 | return encId, nil | ||
| 426 | - | case reflect.TypeOf(SEL(0)): | 441 | + | case reflect.TypeFor[SEL](): |
| 427 | return encSelector, nil | 442 | return encSelector, nil | ||
| 428 | } | 443 | } | ||
| 429 | 444 | ||||
| @@ -432,7 +447,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||||
| 432 | case reflect.Bool: | 447 | case reflect.Bool: | ||
| 433 | return encBool, nil | 448 | return encBool, nil | ||
| 434 | case reflect.Int: | 449 | case reflect.Int: | ||
| 435 | - | return encLong, nil | 450 | + | return encLongLong, nil |
| 436 | case reflect.Int8: | 451 | case reflect.Int8: | ||
| 437 | return encChar, nil | 452 | return encChar, nil | ||
| 438 | case reflect.Int16: | 453 | case reflect.Int16: | ||
| @@ -440,9 +455,9 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||||
| 440 | case reflect.Int32: | 455 | case reflect.Int32: | ||
| 441 | return encInt, nil | 456 | return encInt, nil | ||
| 442 | case reflect.Int64: | 457 | case reflect.Int64: | ||
| 443 | - | return encULong, nil | 458 | + | return encLongLong, nil |
| 444 | case reflect.Uint: | 459 | case reflect.Uint: | ||
| 445 | - | return encULong, nil | 460 | + | return encULongLong, nil |
| 446 | case reflect.Uint8: | 461 | case reflect.Uint8: | ||
| 447 | return encUChar, nil | 462 | return encUChar, nil | ||
| 448 | case reflect.Uint16: | 463 | case reflect.Uint16: | ||
| @@ -450,7 +465,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||||
| 450 | case reflect.Uint32: | 465 | case reflect.Uint32: | ||
| 451 | return encUInt, nil | 466 | return encUInt, nil | ||
| 452 | case reflect.Uint64: | 467 | case reflect.Uint64: | ||
| 453 | - | return encULong, nil | 468 | + | return encULongLong, nil |
| 454 | case reflect.Uintptr: | 469 | case reflect.Uintptr: | ||
| 455 | return encPtr, nil | 470 | return encPtr, nil | ||
| 456 | case reflect.Float32: | 471 | case reflect.Float32: | ||
| @@ -468,8 +483,15 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |||||
| 468 | encoding.WriteString(encStructBegin) | 483 | encoding.WriteString(encStructBegin) | ||
| 469 | encoding.WriteString(typ.Name()) | 484 | encoding.WriteString(typ.Name()) | ||
| 470 | encoding.WriteString("=") | 485 | encoding.WriteString("=") | ||
| 471 | - | for i := 0; i < typ.NumField(); i++ { | 486 | + | for i := range typ.NumField() { |
| 472 | f := typ.Field(i) | 487 | f := typ.Field(i) | ||
| 488 | + | if f.Type.ConvertibleTo(hostLayoutType) { | |||
| 489 | + | // The structs.HostLayout marker is a signal to the Go compiler | |||
| 490 | + | // with no counterpart in C, so it is not a member as far as | |||
| 491 | + | // @encode is concerned. Other zero-sized fields are: clang | |||
| 492 | + | // encodes a zero-length array member as [0c]. | |||
| 493 | + | continue | |||
| 494 | + | } | |||
| 473 | tmp, err := encodeType(f.Type, false) | 495 | tmp, err := encodeType(f.Type, false) | ||
| 474 | if err != nil { | 496 | if err != nil { | ||
| 475 | return "", err | 497 | return "", err | ||
| @@ -571,7 +593,9 @@ func (i Ivar) Name() string { | |||||
| 571 | } | 593 | } | ||
| 572 | 594 | ||||
| 573 | // MethodDescription holds the name and type definition of a method. | 595 | // MethodDescription holds the name and type definition of a method. | ||
| 596 | + | // It matches the Objective-C runtime's struct objc_method_description. | |||
| 574 | type MethodDescription struct { | 597 | type MethodDescription struct { | ||
| 598 | + | _ structs.HostLayout | |||
| 575 | name, types uintptr | 599 | name, types uintptr | ||
| 576 | } | 600 | } | ||
| 577 | 601 | ||||
| @@ -586,7 +610,9 @@ func (m MethodDescription) Types() string { | |||||
| 586 | } | 610 | } | ||
| 587 | 611 | ||||
| 588 | // PropertyAttribute contains the null-terminated Name and Value pair of a Properties internal description. | 612 | // PropertyAttribute contains the null-terminated Name and Value pair of a Properties internal description. | ||
| 613 | + | // It matches the Objective-C runtime's objc_property_attribute_t. | |||
| 589 | type PropertyAttribute struct { | 614 | type PropertyAttribute struct { | ||
| 615 | + | _ structs.HostLayout | |||
| 590 | Name, Value *byte | 616 | Name, Value *byte | ||
| 591 | } | 617 | } | ||
| 592 | 618 | ||||
| @@ -626,7 +652,7 @@ func (p *Protocol) Register() { | |||||
| 626 | func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription { | 652 | func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription { | ||
| 627 | count := uint32(0) | 653 | count := uint32(0) | ||
| 628 | desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count) | 654 | desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count) | ||
| 629 | - | methods := clone(unsafe.Slice(desc, count)) | 655 | + | methods := slices.Clone(unsafe.Slice(desc, count)) |
| 630 | free(unsafe.Pointer(desc)) | 656 | free(unsafe.Pointer(desc)) | ||
| 631 | return methods | 657 | return methods | ||
| 632 | } | 658 | } | ||
| @@ -635,7 +661,7 @@ func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod | |||||
| 635 | func (p *Protocol) CopyProtocolList() []*Protocol { | 661 | func (p *Protocol) CopyProtocolList() []*Protocol { | ||
| 636 | count := uint32(0) | 662 | count := uint32(0) | ||
| 637 | desc := protocol_copyProtocolList(p, &count) | 663 | desc := protocol_copyProtocolList(p, &count) | ||
| 638 | - | protocols := clone(unsafe.Slice(desc, count)) | 664 | + | protocols := slices.Clone(unsafe.Slice(desc, count)) |
| 639 | free(unsafe.Pointer(desc)) | 665 | free(unsafe.Pointer(desc)) | ||
| 640 | return protocols | 666 | return protocols | ||
| 641 | } | 667 | } | ||
| @@ -644,7 +670,7 @@ func (p *Protocol) CopyProtocolList() []*Protocol { | |||||
| 644 | func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property { | 670 | func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property { | ||
| 645 | count := uint32(0) | 671 | count := uint32(0) | ||
| 646 | desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty) | 672 | desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty) | ||
| 647 | - | protocols := clone(unsafe.Slice(desc, count)) | 673 | + | protocols := slices.Clone(unsafe.Slice(desc, count)) |
| 648 | free(unsafe.Pointer(desc)) | 674 | free(unsafe.Pointer(desc)) | ||
| 649 | return protocols | 675 | return protocols | ||
| 650 | } | 676 | } | ||
| @@ -691,21 +717,10 @@ func NewIMP(fn any) IMP { | |||||
| 691 | switch { | 717 | switch { | ||
| 692 | case ty.NumIn() < 2: | 718 | case ty.NumIn() < 2: | ||
| 693 | fallthrough | 719 | fallthrough | ||
| 694 | - | case ty.In(0) != reflect.TypeOf(ID(0)): | 720 | + | case ty.In(0) != reflect.TypeFor[ID](): |
| 695 | fallthrough | 721 | fallthrough | ||
| 696 | - | case ty.In(1) != reflect.TypeOf(SEL(0)): | 722 | + | case ty.In(1) != reflect.TypeFor[SEL](): |
| 697 | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | 723 | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | ||
| 698 | } | 724 | } | ||
| 699 | return IMP(purego.NewCallback(fn)) | 725 | return IMP(purego.NewCallback(fn)) | ||
| 700 | } | 726 | } | ||
| 701 | - | ||||
| 702 | - | // TODO: remove and use slices.Clone when minimum version for purego is 1.21 | |||
| 703 | - | func clone[S ~[]E, E any](s S) S { | |||
| 704 | - | // Preserve nilness in case it matters. | |||
| 705 | - | if s == nil { | |||
| 706 | - | return nil | |||
| 707 | - | } | |||
| 708 | - | // Avoid s[:0:0] as it leads to unwanted liveness when cloning a | |||
| 709 | - | // zero-length slice of a large array; see https://go.dev/issue/68488. | |||
| 710 | - | return append(S{}, s...) | |||
| 711 | - | } | |||
M objc/objc_runtime_darwin_test.go +56 -24
| @@ -7,6 +7,7 @@ import ( | |||||
| 7 | "fmt" | 7 | "fmt" | ||
| 8 | "log" | 8 | "log" | ||
| 9 | "reflect" | 9 | "reflect" | ||
| 10 | + | "structs" | |||
| 10 | "testing" | 11 | "testing" | ||
| 11 | 12 | ||||
| 12 | "github.com/bnema/purego" | 13 | "github.com/bnema/purego" | ||
| @@ -38,6 +39,7 @@ func ExampleRegisterClass_helloworld() { | |||||
| 38 | } | 39 | } | ||
| 39 | 40 | ||||
| 40 | func ExampleRegisterClass() { | 41 | func ExampleRegisterClass() { | ||
| 42 | + | var class objc.Class | |||
| 41 | var ( | 43 | var ( | ||
| 42 | sel_new = objc.RegisterName("new") | 44 | sel_new = objc.RegisterName("new") | ||
| 43 | sel_init = objc.RegisterName("init") | 45 | sel_init = objc.RegisterName("init") | ||
| @@ -45,11 +47,12 @@ func ExampleRegisterClass() { | |||||
| 45 | sel_bar = objc.RegisterName("bar") | 47 | sel_bar = objc.RegisterName("bar") | ||
| 46 | 48 | ||||
| 47 | BarInit = func(id objc.ID, cmd objc.SEL) objc.ID { | 49 | BarInit = func(id objc.ID, cmd objc.SEL) objc.ID { | ||
| 48 | - | return id.SendSuper(cmd) | 50 | + | return id.SendSuper2(class, cmd) |
| 49 | } | 51 | } | ||
| 50 | ) | 52 | ) | ||
| 51 | 53 | ||||
| 52 | - | class, err := objc.RegisterClass( | 54 | + | var err error |
| 55 | + | class, err = objc.RegisterClass( | |||
| 53 | "BarObject", | 56 | "BarObject", | ||
| 54 | objc.GetClass("NSObject"), | 57 | objc.GetClass("NSObject"), | ||
| 55 | []*objc.Protocol{ | 58 | []*objc.Protocol{ | ||
| @@ -58,12 +61,12 @@ func ExampleRegisterClass() { | |||||
| 58 | []objc.FieldDef{ | 61 | []objc.FieldDef{ | ||
| 59 | { | 62 | { | ||
| 60 | Name: "bar", | 63 | Name: "bar", | ||
| 61 | - | Type: reflect.TypeOf(int(0)), | 64 | + | Type: reflect.TypeFor[int](), |
| 62 | Attribute: objc.ReadWrite, | 65 | Attribute: objc.ReadWrite, | ||
| 63 | }, | 66 | }, | ||
| 64 | { | 67 | { | ||
| 65 | Name: "foo", | 68 | Name: "foo", | ||
| 66 | - | Type: reflect.TypeOf(false), | 69 | + | Type: reflect.TypeFor[bool](), |
| 67 | Attribute: objc.ReadWrite, | 70 | Attribute: objc.ReadWrite, | ||
| 68 | }, | 71 | }, | ||
| 69 | }, | 72 | }, | ||
| @@ -94,17 +97,25 @@ func ExampleIMP() { | |||||
| 94 | // Output: IMP: 105 567 9 2 3 -5 4 8 9 | 97 | // Output: IMP: 105 567 9 2 3 -5 4 8 9 | ||
| 95 | } | 98 | } | ||
| 96 | 99 | ||||
| 97 | - | func ExampleID_SendSuper() { | 100 | + | // This example shows three-level inheritance with Base <- Child1 <- Child2. |
| 98 | - | super, err := objc.RegisterClass( | 101 | + | // SendSuper2 takes Child1, the class defining the method. |
| 99 | - | "SuperObject", | 102 | + | func ExampleID_SendSuper2() { |
| 103 | + | _, err := purego.Dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", purego.RTLD_GLOBAL|purego.RTLD_NOW) | |||
| 104 | + | if err != nil { | |||
| 105 | + | panic(err) | |||
| 106 | + | } | |||
| 107 | + | ||||
| 108 | + | sel := objc.RegisterName("run") | |||
| 109 | + | base, err := objc.RegisterClass( | |||
| 110 | + | "SuperExampleBase", | |||
| 100 | objc.GetClass("NSObject"), | 111 | objc.GetClass("NSObject"), | ||
| 101 | nil, | 112 | nil, | ||
| 102 | nil, | 113 | nil, | ||
| 103 | []objc.MethodDef{ | 114 | []objc.MethodDef{ | ||
| 104 | { | 115 | { | ||
| 105 | - | Cmd: objc.RegisterName("doSomething"), | 116 | + | Cmd: sel, |
| 106 | - | Fn: func(self objc.ID, _cmd objc.SEL) { | 117 | + | Fn: func(self objc.ID, cmd objc.SEL) { |
| 107 | - | fmt.Println("In Super!") | 118 | + | fmt.Println("Base") |
| 108 | }, | 119 | }, | ||
| 109 | }, | 120 | }, | ||
| 110 | }, | 121 | }, | ||
| @@ -113,17 +124,19 @@ func ExampleID_SendSuper() { | |||||
| 113 | panic(err) | 124 | panic(err) | ||
| 114 | } | 125 | } | ||
| 115 | 126 | ||||
| 116 | - | child, err := objc.RegisterClass( | 127 | + | var child1 objc.Class |
| 117 | - | "ChildObject", | 128 | + | child1, err = objc.RegisterClass( |
| 118 | - | super, | 129 | + | "SuperExampleChild1", |
| 130 | + | base, | |||
| 119 | nil, | 131 | nil, | ||
| 120 | nil, | 132 | nil, | ||
| 121 | []objc.MethodDef{ | 133 | []objc.MethodDef{ | ||
| 122 | { | 134 | { | ||
| 123 | - | Cmd: objc.RegisterName("doSomething"), | 135 | + | Cmd: sel, |
| 124 | - | Fn: func(self objc.ID, _cmd objc.SEL) { | 136 | + | Fn: func(self objc.ID, cmd objc.SEL) { |
| 125 | - | fmt.Println("In Child") | 137 | + | // Start lookup in the superclass of Child1. |
| 126 | - | self.SendSuper(_cmd) | 138 | + | fmt.Print("SendSuper2: ") |
| 139 | + | self.SendSuper2(child1, cmd) | |||
| 127 | }, | 140 | }, | ||
| 128 | }, | 141 | }, | ||
| 129 | }, | 142 | }, | ||
| @@ -132,9 +145,27 @@ func ExampleID_SendSuper() { | |||||
| 132 | panic(err) | 145 | panic(err) | ||
| 133 | } | 146 | } | ||
| 134 | 147 | ||||
| 135 | - | objc.ID(child).Send(objc.RegisterName("new")).Send(objc.RegisterName("doSomething")) | 148 | + | // Child2 inherits Child1's implementation of run. |
| 136 | - | // Output: In Child | 149 | + | child2, err := objc.RegisterClass("SuperExampleChild2", child1, nil, nil, nil) |
| 137 | - | // In Super! | 150 | + | if err != nil { |
| 151 | + | panic(err) | |||
| 152 | + | } | |||
| 153 | + | ||||
| 154 | + | for _, class := range []objc.Class{child1, child2} { | |||
| 155 | + | object := objc.ID(class).Send(objc.RegisterName("new")) | |||
| 156 | + | defer object.Send(objc.RegisterName("release")) | |||
| 157 | + | if class == child1 { | |||
| 158 | + | fmt.Println("Child1 instance") | |||
| 159 | + | } else { | |||
| 160 | + | fmt.Println("Child2 instance") | |||
| 161 | + | } | |||
| 162 | + | object.Send(sel) | |||
| 163 | + | } | |||
| 164 | + | // Output: | |||
| 165 | + | // Child1 instance | |||
| 166 | + | // SendSuper2: Base | |||
| 167 | + | // Child2 instance | |||
| 168 | + | // SendSuper2: Base | |||
| 138 | } | 169 | } | ||
| 139 | 170 | ||||
| 140 | func TestSend(t *testing.T) { | 171 | func TestSend(t *testing.T) { | ||
| @@ -155,6 +186,7 @@ func TestSend(t *testing.T) { | |||||
| 155 | 186 | ||||
| 156 | func ExampleSend() { | 187 | func ExampleSend() { | ||
| 157 | type NSRange struct { | 188 | type NSRange struct { | ||
| 189 | + | _ structs.HostLayout | |||
| 158 | Location, Range uint | 190 | Location, Range uint | ||
| 159 | } | 191 | } | ||
| 160 | class_NSString := objc.GetClass("NSString") | 192 | class_NSString := objc.GetClass("NSString") | ||
| @@ -164,11 +196,11 @@ func ExampleSend() { | |||||
| 164 | subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00") | 196 | subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00") | ||
| 165 | 197 | ||||
| 166 | r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString) | 198 | r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString) | ||
| 167 | - | fmt.Println(r) | 199 | + | fmt.Println(r.Location, r.Range) |
| 168 | - | // Output: {3 7} | 200 | + | // Output: 3 7 |
| 169 | } | 201 | } | ||
| 170 | 202 | ||||
| 171 | - | func ExampleSendSuper() { | 203 | + | func ExampleSendSuper2() { |
| 172 | super, err := objc.RegisterClass( | 204 | super, err := objc.RegisterClass( | ||
| 173 | "SuperObject2", | 205 | "SuperObject2", | ||
| 174 | objc.GetClass("NSObject"), | 206 | objc.GetClass("NSObject"), | ||
| @@ -205,7 +237,7 @@ func ExampleSendSuper() { | |||||
| 205 | panic(err) | 237 | panic(err) | ||
| 206 | } | 238 | } | ||
| 207 | 239 | ||||
| 208 | - | res := objc.SendSuper[int](objc.ID(child).Send(objc.RegisterName("new")), objc.RegisterName("doSomething")) | 240 | + | res := objc.SendSuper2[int](objc.ID(child).Send(objc.RegisterName("new")), child, objc.RegisterName("doSomething")) |
| 209 | fmt.Println(res) | 241 | fmt.Println(res) | ||
| 210 | // Output: 16 | 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 | "unsafe" | 10 | "unsafe" | ||
| 11 | ) | 11 | ) | ||
| 12 | 12 | ||||
| 13 | - | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | 13 | + | // structReturnInMemory reports whether a struct return value of the given size |
| 14 | + | // is returned through a caller-allocated hidden pointer passed as the first | |||
| 15 | + | // integer argument (true) rather than in registers (false). | |||
| 16 | + | func structReturnInMemory(outType reflect.Type) bool { | |||
| 17 | + | size := outType.Size() | |||
| 18 | + | if size == 0 { | |||
| 19 | + | return false | |||
| 20 | + | } | |||
| 21 | + | if runtime.GOOS == "windows" { | |||
| 22 | + | // The Win64 ABI returns aggregates of exactly 1, 2, 4, or 8 bytes in | |||
| 23 | + | // RAX. Every other size is returned through a caller-allocated hidden | |||
| 24 | + | // pointer that the callee also returns in RAX. | |||
| 25 | + | switch size { | |||
| 26 | + | case 1, 2, 4, 8: | |||
| 27 | + | return false | |||
| 28 | + | default: | |||
| 29 | + | return true | |||
| 30 | + | } | |||
| 31 | + | } | |||
| 32 | + | // The System V ABI returns aggregates of up to two eightbytes in registers. | |||
| 33 | + | return size > maxRegAllocStructSize | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | |||
| 14 | outSize := outType.Size() | 37 | outSize := outType.Size() | ||
| 38 | + | if runtime.GOOS == "windows" { | |||
| 39 | + | switch { | |||
| 40 | + | case outSize == 0: | |||
| 41 | + | return reflect.New(outType).Elem() | |||
| 42 | + | case structReturnInMemory(outType): | |||
| 43 | + | // Returned through the caller-allocated hidden pointer, which the | |||
| 44 | + | // callee also returns in RAX. | |||
| 45 | + | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | |||
| 46 | + | default: | |||
| 47 | + | // 1, 2, 4, or 8 byte aggregates are returned in RAX. | |||
| 48 | + | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() | |||
| 49 | + | } | |||
| 50 | + | } | |||
| 15 | switch { | 51 | switch { | ||
| 16 | case outSize == 0: | 52 | case outSize == 0: | ||
| 17 | return reflect.New(outType).Elem() | 53 | return reflect.New(outType).Elem() | ||
| @@ -23,30 +59,36 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||||
| 23 | // up to 8 bytes is returned in RAX | 59 | // up to 8 bytes is returned in RAX | ||
| 24 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() | 60 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() | ||
| 25 | case outSize <= 16: | 61 | case outSize <= 16: | ||
| 26 | - | var r1, r2 uintptr | 62 | + | r1, r2 := syscall.a1, syscall.a2 |
| 27 | - | var intRegs, floatRegs int | 63 | + | if isAllFloats(outType) { |
| 28 | - | for i := 0; i < int((outSize+7)/8); i++ { | 64 | + | r1 = syscall.f1 |
| 29 | - | class := classifyEightbyte(outType, uintptr(i)*8, uintptr(i)*8+8) | 65 | + | r2 = syscall.f2 |
| 30 | - | var reg uintptr | 66 | + | } else { |
| 31 | - | if class == _SSE { | 67 | + | // check first 8 bytes if it's floats |
| 32 | - | if floatRegs == 0 { | 68 | + | hasFirstFloat := false |
| 33 | - | reg = syscall.f1 | 69 | + | numFields := numABIFields(outType) |
| 34 | - | } else { | 70 | + | f1 := abiField(outType, 0).Type |
| 35 | - | reg = syscall.f2 | 71 | + | if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && abiField(outType, 1).Type.Kind() == reflect.Float32 { |
| 36 | - | } | 72 | + | r1 = syscall.f1 |
| 37 | - | floatRegs++ | 73 | + | hasFirstFloat = true |
| 38 | - | } else { | 74 | + | } |
| 39 | - | if intRegs == 0 { | 75 | + | |
| 40 | - | reg = syscall.a1 | 76 | + | // find index of the field that starts the second 8 bytes |
| 41 | - | } else { | 77 | + | var i int |
| 42 | - | reg = syscall.a2 | 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 { | 83 | + | |
| 47 | - | r1 = reg | 84 | + | // check last 8 bytes if they are floats |
| 48 | - | } else { | 85 | + | f1 = abiField(outType, i).Type |
| 49 | - | r2 = reg | 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 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | 94 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | ||
| @@ -58,8 +100,11 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||||
| 58 | } | 100 | } | ||
| 59 | 101 | ||||
| 60 | func isAllFloats(ty reflect.Type) bool { | 102 | func isAllFloats(ty reflect.Type) bool { | ||
| 61 | - | for i := 0; i < ty.NumField(); i++ { | 103 | + | for i := range ty.NumField() { |
| 62 | f := ty.Field(i) | 104 | f := ty.Field(i) | ||
| 105 | + | if !isABIField(f) { | |||
| 106 | + | continue | |||
| 107 | + | } | |||
| 63 | switch f.Type.Kind() { | 108 | switch f.Type.Kind() { | ||
| 64 | case reflect.Float64, reflect.Float32: | 109 | case reflect.Float64, reflect.Float32: | ||
| 65 | default: | 110 | default: | ||
| @@ -82,6 +127,12 @@ const ( | |||||
| 82 | ) | 127 | ) | ||
| 83 | 128 | ||||
| 84 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | 129 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | ||
| 130 | + | if runtime.GOOS == "windows" { | |||
| 131 | + | // Win64 still passes an empty struct as an argument slot, so this must | |||
| 132 | + | // run before the zero-size early return used by the System V path. | |||
| 133 | + | return addStructWindows(v, addInt, keepAlive) | |||
| 134 | + | } | |||
| 135 | + | ||||
| 85 | if v.Type().Size() == 0 { | 136 | if v.Type().Size() == 0 { | ||
| 86 | return keepAlive | 137 | return keepAlive | ||
| 87 | } | 138 | } | ||
| @@ -107,6 +158,27 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||||
| 107 | return keepAlive | 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 | func postMerger(t reflect.Type) (passInMemory bool) { | 182 | func postMerger(t reflect.Type) (passInMemory bool) { | ||
| 111 | // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other | 183 | // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other | ||
| 112 | // eightbyte isn’t SSEUP, the whole argument is passed in memory. | 184 | // eightbyte isn’t SSEUP, the whole argument is passed in memory. | ||
| @@ -148,7 +220,10 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp | |||||
| 148 | numFields = v.Type().Len() | 220 | numFields = v.Type().Len() | ||
| 149 | } | 221 | } | ||
| 150 | 222 | ||||
| 151 | - | for i := 0; i < numFields; i++ { | 223 | + | for i := range numFields { |
| 224 | + | if v.Kind() == reflect.Struct && !isABIField(v.Type().Field(i)) { | |||
| 225 | + | continue | |||
| 226 | + | } | |||
| 152 | flushed = false | 227 | flushed = false | ||
| 153 | var f reflect.Value | 228 | var f reflect.Value | ||
| 154 | if v.Kind() == reflect.Struct { | 229 | if v.Kind() == reflect.Struct { | ||
| @@ -294,7 +369,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||||
| 294 | // - If not enough registers for all eightbytes: entire struct goes on the stack | 369 | // - If not enough registers for all eightbytes: entire struct goes on the stack | ||
| 295 | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | 370 | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | ||
| 296 | switch runtime.GOOS { | 371 | switch runtime.GOOS { | ||
| 297 | - | case "darwin", "freebsd", "linux", "netbsd": | 372 | + | case "android", "darwin", "freebsd", "ios", "linux", "netbsd": |
| 298 | default: | 373 | default: | ||
| 299 | panic("purego: getCallbackStruct is not supported on " + runtime.GOOS) | 374 | panic("purego: getCallbackStruct is not supported on " + runtime.GOOS) | ||
| 300 | } | 375 | } | ||
| @@ -315,7 +390,7 @@ func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, | |||||
| 315 | 390 | ||||
| 316 | // Count how many integer and SSE registers this struct needs. | 391 | // Count how many integer and SSE registers this struct needs. | ||
| 317 | var needInts, needFloats int | 392 | var needInts, needFloats int | ||
| 318 | - | for i := 0; i < numEightbytes; i++ { | 393 | + | for i := range numEightbytes { |
| 319 | class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8) | 394 | class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8) | ||
| 320 | if class == _SSE { | 395 | if class == _SSE { | ||
| 321 | needFloats++ | 396 | needFloats++ | ||
| @@ -333,7 +408,7 @@ func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, | |||||
| 333 | 408 | ||||
| 334 | // Read each eightbyte from its appropriate register class. | 409 | // Read each eightbyte from its appropriate register class. | ||
| 335 | var r1, r2 uintptr | 410 | var r1, r2 uintptr | ||
| 336 | - | for i := 0; i < numEightbytes; i++ { | 411 | + | for i := range numEightbytes { |
| 337 | class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8) | 412 | class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8) | ||
| 338 | if class == _SSE { | 413 | if class == _SSE { | ||
| 339 | if i == 0 { | 414 | if i == 0 { | ||
| @@ -415,7 +490,7 @@ func doClassifyEightbyte(t reflect.Type, base, start, end uintptr) int { | |||||
| 415 | switch t.Kind() { | 490 | switch t.Kind() { | ||
| 416 | case reflect.Struct: | 491 | case reflect.Struct: | ||
| 417 | class := _NO_CLASS | 492 | class := _NO_CLASS | ||
| 418 | - | for i := 0; i < t.NumField(); i++ { | 493 | + | for i := range t.NumField() { |
| 419 | f := t.Field(i) | 494 | f := t.Field(i) | ||
| 420 | class |= doClassifyEightbyte(f.Type, base+f.Offset, start, end) | 495 | class |= doClassifyEightbyte(f.Type, base+f.Offset, start, end) | ||
| 421 | } | 496 | } | ||
| @@ -423,7 +498,7 @@ func doClassifyEightbyte(t reflect.Type, base, start, end uintptr) int { | |||||
| 423 | case reflect.Array: | 498 | case reflect.Array: | ||
| 424 | class := _NO_CLASS | 499 | class := _NO_CLASS | ||
| 425 | elemSize := t.Elem().Size() | 500 | elemSize := t.Elem().Size() | ||
| 426 | - | for i := 0; i < t.Len(); i++ { | 501 | + | for i := range t.Len() { |
| 427 | class |= doClassifyEightbyte(t.Elem(), base+uintptr(i)*elemSize, start, end) | 502 | class |= doClassifyEightbyte(t.Elem(), base+uintptr(i)*elemSize, start, end) | ||
| 428 | } | 503 | } | ||
| 429 | return class | 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 | import ( | 6 | import ( | ||
| 7 | "reflect" | 7 | "reflect" | ||
| 8 | - | "runtime" | |||
| 9 | "unsafe" | 8 | "unsafe" | ||
| 10 | ) | 9 | ) | ||
| 11 | 10 | ||||
| @@ -17,10 +16,8 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||||
| 17 | 16 | ||||
| 18 | // TODO: ARM EABI: small structs are passed in registers or on stack | 17 | // TODO: ARM EABI: small structs are passed in registers or on stack | ||
| 19 | // For simplicity, pass by pointer for now | 18 | // For simplicity, pass by pointer for now | ||
| 20 | - | ptr, live := stableValuePointer(v) | 19 | + | ptr := v.Addr().UnsafePointer() |
| 21 | - | if live != nil { | 20 | + | keepAlive = append(keepAlive, ptr) |
| 22 | - | keepAlive = append(keepAlive, live) | |||
| 23 | - | } | |||
| 24 | if *numInts < 4 { | 21 | if *numInts < 4 { | ||
| 25 | addInt(uintptr(ptr)) | 22 | addInt(uintptr(ptr)) | ||
| 26 | *numInts++ | 23 | *numInts++ | ||
| @@ -28,11 +25,17 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||||
| 28 | addStack(uintptr(ptr)) | 25 | addStack(uintptr(ptr)) | ||
| 29 | *numStack++ | 26 | *numStack++ | ||
| 30 | } | 27 | } | ||
| 31 | - | runtime.KeepAlive(live) | |||
| 32 | return keepAlive | 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 | outSize := outType.Size() | 39 | outSize := outType.Size() | ||
| 37 | if outSize == 0 { | 40 | if outSize == 0 { | ||
| 38 | return reflect.New(outType).Elem() | 41 | return reflect.New(outType).Elem() | ||
| @@ -56,14 +59,13 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr | |||||
| 56 | if size == 0 { | 59 | if size == 0 { | ||
| 57 | return | 60 | return | ||
| 58 | } | 61 | } | ||
| 59 | - | ptr, live := stableValuePointer(v) | 62 | + | ptr := unsafe.Pointer(v.UnsafeAddr()) |
| 60 | if size <= 4 { | 63 | if size <= 4 { | ||
| 61 | addInt(*(*uintptr)(ptr)) | 64 | addInt(*(*uintptr)(ptr)) | ||
| 62 | } else if size <= 8 { | 65 | } else if size <= 8 { | ||
| 63 | addInt(*(*uintptr)(ptr)) | 66 | addInt(*(*uintptr)(ptr)) | ||
| 64 | addInt(*(*uintptr)(unsafe.Add(ptr, 4))) | 67 | addInt(*(*uintptr)(unsafe.Add(ptr, 4))) | ||
| 65 | } | 68 | } | ||
| 66 | - | runtime.KeepAlive(live) | |||
| 67 | } | 69 | } | ||
| 68 | 70 | ||||
| 69 | // shouldBundleStackArgs always returns false on arm | 71 | // shouldBundleStackArgs always returns false on arm | ||
M struct_arm64.go +65 -67
| @@ -14,7 +14,14 @@ import ( | |||||
| 14 | "github.com/bnema/purego/internal/strings" | 14 | "github.com/bnema/purego/internal/strings" | ||
| 15 | ) | 15 | ) | ||
| 16 | 16 | ||||
| 17 | - | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | 17 | + | // structReturnInMemory always reports false on arm64: a struct returned in |
| 18 | + | // memory is passed through the dedicated indirect result register (R8), not as | |||
| 19 | + | // an ordinary integer argument, so it is handled separately. | |||
| 20 | + | func structReturnInMemory(reflect.Type) bool { | |||
| 21 | + | return false | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | |||
| 18 | outSize := outType.Size() | 25 | outSize := outType.Size() | ||
| 19 | switch { | 26 | switch { | ||
| 20 | case outSize == 0: | 27 | case outSize == 0: | ||
| @@ -78,9 +85,9 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||||
| 78 | if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 { | 85 | if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 { | ||
| 79 | // if this doesn't fit entirely in registers then | 86 | // if this doesn't fit entirely in registers then | ||
| 80 | // each element goes onto the stack | 87 | // each element goes onto the stack | ||
| 81 | - | if hfa && *numFloats+v.NumField() > numOfFloatRegisters() { | 88 | + | if hfa && *numFloats+numABIFields(v.Type()) > numOfFloatRegisters() { |
| 82 | *numFloats = numOfFloatRegisters() | 89 | *numFloats = numOfFloatRegisters() | ||
| 83 | - | } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() { | 90 | + | } else if hva && *numInts+numABIFields(v.Type()) > numOfIntegerRegisters() { |
| 84 | *numInts = numOfIntegerRegisters() | 91 | *numInts = numOfIntegerRegisters() | ||
| 85 | } | 92 | } | ||
| 86 | 93 | ||||
| @@ -92,7 +99,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||||
| 92 | } | 99 | } | ||
| 93 | 100 | ||||
| 94 | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | 101 | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | ||
| 95 | - | if runtime.GOOS == "darwin" { | 102 | + | if isDarwin { |
| 96 | placeRegistersDarwin(v, addFloat, addInt) | 103 | placeRegistersDarwin(v, addFloat, addInt) | ||
| 97 | return | 104 | return | ||
| 98 | } | 105 | } | ||
| @@ -112,7 +119,10 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui | |||||
| 112 | } else { | 119 | } else { | ||
| 113 | numFields = v.Type().Len() | 120 | numFields = v.Type().Len() | ||
| 114 | } | 121 | } | ||
| 115 | - | for k := 0; k < numFields; k++ { | 122 | + | for k := range numFields { |
| 123 | + | if v.Kind() == reflect.Struct && !isABIField(v.Type().Field(k)) { | |||
| 124 | + | continue | |||
| 125 | + | } | |||
| 116 | flushed = false | 126 | flushed = false | ||
| 117 | var f reflect.Value | 127 | var f reflect.Value | ||
| 118 | if v.Kind() == reflect.Struct { | 128 | if v.Kind() == reflect.Struct { | ||
| @@ -229,52 +239,39 @@ func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |||||
| 229 | // | 239 | // | ||
| 230 | // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst | 240 | // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst | ||
| 231 | func isHFA(t reflect.Type) bool { | 241 | func isHFA(t reflect.Type) bool { | ||
| 242 | + | // round up struct size to nearest 8 see section B.4 | |||
| 232 | structSize := roundUpTo8(t.Size()) | 243 | structSize := roundUpTo8(t.Size()) | ||
| 233 | - | if structSize == 0 { | 244 | + | numFields := numABIFields(t) |
| 245 | + | if structSize == 0 || numFields > 4 { | |||
| 234 | return false | 246 | return false | ||
| 235 | } | 247 | } | ||
| 236 | - | var kind reflect.Kind | 248 | + | first := abiField(t, 0) |
| 237 | - | var members int | 249 | + | switch first.Type.Kind() { |
| 238 | - | var walk func(reflect.Type) bool | 250 | + | case reflect.Float32, reflect.Float64: |
| 239 | - | walk = func(t reflect.Type) bool { | 251 | + | firstKind := first.Type.Kind() |
| 240 | - | for i := 0; i < t.NumField(); i++ { | 252 | + | for i := range numFields { |
| 241 | - | ft := t.Field(i).Type | 253 | + | if abiField(t, i).Type.Kind() != firstKind { |
| 242 | - | switch ft.Kind() { | |||
| 243 | - | case reflect.Float32, reflect.Float64: | |||
| 244 | - | if kind == 0 { | |||
| 245 | - | kind = ft.Kind() | |||
| 246 | - | } else if ft.Kind() != kind { | |||
| 247 | - | return false | |||
| 248 | - | } | |||
| 249 | - | members++ | |||
| 250 | - | if members > 4 { | |||
| 251 | - | return false | |||
| 252 | - | } | |||
| 253 | - | case reflect.Struct: | |||
| 254 | - | if !walk(ft) { | |||
| 255 | - | return false | |||
| 256 | - | } | |||
| 257 | - | case reflect.Array: | |||
| 258 | - | elemKind := ft.Elem().Kind() | |||
| 259 | - | if elemKind != reflect.Float32 && elemKind != reflect.Float64 { | |||
| 260 | - | return false | |||
| 261 | - | } | |||
| 262 | - | if kind == 0 { | |||
| 263 | - | kind = elemKind | |||
| 264 | - | } else if elemKind != kind { | |||
| 265 | - | return false | |||
| 266 | - | } | |||
| 267 | - | members += ft.Len() | |||
| 268 | - | if members > 4 { | |||
| 269 | - | return false | |||
| 270 | - | } | |||
| 271 | - | default: | |||
| 272 | return false | 254 | return false | ||
| 273 | } | 255 | } | ||
| 274 | } | 256 | } | ||
| 275 | return true | 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 | // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type | 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 | if structSize == 0 || (structSize != 8 && structSize != 16) { | 287 | if structSize == 0 || (structSize != 8 && structSize != 16) { | ||
| 291 | return false | 288 | return false | ||
| 292 | } | 289 | } | ||
| 293 | - | first := t.Field(0) | 290 | + | numFields := numABIFields(t) |
| 291 | + | first := abiField(t, 0) | |||
| 294 | switch first.Type.Kind() { | 292 | switch first.Type.Kind() { | ||
| 295 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: | 293 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: | ||
| 296 | firstKind := first.Type.Kind() | 294 | firstKind := first.Type.Kind() | ||
| 297 | - | for i := 0; i < t.NumField(); i++ { | 295 | + | for i := range numFields { |
| 298 | - | if t.Field(i).Type.Kind() != firstKind { | 296 | + | if abiField(t, i).Type.Kind() != firstKind { |
| 299 | return false | 297 | return false | ||
| 300 | } | 298 | } | ||
| 301 | } | 299 | } | ||
| @@ -315,7 +313,7 @@ func isHVA(t reflect.Type) bool { | |||||
| 315 | // copyStruct8ByteChunks copies struct memory in 8-byte chunks to the provided callback. | 313 | // copyStruct8ByteChunks copies struct memory in 8-byte chunks to the provided callback. | ||
| 316 | // This is used for Darwin ARM64's byte-level packing of non-HFA/HVA structs. | 314 | // This is used for Darwin ARM64's byte-level packing of non-HFA/HVA structs. | ||
| 317 | func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintptr)) { | 315 | func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintptr)) { | ||
| 318 | - | if runtime.GOOS != "darwin" { | 316 | + | if !isDarwin { |
| 319 | panic("purego: should only be called on darwin") | 317 | panic("purego: should only be called on darwin") | ||
| 320 | } | 318 | } | ||
| 321 | for offset := uintptr(0); offset < size; offset += 8 { | 319 | for offset := uintptr(0); offset < size; offset += 8 { | ||
| @@ -325,7 +323,7 @@ func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintp | |||||
| 325 | chunk = *(*uintptr)(unsafe.Add(ptr, offset)) | 323 | chunk = *(*uintptr)(unsafe.Add(ptr, offset)) | ||
| 326 | } else { | 324 | } else { | ||
| 327 | // Read byte-by-byte to avoid reading beyond allocation | 325 | // Read byte-by-byte to avoid reading beyond allocation | ||
| 328 | - | for i := uintptr(0); i < remaining; i++ { | 326 | + | for i := range remaining { |
| 329 | b := *(*byte)(unsafe.Add(ptr, offset+i)) | 327 | b := *(*byte)(unsafe.Add(ptr, offset+i)) | ||
| 330 | chunk |= uintptr(b) << (i * 8) | 328 | chunk |= uintptr(b) << (i * 8) | ||
| 331 | } | 329 | } | ||
| @@ -342,7 +340,7 @@ func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintp | |||||
| 342 | // For non-HFA/HVA structs, Darwin uses byte-level packing. We copy the struct memory in | 340 | // For non-HFA/HVA structs, Darwin uses byte-level packing. We copy the struct memory in | ||
| 343 | // 8-byte chunks, which works correctly for both register and stack placement. | 341 | // 8-byte chunks, which works correctly for both register and stack placement. | ||
| 344 | func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | 342 | func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | ||
| 345 | - | if runtime.GOOS != "darwin" { | 343 | + | if !isDarwin { |
| 346 | panic("purego: placeRegistersDarwin should only be called on darwin") | 344 | panic("purego: placeRegistersDarwin should only be called on darwin") | ||
| 347 | } | 345 | } | ||
| 348 | // Check if this is an HFA/HVA | 346 | // Check if this is an HFA/HVA | ||
| @@ -370,7 +368,7 @@ func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(u | |||||
| 370 | // shouldBundleStackArgs determines if we need to start C-style packing for | 368 | // shouldBundleStackArgs determines if we need to start C-style packing for | ||
| 371 | // Darwin ARM64 stack arguments. This happens when registers are exhausted. | 369 | // Darwin ARM64 stack arguments. This happens when registers are exhausted. | ||
| 372 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | 370 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | ||
| 373 | - | if runtime.GOOS != "darwin" { | 371 | + | if !isDarwin { |
| 374 | return false | 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 | if hfa { | 395 | if hfa { | ||
| 398 | - | need := v.NumField() | 396 | + | need := numABIFields(v.Type()) |
| 399 | return numFloats+need > numOfFloatRegisters() | 397 | return numFloats+need > numOfFloatRegisters() | ||
| 400 | } | 398 | } | ||
| 401 | 399 | ||||
| 402 | if hva { | 400 | if hva { | ||
| 403 | - | need := v.NumField() | 401 | + | need := numABIFields(v.Type()) |
| 404 | return numInts+need > numOfIntegerRegisters() | 402 | return numInts+need > numOfIntegerRegisters() | ||
| 405 | } | 403 | } | ||
| 406 | 404 | ||||
| @@ -412,7 +410,7 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |||||
| 412 | // registers, used during stack argument bundling to decide if a struct | 410 | // registers, used during stack argument bundling to decide if a struct | ||
| 413 | // should go through normal register allocation or be bundled with stack args. | 411 | // should go through normal register allocation or be bundled with stack args. | ||
| 414 | func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) { | 412 | func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) { | ||
| 415 | - | if runtime.GOOS != "darwin" { | 413 | + | if !isDarwin { |
| 416 | panic("purego: structFitsInRegisters should only be called on darwin") | 414 | panic("purego: structFitsInRegisters should only be called on darwin") | ||
| 417 | } | 415 | } | ||
| 418 | hfa := isHFA(val.Type()) | 416 | hfa := isHFA(val.Type()) | ||
| @@ -421,13 +419,13 @@ func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (b | |||||
| 421 | 419 | ||||
| 422 | if hfa { | 420 | if hfa { | ||
| 423 | // HFA: check if elements fit in float registers | 421 | // HFA: check if elements fit in float registers | ||
| 424 | - | if tempNumFloats+val.NumField() <= numOfFloatRegisters() { | 422 | + | if numFields := numABIFields(val.Type()); tempNumFloats+numFields <= numOfFloatRegisters() { |
| 425 | - | return true, tempNumInts, tempNumFloats + val.NumField() | 423 | + | return true, tempNumInts, tempNumFloats + numFields |
| 426 | } | 424 | } | ||
| 427 | } else if hva { | 425 | } else if hva { | ||
| 428 | // HVA: check if elements fit in int registers | 426 | // HVA: check if elements fit in int registers | ||
| 429 | - | if tempNumInts+val.NumField() <= numOfIntegerRegisters() { | 427 | + | if numFields := numABIFields(val.Type()); tempNumInts+numFields <= numOfIntegerRegisters() { |
| 430 | - | return true, tempNumInts + val.NumField(), tempNumFloats | 428 | + | return true, tempNumInts + numFields, tempNumFloats |
| 431 | } | 429 | } | ||
| 432 | } else if size <= 16 { | 430 | } else if size <= 16 { | ||
| 433 | // Non-HFA/HVA small structs use int registers for byte-packing | 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 | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | 443 | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | ||
| 446 | keepAlive []any, addInt, addFloat, addStack func(uintptr), | 444 | keepAlive []any, addInt, addFloat, addStack func(uintptr), | ||
| 447 | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | 445 | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | ||
| 448 | - | if runtime.GOOS != "darwin" { | 446 | + | if !isDarwin { |
| 449 | panic("purego: collectStackArgs should only be called on darwin") | 447 | panic("purego: collectStackArgs should only be called on darwin") | ||
| 450 | } | 448 | } | ||
| 451 | 449 | ||||
| @@ -502,7 +500,7 @@ const ( | |||||
| 502 | // bundleStackArgs bundles remaining arguments for Darwin ARM64 C-style stack packing. | 500 | // bundleStackArgs bundles remaining arguments for Darwin ARM64 C-style stack packing. | ||
| 503 | // It creates a packed struct with proper alignment and copies it to the stack in 8-byte chunks. | 501 | // It creates a packed struct with proper alignment and copies it to the stack in 8-byte chunks. | ||
| 504 | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | 502 | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | ||
| 505 | - | if runtime.GOOS != "darwin" { | 503 | + | if !isDarwin { |
| 506 | panic("purego: bundleStackArgs should only be called on darwin") | 504 | panic("purego: bundleStackArgs should only be called on darwin") | ||
| 507 | } | 505 | } | ||
| 508 | if len(stackArgs) == 0 { | 506 | if len(stackArgs) == 0 { | ||
| @@ -528,7 +526,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||||
| 528 | paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign)) | 526 | paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign)) | ||
| 529 | fields = append(fields, reflect.StructField{ | 527 | fields = append(fields, reflect.StructField{ | ||
| 530 | Name: paddingFieldPrefix + strconv.Itoa(fieldIndex), | 528 | Name: paddingFieldPrefix + strconv.Itoa(fieldIndex), | ||
| 531 | - | Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeOf(byte(0))), | 529 | + | Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeFor[byte]()), |
| 532 | }) | 530 | }) | ||
| 533 | currentOffset += paddingNeeded | 531 | currentOffset += paddingNeeded | ||
| 534 | fieldIndex++ | 532 | fieldIndex++ | ||
| @@ -548,7 +546,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||||
| 548 | 546 | ||||
| 549 | // Set values (skip padding fields) | 547 | // Set values (skip padding fields) | ||
| 550 | argIndex := 0 | 548 | argIndex := 0 | ||
| 551 | - | for j := 0; j < structInstance.NumField(); j++ { | 549 | + | for j := range structInstance.NumField() { |
| 552 | fieldName := structType.Field(j).Name | 550 | fieldName := structType.Field(j).Name | ||
| 553 | if stdstrings.HasPrefix(fieldName, paddingFieldPrefix) { | 551 | if stdstrings.HasPrefix(fieldName, paddingFieldPrefix) { | ||
| 554 | continue | 552 | continue | ||
| @@ -576,7 +574,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||||
| 576 | // - Darwin ARM64: byte-level packing on the stack | 574 | // - Darwin ARM64: byte-level packing on the stack | ||
| 577 | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | 575 | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | ||
| 578 | switch runtime.GOOS { | 576 | switch runtime.GOOS { | ||
| 579 | - | case "darwin", "freebsd", "linux", "netbsd": | 577 | + | case "android", "darwin", "freebsd", "ios", "linux", "netbsd": |
| 580 | default: | 578 | default: | ||
| 581 | panic("purego: getCallbackStruct is not supported on " + runtime.GOOS) | 579 | panic("purego: getCallbackStruct is not supported on " + runtime.GOOS) | ||
| 582 | } | 580 | } | ||
| @@ -623,8 +621,8 @@ func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, | |||||
| 623 | } | 621 | } | ||
| 624 | 622 | ||||
| 625 | // Pointer on stack (rare: all integer registers exhausted). | 623 | // Pointer on stack (rare: all integer registers exhausted). | ||
| 626 | - | if runtime.GOOS == "darwin" { | 624 | + | if isDarwin { |
| 627 | - | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeOf(uintptr(0))) | 625 | + | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeFor[uintptr]()) |
| 628 | ptr := uintptr(ptrVal.Uint()) | 626 | ptr := uintptr(ptrVal.Uint()) | ||
| 629 | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | 627 | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | ||
| 630 | } | 628 | } | ||
| @@ -640,9 +638,9 @@ func readHFAFromRegisters(inType reflect.Type, f *[callbackMaxFrame]uintptr, flo | |||||
| 640 | size := inType.Size() | 638 | size := inType.Size() | ||
| 641 | 639 | ||||
| 642 | // Determine the element type | 640 | // Determine the element type | ||
| 643 | - | root := inType.Field(0).Type | 641 | + | root := abiField(inType, 0).Type |
| 644 | for root.Kind() == reflect.Struct { | 642 | for root.Kind() == reflect.Struct { | ||
| 645 | - | root = root.Field(0).Type | 643 | + | root = abiField(root, 0).Type |
| 646 | } | 644 | } | ||
| 647 | isFloat32 := root.Kind() == reflect.Float32 | 645 | isFloat32 := root.Kind() == reflect.Float32 | ||
| 648 | 646 | ||||
| @@ -704,7 +702,7 @@ func readHFAFromRegisters(inType reflect.Type, f *[callbackMaxFrame]uintptr, flo | |||||
| 704 | // callback frame. On Darwin ARM64, arguments are byte-packed on the stack. | 702 | // callback frame. On Darwin ARM64, arguments are byte-packed on the stack. | ||
| 705 | // On Linux ARM64, arguments are 8-byte aligned. | 703 | // On Linux ARM64, arguments are 8-byte aligned. | ||
| 706 | func readStructFromStackArm64(inType reflect.Type, f *[callbackMaxFrame]uintptr, frame unsafe.Pointer, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | 704 | func readStructFromStackArm64(inType reflect.Type, f *[callbackMaxFrame]uintptr, frame unsafe.Pointer, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | ||
| 707 | - | if runtime.GOOS == "darwin" { | 705 | + | if isDarwin { |
| 708 | return callbackArgFromStack(frame, *stackSlot, stackByteOffset, inType) | 706 | return callbackArgFromStack(frame, *stackSlot, stackByteOffset, inType) | ||
| 709 | } | 707 | } | ||
| 710 | // Linux ARM64: 8-byte aligned slots. | 708 | // Linux ARM64: 8-byte aligned slots. | ||
D struct_arm64_regression_test.go +0 -37
| @@ -1,37 +0,0 @@ | |||||
| 1 | - | //go:build arm64 && (darwin || linux) | |||
| 2 | - | ||||
| 3 | - | package purego | |||
| 4 | - | ||||
| 5 | - | import ( | |||
| 6 | - | "reflect" | |||
| 7 | - | "testing" | |||
| 8 | - | ) | |||
| 9 | - | ||||
| 10 | - | func TestIsHFARejectsNestedMixedStruct(t *testing.T) { | |||
| 11 | - | type inner struct{ X, Y float32 } | |||
| 12 | - | type mixed struct { | |||
| 13 | - | Inner inner | |||
| 14 | - | Tail int32 | |||
| 15 | - | } | |||
| 16 | - | if isHFA(reflect.TypeOf(mixed{})) { | |||
| 17 | - | t.Fatal("expected mixed nested struct to be non-HFA") | |||
| 18 | - | } | |||
| 19 | - | } | |||
| 20 | - | ||||
| 21 | - | func TestIsHFAAcceptsNestedFloatStructs(t *testing.T) { | |||
| 22 | - | type inner struct{ X, Y float32 } | |||
| 23 | - | type outer struct { | |||
| 24 | - | Left inner | |||
| 25 | - | Right inner | |||
| 26 | - | } | |||
| 27 | - | if !isHFA(reflect.TypeOf(outer{})) { | |||
| 28 | - | t.Fatal("expected nested float-only struct to be HFA") | |||
| 29 | - | } | |||
| 30 | - | } | |||
| 31 | - | ||||
| 32 | - | func TestIsHFAAcceptsFloatArrayStruct(t *testing.T) { | |||
| 33 | - | type arrayed struct{ A [2]float64 } | |||
| 34 | - | if !isHFA(reflect.TypeOf(arrayed{})) { | |||
| 35 | - | t.Fatal("expected float array struct to be HFA") | |||
| 36 | - | } | |||
| 37 | - | } | |||
D struct_arm_regression_test.go +0 -41
| @@ -1,41 +0,0 @@ | |||||
| 1 | - | //go:build arm && (freebsd || linux || netbsd || windows) | |||
| 2 | - | ||||
| 3 | - | package purego | |||
| 4 | - | ||||
| 5 | - | import ( | |||
| 6 | - | "reflect" | |||
| 7 | - | "testing" | |||
| 8 | - | "unsafe" | |||
| 9 | - | ) | |||
| 10 | - | ||||
| 11 | - | func TestARMAddStructCopiesUnaddressableValue(t *testing.T) { | |||
| 12 | - | type sample struct{ X uint32 } | |||
| 13 | - | var numInts, numFloats, numStack int | |||
| 14 | - | var ptr uintptr | |||
| 15 | - | keepAlive := addStruct(reflect.ValueOf(sample{X: 7}), &numInts, &numFloats, &numStack, func(v uintptr) { | |||
| 16 | - | ptr = v | |||
| 17 | - | }, func(uintptr) { | |||
| 18 | - | t.Fatal("unexpected float register") | |||
| 19 | - | }, func(uintptr) { | |||
| 20 | - | t.Fatal("unexpected stack slot") | |||
| 21 | - | }, nil) | |||
| 22 | - | if len(keepAlive) == 0 { | |||
| 23 | - | t.Fatal("expected copied keepAlive value") | |||
| 24 | - | } | |||
| 25 | - | if got := *(*uint32)(unsafe.Pointer(ptr)); got != 7 { | |||
| 26 | - | t.Fatalf("copied value = %#x, want %#x", got, uint32(7)) | |||
| 27 | - | } | |||
| 28 | - | } | |||
| 29 | - | ||||
| 30 | - | func TestARMPlaceRegistersReadsUnaddressableValue(t *testing.T) { | |||
| 31 | - | type sample struct{ X uint32 } | |||
| 32 | - | var ints []uintptr | |||
| 33 | - | placeRegisters(reflect.ValueOf(sample{X: 7}), func(uintptr) { | |||
| 34 | - | t.Fatal("unexpected float register") | |||
| 35 | - | }, func(v uintptr) { | |||
| 36 | - | ints = append(ints, v) | |||
| 37 | - | }) | |||
| 38 | - | if len(ints) != 1 || ints[0] != 7 { | |||
| 39 | - | t.Fatalf("placeRegisters() = %#v, want [7]", ints) | |||
| 40 | - | } | |||
| 41 | - | } | |||
M struct_loong64.go +170 -142
| @@ -4,172 +4,200 @@ | |||||
| 4 | package purego | 4 | package purego | ||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | - | "math" | |||
| 8 | "reflect" | 7 | "reflect" | ||
| 9 | "unsafe" | 8 | "unsafe" | ||
| 10 | ) | 9 | ) | ||
| 11 | 10 | ||||
| 12 | - | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | 11 | + | // loong64Leaf is a scalar member of an aggregate after flattening nested structs |
| 13 | - | outSize := outType.Size() | 12 | + | // and arrays. offset is the byte offset of the member within the aggregate. |
| 14 | - | switch { | 13 | + | type loong64Leaf struct { |
| 15 | - | case outSize == 0: | 14 | + | isFloat bool |
| 16 | - | return reflect.New(outType).Elem() | 15 | + | kind reflect.Kind |
| 17 | - | case outSize <= 8: | 16 | + | offset uintptr |
| 18 | - | r1 := syscall.a1 | 17 | + | size uintptr |
| 19 | - | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | 18 | + | } |
| 20 | - | r1 = syscall.f1 | 19 | + | |
| 21 | - | if numFields == 2 { | 20 | + | // loong64Flatten appends the scalar leaves of t to leaves, offsetting each member |
| 22 | - | r1 = packFloat32Pair(syscall.f1, syscall.f2) | 21 | + | // by base. Nested structs and arrays are expanded into their members. |
| 22 | + | func loong64Flatten(t reflect.Type, base uintptr, leaves *[]loong64Leaf) { | |||
| 23 | + | switch t.Kind() { | |||
| 24 | + | case reflect.Struct: | |||
| 25 | + | for i := range t.NumField() { | |||
| 26 | + | f := t.Field(i) | |||
| 27 | + | if f.Name == "_" { | |||
| 28 | + | // Blank fields are explicit padding to match the C layout, not | |||
| 29 | + | // members that the calling convention counts. | |||
| 30 | + | continue | |||
| 23 | } | 31 | } | ||
| 32 | + | loong64Flatten(f.Type, base+f.Offset, leaves) | |||
| 24 | } | 33 | } | ||
| 25 | - | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | 34 | + | case reflect.Array: |
| 26 | - | case outSize <= 16: | 35 | + | elem := t.Elem() |
| 27 | - | r1, r2 := syscall.a1, syscall.a2 | 36 | + | for i := range t.Len() { |
| 28 | - | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | 37 | + | loong64Flatten(elem, base+uintptr(i)*elem.Size(), leaves) |
| 29 | - | switch numFields { | 38 | + | } |
| 30 | - | case 4: | 39 | + | default: |
| 31 | - | r1 = packFloat32Pair(syscall.f1, syscall.f2) | 40 | + | k := t.Kind() |
| 32 | - | r2 = packFloat32Pair(syscall.f3, syscall.f4) | 41 | + | *leaves = append(*leaves, loong64Leaf{ |
| 33 | - | case 3: | 42 | + | isFloat: k == reflect.Float32 || k == reflect.Float64, |
| 34 | - | r1 = packFloat32Pair(syscall.f1, syscall.f2) | 43 | + | kind: k, |
| 35 | - | r2 = syscall.f3 | 44 | + | offset: base, |
| 36 | - | case 2: | 45 | + | size: t.Size(), |
| 37 | - | r1 = syscall.f1 | 46 | + | }) |
| 38 | - | r2 = syscall.f2 | 47 | + | } |
| 39 | - | default: | 48 | + | } |
| 40 | - | panic("not reached") | 49 | + | |
| 41 | - | } | 50 | + | // loong64Classify flattens t and reports whether it is passed and returned through |
| 51 | + | // the floating-point calling convention. Under the LoongArch hard-float ABI an | |||
| 52 | + | // aggregate uses FP registers only when, after flattening, it has one or two | |||
| 53 | + | // floating-point members and nothing else, or exactly one floating-point member | |||
| 54 | + | // together with one integer member. | |||
| 55 | + | func loong64Classify(t reflect.Type) (leaves []loong64Leaf, useFP bool) { | |||
| 56 | + | loong64Flatten(t, 0, &leaves) | |||
| 57 | + | var floats, ints int | |||
| 58 | + | for _, l := range leaves { | |||
| 59 | + | if l.isFloat { | |||
| 60 | + | floats++ | |||
| 61 | + | } else { | |||
| 62 | + | ints++ | |||
| 42 | } | 63 | } | ||
| 43 | - | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | 64 | + | } |
| 65 | + | switch { | |||
| 66 | + | case ints == 0 && (floats == 1 || floats == 2): | |||
| 67 | + | return leaves, true | |||
| 68 | + | case ints == 1 && floats == 1: | |||
| 69 | + | return leaves, true | |||
| 44 | default: | 70 | default: | ||
| 45 | - | // create struct from the Go pointer created above | 71 | + | return leaves, false |
| 46 | - | // weird pointer dereference to circumvent go vet | 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 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | 89 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | ||
| 48 | } | 90 | } | ||
| 91 | + | ||||
| 92 | + | var buf [16]byte | |||
| 93 | + | base := unsafe.Pointer(&buf[0]) | |||
| 94 | + | if leaves, useFP := loong64Classify(outType); useFP { | |||
| 95 | + | floatRegs := [2]uintptr{syscall.f1, syscall.f2} | |||
| 96 | + | intRegs := [2]uintptr{syscall.a1, syscall.a2} | |||
| 97 | + | var fi, ii int | |||
| 98 | + | for _, l := range leaves { | |||
| 99 | + | dst := unsafe.Add(base, l.offset) | |||
| 100 | + | if !l.isFloat { | |||
| 101 | + | loong64StoreInt(dst, l.size, intRegs[ii]) | |||
| 102 | + | ii++ | |||
| 103 | + | continue | |||
| 104 | + | } | |||
| 105 | + | r := floatRegs[fi] | |||
| 106 | + | fi++ | |||
| 107 | + | if l.kind == reflect.Float32 { | |||
| 108 | + | // A single-precision value is NaN-boxed in the register. | |||
| 109 | + | *(*uint32)(dst) = uint32(r) | |||
| 110 | + | } else { | |||
| 111 | + | *(*uint64)(dst) = uint64(r) | |||
| 112 | + | } | |||
| 113 | + | } | |||
| 114 | + | } else { | |||
| 115 | + | *(*uintptr)(base) = syscall.a1 | |||
| 116 | + | if outSize > 8 { | |||
| 117 | + | *(*uintptr)(unsafe.Add(base, 8)) = syscall.a2 | |||
| 118 | + | } | |||
| 119 | + | } | |||
| 120 | + | return reflect.NewAt(outType, base).Elem() | |||
| 49 | } | 121 | } | ||
| 50 | 122 | ||||
| 51 | - | const ( | 123 | + | func loong64StoreInt(dst unsafe.Pointer, size uintptr, r uintptr) { |
| 52 | - | _NO_CLASS = 0b00 | 124 | + | switch size { |
| 53 | - | _FLOAT = 0b01 | 125 | + | case 1: |
| 54 | - | _INT = 0b11 | 126 | + | *(*uint8)(dst) = uint8(r) |
| 55 | - | ) | 127 | + | case 2: |
| 128 | + | *(*uint16)(dst) = uint16(r) | |||
| 129 | + | case 4: | |||
| 130 | + | *(*uint32)(dst) = uint32(r) | |||
| 131 | + | default: | |||
| 132 | + | *(*uint64)(dst) = uint64(r) | |||
| 133 | + | } | |||
| 134 | + | } | |||
| 56 | 135 | ||||
| 57 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | 136 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | ||
| 58 | - | if v.Type().Size() == 0 { | 137 | + | size := v.Type().Size() |
| 138 | + | if size == 0 { | |||
| 59 | return keepAlive | 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 { | 145 | + | var ptr unsafe.Pointer |
| 63 | - | placeRegisters(v, addFloat, addInt) | 146 | + | if v.CanAddr() { |
| 147 | + | ptr = v.Addr().UnsafePointer() | |||
| 64 | } else { | 148 | } else { | ||
| 65 | - | keepAlive = placeStack(v, keepAlive, addInt) | 149 | + | tmp := reflect.New(v.Type()) |
| 150 | + | tmp.Elem().Set(v) | |||
| 151 | + | ptr = tmp.UnsafePointer() | |||
| 152 | + | keepAlive = append(keepAlive, tmp.Interface()) | |||
| 66 | } | 153 | } | ||
| 67 | - | return keepAlive // the struct was allocated so don't panic | |||
| 68 | - | } | |||
| 69 | 154 | ||||
| 70 | - | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | 155 | + | if leaves, useFP := loong64Classify(v.Type()); useFP { |
| 71 | - | var val uint64 | 156 | + | for _, l := range leaves { |
| 72 | - | var shift byte | 157 | + | src := unsafe.Add(ptr, l.offset) |
| 73 | - | var flushed bool | 158 | + | switch { |
| 74 | - | class := _NO_CLASS | 159 | + | case l.isFloat && l.kind == reflect.Float32: |
| 75 | - | var place func(v reflect.Value) | 160 | + | // NaN-box the single-precision value in the 64-bit FP register. |
| 76 | - | place = func(v reflect.Value) { | 161 | + | addFloat(uintptr(*(*uint32)(src)) | 0xFFFFFFFF_00000000) |
| 77 | - | var numFields int | 162 | + | case l.isFloat: |
| 78 | - | if v.Kind() == reflect.Struct { | 163 | + | addFloat(uintptr(*(*uint64)(src))) |
| 79 | - | numFields = v.Type().NumField() | |||
| 80 | - | } else { | |||
| 81 | - | numFields = v.Type().Len() | |||
| 82 | - | } | |||
| 83 | - | for k := 0; k < numFields; k++ { | |||
| 84 | - | flushed = false | |||
| 85 | - | var f reflect.Value | |||
| 86 | - | if v.Kind() == reflect.Struct { | |||
| 87 | - | f = v.Field(k) | |||
| 88 | - | } else { | |||
| 89 | - | f = v.Index(k) | |||
| 90 | - | } | |||
| 91 | - | align := byte(f.Type().Align()*8 - 1) | |||
| 92 | - | shift = (shift + align) &^ align | |||
| 93 | - | if shift >= 64 { | |||
| 94 | - | flushPackedWord(&val, &shift, &flushed, class == _FLOAT, addFloat, addInt) | |||
| 95 | - | class = _NO_CLASS | |||
| 96 | - | } | |||
| 97 | - | switch f.Type().Kind() { | |||
| 98 | - | case reflect.Struct: | |||
| 99 | - | place(f) | |||
| 100 | - | case reflect.Bool: | |||
| 101 | - | if f.Bool() { | |||
| 102 | - | val |= 1 << shift | |||
| 103 | - | } | |||
| 104 | - | shift += 8 | |||
| 105 | - | class |= _INT | |||
| 106 | - | case reflect.Uint8: | |||
| 107 | - | val |= f.Uint() << shift | |||
| 108 | - | shift += 8 | |||
| 109 | - | class |= _INT | |||
| 110 | - | case reflect.Uint16: | |||
| 111 | - | val |= f.Uint() << shift | |||
| 112 | - | shift += 16 | |||
| 113 | - | class |= _INT | |||
| 114 | - | case reflect.Uint32: | |||
| 115 | - | val |= f.Uint() << shift | |||
| 116 | - | shift += 32 | |||
| 117 | - | class |= _INT | |||
| 118 | - | case reflect.Uint64, reflect.Uint, reflect.Uintptr: | |||
| 119 | - | addInt(uintptr(f.Uint())) | |||
| 120 | - | shift = 0 | |||
| 121 | - | flushed = true | |||
| 122 | - | class = _NO_CLASS | |||
| 123 | - | case reflect.Int8: | |||
| 124 | - | val |= uint64(f.Int()&0xFF) << shift | |||
| 125 | - | shift += 8 | |||
| 126 | - | class |= _INT | |||
| 127 | - | case reflect.Int16: | |||
| 128 | - | val |= uint64(f.Int()&0xFFFF) << shift | |||
| 129 | - | shift += 16 | |||
| 130 | - | class |= _INT | |||
| 131 | - | case reflect.Int32: | |||
| 132 | - | val |= uint64(f.Int()&0xFFFF_FFFF) << shift | |||
| 133 | - | shift += 32 | |||
| 134 | - | class |= _INT | |||
| 135 | - | case reflect.Int64, reflect.Int: | |||
| 136 | - | addInt(uintptr(f.Int())) | |||
| 137 | - | shift = 0 | |||
| 138 | - | flushed = true | |||
| 139 | - | class = _NO_CLASS | |||
| 140 | - | case reflect.Float32: | |||
| 141 | - | if class == _FLOAT { | |||
| 142 | - | addFloat(uintptr(val)) | |||
| 143 | - | val = 0 | |||
| 144 | - | shift = 0 | |||
| 145 | - | } | |||
| 146 | - | val |= uint64(math.Float32bits(float32(f.Float()))) << shift | |||
| 147 | - | shift += 32 | |||
| 148 | - | class |= _FLOAT | |||
| 149 | - | case reflect.Float64: | |||
| 150 | - | addFloat(uintptr(math.Float64bits(float64(f.Float())))) | |||
| 151 | - | shift = 0 | |||
| 152 | - | flushed = true | |||
| 153 | - | class = _NO_CLASS | |||
| 154 | - | case reflect.Ptr, reflect.UnsafePointer: | |||
| 155 | - | addInt(f.Pointer()) | |||
| 156 | - | shift = 0 | |||
| 157 | - | flushed = true | |||
| 158 | - | class = _NO_CLASS | |||
| 159 | - | case reflect.Array: | |||
| 160 | - | place(f) | |||
| 161 | default: | 164 | default: | ||
| 162 | - | panic("purego: unsupported kind " + f.Kind().String()) | 165 | + | addInt(loong64LoadInt(src, l)) |
| 163 | } | 166 | } | ||
| 164 | } | 167 | } | ||
| 168 | + | return keepAlive | |||
| 165 | } | 169 | } | ||
| 166 | - | place(v) | 170 | + | |
| 167 | - | if !flushed { | 171 | + | // Integer calling convention: pass the raw aggregate in one or two GARs. |
| 168 | - | if class == _FLOAT { | 172 | + | var words [16]byte |
| 169 | - | addFloat(uintptr(val)) | 173 | + | copy(words[:], unsafe.Slice((*byte)(ptr), size)) |
| 170 | - | } else { | 174 | + | addInt(*(*uintptr)(unsafe.Pointer(&words[0]))) |
| 171 | - | addInt(uintptr(val)) | 175 | + | if size > 8 { |
| 172 | - | } | 176 | + | addInt(*(*uintptr)(unsafe.Pointer(&words[8]))) |
| 177 | + | } | |||
| 178 | + | return keepAlive | |||
| 179 | + | } | |||
| 180 | + | ||||
| 181 | + | // loong64LoadInt reads an integer leaf into a register value, sign-extending | |||
| 182 | + | // signed members and zero-extending the rest. | |||
| 183 | + | func loong64LoadInt(src unsafe.Pointer, l loong64Leaf) uintptr { | |||
| 184 | + | switch l.kind { | |||
| 185 | + | case reflect.Int8: | |||
| 186 | + | return uintptr(int64(*(*int8)(src))) | |||
| 187 | + | case reflect.Int16: | |||
| 188 | + | return uintptr(int64(*(*int16)(src))) | |||
| 189 | + | case reflect.Int32: | |||
| 190 | + | return uintptr(int64(*(*int32)(src))) | |||
| 191 | + | case reflect.Int, reflect.Int64: | |||
| 192 | + | return uintptr(*(*int64)(src)) | |||
| 193 | + | case reflect.Bool, reflect.Uint8: | |||
| 194 | + | return uintptr(*(*uint8)(src)) | |||
| 195 | + | case reflect.Uint16: | |||
| 196 | + | return uintptr(*(*uint16)(src)) | |||
| 197 | + | case reflect.Uint32: | |||
| 198 | + | return uintptr(*(*uint32)(src)) | |||
| 199 | + | default: | |||
| 200 | + | return uintptr(*(*uint64)(src)) | |||
| 173 | } | 201 | } | ||
| 174 | } | 202 | } | ||
| 175 | 203 | ||||
D struct_loong64_regression_test.go +0 -35
| @@ -1,35 +0,0 @@ | |||||
| 1 | - | //go:build loong64 && linux | |||
| 2 | - | ||||
| 3 | - | package purego | |||
| 4 | - | ||||
| 5 | - | import ( | |||
| 6 | - | "math" | |||
| 7 | - | "reflect" | |||
| 8 | - | "testing" | |||
| 9 | - | ) | |||
| 10 | - | ||||
| 11 | - | func TestLoong64GetStructMasksFloat32Pairs(t *testing.T) { | |||
| 12 | - | type pair struct{ X, Y float32 } | |||
| 13 | - | sys := syscall15Args{ | |||
| 14 | - | f1: uintptr(0xDEADBEEF00000000 | uint64(math.Float32bits(1.5))), | |||
| 15 | - | f2: uintptr(math.Float32bits(2.5)), | |||
| 16 | - | } | |||
| 17 | - | got := getStruct(reflect.TypeOf(pair{}), sys).Interface().(pair) | |||
| 18 | - | if got.X != 1.5 || got.Y != 2.5 { | |||
| 19 | - | t.Fatalf("got %+v, want {1.5 2.5}", got) | |||
| 20 | - | } | |||
| 21 | - | } | |||
| 22 | - | ||||
| 23 | - | func TestLoong64PlaceRegistersResetsAfterFlush(t *testing.T) { | |||
| 24 | - | type triple struct{ A, B, C uint32 } | |||
| 25 | - | var ints []uintptr | |||
| 26 | - | placeRegisters(reflect.ValueOf(triple{A: 1, B: 2, C: 3}), func(uintptr) { | |||
| 27 | - | t.Fatal("unexpected float register") | |||
| 28 | - | }, func(v uintptr) { | |||
| 29 | - | ints = append(ints, v) | |||
| 30 | - | }) | |||
| 31 | - | want := []uintptr{0x0000000200000001, 0x0000000000000003} | |||
| 32 | - | if len(ints) != len(want) || ints[0] != want[0] || ints[1] != want[1] { | |||
| 33 | - | t.Fatalf("placeRegisters() = %#v, want %#v", ints, want) | |||
| 34 | - | } | |||
| 35 | - | } | |||
A struct_other.go +54 -0
| @@ -0,0 +1,54 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build !(amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x) | |||
| 5 | + | ||||
| 6 | + | package purego | |||
| 7 | + | ||||
| 8 | + | import ( | |||
| 9 | + | "reflect" | |||
| 10 | + | "unsafe" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // This file provides the struct-handling helpers for architectures that do | |||
| 14 | + | // not support struct arguments or returns: 386, and any architecture reachable | |||
| 15 | + | // only through the integer-only cgo fallback. Every struct operation panics. | |||
| 16 | + | ||||
| 17 | + | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |||
| 18 | + | panic("purego: struct arguments are not supported on this architecture") | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |||
| 22 | + | panic("purego: struct returns are not supported on this architecture") | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | // structReturnInMemory reports whether a struct return value is returned through | |||
| 26 | + | // a caller-allocated hidden pointer. Structs are unsupported on this | |||
| 27 | + | // architecture, so it always reports false. | |||
| 28 | + | func structReturnInMemory(reflect.Type) bool { | |||
| 29 | + | return false | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | // shouldBundleStackArgs always returns false because C-style stack argument | |||
| 33 | + | // bundling is only needed on Darwin ARM64. | |||
| 34 | + | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |||
| 35 | + | return false | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | |||
| 39 | + | keepAlive []any, addInt, addFloat, addStack func(uintptr), | |||
| 40 | + | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | |||
| 41 | + | panic("purego: collectStackArgs should not be called on this architecture") | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 45 | + | panic("purego: bundleStackArgs should not be called on this architecture") | |||
| 46 | + | } | |||
| 47 | + | ||||
| 48 | + | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |||
| 49 | + | panic("purego: struct callback arguments are not supported on this architecture") | |||
| 50 | + | } | |||
| 51 | + | ||||
| 52 | + | func setStruct(a *callbackArgs, ret reflect.Value) { | |||
| 53 | + | panic("purego: struct returns are not supported on this architecture") | |||
| 54 | + | } | |||
M struct_ppc64le.go +118 -89
| @@ -4,77 +4,76 @@ | |||||
| 4 | package purego | 4 | package purego | ||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | + | "math" | |||
| 7 | "reflect" | 8 | "reflect" | ||
| 8 | "unsafe" | 9 | "unsafe" | ||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| 11 | - | func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | 12 | + | // ppc64leLeaf is a scalar member of a flattened aggregate at byte offset offset. |
| 12 | - | outSize := outType.Size() | 13 | + | type ppc64leLeaf struct { |
| 13 | - | 14 | + | kind reflect.Kind | |
| 14 | - | switch { | 15 | + | offset uintptr |
| 15 | - | case outSize == 0: | 16 | + | } |
| 16 | - | return reflect.New(outType).Elem() | |||
| 17 | - | ||||
| 18 | - | case outSize <= 16: | |||
| 19 | - | // Reconstruct from registers by copying raw bytes | |||
| 20 | - | var buf [16]byte | |||
| 21 | - | ||||
| 22 | - | // Integer registers | |||
| 23 | - | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.a1 | |||
| 24 | - | if outSize > 8 { | |||
| 25 | - | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.a2 | |||
| 26 | - | } | |||
| 27 | 17 | ||||
| 28 | - | // Homogeneous float aggregates override integer regs | 18 | + | // ppc64leFlatten appends the scalar leaves of t to leaves, expanding nested |
| 29 | - | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | 19 | + | // structs and arrays. |
| 30 | - | if outType.Field(0).Type.Kind() == reflect.Float32 { | 20 | + | func ppc64leFlatten(t reflect.Type, base uintptr, leaves *[]ppc64leLeaf) { |
| 31 | - | // float32 values in FP regs | 21 | + | switch t.Kind() { |
| 32 | - | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | 22 | + | case reflect.Struct: |
| 33 | - | for i := 0; i < numFields; i++ { | 23 | + | for i := range t.NumField() { |
| 34 | - | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | 24 | + | f := t.Field(i) |
| 35 | - | } | 25 | + | if f.Name == "_" { |
| 36 | - | } else { | 26 | + | // Blank fields are padding, not counted members. |
| 37 | - | // float64: whole register value is valid | 27 | + | continue |
| 38 | - | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.f1 | |||
| 39 | - | if outSize > 8 { | |||
| 40 | - | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.f2 | |||
| 41 | - | } | |||
| 42 | } | 28 | } | ||
| 29 | + | ppc64leFlatten(f.Type, base+f.Offset, leaves) | |||
| 30 | + | } | |||
| 31 | + | case reflect.Array: | |||
| 32 | + | elem := t.Elem() | |||
| 33 | + | for i := range t.Len() { | |||
| 34 | + | ppc64leFlatten(elem, base+uintptr(i)*elem.Size(), leaves) | |||
| 43 | } | 35 | } | ||
| 44 | - | ||||
| 45 | - | return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem() | |||
| 46 | - | ||||
| 47 | default: | 36 | default: | ||
| 48 | - | // Returned indirectly via pointer in a1 | 37 | + | *leaves = append(*leaves, ppc64leLeaf{kind: t.Kind(), offset: base}) |
| 49 | - | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)) | |||
| 50 | - | return reflect.NewAt(outType, ptr).Elem() | |||
| 51 | } | 38 | } | ||
| 52 | } | 39 | } | ||
| 53 | 40 | ||||
| 54 | - | func addStruct( | 41 | + | // ppc64leClassifyHFA reports whether t is a homogeneous floating-point |
| 55 | - | v reflect.Value, | 42 | + | // aggregate: one to eight members all of the same floating-point type. |
| 56 | - | numInts, numFloats, numStack *int, | 43 | + | func ppc64leClassifyHFA(t reflect.Type) (leaves []ppc64leLeaf, isHFA bool) { |
| 57 | - | addInt, addFloat, addStack func(uintptr), | 44 | + | ppc64leFlatten(t, 0, &leaves) |
| 58 | - | keepAlive []any, | 45 | + | if len(leaves) == 0 || len(leaves) > 8 { |
| 59 | - | ) []any { | 46 | + | return leaves, false |
| 60 | - | size := v.Type().Size() | |||
| 61 | - | if size == 0 { | |||
| 62 | - | return keepAlive | |||
| 63 | } | 47 | } | ||
| 64 | - | 48 | + | first := leaves[0].kind | |
| 65 | - | if size <= 16 { | 49 | + | if first != reflect.Float32 && first != reflect.Float64 { |
| 66 | - | return placeSmallAggregatePPC64LE(v, addFloat, addInt, keepAlive) | 50 | + | return leaves, false |
| 51 | + | } | |||
| 52 | + | for _, l := range leaves { | |||
| 53 | + | if l.kind != first { | |||
| 54 | + | return leaves, false | |||
| 55 | + | } | |||
| 67 | } | 56 | } | ||
| 57 | + | return leaves, true | |||
| 58 | + | } | |||
| 68 | 59 | ||||
| 69 | - | return placeStack(v, keepAlive, addInt) | 60 | + | // structReturnInMemory reports whether a struct return value is returned through |
| 61 | + | // a caller-allocated hidden pointer rather than in registers. A homogeneous | |||
| 62 | + | // floating-point aggregate is returned in the floating-point registers whatever | |||
| 63 | + | // its size. | |||
| 64 | + | func structReturnInMemory(outType reflect.Type) bool { | |||
| 65 | + | if outType.Size() <= maxRegAllocStructSize { | |||
| 66 | + | return false | |||
| 67 | + | } | |||
| 68 | + | _, isHFA := ppc64leClassifyHFA(outType) | |||
| 69 | + | return !isHFA | |||
| 70 | } | 70 | } | ||
| 71 | 71 | ||||
| 72 | - | func placeSmallAggregatePPC64LE( | 72 | + | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { |
| 73 | - | v reflect.Value, | |||
| 74 | - | addFloat, addInt func(uintptr), | |||
| 75 | - | keepAlive []any, | |||
| 76 | - | ) []any { | |||
| 77 | size := v.Type().Size() | 73 | size := v.Type().Size() | ||
| 74 | + | if size == 0 { | |||
| 75 | + | return keepAlive | |||
| 76 | + | } | |||
| 78 | 77 | ||||
| 79 | var ptr unsafe.Pointer | 78 | var ptr unsafe.Pointer | ||
| 80 | if v.CanAddr() { | 79 | if v.CanAddr() { | ||
| @@ -86,58 +85,88 @@ func placeSmallAggregatePPC64LE( | |||||
| 86 | keepAlive = append(keepAlive, tmp.Interface()) | 85 | keepAlive = append(keepAlive, tmp.Interface()) | ||
| 87 | } | 86 | } | ||
| 88 | 87 | ||||
| 89 | - | var buf [16]byte | 88 | + | // An HFA passes each member in its own floating-point register. |
| 90 | - | src := unsafe.Slice((*byte)(ptr), size) | 89 | + | if leaves, isHFA := ppc64leClassifyHFA(v.Type()); isHFA { |
| 91 | - | copy(buf[:], src) | 90 | + | for _, l := range leaves { |
| 92 | - | 91 | + | src := unsafe.Add(ptr, l.offset) | |
| 93 | - | w0 := *(*uintptr)(unsafe.Pointer(&buf[0])) | 92 | + | if l.kind == reflect.Float32 { |
| 94 | - | w1 := uintptr(0) | 93 | + | // Single precision occupies the register in double format. |
| 95 | - | if size > 8 { | 94 | + | addFloat(uintptr(math.Float64bits(float64(*(*float32)(src))))) |
| 96 | - | w1 = *(*uintptr)(unsafe.Pointer(&buf[8])) | 95 | + | } else { |
| 96 | + | addFloat(uintptr(*(*uint64)(src))) | |||
| 97 | + | } | |||
| 98 | + | } | |||
| 97 | } | 99 | } | ||
| 98 | 100 | ||||
| 99 | - | if isFloats, _ := isAllSameFloat(v.Type()); isFloats { | 101 | + | // Every aggregate also occupies its size rounded up to doublewords in the |
| 100 | - | addFloat(w0) | 102 | + | // GPRs r3-r10 / parameter save area: shadow (skipped) for an HFA, the value |
| 101 | - | if size > 8 { | 103 | + | // otherwise. |
| 102 | - | addFloat(w1) | 104 | + | for off := uintptr(0); off < size; off += 8 { |
| 103 | - | } | 105 | + | if remaining := size - off; remaining >= 8 { |
| 104 | - | } else { | 106 | + | addInt(*(*uintptr)(unsafe.Add(ptr, off))) |
| 105 | - | addInt(w0) | 107 | + | } else { |
| 106 | - | if size > 8 { | 108 | + | var word [8]byte |
| 107 | - | addInt(w1) | 109 | + | copy(word[:], unsafe.Slice((*byte)(unsafe.Add(ptr, off)), int(remaining))) |
| 110 | + | addInt(*(*uintptr)(unsafe.Pointer(&word[0]))) | |||
| 108 | } | 111 | } | ||
| 109 | } | 112 | } | ||
| 110 | 113 | ||||
| 111 | return keepAlive | 114 | return keepAlive | ||
| 112 | } | 115 | } | ||
| 113 | 116 | ||||
| 114 | - | // placeStack is a fallback for structs that are too large to fit in registers | 117 | + | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { |
| 115 | - | func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | 118 | + | if outType.Size() == 0 { |
| 116 | - | if v.CanAddr() { | 119 | + | return reflect.New(outType).Elem() |
| 117 | - | addInt(v.Addr().Pointer()) | 120 | + | } |
| 118 | - | return keepAlive | 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()) | 153 | + | return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem() |
| 121 | - | ptr.Elem().Set(v) | |||
| 122 | - | addInt(ptr.Pointer()) | |||
| 123 | - | return append(keepAlive, ptr.Interface()) | |||
| 124 | } | 154 | } | ||
| 125 | 155 | ||||
| 156 | + | // shouldBundleStackArgs always returns false on ppc64le | |||
| 157 | + | // since C-style stack argument bundling is only needed on Darwin ARM64. | |||
| 126 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | 158 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | ||
| 127 | - | // PPC64LE does not bundle stack args | |||
| 128 | return false | 159 | return false | ||
| 129 | } | 160 | } | ||
| 130 | 161 | ||||
| 131 | - | func collectStackArgs( | 162 | + | // collectStackArgs is not used on ppc64le. |
| 132 | - | args []reflect.Value, | 163 | + | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, |
| 133 | - | i, numInts, numFloats int, | 164 | + | keepAlive []any, addInt, addFloat, addStack func(uintptr), |
| 134 | - | keepAlive []any, | 165 | + | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { |
| 135 | - | addInt, addFloat, addStack func(uintptr), | 166 | + | panic("purego: collectStackArgs should not be called on ppc64le") |
| 136 | - | numIntsPtr, numFloatsPtr, numStackPtr *int, | |||
| 137 | - | ) ([]reflect.Value, []any) { | |||
| 138 | - | return nil, keepAlive | |||
| 139 | } | 167 | } | ||
| 140 | 168 | ||||
| 169 | + | // bundleStackArgs is not used on ppc64le. | |||
| 141 | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | 170 | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | ||
| 142 | panic("purego: bundleStackArgs should not be called on ppc64le") | 171 | panic("purego: bundleStackArgs should not be called on ppc64le") | ||
| 143 | } | 172 | } | ||
M struct_riscv64.go +10 -3
| @@ -8,7 +8,14 @@ import ( | |||||
| 8 | "unsafe" | 8 | "unsafe" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | - | func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | 11 | + | // structReturnInMemory reports whether a struct return value is returned through |
| 12 | + | // a caller-allocated hidden pointer passed as the first integer argument. | |||
| 13 | + | // Aggregates larger than two eightbytes are returned in memory. | |||
| 14 | + | func structReturnInMemory(outType reflect.Type) bool { | |||
| 15 | + | return outType.Size() > maxRegAllocStructSize | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |||
| 12 | outSize := outType.Size() | 19 | outSize := outType.Size() | ||
| 13 | 20 | ||||
| 14 | switch { | 21 | switch { | ||
| @@ -27,10 +34,10 @@ func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | |||||
| 27 | 34 | ||||
| 28 | // Homogeneous float aggregates override integer regs | 35 | // Homogeneous float aggregates override integer regs | ||
| 29 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | 36 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | ||
| 30 | - | if outType.Field(0).Type.Kind() == reflect.Float32 { | 37 | + | if abiField(outType, 0).Type.Kind() == reflect.Float32 { |
| 31 | // float32 values are NaN-boxed in FP regs; use low 32 bits only | 38 | // float32 values are NaN-boxed in FP regs; use low 32 bits only | ||
| 32 | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | 39 | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | ||
| 33 | - | for i := 0; i < numFields; i++ { | 40 | + | for i := range numFields { |
| 34 | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | 41 | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | ||
| 35 | } | 42 | } | ||
| 36 | } else { | 43 | } else { | ||
M struct_s390x.go +11 -4
| @@ -8,7 +8,14 @@ import ( | |||||
| 8 | "unsafe" | 8 | "unsafe" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | - | func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | 11 | + | // structReturnInMemory reports whether a struct return value is returned through |
| 12 | + | // a caller-allocated hidden pointer passed as the first integer argument. | |||
| 13 | + | // Aggregates larger than two eightbytes are returned in memory. | |||
| 14 | + | func structReturnInMemory(outType reflect.Type) bool { | |||
| 15 | + | return outType.Size() > maxRegAllocStructSize | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |||
| 12 | outSize := outType.Size() | 19 | outSize := outType.Size() | ||
| 13 | 20 | ||||
| 14 | switch { | 21 | switch { | ||
| @@ -27,11 +34,11 @@ func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | |||||
| 27 | 34 | ||||
| 28 | // Homogeneous float aggregates override integer regs | 35 | // Homogeneous float aggregates override integer regs | ||
| 29 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | 36 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | ||
| 30 | - | if outType.Field(0).Type.Kind() == reflect.Float32 { | 37 | + | if abiField(outType, 0).Type.Kind() == reflect.Float32 { |
| 31 | // float32 values in FP regs | 38 | // float32 values in FP regs | ||
| 32 | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | 39 | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | ||
| 33 | - | for i := 0; i < numFields; i++ { | 40 | + | for i := range numFields { |
| 34 | - | *(*uint32)(unsafe.Pointer(&buf[i*4])) = highFloat32Bits(f[i]) | 41 | + | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) |
| 35 | } | 42 | } | ||
| 36 | } else { | 43 | } else { | ||
| 37 | // float64: whole register value is valid | 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 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | 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 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
| 8 | import ( | 8 | import ( | ||
| 9 | + | "iter" | |||
| 9 | "math" | 10 | "math" | ||
| 10 | "os" | 11 | "os" | ||
| 11 | "path/filepath" | 12 | "path/filepath" | ||
| 12 | "reflect" | 13 | "reflect" | ||
| 13 | "runtime" | 14 | "runtime" | ||
| 15 | + | "slices" | |||
| 16 | + | "structs" | |||
| 14 | "testing" | 17 | "testing" | ||
| 15 | "unsafe" | 18 | "unsafe" | ||
| 16 | 19 | ||||
| 17 | "github.com/bnema/purego" | 20 | "github.com/bnema/purego" | ||
| 21 | + | "github.com/bnema/purego/internal/load" | |||
| 18 | ) | 22 | ) | ||
| 19 | 23 | ||||
| 20 | func TestRegisterFunc_structArgs(t *testing.T) { | 24 | func TestRegisterFunc_structArgs(t *testing.T) { | ||
| 21 | libFileName := filepath.Join(t.TempDir(), "structtest.so") | 25 | libFileName := filepath.Join(t.TempDir(), "structtest.so") | ||
| 22 | t.Logf("Build %v", libFileName) | 26 | t.Logf("Build %v", libFileName) | ||
| 23 | 27 | ||||
| 24 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "structtest", "struct_test.c")); err != nil { | 28 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "structtest", "struct_test.c")); err != nil { |
| 25 | t.Fatal(err) | 29 | t.Fatal(err) | ||
| 26 | } | 30 | } | ||
| 27 | defer os.Remove(libFileName) | 31 | defer os.Remove(libFileName) | ||
| 28 | 32 | ||||
| 29 | - | lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL) | 33 | + | lib, err := load.OpenLibrary(libFileName) |
| 30 | if err != nil { | 34 | if err != nil { | ||
| 31 | - | t.Fatalf("Dlopen(%q) failed: %v", libFileName, err) | 35 | + | t.Fatalf("OpenLibrary(%q) failed: %v", libFileName, err) |
| 32 | } | 36 | } | ||
| 37 | + | defer func() { | |||
| 38 | + | if err := load.CloseLibrary(lib); err != nil { | |||
| 39 | + | t.Fatalf("failed to close library: %v", err) | |||
| 40 | + | } | |||
| 41 | + | }() | |||
| 33 | 42 | ||||
| 34 | const ( | 43 | const ( | ||
| 35 | expectedUnsigned = 0xdeadbeef | 44 | expectedUnsigned = 0xdeadbeef | ||
| @@ -41,8 +50,9 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 41 | ) | 50 | ) | ||
| 42 | 51 | ||||
| 43 | implementations := []struct { | 52 | implementations := []struct { | ||
| 44 | - | name string | 53 | + | name string |
| 45 | - | register func(fptr any, handle uintptr, name string, goFn any) | 54 | + | usesCallbacks bool |
| 55 | + | register func(fptr any, handle uintptr, name string, goFn any) | |||
| 46 | }{ | 56 | }{ | ||
| 47 | { | 57 | { | ||
| 48 | name: "RegisterLibFunc", | 58 | name: "RegisterLibFunc", | ||
| @@ -51,7 +61,8 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 51 | }, | 61 | }, | ||
| 52 | }, | 62 | }, | ||
| 53 | { | 63 | { | ||
| 54 | - | name: "GoCallbackFunc", | 64 | + | name: "GoCallbackFunc", |
| 65 | + | usesCallbacks: true, | |||
| 55 | register: func(fptr any, handle uintptr, name string, goFn any) { | 66 | register: func(fptr any, handle uintptr, name string, goFn any) { | ||
| 56 | fnType := reflect.TypeOf(fptr).Elem() | 67 | fnType := reflect.TypeOf(fptr).Elem() | ||
| 57 | if fnType.NumOut() > 0 { | 68 | if fnType.NumOut() > 0 { | ||
| @@ -68,11 +79,20 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 68 | }, | 79 | }, | ||
| 69 | } | 80 | } | ||
| 70 | for _, imp := range implementations { | 81 | for _, imp := range implementations { | ||
| 71 | - | imp := 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 | t.Run(imp.name, func(t *testing.T) { | 90 | t.Run(imp.name, func(t *testing.T) { | ||
| 73 | register := imp.register | 91 | register := imp.register | ||
| 74 | { | 92 | { | ||
| 75 | - | type Empty struct{} | 93 | + | type Empty struct { |
| 94 | + | _ structs.HostLayout | |||
| 95 | + | } | |||
| 76 | var NoStruct func(Empty) int64 | 96 | var NoStruct func(Empty) int64 | ||
| 77 | register(&NoStruct, lib, "NoStruct", func(Empty) int64 { | 97 | register(&NoStruct, lib, "NoStruct", func(Empty) int64 { | ||
| 78 | return 0xdeadbeef | 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 | var EmptyEmptyFn func(EmptyEmpty) int64 | 108 | var EmptyEmptyFn func(EmptyEmpty) int64 | ||
| 87 | register(&EmptyEmptyFn, lib, "EmptyEmpty", func(EmptyEmpty) int64 { | 109 | register(&EmptyEmptyFn, lib, "EmptyEmpty", func(EmptyEmpty) int64 { | ||
| 88 | return 0xdeadbeef | 110 | return 0xdeadbeef | ||
| @@ -100,6 +122,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 100 | } | 122 | } | ||
| 101 | { | 123 | { | ||
| 102 | type GreaterThan16Bytes struct { | 124 | type GreaterThan16Bytes struct { | ||
| 125 | + | _ structs.HostLayout | |||
| 103 | x, y, z *int64 | 126 | x, y, z *int64 | ||
| 104 | } | 127 | } | ||
| 105 | var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000 | 128 | var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000 | ||
| @@ -113,7 +136,9 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 113 | } | 136 | } | ||
| 114 | { | 137 | { | ||
| 115 | type GreaterThan16BytesStruct struct { | 138 | type GreaterThan16BytesStruct struct { | ||
| 139 | + | _ structs.HostLayout | |||
| 116 | a struct { | 140 | a struct { | ||
| 141 | + | _ structs.HostLayout | |||
| 117 | x, y, z *int64 | 142 | x, y, z *int64 | ||
| 118 | } | 143 | } | ||
| 119 | } | 144 | } | ||
| @@ -122,12 +147,16 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 122 | register(&GreaterThan16BytesStructFn, lib, "GreaterThan16BytesStruct", func(g GreaterThan16BytesStruct) int64 { | 147 | register(&GreaterThan16BytesStructFn, lib, "GreaterThan16BytesStruct", func(g GreaterThan16BytesStruct) int64 { | ||
| 123 | return *g.a.x + *g.a.y + *g.a.z | 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 | t.Fatalf("GreaterThan16BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned) | 154 | t.Fatalf("GreaterThan16BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned) | ||
| 127 | } | 155 | } | ||
| 128 | } | 156 | } | ||
| 129 | { | 157 | { | ||
| 130 | type GreaterThan16Bytes struct { | 158 | type GreaterThan16Bytes struct { | ||
| 159 | + | _ structs.HostLayout | |||
| 131 | x, y, z *int64 | 160 | x, y, z *int64 | ||
| 132 | } | 161 | } | ||
| 133 | var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000 | 162 | var x, y, z int64 = 0xEF, 0xBE00, 0xDEAD0000 | ||
| @@ -151,48 +180,52 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 151 | register(&BeforeRegisters, lib, "BeforeRegisters", func(bytes GreaterThan16Bytes, a, b int64) uint64 { | 180 | register(&BeforeRegisters, lib, "BeforeRegisters", func(bytes GreaterThan16Bytes, a, b int64) uint64 { | ||
| 152 | return uint64(*bytes.x + *bytes.y + *bytes.z + a + b) | 181 | return uint64(*bytes.x + *bytes.y + *bytes.z + a + b) | ||
| 153 | }) | 182 | }) | ||
| 154 | - | if ret := BeforeRegisters(GreaterThan16Bytes{&x, &y, &z}, 0x0F, 0xF0); ret != expectedUnsigned { | 183 | + | if ret := BeforeRegisters(GreaterThan16Bytes{x: &x, y: &y, z: &z}, 0x0F, 0xF0); ret != expectedUnsigned { |
| 155 | t.Fatalf("BeforeRegisters returned %#x wanted %#x", ret, expectedUnsigned) | 184 | t.Fatalf("BeforeRegisters returned %#x wanted %#x", ret, expectedUnsigned) | ||
| 156 | } | 185 | } | ||
| 157 | } | 186 | } | ||
| 158 | { | 187 | { | ||
| 159 | type IntLessThan16Bytes struct { | 188 | type IntLessThan16Bytes struct { | ||
| 189 | + | _ structs.HostLayout | |||
| 160 | x, y int64 | 190 | x, y int64 | ||
| 161 | } | 191 | } | ||
| 162 | var IntLessThan16BytesFn func(bytes IntLessThan16Bytes) int64 | 192 | var IntLessThan16BytesFn func(bytes IntLessThan16Bytes) int64 | ||
| 163 | register(&IntLessThan16BytesFn, lib, "IntLessThan16Bytes", func(l IntLessThan16Bytes) int64 { | 193 | register(&IntLessThan16BytesFn, lib, "IntLessThan16Bytes", func(l IntLessThan16Bytes) int64 { | ||
| 164 | return l.x + l.y | 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 | t.Fatalf("IntLessThan16BytesFn returned %#x wanted %#x", ret, expectedUnsigned) | 197 | t.Fatalf("IntLessThan16BytesFn returned %#x wanted %#x", ret, expectedUnsigned) | ||
| 168 | } | 198 | } | ||
| 169 | } | 199 | } | ||
| 170 | { | 200 | { | ||
| 171 | type FloatLessThan16Bytes struct { | 201 | type FloatLessThan16Bytes struct { | ||
| 202 | + | _ structs.HostLayout | |||
| 172 | x, y float32 | 203 | x, y float32 | ||
| 173 | } | 204 | } | ||
| 174 | var FloatLessThan16BytesFn func(FloatLessThan16Bytes) float32 | 205 | var FloatLessThan16BytesFn func(FloatLessThan16Bytes) float32 | ||
| 175 | register(&FloatLessThan16BytesFn, lib, "FloatLessThan16Bytes", func(f FloatLessThan16Bytes) float32 { | 206 | register(&FloatLessThan16BytesFn, lib, "FloatLessThan16Bytes", func(f FloatLessThan16Bytes) float32 { | ||
| 176 | return f.x + f.y | 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 | t.Fatalf("FloatLessThan16Bytes returned %f wanted %f", ret, expectedFloat) | 210 | t.Fatalf("FloatLessThan16Bytes returned %f wanted %f", ret, expectedFloat) | ||
| 180 | } | 211 | } | ||
| 181 | } | 212 | } | ||
| 182 | { | 213 | { | ||
| 183 | type ThreeSmallFields struct { | 214 | type ThreeSmallFields struct { | ||
| 215 | + | _ structs.HostLayout | |||
| 184 | x, y, z float32 | 216 | x, y, z float32 | ||
| 185 | } | 217 | } | ||
| 186 | var ThreeSmallFieldsFn func(ThreeSmallFields) float32 | 218 | var ThreeSmallFieldsFn func(ThreeSmallFields) float32 | ||
| 187 | register(&ThreeSmallFieldsFn, lib, "ThreeSmallFields", func(f ThreeSmallFields) float32 { | 219 | register(&ThreeSmallFieldsFn, lib, "ThreeSmallFields", func(f ThreeSmallFields) float32 { | ||
| 188 | return f.x + f.y + f.z | 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 | t.Fatalf("ThreeSmallFields returned %f wanted %f", ret, expectedFloat) | 223 | t.Fatalf("ThreeSmallFields returned %f wanted %f", ret, expectedFloat) | ||
| 192 | } | 224 | } | ||
| 193 | } | 225 | } | ||
| 194 | { | 226 | { | ||
| 195 | type FloatAndInt struct { | 227 | type FloatAndInt struct { | ||
| 228 | + | _ structs.HostLayout | |||
| 196 | x float32 | 229 | x float32 | ||
| 197 | y int32 | 230 | y int32 | ||
| 198 | } | 231 | } | ||
| @@ -200,37 +233,41 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 200 | register(&FloatAndIntFn, lib, "FloatAndInt", func(f FloatAndInt) float32 { | 233 | register(&FloatAndIntFn, lib, "FloatAndInt", func(f FloatAndInt) float32 { | ||
| 201 | return f.x + float32(f.y) | 234 | return f.x + float32(f.y) | ||
| 202 | }) | 235 | }) | ||
| 203 | - | if ret := FloatAndIntFn(FloatAndInt{3, 7}); ret != expectedFloat { | 236 | + | if ret := FloatAndIntFn(FloatAndInt{x: 3, y: 7}); ret != expectedFloat { |
| 204 | t.Fatalf("FloatAndIntFn returned %f wanted %f", ret, expectedFloat) | 237 | t.Fatalf("FloatAndIntFn returned %f wanted %f", ret, expectedFloat) | ||
| 205 | } | 238 | } | ||
| 206 | } | 239 | } | ||
| 207 | { | 240 | { | ||
| 208 | type DoubleStruct struct { | 241 | type DoubleStruct struct { | ||
| 242 | + | _ structs.HostLayout | |||
| 209 | x float64 | 243 | x float64 | ||
| 210 | } | 244 | } | ||
| 211 | var DoubleStructFn func(DoubleStruct) float64 | 245 | var DoubleStructFn func(DoubleStruct) float64 | ||
| 212 | register(&DoubleStructFn, lib, "DoubleStruct", func(d DoubleStruct) float64 { | 246 | register(&DoubleStructFn, lib, "DoubleStruct", func(d DoubleStruct) float64 { | ||
| 213 | return d.x | 247 | return d.x | ||
| 214 | }) | 248 | }) | ||
| 215 | - | if ret := DoubleStructFn(DoubleStruct{10}); ret != expectedDouble { | 249 | + | if ret := DoubleStructFn(DoubleStruct{x: 10}); ret != expectedDouble { |
| 216 | t.Fatalf("DoubleStruct returned %f wanted %f", ret, expectedDouble) | 250 | t.Fatalf("DoubleStruct returned %f wanted %f", ret, expectedDouble) | ||
| 217 | } | 251 | } | ||
| 218 | } | 252 | } | ||
| 219 | { | 253 | { | ||
| 220 | type TwoDoubleStruct struct { | 254 | type TwoDoubleStruct struct { | ||
| 255 | + | _ structs.HostLayout | |||
| 221 | x, y float64 | 256 | x, y float64 | ||
| 222 | } | 257 | } | ||
| 223 | var TwoDoubleStructFn func(TwoDoubleStruct) float64 | 258 | var TwoDoubleStructFn func(TwoDoubleStruct) float64 | ||
| 224 | register(&TwoDoubleStructFn, lib, "TwoDoubleStruct", func(d TwoDoubleStruct) float64 { | 259 | register(&TwoDoubleStructFn, lib, "TwoDoubleStruct", func(d TwoDoubleStruct) float64 { | ||
| 225 | return d.x + d.y | 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 | t.Fatalf("TwoDoubleStruct returned %f wanted %f", ret, expectedDouble) | 263 | t.Fatalf("TwoDoubleStruct returned %f wanted %f", ret, expectedDouble) | ||
| 229 | } | 264 | } | ||
| 230 | } | 265 | } | ||
| 231 | { | 266 | { | ||
| 232 | type TwoDoubleTwoStruct struct { | 267 | type TwoDoubleTwoStruct struct { | ||
| 268 | + | _ structs.HostLayout | |||
| 233 | x struct { | 269 | x struct { | ||
| 270 | + | _ structs.HostLayout | |||
| 234 | x, y float64 | 271 | x, y float64 | ||
| 235 | } | 272 | } | ||
| 236 | } | 273 | } | ||
| @@ -238,69 +275,84 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 238 | register(&TwoDoubleTwoStructFn, lib, "TwoDoubleTwoStruct", func(d TwoDoubleTwoStruct) float64 { | 275 | register(&TwoDoubleTwoStructFn, lib, "TwoDoubleTwoStruct", func(d TwoDoubleTwoStruct) float64 { | ||
| 239 | return d.x.x + d.x.y | 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 | t.Fatalf("TwoDoubleTwoStruct returned %f wanted %f", ret, expectedDouble) | 282 | t.Fatalf("TwoDoubleTwoStruct returned %f wanted %f", ret, expectedDouble) | ||
| 243 | } | 283 | } | ||
| 244 | } | 284 | } | ||
| 245 | { | 285 | { | ||
| 246 | type ThreeDoubleStruct struct { | 286 | type ThreeDoubleStruct struct { | ||
| 287 | + | _ structs.HostLayout | |||
| 247 | x, y, z float64 | 288 | x, y, z float64 | ||
| 248 | } | 289 | } | ||
| 249 | var ThreeDoubleStructFn func(ThreeDoubleStruct) float64 | 290 | var ThreeDoubleStructFn func(ThreeDoubleStruct) float64 | ||
| 250 | register(&ThreeDoubleStructFn, lib, "ThreeDoubleStruct", func(d ThreeDoubleStruct) float64 { | 291 | register(&ThreeDoubleStructFn, lib, "ThreeDoubleStruct", func(d ThreeDoubleStruct) float64 { | ||
| 251 | return d.x + d.y + d.z | 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 | t.Fatalf("ThreeDoubleStructFn returned %f wanted %f", ret, expectedDouble) | 295 | t.Fatalf("ThreeDoubleStructFn returned %f wanted %f", ret, expectedDouble) | ||
| 255 | } | 296 | } | ||
| 256 | } | 297 | } | ||
| 257 | { | 298 | { | ||
| 258 | type LargeFloatStruct struct { | 299 | type LargeFloatStruct struct { | ||
| 300 | + | _ structs.HostLayout | |||
| 259 | a, b, c, d, e, f float64 | 301 | a, b, c, d, e, f float64 | ||
| 260 | } | 302 | } | ||
| 261 | var LargeFloatStructFn func(LargeFloatStruct) float64 | 303 | var LargeFloatStructFn func(LargeFloatStruct) float64 | ||
| 262 | register(&LargeFloatStructFn, lib, "LargeFloatStruct", func(s LargeFloatStruct) float64 { | 304 | register(&LargeFloatStructFn, lib, "LargeFloatStruct", func(s LargeFloatStruct) float64 { | ||
| 263 | return s.a + s.b + s.c + s.d + s.e + s.f | 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 | t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat) | 308 | t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat) | ||
| 267 | } | 309 | } | ||
| 268 | - | var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64 | 310 | + | if runtime.GOARCH != "ppc64le" { |
| 269 | - | register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 { | 311 | + | // Needs 9 float argument registers (3 doubles + a 6-double HFA); |
| 270 | - | return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f | 312 | + | // ppc64le wires 8. |
| 271 | - | }) | 313 | + | var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64 |
| 272 | - | if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble { | 314 | + | register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 { |
| 273 | - | t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat) | 315 | + | return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f |
| 316 | + | }) | |||
| 317 | + | if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{a: 1, b: 2, c: 3, d: 4, e: 5, f: -5}); ret != expectedDouble { | |||
| 318 | + | t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat) | |||
| 319 | + | } | |||
| 274 | } | 320 | } | ||
| 275 | } | 321 | } | ||
| 276 | { | 322 | { | ||
| 277 | type Rect struct { | 323 | type Rect struct { | ||
| 324 | + | _ structs.HostLayout | |||
| 278 | x, y, w, h float64 | 325 | x, y, w, h float64 | ||
| 279 | } | 326 | } | ||
| 280 | - | var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64 | 327 | + | if runtime.GOARCH != "ppc64le" { |
| 281 | - | register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 { | 328 | + | // Needs 9 float argument registers (5 doubles + a 4-double HFA); |
| 282 | - | return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h | 329 | + | // ppc64le wires 8. |
| 283 | - | }) | 330 | + | var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64 |
| 284 | - | if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{1, 2, 3, -4}); ret != expectedDouble { | 331 | + | register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 { |
| 285 | - | t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble) | 332 | + | return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h |
| 333 | + | }) | |||
| 334 | + | if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{x: 1, y: 2, w: 3, h: -4}); ret != expectedDouble { | |||
| 335 | + | t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble) | |||
| 336 | + | } | |||
| 286 | } | 337 | } | ||
| 287 | var RectangleSubtract func(rect Rect) float64 | 338 | var RectangleSubtract func(rect Rect) float64 | ||
| 288 | register(&RectangleSubtract, lib, "RectangleSubtract", func(rect Rect) float64 { | 339 | register(&RectangleSubtract, lib, "RectangleSubtract", func(rect Rect) float64 { | ||
| 289 | return (rect.x + rect.y) - (rect.w + rect.h) | 340 | return (rect.x + rect.y) - (rect.w + rect.h) | ||
| 290 | }) | 341 | }) | ||
| 291 | - | if ret := RectangleSubtract(Rect{15, 5, 3, 7}); ret != expectedDouble { | 342 | + | if ret := RectangleSubtract(Rect{x: 15, y: 5, w: 3, h: 7}); ret != expectedDouble { |
| 292 | t.Fatalf("RectangleSubtract returned %f wanted %f", ret, expectedDouble) | 343 | t.Fatalf("RectangleSubtract returned %f wanted %f", ret, expectedDouble) | ||
| 293 | } | 344 | } | ||
| 294 | var Rectangle func(rect Rect) float64 | 345 | var Rectangle func(rect Rect) float64 | ||
| 295 | register(&Rectangle, lib, "Rectangle", func(rect Rect) float64 { | 346 | register(&Rectangle, lib, "Rectangle", func(rect Rect) float64 { | ||
| 296 | return rect.x + rect.y + rect.w + rect.h | 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 | t.Fatalf("Rectangle returned %f wanted %f", ret, expectedFloat) | 350 | t.Fatalf("Rectangle returned %f wanted %f", ret, expectedFloat) | ||
| 300 | } | 351 | } | ||
| 301 | } | 352 | } | ||
| 302 | { | 353 | { | ||
| 303 | type FloatArray struct { | 354 | type FloatArray struct { | ||
| 355 | + | _ structs.HostLayout | |||
| 304 | a [2]float64 | 356 | a [2]float64 | ||
| 305 | } | 357 | } | ||
| 306 | var FloatArrayFn func(rect FloatArray) float64 | 358 | var FloatArrayFn func(rect FloatArray) float64 | ||
| @@ -313,6 +365,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 313 | } | 365 | } | ||
| 314 | { | 366 | { | ||
| 315 | type UnsignedChar4Bytes struct { | 367 | type UnsignedChar4Bytes struct { | ||
| 368 | + | _ structs.HostLayout | |||
| 316 | a, b, c, d byte | 369 | a, b, c, d byte | ||
| 317 | } | 370 | } | ||
| 318 | var UnsignedChar4BytesFn func(UnsignedChar4Bytes) uint32 | 371 | var UnsignedChar4BytesFn func(UnsignedChar4Bytes) uint32 | ||
| @@ -325,16 +378,21 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 325 | } | 378 | } | ||
| 326 | { | 379 | { | ||
| 327 | type UnsignedChar4BytesStruct struct { | 380 | type UnsignedChar4BytesStruct struct { | ||
| 381 | + | _ structs.HostLayout | |||
| 328 | x struct { | 382 | x struct { | ||
| 383 | + | _ structs.HostLayout | |||
| 329 | a byte | 384 | a byte | ||
| 330 | } | 385 | } | ||
| 331 | y struct { | 386 | y struct { | ||
| 387 | + | _ structs.HostLayout | |||
| 332 | b byte | 388 | b byte | ||
| 333 | } | 389 | } | ||
| 334 | z struct { | 390 | z struct { | ||
| 391 | + | _ structs.HostLayout | |||
| 335 | c byte | 392 | c byte | ||
| 336 | } | 393 | } | ||
| 337 | w struct { | 394 | w struct { | ||
| 395 | + | _ structs.HostLayout | |||
| 338 | d byte | 396 | d byte | ||
| 339 | } | 397 | } | ||
| 340 | } | 398 | } | ||
| @@ -343,16 +401,29 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 343 | return uint32(b.x.a)<<24 | uint32(b.y.b)<<16 | uint32(b.z.c)<<8 | uint32(b.w.d) | 401 | return uint32(b.x.a)<<24 | uint32(b.y.b)<<16 | uint32(b.z.c)<<8 | uint32(b.w.d) | ||
| 344 | }) | 402 | }) | ||
| 345 | if ret := UnsignedChar4BytesStructFn(UnsignedChar4BytesStruct{ | 403 | if ret := UnsignedChar4BytesStructFn(UnsignedChar4BytesStruct{ | ||
| 346 | - | x: struct{ a byte }{a: 0xDE}, | 404 | + | x: struct { |
| 347 | - | y: struct{ b byte }{b: 0xAD}, | 405 | + | _ structs.HostLayout |
| 348 | - | z: struct{ c byte }{c: 0xBE}, | 406 | + | a byte |
| 349 | - | w: struct{ d byte }{d: 0xEF}, | 407 | + | }{a: 0xDE}, |
| 408 | + | y: struct { | |||
| 409 | + | _ structs.HostLayout | |||
| 410 | + | b byte | |||
| 411 | + | }{b: 0xAD}, | |||
| 412 | + | z: struct { | |||
| 413 | + | _ structs.HostLayout | |||
| 414 | + | c byte | |||
| 415 | + | }{c: 0xBE}, | |||
| 416 | + | w: struct { | |||
| 417 | + | _ structs.HostLayout | |||
| 418 | + | d byte | |||
| 419 | + | }{d: 0xEF}, | |||
| 350 | }); ret != expectedUnsigned { | 420 | }); ret != expectedUnsigned { | ||
| 351 | t.Fatalf("UnsignedChar4BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned) | 421 | t.Fatalf("UnsignedChar4BytesStructFn returned %#x wanted %#x", ret, expectedUnsigned) | ||
| 352 | } | 422 | } | ||
| 353 | } | 423 | } | ||
| 354 | { | 424 | { | ||
| 355 | type Short struct { | 425 | type Short struct { | ||
| 426 | + | _ structs.HostLayout | |||
| 356 | a, b, c, d uint16 | 427 | a, b, c, d uint16 | ||
| 357 | } | 428 | } | ||
| 358 | var ShortFn func(Short) uint64 | 429 | var ShortFn func(Short) uint64 | ||
| @@ -365,6 +436,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 365 | } | 436 | } | ||
| 366 | { | 437 | { | ||
| 367 | type Int struct { | 438 | type Int struct { | ||
| 439 | + | _ structs.HostLayout | |||
| 368 | a, b uint32 | 440 | a, b uint32 | ||
| 369 | } | 441 | } | ||
| 370 | var IntFn func(Int) uint64 | 442 | var IntFn func(Int) uint64 | ||
| @@ -377,6 +449,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 377 | } | 449 | } | ||
| 378 | { | 450 | { | ||
| 379 | type Long struct { | 451 | type Long struct { | ||
| 452 | + | _ structs.HostLayout | |||
| 380 | a uint64 | 453 | a uint64 | ||
| 381 | } | 454 | } | ||
| 382 | var LongFn func(Long) uint64 | 455 | var LongFn func(Long) uint64 | ||
| @@ -389,6 +462,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 389 | } | 462 | } | ||
| 390 | { | 463 | { | ||
| 391 | type Char8Bytes struct { | 464 | type Char8Bytes struct { | ||
| 465 | + | _ structs.HostLayout | |||
| 392 | a, b, c, d, e, f, g, h int8 | 466 | a, b, c, d, e, f, g, h int8 | ||
| 393 | } | 467 | } | ||
| 394 | var Char8BytesFn func(Char8Bytes) int32 | 468 | var Char8BytesFn func(Char8Bytes) int32 | ||
| @@ -401,6 +475,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 401 | } | 475 | } | ||
| 402 | { | 476 | { | ||
| 403 | type Odd struct { | 477 | type Odd struct { | ||
| 478 | + | _ structs.HostLayout | |||
| 404 | a, b, c byte | 479 | a, b, c byte | ||
| 405 | } | 480 | } | ||
| 406 | var OddFn func(Odd) int32 | 481 | var OddFn func(Odd) int32 | ||
| @@ -413,6 +488,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 413 | } | 488 | } | ||
| 414 | { | 489 | { | ||
| 415 | type Char2Short1 struct { | 490 | type Char2Short1 struct { | ||
| 491 | + | _ structs.HostLayout | |||
| 416 | a, b byte | 492 | a, b byte | ||
| 417 | c uint16 | 493 | c uint16 | ||
| 418 | } | 494 | } | ||
| @@ -426,6 +502,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 426 | } | 502 | } | ||
| 427 | { | 503 | { | ||
| 428 | type SignedChar2Short1 struct { | 504 | type SignedChar2Short1 struct { | ||
| 505 | + | _ structs.HostLayout | |||
| 429 | a, b int8 | 506 | a, b int8 | ||
| 430 | c int16 | 507 | c int16 | ||
| 431 | } | 508 | } | ||
| @@ -439,6 +516,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 439 | } | 516 | } | ||
| 440 | { | 517 | { | ||
| 441 | type Array4UnsignedChars struct { | 518 | type Array4UnsignedChars struct { | ||
| 519 | + | _ structs.HostLayout | |||
| 442 | a [4]uint8 | 520 | a [4]uint8 | ||
| 443 | } | 521 | } | ||
| 444 | var Array4UnsignedCharsFn func(chars Array4UnsignedChars) uint32 | 522 | var Array4UnsignedCharsFn func(chars Array4UnsignedChars) uint32 | ||
| @@ -451,6 +529,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 451 | } | 529 | } | ||
| 452 | { | 530 | { | ||
| 453 | type Array3UnsignedChar struct { | 531 | type Array3UnsignedChar struct { | ||
| 532 | + | _ structs.HostLayout | |||
| 454 | a [3]uint8 | 533 | a [3]uint8 | ||
| 455 | } | 534 | } | ||
| 456 | var Array3UnsignedChars func(chars Array3UnsignedChar) uint32 | 535 | var Array3UnsignedChars func(chars Array3UnsignedChar) uint32 | ||
| @@ -463,6 +542,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 463 | } | 542 | } | ||
| 464 | { | 543 | { | ||
| 465 | type Array2UnsignedShort struct { | 544 | type Array2UnsignedShort struct { | ||
| 545 | + | _ structs.HostLayout | |||
| 466 | a [2]uint16 | 546 | a [2]uint16 | ||
| 467 | } | 547 | } | ||
| 468 | var Array2UnsignedShorts func(chars Array2UnsignedShort) uint32 | 548 | var Array2UnsignedShorts func(chars Array2UnsignedShort) uint32 | ||
| @@ -475,6 +555,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 475 | } | 555 | } | ||
| 476 | { | 556 | { | ||
| 477 | type Array4Chars struct { | 557 | type Array4Chars struct { | ||
| 558 | + | _ structs.HostLayout | |||
| 478 | a [4]int8 | 559 | a [4]int8 | ||
| 479 | } | 560 | } | ||
| 480 | var Array4CharsFn func(chars Array4Chars) int32 | 561 | var Array4CharsFn func(chars Array4Chars) int32 | ||
| @@ -488,6 +569,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 488 | } | 569 | } | ||
| 489 | { | 570 | { | ||
| 490 | type Array2Short struct { | 571 | type Array2Short struct { | ||
| 572 | + | _ structs.HostLayout | |||
| 491 | a [2]int16 | 573 | a [2]int16 | ||
| 492 | } | 574 | } | ||
| 493 | var Array2Shorts func(chars Array2Short) int32 | 575 | var Array2Shorts func(chars Array2Short) int32 | ||
| @@ -500,6 +582,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 500 | } | 582 | } | ||
| 501 | { | 583 | { | ||
| 502 | type Array3Short struct { | 584 | type Array3Short struct { | ||
| 585 | + | _ structs.HostLayout | |||
| 503 | a [3]int16 | 586 | a [3]int16 | ||
| 504 | } | 587 | } | ||
| 505 | var Array3Shorts func(chars Array3Short) int32 | 588 | var Array3Shorts func(chars Array3Short) int32 | ||
| @@ -512,21 +595,23 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 512 | } | 595 | } | ||
| 513 | { | 596 | { | ||
| 514 | type BoolStruct struct { | 597 | type BoolStruct struct { | ||
| 598 | + | _ structs.HostLayout | |||
| 515 | b bool | 599 | b bool | ||
| 516 | } | 600 | } | ||
| 517 | var BoolStructFn func(BoolStruct) bool | 601 | var BoolStructFn func(BoolStruct) bool | ||
| 518 | register(&BoolStructFn, lib, "BoolStruct", func(b BoolStruct) bool { | 602 | register(&BoolStructFn, lib, "BoolStruct", func(b BoolStruct) bool { | ||
| 519 | return b.b | 603 | return b.b | ||
| 520 | }) | 604 | }) | ||
| 521 | - | if ret := BoolStructFn(BoolStruct{true}); ret != true { | 605 | + | if ret := BoolStructFn(BoolStruct{b: true}); ret != true { |
| 522 | t.Fatalf("BoolStructFn returned %v wanted %v", ret, true) | 606 | t.Fatalf("BoolStructFn returned %v wanted %v", ret, true) | ||
| 523 | } | 607 | } | ||
| 524 | - | if ret := BoolStructFn(BoolStruct{false}); ret != false { | 608 | + | if ret := BoolStructFn(BoolStruct{b: false}); ret != false { |
| 525 | t.Fatalf("BoolStructFn returned %v wanted %v", ret, false) | 609 | t.Fatalf("BoolStructFn returned %v wanted %v", ret, false) | ||
| 526 | } | 610 | } | ||
| 527 | } | 611 | } | ||
| 528 | { | 612 | { | ||
| 529 | type BoolFloat struct { | 613 | type BoolFloat struct { | ||
| 614 | + | _ structs.HostLayout | |||
| 530 | b bool | 615 | b bool | ||
| 531 | _ [3]byte // purego won't do padding for you so make sure it aligns properly with C struct | 616 | _ [3]byte // purego won't do padding for you so make sure it aligns properly with C struct | ||
| 532 | f float32 | 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 } | 634 | + | type point struct { |
| 550 | - | type size struct{ width, height float64 } | 635 | + | _ structs.HostLayout |
| 636 | + | x, y float64 | |||
| 637 | + | } | |||
| 638 | + | type size struct { | |||
| 639 | + | _ structs.HostLayout | |||
| 640 | + | width, height float64 | |||
| 641 | + | } | |||
| 551 | type Content struct { | 642 | type Content struct { | ||
| 643 | + | _ structs.HostLayout | |||
| 552 | point point | 644 | point point | ||
| 553 | size size | 645 | size size | ||
| 554 | } | 646 | } | ||
| @@ -564,13 +656,14 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 564 | }) | 656 | }) | ||
| 565 | if ret := InitWithContentRect(new(int), | 657 | if ret := InitWithContentRect(new(int), | ||
| 566 | // These numbers are created so that when added together and then divided by 11 it produces 0xdeadbeef | 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 | 15, 4, true); ret != expectedUnsigned { | 660 | 15, 4, true); ret != expectedUnsigned { | ||
| 569 | t.Fatalf("InitWithContentRect returned %d wanted %#x", ret, expectedUnsigned) | 661 | t.Fatalf("InitWithContentRect returned %d wanted %#x", ret, expectedUnsigned) | ||
| 570 | } | 662 | } | ||
| 571 | } | 663 | } | ||
| 572 | { | 664 | { | ||
| 573 | type GoInt4 struct { | 665 | type GoInt4 struct { | ||
| 666 | + | _ structs.HostLayout | |||
| 574 | A, B, C, D int | 667 | A, B, C, D int | ||
| 575 | } | 668 | } | ||
| 576 | var GoInt4Fn func(GoInt4) int | 669 | var GoInt4Fn func(GoInt4) int | ||
| @@ -578,12 +671,13 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 578 | return g.A + g.B - g.C + g.D | 671 | return g.A + g.B - g.C + g.D | ||
| 579 | }) | 672 | }) | ||
| 580 | const expected = math.MaxInt - 52 - 3 + 4 | 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 | t.Fatalf("GoInt4Fn returned %d wanted %#x", ret, expected) | 675 | t.Fatalf("GoInt4Fn returned %d wanted %#x", ret, expected) | ||
| 583 | } | 676 | } | ||
| 584 | } | 677 | } | ||
| 585 | { | 678 | { | ||
| 586 | type GoUint4 struct { | 679 | type GoUint4 struct { | ||
| 680 | + | _ structs.HostLayout | |||
| 587 | A, B, C, D uint | 681 | A, B, C, D uint | ||
| 588 | } | 682 | } | ||
| 589 | var GoUint4Fn func(GoUint4) uint | 683 | var GoUint4Fn func(GoUint4) uint | ||
| @@ -591,23 +685,27 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 591 | return g.A + g.B + g.C + g.D | 685 | return g.A + g.B + g.C + g.D | ||
| 592 | }) | 686 | }) | ||
| 593 | const expected = 1_000_000 + 53 + 71 + 8 | 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 | t.Fatalf("GoUint4Fn returned %d wanted %#x", ret, expected) | 689 | t.Fatalf("GoUint4Fn returned %d wanted %#x", ret, expected) | ||
| 596 | } | 690 | } | ||
| 597 | } | 691 | } | ||
| 598 | { | 692 | { | ||
| 599 | - | type OneLong struct{ a uintptr } | 693 | + | type OneLong struct { |
| 694 | + | _ structs.HostLayout | |||
| 695 | + | a uintptr | |||
| 696 | + | } | |||
| 600 | var TakeGoUintAndReturn func(a OneLong) uint64 | 697 | var TakeGoUintAndReturn func(a OneLong) uint64 | ||
| 601 | register(&TakeGoUintAndReturn, lib, "TakeGoUintAndReturn", func(a OneLong) uint64 { | 698 | register(&TakeGoUintAndReturn, lib, "TakeGoUintAndReturn", func(a OneLong) uint64 { | ||
| 602 | return uint64(a.a) | 699 | return uint64(a.a) | ||
| 603 | }) | 700 | }) | ||
| 604 | expected := uint64(7) | 701 | expected := uint64(7) | ||
| 605 | - | if ret := TakeGoUintAndReturn(OneLong{7}); ret != expected { | 702 | + | if ret := TakeGoUintAndReturn(OneLong{a: 7}); ret != expected { |
| 606 | t.Fatalf("TakeGoUintAndReturn returned %+v wanted %+v", ret, expected) | 703 | t.Fatalf("TakeGoUintAndReturn returned %+v wanted %+v", ret, expected) | ||
| 607 | } | 704 | } | ||
| 608 | } | 705 | } | ||
| 609 | { | 706 | { | ||
| 610 | type FloatAndBool struct { | 707 | type FloatAndBool struct { | ||
| 708 | + | _ structs.HostLayout | |||
| 611 | x float32 | 709 | x float32 | ||
| 612 | y bool | 710 | y bool | ||
| 613 | } | 711 | } | ||
| @@ -627,13 +725,14 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 627 | } | 725 | } | ||
| 628 | { | 726 | { | ||
| 629 | type FourInt32s struct { | 727 | type FourInt32s struct { | ||
| 728 | + | _ structs.HostLayout | |||
| 630 | f0, f1, f2, f3 int32 | 729 | f0, f1, f2, f3 int32 | ||
| 631 | } | 730 | } | ||
| 632 | var FourInt32sFn func(FourInt32s) int32 | 731 | var FourInt32sFn func(FourInt32s) int32 | ||
| 633 | register(&FourInt32sFn, lib, "FourInt32s", func(s FourInt32s) int32 { | 732 | register(&FourInt32sFn, lib, "FourInt32s", func(s FourInt32s) int32 { | ||
| 634 | return s.f0 + s.f1 + s.f2 + s.f3 | 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 | const want = 100 - 127 + 4 - 100 | 736 | const want = 100 - 127 + 4 - 100 | ||
| 638 | if result != want { | 737 | if result != want { | ||
| 639 | t.Fatalf("FourInt32s returned %d wanted %d", result, want) | 738 | t.Fatalf("FourInt32s returned %d wanted %d", result, want) | ||
| @@ -641,6 +740,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 641 | } | 740 | } | ||
| 642 | { | 741 | { | ||
| 643 | type PointerWrapper struct { | 742 | type PointerWrapper struct { | ||
| 743 | + | _ structs.HostLayout | |||
| 644 | ctx unsafe.Pointer | 744 | ctx unsafe.Pointer | ||
| 645 | } | 745 | } | ||
| 646 | var ExtractPointer func(wrapper PointerWrapper) uintptr | 746 | var ExtractPointer func(wrapper PointerWrapper) uintptr | ||
| @@ -658,6 +758,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 658 | } | 758 | } | ||
| 659 | { | 759 | { | ||
| 660 | type TwoPointers struct { | 760 | type TwoPointers struct { | ||
| 761 | + | _ structs.HostLayout | |||
| 661 | ptr1, ptr2 unsafe.Pointer | 762 | ptr1, ptr2 unsafe.Pointer | ||
| 662 | } | 763 | } | ||
| 663 | var AddPointers func(wrapper TwoPointers) uintptr | 764 | var AddPointers func(wrapper TwoPointers) uintptr | ||
| @@ -669,7 +770,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 669 | ptr1 := unsafe.Pointer(&v1) | 770 | ptr1 := unsafe.Pointer(&v1) | ||
| 670 | ptr2 := unsafe.Pointer(&v2) | 771 | ptr2 := unsafe.Pointer(&v2) | ||
| 671 | expected := uintptr(ptr1) + uintptr(ptr2) | 772 | expected := uintptr(ptr1) + uintptr(ptr2) | ||
| 672 | - | result := AddPointers(TwoPointers{ptr1, ptr2}) | 773 | + | result := AddPointers(TwoPointers{ptr1: ptr1, ptr2: ptr2}) |
| 673 | if result != expected { | 774 | if result != expected { | ||
| 674 | t.Fatalf("AddPointers returned %#x wanted %#x", result, expected) | 775 | t.Fatalf("AddPointers returned %#x wanted %#x", result, expected) | ||
| 675 | } | 776 | } | ||
| @@ -678,48 +779,60 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 678 | // Identity struct arg tests | 779 | // Identity struct arg tests | ||
| 679 | { | 780 | { | ||
| 680 | // Single int64: one integer register. | 781 | // Single int64: one integer register. | ||
| 681 | - | type OneInt64 struct{ A int64 } | 782 | + | type OneInt64 struct { |
| 783 | + | _ structs.HostLayout | |||
| 784 | + | A int64 | |||
| 785 | + | } | |||
| 682 | var fn func(OneInt64) OneInt64 | 786 | var fn func(OneInt64) OneInt64 | ||
| 683 | register(&fn, lib, "IdentityOneInt64", func(s OneInt64) OneInt64 { | 787 | register(&fn, lib, "IdentityOneInt64", func(s OneInt64) OneInt64 { | ||
| 684 | return s | 788 | return s | ||
| 685 | }) | 789 | }) | ||
| 686 | - | expected := OneInt64{0xDEADBEEF} | 790 | + | expected := OneInt64{A: 0xDEADBEEF} |
| 687 | if ret := fn(expected); ret != expected { | 791 | if ret := fn(expected); ret != expected { | ||
| 688 | t.Fatalf("IdentityOneInt64 returned %+v wanted %+v", ret, expected) | 792 | t.Fatalf("IdentityOneInt64 returned %+v wanted %+v", ret, expected) | ||
| 689 | } | 793 | } | ||
| 690 | } | 794 | } | ||
| 691 | { | 795 | { | ||
| 692 | // Two int64 fields: two integer registers. | 796 | // Two int64 fields: two integer registers. | ||
| 693 | - | type IntLessThan16Bytes struct{ X, Y int64 } | 797 | + | type IntLessThan16Bytes struct { |
| 798 | + | _ structs.HostLayout | |||
| 799 | + | X, Y int64 | |||
| 800 | + | } | |||
| 694 | var fn func(IntLessThan16Bytes) IntLessThan16Bytes | 801 | var fn func(IntLessThan16Bytes) IntLessThan16Bytes | ||
| 695 | register(&fn, lib, "IdentityIntLessThan16Bytes", func(s IntLessThan16Bytes) IntLessThan16Bytes { | 802 | register(&fn, lib, "IdentityIntLessThan16Bytes", func(s IntLessThan16Bytes) IntLessThan16Bytes { | ||
| 696 | return s | 803 | return s | ||
| 697 | }) | 804 | }) | ||
| 698 | - | expected := IntLessThan16Bytes{0xDEADBEEF, -42} | 805 | + | expected := IntLessThan16Bytes{X: 0xDEADBEEF, Y: -42} |
| 699 | if ret := fn(expected); ret != expected { | 806 | if ret := fn(expected); ret != expected { | ||
| 700 | t.Fatalf("IdentityIntLessThan16Bytes returned %+v wanted %+v", ret, expected) | 807 | t.Fatalf("IdentityIntLessThan16Bytes returned %+v wanted %+v", ret, expected) | ||
| 701 | } | 808 | } | ||
| 702 | } | 809 | } | ||
| 703 | { | 810 | { | ||
| 704 | // Two float64 fields: SSE on amd64, HFA on arm64. | 811 | // Two float64 fields: SSE on amd64, HFA on arm64. | ||
| 705 | - | type TwoDoubleStruct struct{ X, Y float64 } | 812 | + | type TwoDoubleStruct struct { |
| 813 | + | _ structs.HostLayout | |||
| 814 | + | X, Y float64 | |||
| 815 | + | } | |||
| 706 | var fn func(TwoDoubleStruct) TwoDoubleStruct | 816 | var fn func(TwoDoubleStruct) TwoDoubleStruct | ||
| 707 | register(&fn, lib, "IdentityTwoDoubleStruct", func(s TwoDoubleStruct) TwoDoubleStruct { | 817 | register(&fn, lib, "IdentityTwoDoubleStruct", func(s TwoDoubleStruct) TwoDoubleStruct { | ||
| 708 | return s | 818 | return s | ||
| 709 | }) | 819 | }) | ||
| 710 | - | expected := TwoDoubleStruct{3.14, -2.71} | 820 | + | expected := TwoDoubleStruct{X: 3.14, Y: -2.71} |
| 711 | if ret := fn(expected); ret != expected { | 821 | if ret := fn(expected); ret != expected { | ||
| 712 | t.Fatalf("IdentityTwoDoubleStruct returned %+v wanted %+v", ret, expected) | 822 | t.Fatalf("IdentityTwoDoubleStruct returned %+v wanted %+v", ret, expected) | ||
| 713 | } | 823 | } | ||
| 714 | } | 824 | } | ||
| 715 | { | 825 | { | ||
| 716 | // Four float32 fields: HFA on arm64, SSE on amd64. | 826 | // Four float32 fields: HFA on arm64, SSE on amd64. | ||
| 717 | - | type FourFloat32 struct{ A, B, C, D float32 } | 827 | + | type FourFloat32 struct { |
| 828 | + | _ structs.HostLayout | |||
| 829 | + | A, B, C, D float32 | |||
| 830 | + | } | |||
| 718 | var fn func(FourFloat32) FourFloat32 | 831 | var fn func(FourFloat32) FourFloat32 | ||
| 719 | register(&fn, lib, "IdentityFourFloat32", func(s FourFloat32) FourFloat32 { | 832 | register(&fn, lib, "IdentityFourFloat32", func(s FourFloat32) FourFloat32 { | ||
| 720 | return s | 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 | if ret := fn(expected); ret != expected { | 836 | if ret := fn(expected); ret != expected { | ||
| 724 | t.Fatalf("IdentityFourFloat32 returned %+v wanted %+v", ret, expected) | 837 | t.Fatalf("IdentityFourFloat32 returned %+v wanted %+v", ret, expected) | ||
| 725 | } | 838 | } | ||
| @@ -727,6 +840,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 727 | { | 840 | { | ||
| 728 | // Mixed float32 + int32: INTEGER class eightbyte on amd64, non-HFA int regs on arm64. | 841 | // Mixed float32 + int32: INTEGER class eightbyte on amd64, non-HFA int regs on arm64. | ||
| 729 | type FloatAndInt struct { | 842 | type FloatAndInt struct { | ||
| 843 | + | _ structs.HostLayout | |||
| 730 | X float32 | 844 | X float32 | ||
| 731 | Y int32 | 845 | Y int32 | ||
| 732 | } | 846 | } | ||
| @@ -734,19 +848,22 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 734 | register(&fn, lib, "IdentityFloatAndInt", func(s FloatAndInt) FloatAndInt { | 848 | register(&fn, lib, "IdentityFloatAndInt", func(s FloatAndInt) FloatAndInt { | ||
| 735 | return s | 849 | return s | ||
| 736 | }) | 850 | }) | ||
| 737 | - | expected := FloatAndInt{7.5, 42} | 851 | + | expected := FloatAndInt{X: 7.5, Y: 42} |
| 738 | if ret := fn(expected); ret != expected { | 852 | if ret := fn(expected); ret != expected { | ||
| 739 | t.Fatalf("IdentityFloatAndInt returned %+v wanted %+v", ret, expected) | 853 | t.Fatalf("IdentityFloatAndInt returned %+v wanted %+v", ret, expected) | ||
| 740 | } | 854 | } | ||
| 741 | } | 855 | } | ||
| 742 | { | 856 | { | ||
| 743 | // Struct > 16 bytes: hidden pointer on amd64, pointer in int register on arm64. | 857 | // Struct > 16 bytes: hidden pointer on amd64, pointer in int register on arm64. | ||
| 744 | - | type ThreeInt64 struct{ A, B, C int64 } | 858 | + | type ThreeInt64 struct { |
| 859 | + | _ structs.HostLayout | |||
| 860 | + | A, B, C int64 | |||
| 861 | + | } | |||
| 745 | var fn func(ThreeInt64) ThreeInt64 | 862 | var fn func(ThreeInt64) ThreeInt64 | ||
| 746 | register(&fn, lib, "IdentityThreeInt64", func(s ThreeInt64) ThreeInt64 { | 863 | register(&fn, lib, "IdentityThreeInt64", func(s ThreeInt64) ThreeInt64 { | ||
| 747 | return s | 864 | return s | ||
| 748 | }) | 865 | }) | ||
| 749 | - | expected := ThreeInt64{1, 2, 3} | 866 | + | expected := ThreeInt64{A: 1, B: 2, C: 3} |
| 750 | if ret := fn(expected); ret != expected { | 867 | if ret := fn(expected); ret != expected { | ||
| 751 | t.Fatalf("IdentityThreeInt64 returned %+v wanted %+v", ret, expected) | 868 | t.Fatalf("IdentityThreeInt64 returned %+v wanted %+v", ret, expected) | ||
| 752 | } | 869 | } | ||
| @@ -754,6 +871,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 754 | { | 871 | { | ||
| 755 | // Struct > 16 bytes with pointer fields. | 872 | // Struct > 16 bytes with pointer fields. | ||
| 756 | type PtrInt64Ptr struct { | 873 | type PtrInt64Ptr struct { | ||
| 874 | + | _ structs.HostLayout | |||
| 757 | A *int64 | 875 | A *int64 | ||
| 758 | B int64 | 876 | B int64 | ||
| 759 | C *int64 | 877 | C *int64 | ||
| @@ -764,7 +882,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 764 | register(&fn, lib, "IdentityPtrInt64Ptr", func(s PtrInt64Ptr) PtrInt64Ptr { | 882 | register(&fn, lib, "IdentityPtrInt64Ptr", func(s PtrInt64Ptr) PtrInt64Ptr { | ||
| 765 | return s | 883 | return s | ||
| 766 | }) | 884 | }) | ||
| 767 | - | expected := PtrInt64Ptr{a, 200, c} | 885 | + | expected := PtrInt64Ptr{A: a, B: 200, C: c} |
| 768 | ret := fn(expected) | 886 | ret := fn(expected) | ||
| 769 | if *ret.A != *expected.A || ret.B != expected.B || *ret.C != *expected.C { | 887 | if *ret.A != *expected.A || ret.B != expected.B || *ret.C != *expected.C { | ||
| 770 | t.Fatalf("IdentityPtrInt64Ptr returned %+v wanted %+v", ret, expected) | 888 | t.Fatalf("IdentityPtrInt64Ptr returned %+v wanted %+v", ret, expected) | ||
| @@ -772,26 +890,34 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 772 | runtime.KeepAlive(a) | 890 | runtime.KeepAlive(a) | ||
| 773 | runtime.KeepAlive(c) | 891 | runtime.KeepAlive(c) | ||
| 774 | } | 892 | } | ||
| 775 | - | { | 893 | + | if runtime.GOARCH != "ppc64le" { |
| 776 | // Struct arg after some primitive args: register offset correctness. | 894 | // Struct arg after some primitive args: register offset correctness. | ||
| 777 | - | type IntLessThan16Bytes struct{ X, Y int64 } | 895 | + | // ppc64le is skipped: a float argument shadows a GPR, which purego |
| 896 | + | // does not model for scalars, so the trailing struct is misplaced. | |||
| 897 | + | type IntLessThan16Bytes struct { | |||
| 898 | + | _ structs.HostLayout | |||
| 899 | + | X, Y int64 | |||
| 900 | + | } | |||
| 778 | var fn func(int64, float64, IntLessThan16Bytes) IntLessThan16Bytes | 901 | var fn func(int64, float64, IntLessThan16Bytes) IntLessThan16Bytes | ||
| 779 | register(&fn, lib, "IdentityTwoInt64AfterPrims", func(x int64, y float64, s IntLessThan16Bytes) IntLessThan16Bytes { | 902 | register(&fn, lib, "IdentityTwoInt64AfterPrims", func(x int64, y float64, s IntLessThan16Bytes) IntLessThan16Bytes { | ||
| 780 | return s | 903 | return s | ||
| 781 | }) | 904 | }) | ||
| 782 | - | expected := IntLessThan16Bytes{100, 200} | 905 | + | expected := IntLessThan16Bytes{X: 100, Y: 200} |
| 783 | if ret := fn(1, 2.0, expected); ret != expected { | 906 | if ret := fn(1, 2.0, expected); ret != expected { | ||
| 784 | t.Fatalf("IdentityTwoInt64AfterPrims returned %+v wanted %+v", ret, expected) | 907 | t.Fatalf("IdentityTwoInt64AfterPrims returned %+v wanted %+v", ret, expected) | ||
| 785 | } | 908 | } | ||
| 786 | } | 909 | } | ||
| 787 | { | 910 | { | ||
| 788 | // Float struct arg after float primitive args: float register offset. | 911 | // Float struct arg after float primitive args: float register offset. | ||
| 789 | - | type FloatLessThan16Bytes struct{ X, Y float32 } | 912 | + | type FloatLessThan16Bytes struct { |
| 913 | + | _ structs.HostLayout | |||
| 914 | + | X, Y float32 | |||
| 915 | + | } | |||
| 790 | var fn func(float64, float64, FloatLessThan16Bytes) FloatLessThan16Bytes | 916 | var fn func(float64, float64, FloatLessThan16Bytes) FloatLessThan16Bytes | ||
| 791 | register(&fn, lib, "IdentityTwoFloat32AfterFloats", func(x, y float64, s FloatLessThan16Bytes) FloatLessThan16Bytes { | 917 | register(&fn, lib, "IdentityTwoFloat32AfterFloats", func(x, y float64, s FloatLessThan16Bytes) FloatLessThan16Bytes { | ||
| 792 | return s | 918 | return s | ||
| 793 | }) | 919 | }) | ||
| 794 | - | expected := FloatLessThan16Bytes{5.0, 6.0} | 920 | + | expected := FloatLessThan16Bytes{X: 5.0, Y: 6.0} |
| 795 | if ret := fn(1.0, 2.0, expected); ret != expected { | 921 | if ret := fn(1.0, 2.0, expected); ret != expected { | ||
| 796 | t.Fatalf("IdentityTwoFloat32AfterFloats returned %+v wanted %+v", ret, expected) | 922 | t.Fatalf("IdentityTwoFloat32AfterFloats returned %+v wanted %+v", ret, expected) | ||
| 797 | } | 923 | } | ||
| @@ -799,6 +925,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 799 | { | 925 | { | ||
| 800 | // Mixed struct with pointer: pointer + int32 + float32 + int32. | 926 | // Mixed struct with pointer: pointer + int32 + float32 + int32. | ||
| 801 | type Mixed5Args struct { | 927 | type Mixed5Args struct { | ||
| 928 | + | _ structs.HostLayout | |||
| 802 | A *int64 | 929 | A *int64 | ||
| 803 | B int32 | 930 | B int32 | ||
| 804 | C float32 | 931 | C float32 | ||
| @@ -810,7 +937,7 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 810 | register(&fn, lib, "IdentityMixed5Args", func(s Mixed5Args) Mixed5Args { | 937 | register(&fn, lib, "IdentityMixed5Args", func(s Mixed5Args) Mixed5Args { | ||
| 811 | return s | 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 | ret := fn(expected) | 941 | ret := fn(expected) | ||
| 815 | if ret.A != expected.A || ret.B != expected.B || ret.C != expected.C || ret.D != expected.D { | 942 | if ret.A != expected.A || ret.B != expected.B || ret.C != expected.C || ret.D != expected.D { | ||
| 816 | t.Fatalf("IdentityMixed5Args returned %+v wanted %+v", ret, expected) | 943 | t.Fatalf("IdentityMixed5Args returned %+v wanted %+v", ret, expected) | ||
| @@ -821,38 +948,23 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 821 | } | 948 | } | ||
| 822 | } | 949 | } | ||
| 823 | 950 | ||||
| 824 | - | // TODO: this could use the iter.Seq interface when purego supports Go 1.23 | 951 | + | func fields(v reflect.Value) iter.Seq[reflect.Value] { |
| 825 | - | func nextFieldFn(v reflect.Value) func() (reflect.Value, bool) { | 952 | + | return func(yield func(reflect.Value) bool) { |
| 826 | - | var fieldIndex int | 953 | + | for i := range v.NumField() { |
| 827 | - | var tracker func() (reflect.Value, bool) | 954 | + | if v.Type().Field(i).Name == "_" { |
| 828 | - | return func() (reflect.Value, bool) { | |||
| 829 | - | if v.NumField() == 0 { | |||
| 830 | - | return reflect.Value{}, false | |||
| 831 | - | } | |||
| 832 | - | if tracker != nil { | |||
| 833 | - | if field, ok := tracker(); ok { | |||
| 834 | - | return field, ok | |||
| 835 | - | } | |||
| 836 | - | tracker = nil | |||
| 837 | - | } | |||
| 838 | - | for fieldIndex < v.NumField() { | |||
| 839 | - | if v.Type().Field(fieldIndex).Name == "_" { | |||
| 840 | - | fieldIndex++ | |||
| 841 | continue | 955 | continue | ||
| 842 | } | 956 | } | ||
| 843 | - | field := v.Field(fieldIndex) | 957 | + | field := v.Field(i) |
| 844 | - | fieldIndex++ | |||
| 845 | if field.Kind() == reflect.Struct { | 958 | if field.Kind() == reflect.Struct { | ||
| 846 | - | tracker = nextFieldFn(field) | 959 | + | for inner := range fields(field) { |
| 847 | - | if inner, ok := tracker(); ok { | 960 | + | if !yield(inner) { |
| 848 | - | return inner, ok | 961 | + | return |
| 962 | + | } | |||
| 849 | } | 963 | } | ||
| 850 | - | tracker = nil | 964 | + | } else if !yield(field) { |
| 851 | - | } else { | 965 | + | return |
| 852 | - | return field, true | |||
| 853 | } | 966 | } | ||
| 854 | } | 967 | } | ||
| 855 | - | return reflect.Value{}, false | |||
| 856 | } | 968 | } | ||
| 857 | } | 969 | } | ||
| 858 | 970 | ||||
| @@ -860,36 +972,42 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 860 | libFileName := filepath.Join(t.TempDir(), "structreturntest.so") | 972 | libFileName := filepath.Join(t.TempDir(), "structreturntest.so") | ||
| 861 | t.Logf("Build %v", libFileName) | 973 | t.Logf("Build %v", libFileName) | ||
| 862 | 974 | ||||
| 863 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "structtest", "structreturn_test.c")); err != nil { | 975 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "structtest", "structreturn_test.c")); err != nil { |
| 864 | t.Fatal(err) | 976 | t.Fatal(err) | ||
| 865 | } | 977 | } | ||
| 866 | defer os.Remove(libFileName) | 978 | defer os.Remove(libFileName) | ||
| 867 | 979 | ||||
| 868 | - | lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL) | 980 | + | lib, err := load.OpenLibrary(libFileName) |
| 869 | if err != nil { | 981 | if err != nil { | ||
| 870 | - | t.Fatalf("Dlopen(%q) failed: %v", libFileName, err) | 982 | + | t.Fatalf("OpenLibrary(%q) failed: %v", libFileName, err) |
| 871 | } | 983 | } | ||
| 984 | + | defer func() { | |||
| 985 | + | if err := load.CloseLibrary(lib); err != nil { | |||
| 986 | + | t.Fatalf("failed to close library: %v", err) | |||
| 987 | + | } | |||
| 988 | + | }() | |||
| 872 | implementations := []struct { | 989 | implementations := []struct { | ||
| 873 | - | name string | 990 | + | name string |
| 874 | - | register func(fptr any, handle uintptr, name string) | 991 | + | usesCallbacks bool |
| 992 | + | register func(fptr any, handle uintptr, name string) | |||
| 875 | }{ | 993 | }{ | ||
| 876 | { | 994 | { | ||
| 877 | name: "RegisterLibFunc", | 995 | name: "RegisterLibFunc", | ||
| 878 | register: purego.RegisterLibFunc, | 996 | register: purego.RegisterLibFunc, | ||
| 879 | }, | 997 | }, | ||
| 880 | { | 998 | { | ||
| 881 | - | name: "GoCallbackFunc", | 999 | + | name: "GoCallbackFunc", |
| 1000 | + | usesCallbacks: true, | |||
| 882 | register: func(fptr any, _ uintptr, _ string) { | 1001 | register: func(fptr any, _ uintptr, _ string) { | ||
| 883 | fn := reflect.MakeFunc(reflect.TypeOf(fptr).Elem(), func(args []reflect.Value) []reflect.Value { | 1002 | fn := reflect.MakeFunc(reflect.TypeOf(fptr).Elem(), func(args []reflect.Value) []reflect.Value { | ||
| 884 | retType := reflect.TypeOf(fptr).Elem().Out(0) | 1003 | retType := reflect.TypeOf(fptr).Elem().Out(0) | ||
| 885 | ret := reflect.New(retType).Elem() | 1004 | ret := reflect.New(retType).Elem() | ||
| 886 | - | next := nextFieldFn(ret) | 1005 | + | leaves := slices.Collect(fields(ret)) |
| 887 | - | for _, a := range args { | 1006 | + | for i, a := range args { |
| 888 | - | field, ok := next() | 1007 | + | if i >= len(leaves) { |
| 889 | - | if !ok { | |||
| 890 | panic("purego: no more fields") | 1008 | panic("purego: no more fields") | ||
| 891 | } | 1009 | } | ||
| 892 | - | field.Set(a) | 1010 | + | leaves[i].Set(a) |
| 893 | } | 1011 | } | ||
| 894 | return []reflect.Value{ret} | 1012 | return []reflect.Value{ret} | ||
| 895 | }) | 1013 | }) | ||
| @@ -898,170 +1016,239 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 898 | }, | 1016 | }, | ||
| 899 | } | 1017 | } | ||
| 900 | for _, imp := range implementations { | 1018 | for _, imp := range implementations { | ||
| 901 | - | imp := 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 | t.Run(imp.name, func(t *testing.T) { | 1027 | t.Run(imp.name, func(t *testing.T) { | ||
| 903 | register := imp.register | 1028 | register := imp.register | ||
| 904 | { | 1029 | { | ||
| 905 | - | type Empty struct{} | 1030 | + | type Empty struct { |
| 1031 | + | _ structs.HostLayout | |||
| 1032 | + | } | |||
| 906 | var ReturnEmpty func() Empty | 1033 | var ReturnEmpty func() Empty | ||
| 907 | register(&ReturnEmpty, lib, "ReturnEmpty") | 1034 | register(&ReturnEmpty, lib, "ReturnEmpty") | ||
| 908 | ret := ReturnEmpty() | 1035 | ret := ReturnEmpty() | ||
| 909 | _ = ret | 1036 | _ = ret | ||
| 910 | } | 1037 | } | ||
| 911 | { | 1038 | { | ||
| 912 | - | type inner struct{ A int16 } | 1039 | + | type inner struct { |
| 1040 | + | _ structs.HostLayout | |||
| 1041 | + | A int16 | |||
| 1042 | + | } | |||
| 913 | type StructInStruct struct { | 1043 | type StructInStruct struct { | ||
| 1044 | + | _ structs.HostLayout | |||
| 914 | A inner | 1045 | A inner | ||
| 915 | B inner | 1046 | B inner | ||
| 916 | C inner | 1047 | C inner | ||
| 917 | } | 1048 | } | ||
| 918 | var ReturnStructInStruct func(a, b, c int16) StructInStruct | 1049 | var ReturnStructInStruct func(a, b, c int16) StructInStruct | ||
| 919 | register(&ReturnStructInStruct, lib, "ReturnStructInStruct") | 1050 | register(&ReturnStructInStruct, lib, "ReturnStructInStruct") | ||
| 920 | - | expected := StructInStruct{inner{^int16(0)}, inner{2}, inner{3}} | 1051 | + | expected := StructInStruct{A: inner{A: ^int16(0)}, B: inner{A: 2}, C: inner{A: 3}} |
| 921 | if ret := ReturnStructInStruct(^int16(0), 2, 3); ret != expected { | 1052 | if ret := ReturnStructInStruct(^int16(0), 2, 3); ret != expected { | ||
| 922 | t.Fatalf("StructInStruct returned %+v wanted %+v", ret, expected) | 1053 | t.Fatalf("StructInStruct returned %+v wanted %+v", ret, expected) | ||
| 923 | } | 1054 | } | ||
| 924 | } | 1055 | } | ||
| 925 | { | 1056 | { | ||
| 926 | - | type ThreeShorts struct{ A, B, C int16 } | 1057 | + | type ThreeShorts struct { |
| 1058 | + | _ structs.HostLayout | |||
| 1059 | + | A, B, C int16 | |||
| 1060 | + | } | |||
| 927 | var ReturnThreeShorts func(a, b, c int16) ThreeShorts | 1061 | var ReturnThreeShorts func(a, b, c int16) ThreeShorts | ||
| 928 | register(&ReturnThreeShorts, lib, "ReturnThreeShorts") | 1062 | register(&ReturnThreeShorts, lib, "ReturnThreeShorts") | ||
| 929 | - | expected := ThreeShorts{^int16(0), 2, 3} | 1063 | + | expected := ThreeShorts{A: ^int16(0), B: 2, C: 3} |
| 930 | if ret := ReturnThreeShorts(^int16(0), 2, 3); ret != expected { | 1064 | if ret := ReturnThreeShorts(^int16(0), 2, 3); ret != expected { | ||
| 931 | t.Fatalf("ReturnThreeShorts returned %+v wanted %+v", ret, expected) | 1065 | t.Fatalf("ReturnThreeShorts returned %+v wanted %+v", ret, expected) | ||
| 932 | } | 1066 | } | ||
| 933 | } | 1067 | } | ||
| 934 | { | 1068 | { | ||
| 935 | - | type FourShorts struct{ A, B, C, D int16 } | 1069 | + | type FourShorts struct { |
| 1070 | + | _ structs.HostLayout | |||
| 1071 | + | A, B, C, D int16 | |||
| 1072 | + | } | |||
| 936 | var ReturnFourShorts func(a, b, c, d int16) FourShorts | 1073 | var ReturnFourShorts func(a, b, c, d int16) FourShorts | ||
| 937 | register(&ReturnFourShorts, lib, "ReturnFourShorts") | 1074 | register(&ReturnFourShorts, lib, "ReturnFourShorts") | ||
| 938 | - | expected := FourShorts{^int16(0), 2, 3, 4} | 1075 | + | expected := FourShorts{A: ^int16(0), B: 2, C: 3, D: 4} |
| 939 | if ret := ReturnFourShorts(^int16(0), 2, 3, 4); ret != expected { | 1076 | if ret := ReturnFourShorts(^int16(0), 2, 3, 4); ret != expected { | ||
| 940 | t.Fatalf("ReturnFourShorts returned %+v wanted %+v", ret, expected) | 1077 | t.Fatalf("ReturnFourShorts returned %+v wanted %+v", ret, expected) | ||
| 941 | } | 1078 | } | ||
| 942 | } | 1079 | } | ||
| 943 | { | 1080 | { | ||
| 944 | - | type OneLong struct{ A int64 } | 1081 | + | type OneLong struct { |
| 1082 | + | _ structs.HostLayout | |||
| 1083 | + | A int64 | |||
| 1084 | + | } | |||
| 945 | var ReturnOneLong func(a int64) OneLong | 1085 | var ReturnOneLong func(a int64) OneLong | ||
| 946 | register(&ReturnOneLong, lib, "ReturnOneLong") | 1086 | register(&ReturnOneLong, lib, "ReturnOneLong") | ||
| 947 | - | expected := OneLong{5} | 1087 | + | expected := OneLong{A: 5} |
| 948 | if ret := ReturnOneLong(5); ret != expected { | 1088 | if ret := ReturnOneLong(5); ret != expected { | ||
| 949 | t.Fatalf("ReturnOneLong returned %+v wanted %+v", ret, expected) | 1089 | t.Fatalf("ReturnOneLong returned %+v wanted %+v", ret, expected) | ||
| 950 | } | 1090 | } | ||
| 951 | } | 1091 | } | ||
| 952 | { | 1092 | { | ||
| 953 | - | type TwoLongs struct{ A, B int64 } | 1093 | + | type TwoLongs struct { |
| 1094 | + | _ structs.HostLayout | |||
| 1095 | + | A, B int64 | |||
| 1096 | + | } | |||
| 954 | var ReturnTwoLongs func(a, b int64) TwoLongs | 1097 | var ReturnTwoLongs func(a, b int64) TwoLongs | ||
| 955 | register(&ReturnTwoLongs, lib, "ReturnTwoLongs") | 1098 | register(&ReturnTwoLongs, lib, "ReturnTwoLongs") | ||
| 956 | - | expected := TwoLongs{1, 2} | 1099 | + | expected := TwoLongs{A: 1, B: 2} |
| 957 | if ret := ReturnTwoLongs(1, 2); ret != expected { | 1100 | if ret := ReturnTwoLongs(1, 2); ret != expected { | ||
| 958 | t.Fatalf("ReturnTwoLongs returned %+v wanted %+v", ret, expected) | 1101 | t.Fatalf("ReturnTwoLongs returned %+v wanted %+v", ret, expected) | ||
| 959 | } | 1102 | } | ||
| 960 | } | 1103 | } | ||
| 961 | { | 1104 | { | ||
| 962 | - | type ThreeLongs struct{ A, B, C int64 } | 1105 | + | type ThreeLongs struct { |
| 1106 | + | _ structs.HostLayout | |||
| 1107 | + | A, B, C int64 | |||
| 1108 | + | } | |||
| 963 | var ReturnThreeLongs func(a, b, c int64) ThreeLongs | 1109 | var ReturnThreeLongs func(a, b, c int64) ThreeLongs | ||
| 964 | register(&ReturnThreeLongs, lib, "ReturnThreeLongs") | 1110 | register(&ReturnThreeLongs, lib, "ReturnThreeLongs") | ||
| 965 | - | expected := ThreeLongs{1, 2, 3} | 1111 | + | expected := ThreeLongs{A: 1, B: 2, C: 3} |
| 966 | if ret := ReturnThreeLongs(1, 2, 3); ret != expected { | 1112 | if ret := ReturnThreeLongs(1, 2, 3); ret != expected { | ||
| 967 | t.Fatalf("ReturnThreeLongs returned %+v wanted %+v", ret, expected) | 1113 | t.Fatalf("ReturnThreeLongs returned %+v wanted %+v", ret, expected) | ||
| 968 | } | 1114 | } | ||
| 969 | } | 1115 | } | ||
| 970 | { | 1116 | { | ||
| 971 | - | type OneFloat struct{ A float32 } | 1117 | + | type OneFloat struct { |
| 1118 | + | _ structs.HostLayout | |||
| 1119 | + | A float32 | |||
| 1120 | + | } | |||
| 972 | var ReturnOneFloat func(a float32) OneFloat | 1121 | var ReturnOneFloat func(a float32) OneFloat | ||
| 973 | register(&ReturnOneFloat, lib, "ReturnOneFloat") | 1122 | register(&ReturnOneFloat, lib, "ReturnOneFloat") | ||
| 974 | - | expected := OneFloat{1} | 1123 | + | expected := OneFloat{A: 1} |
| 975 | if ret := ReturnOneFloat(1); ret != expected { | 1124 | if ret := ReturnOneFloat(1); ret != expected { | ||
| 976 | t.Fatalf("ReturnOneFloat returned %+v wanted %+v", ret, expected) | 1125 | t.Fatalf("ReturnOneFloat returned %+v wanted %+v", ret, expected) | ||
| 977 | } | 1126 | } | ||
| 978 | } | 1127 | } | ||
| 979 | { | 1128 | { | ||
| 980 | - | type TwoFloats struct{ A, B float32 } | 1129 | + | type TwoFloats struct { |
| 1130 | + | _ structs.HostLayout | |||
| 1131 | + | A, B float32 | |||
| 1132 | + | } | |||
| 981 | var ReturnTwoFloats func(a, b float32) TwoFloats | 1133 | var ReturnTwoFloats func(a, b float32) TwoFloats | ||
| 982 | register(&ReturnTwoFloats, lib, "ReturnTwoFloats") | 1134 | register(&ReturnTwoFloats, lib, "ReturnTwoFloats") | ||
| 983 | - | expected := TwoFloats{5, 2} | 1135 | + | expected := TwoFloats{A: 5, B: 2} |
| 984 | if ret := ReturnTwoFloats(5, 2); ret != expected { | 1136 | if ret := ReturnTwoFloats(5, 2); ret != expected { | ||
| 985 | t.Fatalf("ReturnTwoFloats returned %+v wanted %+v", ret, expected) | 1137 | t.Fatalf("ReturnTwoFloats returned %+v wanted %+v", ret, expected) | ||
| 986 | } | 1138 | } | ||
| 987 | } | 1139 | } | ||
| 988 | { | 1140 | { | ||
| 989 | - | type ThreeFloats struct{ A, B, C float32 } | 1141 | + | type ThreeFloats struct { |
| 1142 | + | _ structs.HostLayout | |||
| 1143 | + | A, B, C float32 | |||
| 1144 | + | } | |||
| 990 | var ReturnThreeFloats func(a, b, c float32) ThreeFloats | 1145 | var ReturnThreeFloats func(a, b, c float32) ThreeFloats | ||
| 991 | register(&ReturnThreeFloats, lib, "ReturnThreeFloats") | 1146 | register(&ReturnThreeFloats, lib, "ReturnThreeFloats") | ||
| 992 | - | expected := ThreeFloats{1, 2, 3} | 1147 | + | expected := ThreeFloats{A: 1, B: 2, C: 3} |
| 993 | if ret := ReturnThreeFloats(1, 2, 3); ret != expected { | 1148 | if ret := ReturnThreeFloats(1, 2, 3); ret != expected { | ||
| 994 | t.Fatalf("ReturnThreeFloats returned %+v wanted %+v", ret, expected) | 1149 | t.Fatalf("ReturnThreeFloats returned %+v wanted %+v", ret, expected) | ||
| 995 | } | 1150 | } | ||
| 996 | } | 1151 | } | ||
| 997 | { | 1152 | { | ||
| 998 | - | type FourFloats struct{ A, B, C, D float32 } | 1153 | + | type FourFloats struct { |
| 1154 | + | _ structs.HostLayout | |||
| 1155 | + | A, B, C, D float32 | |||
| 1156 | + | } | |||
| 999 | var ReturnFourFloats func(a, b, c, d float32) FourFloats | 1157 | var ReturnFourFloats func(a, b, c, d float32) FourFloats | ||
| 1000 | register(&ReturnFourFloats, lib, "ReturnFourFloats") | 1158 | register(&ReturnFourFloats, lib, "ReturnFourFloats") | ||
| 1001 | - | expected := FourFloats{1, 2, 3, 4} | 1159 | + | expected := FourFloats{A: 1, B: 2, C: 3, D: 4} |
| 1002 | if ret := ReturnFourFloats(1, 2, 3, 4); ret != expected { | 1160 | if ret := ReturnFourFloats(1, 2, 3, 4); ret != expected { | ||
| 1003 | t.Fatalf("ReturnFourFloats returned %+v wanted %+v", ret, expected) | 1161 | t.Fatalf("ReturnFourFloats returned %+v wanted %+v", ret, expected) | ||
| 1004 | } | 1162 | } | ||
| 1005 | } | 1163 | } | ||
| 1006 | { | 1164 | { | ||
| 1007 | - | type OneDouble struct{ A float64 } | 1165 | + | type OneDouble struct { |
| 1166 | + | _ structs.HostLayout | |||
| 1167 | + | A float64 | |||
| 1168 | + | } | |||
| 1008 | var ReturnOneDouble func(a float64) OneDouble | 1169 | var ReturnOneDouble func(a float64) OneDouble | ||
| 1009 | register(&ReturnOneDouble, lib, "ReturnOneDouble") | 1170 | register(&ReturnOneDouble, lib, "ReturnOneDouble") | ||
| 1010 | - | expected := OneDouble{1} | 1171 | + | expected := OneDouble{A: 1} |
| 1011 | if ret := ReturnOneDouble(1); ret != expected { | 1172 | if ret := ReturnOneDouble(1); ret != expected { | ||
| 1012 | t.Fatalf("ReturnOneDouble returned %+v wanted %+v", ret, expected) | 1173 | t.Fatalf("ReturnOneDouble returned %+v wanted %+v", ret, expected) | ||
| 1013 | } | 1174 | } | ||
| 1014 | } | 1175 | } | ||
| 1015 | { | 1176 | { | ||
| 1016 | - | type TwoDoubles struct{ A, B float64 } | 1177 | + | type TwoDoubles struct { |
| 1178 | + | _ structs.HostLayout | |||
| 1179 | + | A, B float64 | |||
| 1180 | + | } | |||
| 1017 | var ReturnTwoDoubles func(a, b float64) TwoDoubles | 1181 | var ReturnTwoDoubles func(a, b float64) TwoDoubles | ||
| 1018 | register(&ReturnTwoDoubles, lib, "ReturnTwoDoubles") | 1182 | register(&ReturnTwoDoubles, lib, "ReturnTwoDoubles") | ||
| 1019 | - | expected := TwoDoubles{7, 3} | 1183 | + | expected := TwoDoubles{A: 7, B: 3} |
| 1020 | if ret := ReturnTwoDoubles(7, 3); ret != expected { | 1184 | if ret := ReturnTwoDoubles(7, 3); ret != expected { | ||
| 1021 | t.Fatalf("ReturnTwoDoubles returned %+v wanted %+v", ret, expected) | 1185 | t.Fatalf("ReturnTwoDoubles returned %+v wanted %+v", ret, expected) | ||
| 1022 | } | 1186 | } | ||
| 1023 | } | 1187 | } | ||
| 1024 | { | 1188 | { | ||
| 1025 | - | type ThreeDoubles struct{ A, B, C float64 } | 1189 | + | type ThreeDoubles struct { |
| 1190 | + | _ structs.HostLayout | |||
| 1191 | + | A, B, C float64 | |||
| 1192 | + | } | |||
| 1026 | var ReturnThreeDoubles func(a, b, c float64) ThreeDoubles | 1193 | var ReturnThreeDoubles func(a, b, c float64) ThreeDoubles | ||
| 1027 | register(&ReturnThreeDoubles, lib, "ReturnThreeDoubles") | 1194 | register(&ReturnThreeDoubles, lib, "ReturnThreeDoubles") | ||
| 1028 | - | expected := ThreeDoubles{1, 2, 3} | 1195 | + | expected := ThreeDoubles{A: 1, B: 2, C: 3} |
| 1029 | if ret := ReturnThreeDoubles(1, 2, 3); ret != expected { | 1196 | if ret := ReturnThreeDoubles(1, 2, 3); ret != expected { | ||
| 1030 | t.Fatalf("ReturnThreeDoubles returned %+v wanted %+v", ret, expected) | 1197 | t.Fatalf("ReturnThreeDoubles returned %+v wanted %+v", ret, expected) | ||
| 1031 | } | 1198 | } | ||
| 1032 | } | 1199 | } | ||
| 1033 | { | 1200 | { | ||
| 1034 | - | type FourDoubles struct{ A, B, C, D float64 } | 1201 | + | type FourDoubles struct { |
| 1202 | + | _ structs.HostLayout | |||
| 1203 | + | A, B, C, D float64 | |||
| 1204 | + | } | |||
| 1035 | var ReturnFourDoubles func(a, b, c, d float64) FourDoubles | 1205 | var ReturnFourDoubles func(a, b, c, d float64) FourDoubles | ||
| 1036 | register(&ReturnFourDoubles, lib, "ReturnFourDoubles") | 1206 | register(&ReturnFourDoubles, lib, "ReturnFourDoubles") | ||
| 1037 | - | expected := FourDoubles{1, 2, 3, 4} | 1207 | + | expected := FourDoubles{A: 1, B: 2, C: 3, D: 4} |
| 1038 | if ret := ReturnFourDoubles(1, 2, 3, 4); ret != expected { | 1208 | if ret := ReturnFourDoubles(1, 2, 3, 4); ret != expected { | ||
| 1039 | t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected) | 1209 | t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected) | ||
| 1040 | } | 1210 | } | ||
| 1041 | } | 1211 | } | ||
| 1042 | { | 1212 | { | ||
| 1043 | type FourDoublesInternal struct { | 1213 | type FourDoublesInternal struct { | ||
| 1044 | - | F struct{ A, B float64 } | 1214 | + | _ structs.HostLayout |
| 1045 | - | G struct{ C, D float64 } | 1215 | + | F struct { |
| 1216 | + | _ structs.HostLayout | |||
| 1217 | + | A, B float64 | |||
| 1218 | + | } | |||
| 1219 | + | G struct { | |||
| 1220 | + | _ structs.HostLayout | |||
| 1221 | + | C, D float64 | |||
| 1222 | + | } | |||
| 1046 | } | 1223 | } | ||
| 1047 | var ReturnFourDoublesInternal func(a, b, c, d float64) FourDoublesInternal | 1224 | var ReturnFourDoublesInternal func(a, b, c, d float64) FourDoublesInternal | ||
| 1048 | register(&ReturnFourDoublesInternal, lib, "ReturnFourDoublesInternal") | 1225 | register(&ReturnFourDoublesInternal, lib, "ReturnFourDoublesInternal") | ||
| 1049 | - | expected := FourDoublesInternal{F: struct{ A, B float64 }{A: 1, B: 2}, G: struct{ C, D float64 }{C: 3, D: 4}} | 1226 | + | expected := FourDoublesInternal{F: struct { |
| 1227 | + | _ structs.HostLayout | |||
| 1228 | + | A, B float64 | |||
| 1229 | + | }{A: 1, B: 2}, G: struct { | |||
| 1230 | + | _ structs.HostLayout | |||
| 1231 | + | C, D float64 | |||
| 1232 | + | }{C: 3, D: 4}} | |||
| 1050 | if ret := ReturnFourDoublesInternal(1, 2, 3, 4); ret != expected { | 1233 | if ret := ReturnFourDoublesInternal(1, 2, 3, 4); ret != expected { | ||
| 1051 | t.Fatalf("ReturnFourDoublesInternal returned %+v wanted %+v", ret, expected) | 1234 | t.Fatalf("ReturnFourDoublesInternal returned %+v wanted %+v", ret, expected) | ||
| 1052 | } | 1235 | } | ||
| 1053 | } | 1236 | } | ||
| 1054 | { | 1237 | { | ||
| 1055 | - | type FiveDoubles struct{ A, B, C, D, E float64 } | 1238 | + | type FiveDoubles struct { |
| 1239 | + | _ structs.HostLayout | |||
| 1240 | + | A, B, C, D, E float64 | |||
| 1241 | + | } | |||
| 1056 | var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles | 1242 | var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles | ||
| 1057 | register(&ReturnFiveDoubles, lib, "ReturnFiveDoubles") | 1243 | register(&ReturnFiveDoubles, lib, "ReturnFiveDoubles") | ||
| 1058 | - | expected := FiveDoubles{1, 2, 3, 4, 5} | 1244 | + | expected := FiveDoubles{A: 1, B: 2, C: 3, D: 4, E: 5} |
| 1059 | if ret := ReturnFiveDoubles(1, 2, 3, 4, 5); ret != expected { | 1245 | if ret := ReturnFiveDoubles(1, 2, 3, 4, 5); ret != expected { | ||
| 1060 | t.Fatalf("ReturnFiveDoubles returned %+v wanted %+v", ret, expected) | 1246 | t.Fatalf("ReturnFiveDoubles returned %+v wanted %+v", ret, expected) | ||
| 1061 | } | 1247 | } | ||
| 1062 | } | 1248 | } | ||
| 1063 | { | 1249 | { | ||
| 1064 | type OneFloatOneDouble struct { | 1250 | type OneFloatOneDouble struct { | ||
| 1251 | + | _ structs.HostLayout | |||
| 1065 | A float32 | 1252 | A float32 | ||
| 1066 | _ float32 | 1253 | _ float32 | ||
| 1067 | B float64 | 1254 | B float64 | ||
| @@ -1075,18 +1262,20 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 1075 | } | 1262 | } | ||
| 1076 | { | 1263 | { | ||
| 1077 | type OneDoubleOneFloat struct { | 1264 | type OneDoubleOneFloat struct { | ||
| 1265 | + | _ structs.HostLayout | |||
| 1078 | A float64 | 1266 | A float64 | ||
| 1079 | B float32 | 1267 | B float32 | ||
| 1080 | } | 1268 | } | ||
| 1081 | var ReturnOneDoubleOneFloat func(a float64, b float32) OneDoubleOneFloat | 1269 | var ReturnOneDoubleOneFloat func(a float64, b float32) OneDoubleOneFloat | ||
| 1082 | register(&ReturnOneDoubleOneFloat, lib, "ReturnOneDoubleOneFloat") | 1270 | register(&ReturnOneDoubleOneFloat, lib, "ReturnOneDoubleOneFloat") | ||
| 1083 | - | expected := OneDoubleOneFloat{1, 2} | 1271 | + | expected := OneDoubleOneFloat{A: 1, B: 2} |
| 1084 | if ret := ReturnOneDoubleOneFloat(1, 2); ret != expected { | 1272 | if ret := ReturnOneDoubleOneFloat(1, 2); ret != expected { | ||
| 1085 | t.Fatalf("ReturnOneDoubleOneFloat returned %+v wanted %+v", ret, expected) | 1273 | t.Fatalf("ReturnOneDoubleOneFloat returned %+v wanted %+v", ret, expected) | ||
| 1086 | } | 1274 | } | ||
| 1087 | } | 1275 | } | ||
| 1088 | { | 1276 | { | ||
| 1089 | type Unaligned1 struct { | 1277 | type Unaligned1 struct { | ||
| 1278 | + | _ structs.HostLayout | |||
| 1090 | A int8 | 1279 | A int8 | ||
| 1091 | _ [1]int8 | 1280 | _ [1]int8 | ||
| 1092 | B int16 | 1281 | B int16 | ||
| @@ -1101,76 +1290,88 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 1101 | } | 1290 | } | ||
| 1102 | } | 1291 | } | ||
| 1103 | 1292 | ||||
| 1104 | - | { | 1293 | + | if runtime.GOARCH != "ppc64le" { |
| 1105 | - | type Mixed1 struct { | 1294 | + | // ppc64le is skipped: a float argument shadows a GPR, which purego |
| 1106 | - | A float32 | 1295 | + | // does not model for scalars, so a trailing integer arg is misplaced. |
| 1107 | - | B int32 | 1296 | + | // TODO: Support mixed scalar floating-point and integer arguments on |
| 1108 | - | } | 1297 | + | // ppc64le by modeling the ELFv2 general-purpose register shadowing. |
| 1109 | - | var ReturnMixed1 func(a float32, b int32) Mixed1 | 1298 | + | { |
| 1110 | - | register(&ReturnMixed1, lib, "ReturnMixed1") | 1299 | + | type Mixed1 struct { |
| 1111 | - | expected := Mixed1{1, 2} | 1300 | + | _ structs.HostLayout |
| 1112 | - | if ret := ReturnMixed1(1, 2); ret != expected { | 1301 | + | A float32 |
| 1113 | - | t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected) | 1302 | + | B int32 |
| 1114 | - | } | 1303 | + | } |
| 1115 | - | } | 1304 | + | var ReturnMixed1 func(a float32, b int32) Mixed1 |
| 1116 | - | { | 1305 | + | register(&ReturnMixed1, lib, "ReturnMixed1") |
| 1117 | - | type Mixed2 struct { | 1306 | + | expected := Mixed1{A: 1, B: 2} |
| 1118 | - | A float32 | 1307 | + | if ret := ReturnMixed1(1, 2); ret != expected { |
| 1119 | - | B int32 | 1308 | + | t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected) |
| 1120 | - | C float32 | 1309 | + | } |
| 1121 | - | D int32 | |||
| 1122 | - | } | |||
| 1123 | - | var ReturnMixed2 func(a float32, b int32, c float32, d int32) Mixed2 | |||
| 1124 | - | register(&ReturnMixed2, lib, "ReturnMixed2") | |||
| 1125 | - | expected := Mixed2{1, 2, 3, 4} | |||
| 1126 | - | if ret := ReturnMixed2(1, 2, 3, 4); ret != expected { | |||
| 1127 | - | t.Fatalf("ReturnMixed2 returned %+v wanted %+v", ret, expected) | |||
| 1128 | - | } | |||
| 1129 | - | } | |||
| 1130 | - | { | |||
| 1131 | - | type Mixed3 struct { | |||
| 1132 | - | A float32 | |||
| 1133 | - | B uint32 | |||
| 1134 | - | C float64 | |||
| 1135 | - | } | |||
| 1136 | - | var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3 | |||
| 1137 | - | register(&ReturnMixed3, lib, "ReturnMixed3") | |||
| 1138 | - | expected := Mixed3{1, 2, 3} | |||
| 1139 | - | if ret := ReturnMixed3(1, 2, 3); ret != expected { | |||
| 1140 | - | t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected) | |||
| 1141 | } | 1310 | } | ||
| 1142 | - | } | 1311 | + | { |
| 1143 | - | { | 1312 | + | type Mixed2 struct { |
| 1144 | - | type Mixed4 struct { | 1313 | + | _ structs.HostLayout |
| 1145 | - | A float64 | 1314 | + | A float32 |
| 1146 | - | B uint32 | 1315 | + | B int32 |
| 1147 | - | C float32 | 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 | 1326 | + | { |
| 1150 | - | register(&ReturnMixed4, lib, "ReturnMixed4") | 1327 | + | type Mixed3 struct { |
| 1151 | - | expected := Mixed4{1, 2, 3} | 1328 | + | _ structs.HostLayout |
| 1152 | - | if ret := ReturnMixed4(1, 2, 3); ret != expected { | 1329 | + | A float32 |
| 1153 | - | t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected) | 1330 | + | B uint32 |
| 1331 | + | C float64 | |||
| 1332 | + | } | |||
| 1333 | + | var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3 | |||
| 1334 | + | register(&ReturnMixed3, lib, "ReturnMixed3") | |||
| 1335 | + | expected := Mixed3{A: 1, B: 2, C: 3} | |||
| 1336 | + | if ret := ReturnMixed3(1, 2, 3); ret != expected { | |||
| 1337 | + | t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected) | |||
| 1338 | + | } | |||
| 1154 | } | 1339 | } | ||
| 1155 | - | } | 1340 | + | { |
| 1156 | - | { | 1341 | + | type Mixed4 struct { |
| 1157 | - | type Mixed5 struct { | 1342 | + | _ structs.HostLayout |
| 1158 | - | A *int64 | 1343 | + | A float64 |
| 1159 | - | B int32 | 1344 | + | B uint32 |
| 1160 | - | C float32 | 1345 | + | C float32 |
| 1161 | - | D int32 | 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 | 1354 | + | { |
| 1164 | - | register(&ReturnMixed5, lib, "ReturnMixed5") | 1355 | + | type Mixed5 struct { |
| 1165 | - | ptr := new(int64) | 1356 | + | _ structs.HostLayout |
| 1166 | - | expected := Mixed5{ptr, 1, 7.2, 9} | 1357 | + | A *int64 |
| 1167 | - | if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected { | 1358 | + | B int32 |
| 1168 | - | t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected) | 1359 | + | C float32 |
| 1360 | + | D int32 | |||
| 1361 | + | } | |||
| 1362 | + | var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5 | |||
| 1363 | + | register(&ReturnMixed5, lib, "ReturnMixed5") | |||
| 1364 | + | ptr := new(int64) | |||
| 1365 | + | expected := Mixed5{A: ptr, B: 1, C: 7.2, D: 9} | |||
| 1366 | + | if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected { | |||
| 1367 | + | t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected) | |||
| 1368 | + | } | |||
| 1369 | + | runtime.KeepAlive(ptr) | |||
| 1169 | } | 1370 | } | ||
| 1170 | - | runtime.KeepAlive(ptr) | |||
| 1171 | } | 1371 | } | ||
| 1172 | { | 1372 | { | ||
| 1173 | type Mixed5 struct { | 1373 | type Mixed5 struct { | ||
| 1374 | + | _ structs.HostLayout | |||
| 1174 | A *int64 | 1375 | A *int64 | ||
| 1175 | B int32 | 1376 | B int32 | ||
| 1176 | C float32 | 1377 | C float32 | ||
| @@ -1182,7 +1383,7 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 1182 | // Struct callback args are tested in TestRegisterFunc_structArgs. | 1383 | // Struct callback args are tested in TestRegisterFunc_structArgs. | ||
| 1183 | purego.RegisterLibFunc(&IdentityMixed5, lib, "IdentityMixed5") | 1384 | purego.RegisterLibFunc(&IdentityMixed5, lib, "IdentityMixed5") | ||
| 1184 | ptr := new(int64) | 1385 | ptr := new(int64) | ||
| 1185 | - | expected := Mixed5{ptr, 1, 7.2, 9} | 1386 | + | expected := Mixed5{A: ptr, B: 1, C: 7.2, D: 9} |
| 1186 | if ret := IdentityMixed5(expected); ret != expected { | 1387 | if ret := IdentityMixed5(expected); ret != expected { | ||
| 1187 | t.Fatalf("IdentityMixed5 returned %+v wanted %+v", ret, expected) | 1388 | t.Fatalf("IdentityMixed5 returned %+v wanted %+v", ret, expected) | ||
| 1188 | } | 1389 | } | ||
| @@ -1190,19 +1391,21 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 1190 | } | 1391 | } | ||
| 1191 | { | 1392 | { | ||
| 1192 | type SmallBool struct { | 1393 | type SmallBool struct { | ||
| 1394 | + | _ structs.HostLayout | |||
| 1193 | A bool | 1395 | A bool | ||
| 1194 | B int32 | 1396 | B int32 | ||
| 1195 | C int64 | 1397 | C int64 | ||
| 1196 | } | 1398 | } | ||
| 1197 | var ReturnSmallBool func(a bool, b int32, c int64) SmallBool | 1399 | var ReturnSmallBool func(a bool, b int32, c int64) SmallBool | ||
| 1198 | register(&ReturnSmallBool, lib, "ReturnSmallBool") | 1400 | register(&ReturnSmallBool, lib, "ReturnSmallBool") | ||
| 1199 | - | expected := SmallBool{true, 42, 123456789} | 1401 | + | expected := SmallBool{A: true, B: 42, C: 123456789} |
| 1200 | if ret := ReturnSmallBool(true, 42, 123456789); ret != expected { | 1402 | if ret := ReturnSmallBool(true, 42, 123456789); ret != expected { | ||
| 1201 | t.Fatalf("ReturnSmallBool returned %+v wanted %+v", ret, expected) | 1403 | t.Fatalf("ReturnSmallBool returned %+v wanted %+v", ret, expected) | ||
| 1202 | } | 1404 | } | ||
| 1203 | } | 1405 | } | ||
| 1204 | { | 1406 | { | ||
| 1205 | type LargeBool struct { | 1407 | type LargeBool struct { | ||
| 1408 | + | _ structs.HostLayout | |||
| 1206 | A bool | 1409 | A bool | ||
| 1207 | B int32 | 1410 | B int32 | ||
| 1208 | C int64 | 1411 | C int64 | ||
| @@ -1210,20 +1413,21 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 1210 | } | 1413 | } | ||
| 1211 | var ReturnLargeBool func(a bool, b int32, c int64, d int64) LargeBool | 1414 | var ReturnLargeBool func(a bool, b int32, c int64, d int64) LargeBool | ||
| 1212 | register(&ReturnLargeBool, lib, "ReturnLargeBool") | 1415 | register(&ReturnLargeBool, lib, "ReturnLargeBool") | ||
| 1213 | - | expected := LargeBool{false, -99, 987654321, 111222333444} | 1416 | + | expected := LargeBool{A: false, B: -99, C: 987654321, D: 111222333444} |
| 1214 | if ret := ReturnLargeBool(false, -99, 987654321, 111222333444); ret != expected { | 1417 | if ret := ReturnLargeBool(false, -99, 987654321, 111222333444); ret != expected { | ||
| 1215 | t.Fatalf("ReturnLargeBool returned %+v wanted %+v", ret, expected) | 1418 | t.Fatalf("ReturnLargeBool returned %+v wanted %+v", ret, expected) | ||
| 1216 | } | 1419 | } | ||
| 1217 | } | 1420 | } | ||
| 1218 | { | 1421 | { | ||
| 1219 | type Ptr1 struct { | 1422 | type Ptr1 struct { | ||
| 1423 | + | _ structs.HostLayout | |||
| 1220 | a *int64 | 1424 | a *int64 | ||
| 1221 | b unsafe.Pointer | 1425 | b unsafe.Pointer | ||
| 1222 | } | 1426 | } | ||
| 1223 | var ReturnPtr1 func(a *int64, b unsafe.Pointer) Ptr1 | 1427 | var ReturnPtr1 func(a *int64, b unsafe.Pointer) Ptr1 | ||
| 1224 | purego.RegisterLibFunc(&ReturnPtr1, lib, "ReturnPtr1") | 1428 | purego.RegisterLibFunc(&ReturnPtr1, lib, "ReturnPtr1") | ||
| 1225 | a, b := new(int64), new(struct{}) | 1429 | a, b := new(int64), new(struct{}) | ||
| 1226 | - | expected := Ptr1{a, unsafe.Pointer(b)} | 1430 | + | expected := Ptr1{a: a, b: unsafe.Pointer(b)} |
| 1227 | if ret := ReturnPtr1(a, unsafe.Pointer(b)); ret != expected { | 1431 | if ret := ReturnPtr1(a, unsafe.Pointer(b)); ret != expected { | ||
| 1228 | t.Fatalf("ReturnPtr1 returned %+v wanted %+v", ret, expected) | 1432 | t.Fatalf("ReturnPtr1 returned %+v wanted %+v", ret, expected) | ||
| 1229 | } | 1433 | } | ||
M sys_386.s +44 -43
| @@ -10,8 +10,8 @@ | |||||
| 10 | #define STACK_SIZE 160 | 10 | #define STACK_SIZE 160 | ||
| 11 | #define PTR_ADDRESS (STACK_SIZE - 4) | 11 | #define PTR_ADDRESS (STACK_SIZE - 4) | ||
| 12 | 12 | ||||
| 13 | - | // syscall15X calls a function in libc on behalf of the syscall package. | 13 | + | // syscallX calls a function in libc on behalf of the syscall package. |
| 14 | - | // syscall15X takes a pointer to a struct like: | 14 | + | // syscallX takes a pointer to a struct like: |
| 15 | // struct { | 15 | // struct { | ||
| 16 | // fn uintptr | 16 | // fn uintptr | ||
| 17 | // a1 uintptr | 17 | // a1 uintptr | ||
| @@ -22,18 +22,18 @@ | |||||
| 22 | // f16 uintptr | 22 | // f16 uintptr | ||
| 23 | // arm64_r8 uintptr | 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 | // C calling convention (use libcCall). | 26 | // C calling convention (use libcCall). | ||
| 27 | // | 27 | // | ||
| 28 | // On i386 System V ABI, all arguments are passed on the stack. | 28 | // On i386 System V ABI, all arguments are passed on the stack. | ||
| 29 | // Return value is in EAX (and EDX for 64-bit values). | 29 | // Return value is in EAX (and EDX for 64-bit values). | ||
| 30 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $4 | 30 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $4 |
| 31 | - | DATA ·syscall15XABI0(SB)/4, $syscall15X(SB) | 31 | + | DATA ·syscallXABI0(SB)/4, $syscallX(SB) |
| 32 | - | TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | 32 | + | TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 |
| 33 | // Called via C calling convention: argument pointer at 4(SP) | 33 | // Called via C calling convention: argument pointer at 4(SP) | ||
| 34 | // NOT via Go calling convention | 34 | // NOT via Go calling convention | ||
| 35 | // On i386, the first argument is at 4(SP) after CALL pushes return address | 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 | // Save callee-saved registers | 38 | // Save callee-saved registers | ||
| 39 | PUSHL BP | 39 | PUSHL BP | ||
| @@ -50,75 +50,75 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |||||
| 50 | MOVL BX, PTR_ADDRESS(SP) // save args pointer | 50 | MOVL BX, PTR_ADDRESS(SP) // save args pointer | ||
| 51 | 51 | ||||
| 52 | // Load function pointer | 52 | // Load function pointer | ||
| 53 | - | MOVL syscall15Args_fn(BX), AX | 53 | + | MOVL syscallArgs_fn(BX), AX |
| 54 | MOVL AX, (PTR_ADDRESS-4)(SP) // save fn pointer | 54 | MOVL AX, (PTR_ADDRESS-4)(SP) // save fn pointer | ||
| 55 | 55 | ||||
| 56 | // Push all integer arguments onto the stack (a1-a32) | 56 | // Push all integer arguments onto the stack (a1-a32) | ||
| 57 | // i386 SysV ABI: arguments pushed right-to-left, but we're | 57 | // i386 SysV ABI: arguments pushed right-to-left, but we're | ||
| 58 | // setting up the stack from low to high addresses | 58 | // setting up the stack from low to high addresses | ||
| 59 | - | MOVL syscall15Args_a1(BX), AX | 59 | + | MOVL syscallArgs_a1(BX), AX |
| 60 | MOVL AX, 0(SP) | 60 | MOVL AX, 0(SP) | ||
| 61 | - | MOVL syscall15Args_a2(BX), AX | 61 | + | MOVL syscallArgs_a2(BX), AX |
| 62 | MOVL AX, 4(SP) | 62 | MOVL AX, 4(SP) | ||
| 63 | - | MOVL syscall15Args_a3(BX), AX | 63 | + | MOVL syscallArgs_a3(BX), AX |
| 64 | MOVL AX, 8(SP) | 64 | MOVL AX, 8(SP) | ||
| 65 | - | MOVL syscall15Args_a4(BX), AX | 65 | + | MOVL syscallArgs_a4(BX), AX |
| 66 | MOVL AX, 12(SP) | 66 | MOVL AX, 12(SP) | ||
| 67 | - | MOVL syscall15Args_a5(BX), AX | 67 | + | MOVL syscallArgs_a5(BX), AX |
| 68 | MOVL AX, 16(SP) | 68 | MOVL AX, 16(SP) | ||
| 69 | - | MOVL syscall15Args_a6(BX), AX | 69 | + | MOVL syscallArgs_a6(BX), AX |
| 70 | MOVL AX, 20(SP) | 70 | MOVL AX, 20(SP) | ||
| 71 | - | MOVL syscall15Args_a7(BX), AX | 71 | + | MOVL syscallArgs_a7(BX), AX |
| 72 | MOVL AX, 24(SP) | 72 | MOVL AX, 24(SP) | ||
| 73 | - | MOVL syscall15Args_a8(BX), AX | 73 | + | MOVL syscallArgs_a8(BX), AX |
| 74 | MOVL AX, 28(SP) | 74 | MOVL AX, 28(SP) | ||
| 75 | - | MOVL syscall15Args_a9(BX), AX | 75 | + | MOVL syscallArgs_a9(BX), AX |
| 76 | MOVL AX, 32(SP) | 76 | MOVL AX, 32(SP) | ||
| 77 | - | MOVL syscall15Args_a10(BX), AX | 77 | + | MOVL syscallArgs_a10(BX), AX |
| 78 | MOVL AX, 36(SP) | 78 | MOVL AX, 36(SP) | ||
| 79 | - | MOVL syscall15Args_a11(BX), AX | 79 | + | MOVL syscallArgs_a11(BX), AX |
| 80 | MOVL AX, 40(SP) | 80 | MOVL AX, 40(SP) | ||
| 81 | - | MOVL syscall15Args_a12(BX), AX | 81 | + | MOVL syscallArgs_a12(BX), AX |
| 82 | MOVL AX, 44(SP) | 82 | MOVL AX, 44(SP) | ||
| 83 | - | MOVL syscall15Args_a13(BX), AX | 83 | + | MOVL syscallArgs_a13(BX), AX |
| 84 | MOVL AX, 48(SP) | 84 | MOVL AX, 48(SP) | ||
| 85 | - | MOVL syscall15Args_a14(BX), AX | 85 | + | MOVL syscallArgs_a14(BX), AX |
| 86 | MOVL AX, 52(SP) | 86 | MOVL AX, 52(SP) | ||
| 87 | - | MOVL syscall15Args_a15(BX), AX | 87 | + | MOVL syscallArgs_a15(BX), AX |
| 88 | MOVL AX, 56(SP) | 88 | MOVL AX, 56(SP) | ||
| 89 | - | MOVL syscall15Args_a16(BX), AX | 89 | + | MOVL syscallArgs_a16(BX), AX |
| 90 | MOVL AX, 60(SP) | 90 | MOVL AX, 60(SP) | ||
| 91 | - | MOVL syscall15Args_a17(BX), AX | 91 | + | MOVL syscallArgs_a17(BX), AX |
| 92 | MOVL AX, 64(SP) | 92 | MOVL AX, 64(SP) | ||
| 93 | - | MOVL syscall15Args_a18(BX), AX | 93 | + | MOVL syscallArgs_a18(BX), AX |
| 94 | MOVL AX, 68(SP) | 94 | MOVL AX, 68(SP) | ||
| 95 | - | MOVL syscall15Args_a19(BX), AX | 95 | + | MOVL syscallArgs_a19(BX), AX |
| 96 | MOVL AX, 72(SP) | 96 | MOVL AX, 72(SP) | ||
| 97 | - | MOVL syscall15Args_a20(BX), AX | 97 | + | MOVL syscallArgs_a20(BX), AX |
| 98 | MOVL AX, 76(SP) | 98 | MOVL AX, 76(SP) | ||
| 99 | - | MOVL syscall15Args_a21(BX), AX | 99 | + | MOVL syscallArgs_a21(BX), AX |
| 100 | MOVL AX, 80(SP) | 100 | MOVL AX, 80(SP) | ||
| 101 | - | MOVL syscall15Args_a22(BX), AX | 101 | + | MOVL syscallArgs_a22(BX), AX |
| 102 | MOVL AX, 84(SP) | 102 | MOVL AX, 84(SP) | ||
| 103 | - | MOVL syscall15Args_a23(BX), AX | 103 | + | MOVL syscallArgs_a23(BX), AX |
| 104 | MOVL AX, 88(SP) | 104 | MOVL AX, 88(SP) | ||
| 105 | - | MOVL syscall15Args_a24(BX), AX | 105 | + | MOVL syscallArgs_a24(BX), AX |
| 106 | MOVL AX, 92(SP) | 106 | MOVL AX, 92(SP) | ||
| 107 | - | MOVL syscall15Args_a25(BX), AX | 107 | + | MOVL syscallArgs_a25(BX), AX |
| 108 | MOVL AX, 96(SP) | 108 | MOVL AX, 96(SP) | ||
| 109 | - | MOVL syscall15Args_a26(BX), AX | 109 | + | MOVL syscallArgs_a26(BX), AX |
| 110 | MOVL AX, 100(SP) | 110 | MOVL AX, 100(SP) | ||
| 111 | - | MOVL syscall15Args_a27(BX), AX | 111 | + | MOVL syscallArgs_a27(BX), AX |
| 112 | MOVL AX, 104(SP) | 112 | MOVL AX, 104(SP) | ||
| 113 | - | MOVL syscall15Args_a28(BX), AX | 113 | + | MOVL syscallArgs_a28(BX), AX |
| 114 | MOVL AX, 108(SP) | 114 | MOVL AX, 108(SP) | ||
| 115 | - | MOVL syscall15Args_a29(BX), AX | 115 | + | MOVL syscallArgs_a29(BX), AX |
| 116 | MOVL AX, 112(SP) | 116 | MOVL AX, 112(SP) | ||
| 117 | - | MOVL syscall15Args_a30(BX), AX | 117 | + | MOVL syscallArgs_a30(BX), AX |
| 118 | MOVL AX, 116(SP) | 118 | MOVL AX, 116(SP) | ||
| 119 | - | MOVL syscall15Args_a31(BX), AX | 119 | + | MOVL syscallArgs_a31(BX), AX |
| 120 | MOVL AX, 120(SP) | 120 | MOVL AX, 120(SP) | ||
| 121 | - | MOVL syscall15Args_a32(BX), AX | 121 | + | MOVL syscallArgs_a32(BX), AX |
| 122 | MOVL AX, 124(SP) | 122 | MOVL AX, 124(SP) | ||
| 123 | 123 | ||||
| 124 | // Call the C function | 124 | // Call the C function | ||
| @@ -127,13 +127,14 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |||||
| 127 | 127 | ||||
| 128 | // Get args pointer back and save results | 128 | // Get args pointer back and save results | ||
| 129 | MOVL PTR_ADDRESS(SP), BX | 129 | MOVL PTR_ADDRESS(SP), BX | ||
| 130 | - | MOVL AX, syscall15Args_a1(BX) // return value r1 | 130 | + | MOVL AX, syscallArgs_a1(BX) // return value r1 |
| 131 | - | MOVL DX, syscall15Args_a2(BX) // return value r2 (for 64-bit returns) | 131 | + | MOVL DX, syscallArgs_a2(BX) // return value r2 (for 64-bit returns) |
| 132 | + | MOVL $0, syscallArgs_a3(BX) // no errno here: clear the input argument | |||
| 132 | 133 | ||||
| 133 | // Save x87 FPU return value (ST0) to f1 field | 134 | // Save x87 FPU return value (ST0) to f1 field | ||
| 134 | // On i386 System V ABI, float/double returns are in ST(0) | 135 | // On i386 System V ABI, float/double returns are in ST(0) | ||
| 135 | // We save as float64 (8 bytes) to preserve precision | 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 | // Clean up stack | 139 | // Clean up stack | ||
| 139 | ADDL $STACK_SIZE, SP | 140 | ADDL $STACK_SIZE, SP | ||
M sys_amd64.s +73 -37
| @@ -8,11 +8,11 @@ | |||||
| 8 | #include "go_asm.h" | 8 | #include "go_asm.h" | ||
| 9 | #include "funcdata.h" | 9 | #include "funcdata.h" | ||
| 10 | 10 | ||||
| 11 | - | #define STACK_SIZE 80 | 11 | + | #define STACK_SIZE 224 |
| 12 | #define PTR_ADDRESS (STACK_SIZE - 8) | 12 | #define PTR_ADDRESS (STACK_SIZE - 8) | ||
| 13 | 13 | ||||
| 14 | - | // syscall15X calls a function in libc on behalf of the syscall package. | 14 | + | // syscallX calls a function in libc on behalf of the syscall package. |
| 15 | - | // syscall15X takes a pointer to a struct like: | 15 | + | // syscallX takes a pointer to a struct like: |
| 16 | // struct { | 16 | // struct { | ||
| 17 | // fn uintptr | 17 | // fn uintptr | ||
| 18 | // a1 uintptr | 18 | // a1 uintptr | ||
| @@ -34,65 +34,101 @@ | |||||
| 34 | // r2 uintptr | 34 | // r2 uintptr | ||
| 35 | // err uintptr | 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 | // C calling convention (use libcCall). | 38 | // C calling convention (use libcCall). | ||
| 39 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | 39 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 |
| 40 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | 40 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) |
| 41 | - | TEXT syscall15X(SB), NOSPLIT, $STACK_SIZE | 41 | + | TEXT syscallX(SB), NOSPLIT, $STACK_SIZE |
| 42 | MOVQ DI, PTR_ADDRESS(SP) // save the pointer | 42 | MOVQ DI, PTR_ADDRESS(SP) // save the pointer | ||
| 43 | MOVQ DI, R11 | 43 | MOVQ DI, R11 | ||
| 44 | 44 | ||||
| 45 | - | MOVQ syscall15Args_f1(R11), X0 // f1 | 45 | + | MOVQ syscallArgs_f1(R11), X0 // f1 |
| 46 | - | MOVQ syscall15Args_f2(R11), X1 // f2 | 46 | + | MOVQ syscallArgs_f2(R11), X1 // f2 |
| 47 | - | MOVQ syscall15Args_f3(R11), X2 // f3 | 47 | + | MOVQ syscallArgs_f3(R11), X2 // f3 |
| 48 | - | MOVQ syscall15Args_f4(R11), X3 // f4 | 48 | + | MOVQ syscallArgs_f4(R11), X3 // f4 |
| 49 | - | MOVQ syscall15Args_f5(R11), X4 // f5 | 49 | + | MOVQ syscallArgs_f5(R11), X4 // f5 |
| 50 | - | MOVQ syscall15Args_f6(R11), X5 // f6 | 50 | + | MOVQ syscallArgs_f6(R11), X5 // f6 |
| 51 | - | MOVQ syscall15Args_f7(R11), X6 // f7 | 51 | + | MOVQ syscallArgs_f7(R11), X6 // f7 |
| 52 | - | MOVQ syscall15Args_f8(R11), X7 // f8 | 52 | + | MOVQ syscallArgs_f8(R11), X7 // f8 |
| 53 | - | 53 | + | ||
| 54 | - | MOVQ syscall15Args_a1(R11), DI // a1 | 54 | + | MOVQ syscallArgs_a1(R11), DI // a1 |
| 55 | - | MOVQ syscall15Args_a2(R11), SI // a2 | 55 | + | MOVQ syscallArgs_a2(R11), SI // a2 |
| 56 | - | MOVQ syscall15Args_a3(R11), DX // a3 | 56 | + | MOVQ syscallArgs_a3(R11), DX // a3 |
| 57 | - | MOVQ syscall15Args_a4(R11), CX // a4 | 57 | + | MOVQ syscallArgs_a4(R11), CX // a4 |
| 58 | - | MOVQ syscall15Args_a5(R11), R8 // a5 | 58 | + | MOVQ syscallArgs_a5(R11), R8 // a5 |
| 59 | - | MOVQ syscall15Args_a6(R11), R9 // a6 | 59 | + | MOVQ syscallArgs_a6(R11), R9 // a6 |
| 60 | 60 | ||||
| 61 | // push the remaining parameters onto the stack | 61 | // push the remaining parameters onto the stack | ||
| 62 | - | MOVQ syscall15Args_a7(R11), R12 | 62 | + | MOVQ syscallArgs_a7(R11), R12 |
| 63 | MOVQ R12, 0(SP) // push a7 | 63 | MOVQ R12, 0(SP) // push a7 | ||
| 64 | - | MOVQ syscall15Args_a8(R11), R12 | 64 | + | MOVQ syscallArgs_a8(R11), R12 |
| 65 | MOVQ R12, 8(SP) // push a8 | 65 | MOVQ R12, 8(SP) // push a8 | ||
| 66 | - | MOVQ syscall15Args_a9(R11), R12 | 66 | + | MOVQ syscallArgs_a9(R11), R12 |
| 67 | MOVQ R12, 16(SP) // push a9 | 67 | MOVQ R12, 16(SP) // push a9 | ||
| 68 | - | MOVQ syscall15Args_a10(R11), R12 | 68 | + | MOVQ syscallArgs_a10(R11), R12 |
| 69 | MOVQ R12, 24(SP) // push a10 | 69 | MOVQ R12, 24(SP) // push a10 | ||
| 70 | - | MOVQ syscall15Args_a11(R11), R12 | 70 | + | MOVQ syscallArgs_a11(R11), R12 |
| 71 | MOVQ R12, 32(SP) // push a11 | 71 | MOVQ R12, 32(SP) // push a11 | ||
| 72 | - | MOVQ syscall15Args_a12(R11), R12 | 72 | + | MOVQ syscallArgs_a12(R11), R12 |
| 73 | MOVQ R12, 40(SP) // push a12 | 73 | MOVQ R12, 40(SP) // push a12 | ||
| 74 | - | MOVQ syscall15Args_a13(R11), R12 | 74 | + | MOVQ syscallArgs_a13(R11), R12 |
| 75 | MOVQ R12, 48(SP) // push a13 | 75 | MOVQ R12, 48(SP) // push a13 | ||
| 76 | - | MOVQ syscall15Args_a14(R11), R12 | 76 | + | MOVQ syscallArgs_a14(R11), R12 |
| 77 | MOVQ R12, 56(SP) // push a14 | 77 | MOVQ R12, 56(SP) // push a14 | ||
| 78 | - | MOVQ syscall15Args_a15(R11), R12 | 78 | + | MOVQ syscallArgs_a15(R11), R12 |
| 79 | MOVQ R12, 64(SP) // push a15 | 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 | XORL AX, AX // vararg: say "no float args" | 114 | XORL AX, AX // vararg: say "no float args" | ||
| 81 | 115 | ||||
| 82 | - | MOVQ syscall15Args_fn(R11), R10 // fn | 116 | + | MOVQ syscallArgs_fn(R11), R10 // fn |
| 83 | CALL R10 | 117 | CALL R10 | ||
| 84 | 118 | ||||
| 85 | MOVQ PTR_ADDRESS(SP), DI // get the pointer back | 119 | MOVQ PTR_ADDRESS(SP), DI // get the pointer back | ||
| 86 | - | MOVQ AX, syscall15Args_a1(DI) // r1 | 120 | + | MOVQ AX, syscallArgs_a1(DI) // r1 |
| 87 | - | MOVQ DX, syscall15Args_a2(DI) // r2 | 121 | + | MOVQ DX, syscallArgs_a2(DI) // r2 |
| 88 | - | MOVQ X0, syscall15Args_f1(DI) // f1 | 122 | + | MOVQ X0, syscallArgs_f1(DI) // f1 |
| 89 | - | MOVQ X1, syscall15Args_f2(DI) // f2 | 123 | + | MOVQ X1, syscallArgs_f2(DI) // f2 |
| 90 | 124 | ||||
| 91 | #ifdef GOOS_darwin | 125 | #ifdef GOOS_darwin | ||
| 92 | CALL purego_error(SB) | 126 | CALL purego_error(SB) | ||
| 93 | MOVQ PTR_ADDRESS(SP), DI // reload (DI clobbered by call) | 127 | MOVQ PTR_ADDRESS(SP), DI // reload (DI clobbered by call) | ||
| 94 | MOVQ (AX), AX | 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 | #endif | 132 | #endif | ||
| 97 | 133 | ||||
| 98 | XORL AX, AX // no error (it's ignored anyway) | 134 | XORL AX, AX // no error (it's ignored anyway) | ||
M sys_arm.s +65 -54
| @@ -10,8 +10,8 @@ | |||||
| 10 | #define STACK_SIZE 128 | 10 | #define STACK_SIZE 128 | ||
| 11 | #define PTR_ADDRESS (STACK_SIZE - 4) | 11 | #define PTR_ADDRESS (STACK_SIZE - 4) | ||
| 12 | 12 | ||||
| 13 | - | // syscall15X calls a function in libc on behalf of the syscall package. | 13 | + | // syscallX calls a function in libc on behalf of the syscall package. |
| 14 | - | // syscall15X takes a pointer to a struct like: | 14 | + | // syscallX takes a pointer to a struct like: |
| 15 | // struct { | 15 | // struct { | ||
| 16 | // fn uintptr | 16 | // fn uintptr | ||
| 17 | // a1 uintptr | 17 | // a1 uintptr | ||
| @@ -22,12 +22,12 @@ | |||||
| 22 | // f16 uintptr | 22 | // f16 uintptr | ||
| 23 | // arm64_r8 uintptr | 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 | // C calling convention (use libcCall). | 26 | // C calling convention (use libcCall). | ||
| 27 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $4 | 27 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $4 |
| 28 | - | DATA ·syscall15XABI0(SB)/4, $syscall15X(SB) | 28 | + | DATA ·syscallXABI0(SB)/4, $syscallX(SB) |
| 29 | - | TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | 29 | + | TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 |
| 30 | - | // Called via C calling convention: R0 = pointer to syscall15Args | 30 | + | // Called via C calling convention: R0 = pointer to syscallArgs |
| 31 | // NOT via Go calling convention | 31 | // NOT via Go calling convention | ||
| 32 | // Save link register and callee-saved registers first | 32 | // Save link register and callee-saved registers first | ||
| 33 | MOVW.W R14, -4(R13) // save LR (decrement and store) | 33 | MOVW.W R14, -4(R13) // save LR (decrement and store) | ||
| @@ -38,82 +38,87 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |||||
| 38 | MOVW R8, PTR_ADDRESS(R13) | 38 | MOVW R8, PTR_ADDRESS(R13) | ||
| 39 | 39 | ||||
| 40 | // Load function pointer first (before anything can corrupt R8) | 40 | // Load function pointer first (before anything can corrupt R8) | ||
| 41 | - | MOVW syscall15Args_fn(R8), R5 | 41 | + | MOVW syscallArgs_fn(R8), R5 |
| 42 | MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 56 | 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 | // Load floating point arguments | 48 | // Load floating point arguments | ||
| 45 | // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 2 | 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 | 50 | + | MOVD syscallArgs_f1(R8), F0 // f1+f2 -> D0 |
| 47 | - | MOVD syscall15Args_f3(R8), F1 // f3+f4 -> D1 | 51 | + | MOVD syscallArgs_f3(R8), F1 // f3+f4 -> D1 |
| 48 | - | MOVD syscall15Args_f5(R8), F2 // f5+f6 -> D2 | 52 | + | MOVD syscallArgs_f5(R8), F2 // f5+f6 -> D2 |
| 49 | - | MOVD syscall15Args_f7(R8), F3 // f7+f8 -> D3 | 53 | + | MOVD syscallArgs_f7(R8), F3 // f7+f8 -> D3 |
| 50 | - | MOVD syscall15Args_f9(R8), F4 // f9+f10 -> D4 | 54 | + | MOVD syscallArgs_f9(R8), F4 // f9+f10 -> D4 |
| 51 | - | MOVD syscall15Args_f11(R8), F5 // f11+f12 -> D5 | 55 | + | MOVD syscallArgs_f11(R8), F5 // f11+f12 -> D5 |
| 52 | - | MOVD syscall15Args_f13(R8), F6 // f13+f14 -> D6 | 56 | + | MOVD syscallArgs_f13(R8), F6 // f13+f14 -> D6 |
| 53 | - | MOVD syscall15Args_f15(R8), F7 // f15+f16 -> D7 | 57 | + | MOVD syscallArgs_f15(R8), F7 // f15+f16 -> D7 |
| 54 | 58 | ||||
| 59 | + | skipfpload: | |||
| 55 | // Load integer arguments into registers (R0-R3 for ARM EABI) | 60 | // Load integer arguments into registers (R0-R3 for ARM EABI) | ||
| 56 | - | MOVW syscall15Args_a1(R8), R0 // a1 | 61 | + | MOVW syscallArgs_a1(R8), R0 // a1 |
| 57 | - | MOVW syscall15Args_a2(R8), R1 // a2 | 62 | + | MOVW syscallArgs_a2(R8), R1 // a2 |
| 58 | - | MOVW syscall15Args_a3(R8), R2 // a3 | 63 | + | MOVW syscallArgs_a3(R8), R2 // a3 |
| 59 | - | MOVW syscall15Args_a4(R8), R3 // a4 | 64 | + | MOVW syscallArgs_a4(R8), R3 // a4 |
| 60 | 65 | ||||
| 61 | // push a5-a32 onto stack | 66 | // push a5-a32 onto stack | ||
| 62 | - | MOVW syscall15Args_a5(R8), R4 | 67 | + | MOVW syscallArgs_a5(R8), R4 |
| 63 | MOVW R4, 0(R13) | 68 | MOVW R4, 0(R13) | ||
| 64 | - | MOVW syscall15Args_a6(R8), R4 | 69 | + | MOVW syscallArgs_a6(R8), R4 |
| 65 | MOVW R4, 4(R13) | 70 | MOVW R4, 4(R13) | ||
| 66 | - | MOVW syscall15Args_a7(R8), R4 | 71 | + | MOVW syscallArgs_a7(R8), R4 |
| 67 | MOVW R4, 8(R13) | 72 | MOVW R4, 8(R13) | ||
| 68 | - | MOVW syscall15Args_a8(R8), R4 | 73 | + | MOVW syscallArgs_a8(R8), R4 |
| 69 | MOVW R4, 12(R13) | 74 | MOVW R4, 12(R13) | ||
| 70 | - | MOVW syscall15Args_a9(R8), R4 | 75 | + | MOVW syscallArgs_a9(R8), R4 |
| 71 | MOVW R4, 16(R13) | 76 | MOVW R4, 16(R13) | ||
| 72 | - | MOVW syscall15Args_a10(R8), R4 | 77 | + | MOVW syscallArgs_a10(R8), R4 |
| 73 | MOVW R4, 20(R13) | 78 | MOVW R4, 20(R13) | ||
| 74 | - | MOVW syscall15Args_a11(R8), R4 | 79 | + | MOVW syscallArgs_a11(R8), R4 |
| 75 | MOVW R4, 24(R13) | 80 | MOVW R4, 24(R13) | ||
| 76 | - | MOVW syscall15Args_a12(R8), R4 | 81 | + | MOVW syscallArgs_a12(R8), R4 |
| 77 | MOVW R4, 28(R13) | 82 | MOVW R4, 28(R13) | ||
| 78 | - | MOVW syscall15Args_a13(R8), R4 | 83 | + | MOVW syscallArgs_a13(R8), R4 |
| 79 | MOVW R4, 32(R13) | 84 | MOVW R4, 32(R13) | ||
| 80 | - | MOVW syscall15Args_a14(R8), R4 | 85 | + | MOVW syscallArgs_a14(R8), R4 |
| 81 | MOVW R4, 36(R13) | 86 | MOVW R4, 36(R13) | ||
| 82 | - | MOVW syscall15Args_a15(R8), R4 | 87 | + | MOVW syscallArgs_a15(R8), R4 |
| 83 | MOVW R4, 40(R13) | 88 | MOVW R4, 40(R13) | ||
| 84 | - | MOVW syscall15Args_a16(R8), R4 | 89 | + | MOVW syscallArgs_a16(R8), R4 |
| 85 | MOVW R4, 44(R13) | 90 | MOVW R4, 44(R13) | ||
| 86 | - | MOVW syscall15Args_a17(R8), R4 | 91 | + | MOVW syscallArgs_a17(R8), R4 |
| 87 | MOVW R4, 48(R13) | 92 | MOVW R4, 48(R13) | ||
| 88 | - | MOVW syscall15Args_a18(R8), R4 | 93 | + | MOVW syscallArgs_a18(R8), R4 |
| 89 | MOVW R4, 52(R13) | 94 | MOVW R4, 52(R13) | ||
| 90 | - | MOVW syscall15Args_a19(R8), R4 | 95 | + | MOVW syscallArgs_a19(R8), R4 |
| 91 | MOVW R4, 56(R13) | 96 | MOVW R4, 56(R13) | ||
| 92 | - | MOVW syscall15Args_a20(R8), R4 | 97 | + | MOVW syscallArgs_a20(R8), R4 |
| 93 | MOVW R4, 60(R13) | 98 | MOVW R4, 60(R13) | ||
| 94 | - | MOVW syscall15Args_a21(R8), R4 | 99 | + | MOVW syscallArgs_a21(R8), R4 |
| 95 | MOVW R4, 64(R13) | 100 | MOVW R4, 64(R13) | ||
| 96 | - | MOVW syscall15Args_a22(R8), R4 | 101 | + | MOVW syscallArgs_a22(R8), R4 |
| 97 | MOVW R4, 68(R13) | 102 | MOVW R4, 68(R13) | ||
| 98 | - | MOVW syscall15Args_a23(R8), R4 | 103 | + | MOVW syscallArgs_a23(R8), R4 |
| 99 | MOVW R4, 72(R13) | 104 | MOVW R4, 72(R13) | ||
| 100 | - | MOVW syscall15Args_a24(R8), R4 | 105 | + | MOVW syscallArgs_a24(R8), R4 |
| 101 | MOVW R4, 76(R13) | 106 | MOVW R4, 76(R13) | ||
| 102 | - | MOVW syscall15Args_a25(R8), R4 | 107 | + | MOVW syscallArgs_a25(R8), R4 |
| 103 | MOVW R4, 80(R13) | 108 | MOVW R4, 80(R13) | ||
| 104 | - | MOVW syscall15Args_a26(R8), R4 | 109 | + | MOVW syscallArgs_a26(R8), R4 |
| 105 | MOVW R4, 84(R13) | 110 | MOVW R4, 84(R13) | ||
| 106 | - | MOVW syscall15Args_a27(R8), R4 | 111 | + | MOVW syscallArgs_a27(R8), R4 |
| 107 | MOVW R4, 88(R13) | 112 | MOVW R4, 88(R13) | ||
| 108 | - | MOVW syscall15Args_a28(R8), R4 | 113 | + | MOVW syscallArgs_a28(R8), R4 |
| 109 | MOVW R4, 92(R13) | 114 | MOVW R4, 92(R13) | ||
| 110 | - | MOVW syscall15Args_a29(R8), R4 | 115 | + | MOVW syscallArgs_a29(R8), R4 |
| 111 | MOVW R4, 96(R13) | 116 | MOVW R4, 96(R13) | ||
| 112 | - | MOVW syscall15Args_a30(R8), R4 | 117 | + | MOVW syscallArgs_a30(R8), R4 |
| 113 | MOVW R4, 100(R13) | 118 | MOVW R4, 100(R13) | ||
| 114 | - | MOVW syscall15Args_a31(R8), R4 | 119 | + | MOVW syscallArgs_a31(R8), R4 |
| 115 | MOVW R4, 104(R13) | 120 | MOVW R4, 104(R13) | ||
| 116 | - | MOVW syscall15Args_a32(R8), R4 | 121 | + | MOVW syscallArgs_a32(R8), R4 |
| 117 | MOVW R4, 108(R13) | 122 | MOVW R4, 108(R13) | ||
| 118 | 123 | ||||
| 119 | // Load saved function pointer and call | 124 | // Load saved function pointer and call | ||
| @@ -128,15 +133,21 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |||||
| 128 | ADD $STACK_SIZE, R13 | 133 | ADD $STACK_SIZE, R13 | ||
| 129 | 134 | ||||
| 130 | // save R0, R1 | 135 | // save R0, R1 | ||
| 131 | - | MOVW R0, syscall15Args_a1(R8) | 136 | + | MOVW R0, syscallArgs_a1(R8) |
| 132 | - | MOVW R1, syscall15Args_a2(R8) | 137 | + | MOVW R1, syscallArgs_a2(R8) |
| 138 | + | MOVW $0, R1 | |||
| 139 | + | MOVW R1, syscallArgs_a3(R8) // no errno here: clear the input argument | |||
| 133 | 140 | ||||
| 141 | + | MOVB runtime·goarmsoftfp(SB), R11 | |||
| 142 | + | CMP $0, R11 | |||
| 143 | + | BNE skipfpsave | |||
| 134 | // save f0-f3 (each float64 spans 2 uintptr slots on ARM32) | 144 | // save f0-f3 (each float64 spans 2 uintptr slots on ARM32) | ||
| 135 | - | MOVD F0, syscall15Args_f1(R8) | 145 | + | MOVD F0, syscallArgs_f1(R8) |
| 136 | - | MOVD F1, syscall15Args_f3(R8) | 146 | + | MOVD F1, syscallArgs_f3(R8) |
| 137 | - | MOVD F2, syscall15Args_f5(R8) | 147 | + | MOVD F2, syscallArgs_f5(R8) |
| 138 | - | MOVD F3, syscall15Args_f7(R8) | 148 | + | MOVD F3, syscallArgs_f7(R8) |
| 139 | 149 | ||||
| 150 | + | skipfpsave: | |||
| 140 | // Restore callee-saved registers and return | 151 | // Restore callee-saved registers and return | ||
| 141 | MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11] | 152 | MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11] | ||
| 142 | MOVW.P 4(R13), R15 // pop LR into PC (return) | 153 | MOVW.P 4(R13), R15 // pop LR into PC (return) | ||
M sys_arm64.s +75 -40
| @@ -7,11 +7,11 @@ | |||||
| 7 | #include "go_asm.h" | 7 | #include "go_asm.h" | ||
| 8 | #include "funcdata.h" | 8 | #include "funcdata.h" | ||
| 9 | 9 | ||||
| 10 | - | #define STACK_SIZE 64 | 10 | + | #define STACK_SIZE 208 |
| 11 | #define PTR_ADDRESS (STACK_SIZE - 8) | 11 | #define PTR_ADDRESS (STACK_SIZE - 8) | ||
| 12 | 12 | ||||
| 13 | - | // syscall15X calls a function in libc on behalf of the syscall package. | 13 | + | // syscallX calls a function in libc on behalf of the syscall package. |
| 14 | - | // syscall15X takes a pointer to a struct like: | 14 | + | // syscallX takes a pointer to a struct like: |
| 15 | // struct { | 15 | // struct { | ||
| 16 | // fn uintptr | 16 | // fn uintptr | ||
| 17 | // a1 uintptr | 17 | // a1 uintptr | ||
| @@ -33,66 +33,101 @@ | |||||
| 33 | // r2 uintptr | 33 | // r2 uintptr | ||
| 34 | // err uintptr | 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 | // C calling convention (use libcCall). | 37 | // C calling convention (use libcCall). | ||
| 38 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | 38 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 |
| 39 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | 39 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) |
| 40 | - | TEXT syscall15X(SB), NOSPLIT, $0 | 40 | + | TEXT syscallX(SB), NOSPLIT, $0 |
| 41 | SUB $STACK_SIZE, RSP // push structure pointer | 41 | SUB $STACK_SIZE, RSP // push structure pointer | ||
| 42 | MOVD R0, PTR_ADDRESS(RSP) | 42 | MOVD R0, PTR_ADDRESS(RSP) | ||
| 43 | MOVD R0, R9 | 43 | MOVD R0, R9 | ||
| 44 | 44 | ||||
| 45 | - | FMOVD syscall15Args_f1(R9), F0 // f1 | 45 | + | FMOVD syscallArgs_f1(R9), F0 // f1 |
| 46 | - | FMOVD syscall15Args_f2(R9), F1 // f2 | 46 | + | FMOVD syscallArgs_f2(R9), F1 // f2 |
| 47 | - | FMOVD syscall15Args_f3(R9), F2 // f3 | 47 | + | FMOVD syscallArgs_f3(R9), F2 // f3 |
| 48 | - | FMOVD syscall15Args_f4(R9), F3 // f4 | 48 | + | FMOVD syscallArgs_f4(R9), F3 // f4 |
| 49 | - | FMOVD syscall15Args_f5(R9), F4 // f5 | 49 | + | FMOVD syscallArgs_f5(R9), F4 // f5 |
| 50 | - | FMOVD syscall15Args_f6(R9), F5 // f6 | 50 | + | FMOVD syscallArgs_f6(R9), F5 // f6 |
| 51 | - | FMOVD syscall15Args_f7(R9), F6 // f7 | 51 | + | FMOVD syscallArgs_f7(R9), F6 // f7 |
| 52 | - | FMOVD syscall15Args_f8(R9), F7 // f8 | 52 | + | FMOVD syscallArgs_f8(R9), F7 // f8 |
| 53 | 53 | ||||
| 54 | - | MOVD syscall15Args_a1(R9), R0 // a1 | 54 | + | MOVD syscallArgs_a1(R9), R0 // a1 |
| 55 | - | MOVD syscall15Args_a2(R9), R1 // a2 | 55 | + | MOVD syscallArgs_a2(R9), R1 // a2 |
| 56 | - | MOVD syscall15Args_a3(R9), R2 // a3 | 56 | + | MOVD syscallArgs_a3(R9), R2 // a3 |
| 57 | - | MOVD syscall15Args_a4(R9), R3 // a4 | 57 | + | MOVD syscallArgs_a4(R9), R3 // a4 |
| 58 | - | MOVD syscall15Args_a5(R9), R4 // a5 | 58 | + | MOVD syscallArgs_a5(R9), R4 // a5 |
| 59 | - | MOVD syscall15Args_a6(R9), R5 // a6 | 59 | + | MOVD syscallArgs_a6(R9), R5 // a6 |
| 60 | - | MOVD syscall15Args_a7(R9), R6 // a7 | 60 | + | MOVD syscallArgs_a7(R9), R6 // a7 |
| 61 | - | MOVD syscall15Args_a8(R9), R7 // a8 | 61 | + | MOVD syscallArgs_a8(R9), R7 // a8 |
| 62 | - | MOVD syscall15Args_arm64_r8(R9), R8 // r8 | 62 | + | MOVD syscallArgs_arm64_r8(R9), R8 // r8 |
| 63 | 63 | ||||
| 64 | - | MOVD syscall15Args_a9(R9), R10 | 64 | + | MOVD syscallArgs_a9(R9), R10 |
| 65 | MOVD R10, 0(RSP) // push a9 onto stack | 65 | MOVD R10, 0(RSP) // push a9 onto stack | ||
| 66 | - | MOVD syscall15Args_a10(R9), R10 | 66 | + | MOVD syscallArgs_a10(R9), R10 |
| 67 | MOVD R10, 8(RSP) // push a10 onto stack | 67 | MOVD R10, 8(RSP) // push a10 onto stack | ||
| 68 | - | MOVD syscall15Args_a11(R9), R10 | 68 | + | MOVD syscallArgs_a11(R9), R10 |
| 69 | MOVD R10, 16(RSP) // push a11 onto stack | 69 | MOVD R10, 16(RSP) // push a11 onto stack | ||
| 70 | - | MOVD syscall15Args_a12(R9), R10 | 70 | + | MOVD syscallArgs_a12(R9), R10 |
| 71 | MOVD R10, 24(RSP) // push a12 onto stack | 71 | MOVD R10, 24(RSP) // push a12 onto stack | ||
| 72 | - | MOVD syscall15Args_a13(R9), R10 | 72 | + | MOVD syscallArgs_a13(R9), R10 |
| 73 | MOVD R10, 32(RSP) // push a13 onto stack | 73 | MOVD R10, 32(RSP) // push a13 onto stack | ||
| 74 | - | MOVD syscall15Args_a14(R9), R10 | 74 | + | MOVD syscallArgs_a14(R9), R10 |
| 75 | MOVD R10, 40(RSP) // push a14 onto stack | 75 | MOVD R10, 40(RSP) // push a14 onto stack | ||
| 76 | - | MOVD syscall15Args_a15(R9), R10 | 76 | + | MOVD syscallArgs_a15(R9), R10 |
| 77 | MOVD R10, 48(RSP) // push a15 onto stack | 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 | BL (R10) | 114 | BL (R10) | ||
| 81 | 115 | ||||
| 82 | MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer | 116 | MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer | ||
| 83 | ADD $STACK_SIZE, RSP | 117 | ADD $STACK_SIZE, RSP | ||
| 84 | 118 | ||||
| 85 | - | MOVD R0, syscall15Args_a1(R2) // save r1 | 119 | + | MOVD R0, syscallArgs_a1(R2) // save r1 |
| 86 | - | MOVD R1, syscall15Args_a2(R2) // save r3 | 120 | + | MOVD R1, syscallArgs_a2(R2) // save r3 |
| 87 | - | FMOVD F0, syscall15Args_f1(R2) // save f0 | 121 | + | FMOVD F0, syscallArgs_f1(R2) // save f0 |
| 88 | - | FMOVD F1, syscall15Args_f2(R2) // save f1 | 122 | + | FMOVD F1, syscallArgs_f2(R2) // save f1 |
| 89 | - | FMOVD F2, syscall15Args_f3(R2) // save f2 | 123 | + | FMOVD F2, syscallArgs_f3(R2) // save f2 |
| 90 | - | FMOVD F3, syscall15Args_f4(R2) // save f3 | 124 | + | FMOVD F3, syscallArgs_f4(R2) // save f3 |
| 91 | 125 | ||||
| 92 | #ifdef GOOS_darwin | 126 | #ifdef GOOS_darwin | ||
| 93 | BL purego_error(SB) | 127 | BL purego_error(SB) | ||
| 94 | MOVD (R0), R0 | 128 | MOVD (R0), R0 | ||
| 95 | - | MOVD R0, syscall15Args_a3(R2) // save errno | 129 | + | MOVD R0, syscallArgs_a3(R2) // save errno |
| 96 | - | 130 | + | #else | |
| 131 | + | MOVD $0, syscallArgs_a3(R2) // no errno here: clear the input argument | |||
| 97 | #endif | 132 | #endif | ||
| 98 | RET | 133 | RET | ||
M sys_loong64.s +72 -37
| @@ -7,11 +7,11 @@ | |||||
| 7 | #include "go_asm.h" | 7 | #include "go_asm.h" | ||
| 8 | #include "funcdata.h" | 8 | #include "funcdata.h" | ||
| 9 | 9 | ||||
| 10 | - | #define STACK_SIZE 64 | 10 | + | #define STACK_SIZE 208 |
| 11 | #define PTR_ADDRESS (STACK_SIZE - 8) | 11 | #define PTR_ADDRESS (STACK_SIZE - 8) | ||
| 12 | 12 | ||||
| 13 | - | // syscall15X calls a function in libc on behalf of the syscall package. | 13 | + | // syscallX calls a function in libc on behalf of the syscall package. |
| 14 | - | // syscall15X takes a pointer to a struct like: | 14 | + | // syscallX takes a pointer to a struct like: |
| 15 | // struct { | 15 | // struct { | ||
| 16 | // fn uintptr | 16 | // fn uintptr | ||
| 17 | // a1 uintptr | 17 | // a1 uintptr | ||
| @@ -33,51 +33,85 @@ | |||||
| 33 | // r2 uintptr | 33 | // r2 uintptr | ||
| 34 | // err uintptr | 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 | // C calling convention (use libcCall). | 37 | // C calling convention (use libcCall). | ||
| 38 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | 38 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 |
| 39 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | 39 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) |
| 40 | - | TEXT syscall15X(SB), NOSPLIT, $0 | 40 | + | TEXT syscallX(SB), NOSPLIT, $0 |
| 41 | // push structure pointer | 41 | // push structure pointer | ||
| 42 | SUBV $STACK_SIZE, R3 | 42 | SUBV $STACK_SIZE, R3 | ||
| 43 | MOVV R4, PTR_ADDRESS(R3) | 43 | MOVV R4, PTR_ADDRESS(R3) | ||
| 44 | MOVV R4, R13 | 44 | MOVV R4, R13 | ||
| 45 | 45 | ||||
| 46 | - | MOVD syscall15Args_f1(R13), F0 // f1 | 46 | + | MOVD syscallArgs_f1(R13), F0 // f1 |
| 47 | - | MOVD syscall15Args_f2(R13), F1 // f2 | 47 | + | MOVD syscallArgs_f2(R13), F1 // f2 |
| 48 | - | MOVD syscall15Args_f3(R13), F2 // f3 | 48 | + | MOVD syscallArgs_f3(R13), F2 // f3 |
| 49 | - | MOVD syscall15Args_f4(R13), F3 // f4 | 49 | + | MOVD syscallArgs_f4(R13), F3 // f4 |
| 50 | - | MOVD syscall15Args_f5(R13), F4 // f5 | 50 | + | MOVD syscallArgs_f5(R13), F4 // f5 |
| 51 | - | MOVD syscall15Args_f6(R13), F5 // f6 | 51 | + | MOVD syscallArgs_f6(R13), F5 // f6 |
| 52 | - | MOVD syscall15Args_f7(R13), F6 // f7 | 52 | + | MOVD syscallArgs_f7(R13), F6 // f7 |
| 53 | - | MOVD syscall15Args_f8(R13), F7 // f8 | 53 | + | MOVD syscallArgs_f8(R13), F7 // f8 |
| 54 | 54 | ||||
| 55 | - | MOVV syscall15Args_a1(R13), R4 // a1 | 55 | + | MOVV syscallArgs_a1(R13), R4 // a1 |
| 56 | - | MOVV syscall15Args_a2(R13), R5 // a2 | 56 | + | MOVV syscallArgs_a2(R13), R5 // a2 |
| 57 | - | MOVV syscall15Args_a3(R13), R6 // a3 | 57 | + | MOVV syscallArgs_a3(R13), R6 // a3 |
| 58 | - | MOVV syscall15Args_a4(R13), R7 // a4 | 58 | + | MOVV syscallArgs_a4(R13), R7 // a4 |
| 59 | - | MOVV syscall15Args_a5(R13), R8 // a5 | 59 | + | MOVV syscallArgs_a5(R13), R8 // a5 |
| 60 | - | MOVV syscall15Args_a6(R13), R9 // a6 | 60 | + | MOVV syscallArgs_a6(R13), R9 // a6 |
| 61 | - | MOVV syscall15Args_a7(R13), R10 // a7 | 61 | + | MOVV syscallArgs_a7(R13), R10 // a7 |
| 62 | - | MOVV syscall15Args_a8(R13), R11 // a8 | 62 | + | MOVV syscallArgs_a8(R13), R11 // a8 |
| 63 | 63 | ||||
| 64 | // push a9-a15 onto stack | 64 | // push a9-a15 onto stack | ||
| 65 | - | MOVV syscall15Args_a9(R13), R12 | 65 | + | MOVV syscallArgs_a9(R13), R12 |
| 66 | MOVV R12, 0(R3) | 66 | MOVV R12, 0(R3) | ||
| 67 | - | MOVV syscall15Args_a10(R13), R12 | 67 | + | MOVV syscallArgs_a10(R13), R12 |
| 68 | MOVV R12, 8(R3) | 68 | MOVV R12, 8(R3) | ||
| 69 | - | MOVV syscall15Args_a11(R13), R12 | 69 | + | MOVV syscallArgs_a11(R13), R12 |
| 70 | MOVV R12, 16(R3) | 70 | MOVV R12, 16(R3) | ||
| 71 | - | MOVV syscall15Args_a12(R13), R12 | 71 | + | MOVV syscallArgs_a12(R13), R12 |
| 72 | MOVV R12, 24(R3) | 72 | MOVV R12, 24(R3) | ||
| 73 | - | MOVV syscall15Args_a13(R13), R12 | 73 | + | MOVV syscallArgs_a13(R13), R12 |
| 74 | MOVV R12, 32(R3) | 74 | MOVV R12, 32(R3) | ||
| 75 | - | MOVV syscall15Args_a14(R13), R12 | 75 | + | MOVV syscallArgs_a14(R13), R12 |
| 76 | MOVV R12, 40(R3) | 76 | MOVV R12, 40(R3) | ||
| 77 | - | MOVV syscall15Args_a15(R13), R12 | 77 | + | MOVV syscallArgs_a15(R13), R12 |
| 78 | MOVV R12, 48(R3) | 78 | MOVV R12, 48(R3) | ||
| 79 | + | MOVV syscallArgs_a16(R13), R12 | |||
| 80 | + | MOVV R12, 56(R3) | |||
| 81 | + | MOVV syscallArgs_a17(R13), R12 | |||
| 82 | + | MOVV R12, 64(R3) | |||
| 83 | + | MOVV syscallArgs_a18(R13), R12 | |||
| 84 | + | MOVV R12, 72(R3) | |||
| 85 | + | MOVV syscallArgs_a19(R13), R12 | |||
| 86 | + | MOVV R12, 80(R3) | |||
| 87 | + | MOVV syscallArgs_a20(R13), R12 | |||
| 88 | + | MOVV R12, 88(R3) | |||
| 89 | + | MOVV syscallArgs_a21(R13), R12 | |||
| 90 | + | MOVV R12, 96(R3) | |||
| 91 | + | MOVV syscallArgs_a22(R13), R12 | |||
| 92 | + | MOVV R12, 104(R3) | |||
| 93 | + | MOVV syscallArgs_a23(R13), R12 | |||
| 94 | + | MOVV R12, 112(R3) | |||
| 95 | + | MOVV syscallArgs_a24(R13), R12 | |||
| 96 | + | MOVV R12, 120(R3) | |||
| 97 | + | MOVV syscallArgs_a25(R13), R12 | |||
| 98 | + | MOVV R12, 128(R3) | |||
| 99 | + | MOVV syscallArgs_a26(R13), R12 | |||
| 100 | + | MOVV R12, 136(R3) | |||
| 101 | + | MOVV syscallArgs_a27(R13), R12 | |||
| 102 | + | MOVV R12, 144(R3) | |||
| 103 | + | MOVV syscallArgs_a28(R13), R12 | |||
| 104 | + | MOVV R12, 152(R3) | |||
| 105 | + | MOVV syscallArgs_a29(R13), R12 | |||
| 106 | + | MOVV R12, 160(R3) | |||
| 107 | + | MOVV syscallArgs_a30(R13), R12 | |||
| 108 | + | MOVV R12, 168(R3) | |||
| 109 | + | MOVV syscallArgs_a31(R13), R12 | |||
| 110 | + | MOVV R12, 176(R3) | |||
| 111 | + | MOVV syscallArgs_a32(R13), R12 | |||
| 112 | + | MOVV R12, 184(R3) | |||
| 79 | 113 | ||||
| 80 | - | MOVV syscall15Args_fn(R13), R12 | 114 | + | MOVV syscallArgs_fn(R13), R12 |
| 81 | JAL (R12) | 115 | JAL (R12) | ||
| 82 | 116 | ||||
| 83 | // pop structure pointer | 117 | // pop structure pointer | ||
| @@ -85,12 +119,13 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||||
| 85 | ADDV $STACK_SIZE, R3 | 119 | ADDV $STACK_SIZE, R3 | ||
| 86 | 120 | ||||
| 87 | // save R4, R5 | 121 | // save R4, R5 | ||
| 88 | - | MOVV R4, syscall15Args_a1(R13) | 122 | + | MOVV R4, syscallArgs_a1(R13) |
| 89 | - | MOVV R5, syscall15Args_a2(R13) | 123 | + | MOVV R5, syscallArgs_a2(R13) |
| 124 | + | MOVV R0, syscallArgs_a3(R13) // no errno here: clear the input argument | |||
| 90 | 125 | ||||
| 91 | // save f0-f3 | 126 | // save f0-f3 | ||
| 92 | - | MOVD F0, syscall15Args_f1(R13) | 127 | + | MOVD F0, syscallArgs_f1(R13) |
| 93 | - | MOVD F1, syscall15Args_f2(R13) | 128 | + | MOVD F1, syscallArgs_f2(R13) |
| 94 | - | MOVD F2, syscall15Args_f3(R13) | 129 | + | MOVD F2, syscallArgs_f3(R13) |
| 95 | - | MOVD F3, syscall15Args_f4(R13) | 130 | + | MOVD F3, syscallArgs_f4(R13) |
| 96 | RET | 131 | RET | ||
M sys_ppc64le.s +42 -36
| @@ -35,10 +35,10 @@ | |||||
| 35 | #define TOC_SAVE 160 | 35 | #define TOC_SAVE 160 | ||
| 36 | #define ARGP_SAVE 168 | 36 | #define ARGP_SAVE 168 | ||
| 37 | 37 | ||||
| 38 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | 38 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 |
| 39 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | 39 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) |
| 40 | 40 | ||||
| 41 | - | TEXT syscall15X(SB), NOSPLIT, $0 | 41 | + | TEXT syscallX(SB), NOSPLIT, $0 |
| 42 | // Prologue: create stack frame | 42 | // Prologue: create stack frame | ||
| 43 | // R3 contains the args pointer on entry | 43 | // R3 contains the args pointer on entry | ||
| 44 | MOVD R1, R12 // save old SP | 44 | MOVD R1, R12 // save old SP | ||
| @@ -51,49 +51,49 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||||
| 51 | // Save args pointer (in R3) | 51 | // Save args pointer (in R3) | ||
| 52 | MOVD R3, ARGP_SAVE(R1) | 52 | MOVD R3, ARGP_SAVE(R1) | ||
| 53 | 53 | ||||
| 54 | - | // R11 := args pointer (syscall15Args*) | 54 | + | // R11 := args pointer (syscallArgs*) |
| 55 | MOVD R3, R11 | 55 | MOVD R3, R11 | ||
| 56 | 56 | ||||
| 57 | // Load float args into F1-F8 | 57 | // Load float args into F1-F8 | ||
| 58 | - | FMOVD syscall15Args_f1(R11), F1 | 58 | + | FMOVD syscallArgs_f1(R11), F1 |
| 59 | - | FMOVD syscall15Args_f2(R11), F2 | 59 | + | FMOVD syscallArgs_f2(R11), F2 |
| 60 | - | FMOVD syscall15Args_f3(R11), F3 | 60 | + | FMOVD syscallArgs_f3(R11), F3 |
| 61 | - | FMOVD syscall15Args_f4(R11), F4 | 61 | + | FMOVD syscallArgs_f4(R11), F4 |
| 62 | - | FMOVD syscall15Args_f5(R11), F5 | 62 | + | FMOVD syscallArgs_f5(R11), F5 |
| 63 | - | FMOVD syscall15Args_f6(R11), F6 | 63 | + | FMOVD syscallArgs_f6(R11), F6 |
| 64 | - | FMOVD syscall15Args_f7(R11), F7 | 64 | + | FMOVD syscallArgs_f7(R11), F7 |
| 65 | - | FMOVD syscall15Args_f8(R11), F8 | 65 | + | FMOVD syscallArgs_f8(R11), F8 |
| 66 | 66 | ||||
| 67 | // Load integer args into R3-R10 | 67 | // Load integer args into R3-R10 | ||
| 68 | - | MOVD syscall15Args_a1(R11), R3 | 68 | + | MOVD syscallArgs_a1(R11), R3 |
| 69 | - | MOVD syscall15Args_a2(R11), R4 | 69 | + | MOVD syscallArgs_a2(R11), R4 |
| 70 | - | MOVD syscall15Args_a3(R11), R5 | 70 | + | MOVD syscallArgs_a3(R11), R5 |
| 71 | - | MOVD syscall15Args_a4(R11), R6 | 71 | + | MOVD syscallArgs_a4(R11), R6 |
| 72 | - | MOVD syscall15Args_a5(R11), R7 | 72 | + | MOVD syscallArgs_a5(R11), R7 |
| 73 | - | MOVD syscall15Args_a6(R11), R8 | 73 | + | MOVD syscallArgs_a6(R11), R8 |
| 74 | - | MOVD syscall15Args_a7(R11), R9 | 74 | + | MOVD syscallArgs_a7(R11), R9 |
| 75 | - | MOVD syscall15Args_a8(R11), R10 | 75 | + | MOVD syscallArgs_a8(R11), R10 |
| 76 | 76 | ||||
| 77 | // Spill a9-a15 onto the stack (stack parameters start at 96(R1)) | 77 | // Spill a9-a15 onto the stack (stack parameters start at 96(R1)) | ||
| 78 | // Per ELFv2: parameter save area is 32-95, stack args start at 96 | 78 | // Per ELFv2: parameter save area is 32-95, stack args start at 96 | ||
| 79 | MOVD ARGP_SAVE(R1), R11 // reload args pointer | 79 | MOVD ARGP_SAVE(R1), R11 // reload args pointer | ||
| 80 | - | MOVD syscall15Args_a9(R11), R12 | 80 | + | MOVD syscallArgs_a9(R11), R12 |
| 81 | MOVD R12, 96(R1) // a9 at 96(R1) | 81 | MOVD R12, 96(R1) // a9 at 96(R1) | ||
| 82 | - | MOVD syscall15Args_a10(R11), R12 | 82 | + | MOVD syscallArgs_a10(R11), R12 |
| 83 | MOVD R12, 104(R1) // a10 at 104(R1) | 83 | MOVD R12, 104(R1) // a10 at 104(R1) | ||
| 84 | - | MOVD syscall15Args_a11(R11), R12 | 84 | + | MOVD syscallArgs_a11(R11), R12 |
| 85 | MOVD R12, 112(R1) // a11 at 112(R1) | 85 | MOVD R12, 112(R1) // a11 at 112(R1) | ||
| 86 | - | MOVD syscall15Args_a12(R11), R12 | 86 | + | MOVD syscallArgs_a12(R11), R12 |
| 87 | MOVD R12, 120(R1) // a12 at 120(R1) | 87 | MOVD R12, 120(R1) // a12 at 120(R1) | ||
| 88 | - | MOVD syscall15Args_a13(R11), R12 | 88 | + | MOVD syscallArgs_a13(R11), R12 |
| 89 | MOVD R12, 128(R1) // a13 at 128(R1) | 89 | MOVD R12, 128(R1) // a13 at 128(R1) | ||
| 90 | - | MOVD syscall15Args_a14(R11), R12 | 90 | + | MOVD syscallArgs_a14(R11), R12 |
| 91 | MOVD R12, 136(R1) // a14 at 136(R1) | 91 | MOVD R12, 136(R1) // a14 at 136(R1) | ||
| 92 | - | MOVD syscall15Args_a15(R11), R12 | 92 | + | MOVD syscallArgs_a15(R11), R12 |
| 93 | MOVD R12, 144(R1) // a15 at 144(R1) | 93 | MOVD R12, 144(R1) // a15 at 144(R1) | ||
| 94 | 94 | ||||
| 95 | // Call function: load fn and call | 95 | // Call function: load fn and call | ||
| 96 | - | MOVD syscall15Args_fn(R11), R12 | 96 | + | MOVD syscallArgs_fn(R11), R12 |
| 97 | MOVD R12, CTR | 97 | MOVD R12, CTR | ||
| 98 | BL (CTR) | 98 | BL (CTR) | ||
| 99 | 99 | ||||
| @@ -104,14 +104,20 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||||
| 104 | MOVD ARGP_SAVE(R1), R11 | 104 | MOVD ARGP_SAVE(R1), R11 | ||
| 105 | 105 | ||||
| 106 | // Store integer results back (R3, R4) | 106 | // Store integer results back (R3, R4) | ||
| 107 | - | MOVD R3, syscall15Args_a1(R11) | 107 | + | MOVD R3, syscallArgs_a1(R11) |
| 108 | - | MOVD R4, syscall15Args_a2(R11) | 108 | + | MOVD R4, syscallArgs_a2(R11) |
| 109 | - | 109 | + | MOVD $0, R3 | |
| 110 | - | // Store float return values (F1-F4) | 110 | + | MOVD R3, syscallArgs_a3(R11) // no errno here: clear the input argument |
| 111 | - | FMOVD F1, syscall15Args_f1(R11) | 111 | + | |
| 112 | - | FMOVD F2, syscall15Args_f2(R11) | 112 | + | // Store float return values F1-F8 (an HFA may span up to eight). |
| 113 | - | FMOVD F3, syscall15Args_f3(R11) | 113 | + | FMOVD F1, syscallArgs_f1(R11) |
| 114 | - | FMOVD F4, syscall15Args_f4(R11) | 114 | + | FMOVD F2, syscallArgs_f2(R11) |
| 115 | + | FMOVD F3, syscallArgs_f3(R11) | |||
| 116 | + | FMOVD F4, syscallArgs_f4(R11) | |||
| 117 | + | FMOVD F5, syscallArgs_f5(R11) | |||
| 118 | + | FMOVD F6, syscallArgs_f6(R11) | |||
| 119 | + | FMOVD F7, syscallArgs_f7(R11) | |||
| 120 | + | FMOVD F8, syscallArgs_f8(R11) | |||
| 115 | 121 | ||||
| 116 | // Epilogue: restore and return | 122 | // Epilogue: restore and return | ||
| 117 | MOVD LR_SAVE(R1), R12 | 123 | MOVD LR_SAVE(R1), R12 | ||
M sys_riscv64.s +84 -50
| @@ -8,22 +8,21 @@ | |||||
| 8 | #include "funcdata.h" | 8 | #include "funcdata.h" | ||
| 9 | 9 | ||||
| 10 | // Stack usage: | 10 | // Stack usage: | ||
| 11 | - | // 0(SP) - 56(SP): stack args a9-a15 (7 * 8 bytes = 56) | 11 | + | // 0(SP) - 192(SP): stack args a9-a32 (24 * 8 bytes) |
| 12 | - | // 56(SP) - 64(SP): saved RA (x1) | 12 | + | // 192(SP) - 200(SP): saved RA (x1) |
| 13 | - | // 64(SP) - 72(SP): saved X9 (s1) | 13 | + | // 200(SP) - 208(SP): saved X9 (s1) |
| 14 | - | // 72(SP) - 80(SP): saved X18 (s2) | 14 | + | // 208(SP) - 216(SP): saved X18 (s2) |
| 15 | - | // 80(SP) - 88(SP): saved args pointer (original X10) | 15 | + | // 216(SP) - 224(SP): saved args pointer (original X10) |
| 16 | - | // 88(SP) - 96(SP): padding | 16 | + | #define STACK_SIZE 224 |
| 17 | - | #define STACK_SIZE 96 | 17 | + | #define SAVE_RA 192 |
| 18 | - | #define SAVE_RA 56 | 18 | + | #define SAVE_X9 200 |
| 19 | - | #define SAVE_X9 64 | 19 | + | #define SAVE_X18 208 |
| 20 | - | #define SAVE_X18 72 | 20 | + | #define SAVE_ARGP 216 |
| 21 | - | #define SAVE_ARGP 80 | 21 | + | |
| 22 | - | 22 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 | |
| 23 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | 23 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) |
| 24 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | 24 | + | |
| 25 | - | 25 | + | TEXT syscallX(SB), NOSPLIT, $0 | |
| 26 | - | TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 27 | // Allocate stack frame (keeps 16-byte alignment) | 26 | // Allocate stack frame (keeps 16-byte alignment) | ||
| 28 | SUB $STACK_SIZE, SP | 27 | SUB $STACK_SIZE, SP | ||
| 29 | 28 | ||||
| @@ -35,62 +34,97 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||||
| 35 | // Save original args pointer (in a0/X10) | 34 | // Save original args pointer (in a0/X10) | ||
| 36 | MOV X10, SAVE_ARGP(SP) | 35 | MOV X10, SAVE_ARGP(SP) | ||
| 37 | 36 | ||||
| 38 | - | // X9 := args pointer (syscall15Args*) | 37 | + | // X9 := args pointer (syscallArgs*) |
| 39 | MOV X10, X9 | 38 | MOV X10, X9 | ||
| 40 | 39 | ||||
| 41 | // Load float args into fa0-fa7 (F10-F17) | 40 | // Load float args into fa0-fa7 (F10-F17) | ||
| 42 | - | MOVD syscall15Args_f1(X9), F10 | 41 | + | MOVD syscallArgs_f1(X9), F10 |
| 43 | - | MOVD syscall15Args_f2(X9), F11 | 42 | + | MOVD syscallArgs_f2(X9), F11 |
| 44 | - | MOVD syscall15Args_f3(X9), F12 | 43 | + | MOVD syscallArgs_f3(X9), F12 |
| 45 | - | MOVD syscall15Args_f4(X9), F13 | 44 | + | MOVD syscallArgs_f4(X9), F13 |
| 46 | - | MOVD syscall15Args_f5(X9), F14 | 45 | + | MOVD syscallArgs_f5(X9), F14 |
| 47 | - | MOVD syscall15Args_f6(X9), F15 | 46 | + | MOVD syscallArgs_f6(X9), F15 |
| 48 | - | MOVD syscall15Args_f7(X9), F16 | 47 | + | MOVD syscallArgs_f7(X9), F16 |
| 49 | - | MOVD syscall15Args_f8(X9), F17 | 48 | + | MOVD syscallArgs_f8(X9), F17 |
| 50 | 49 | ||||
| 51 | // Load integer args into a0-a7 (X10-X17) | 50 | // Load integer args into a0-a7 (X10-X17) | ||
| 52 | - | MOV syscall15Args_a1(X9), X10 | 51 | + | MOV syscallArgs_a1(X9), X10 |
| 53 | - | MOV syscall15Args_a2(X9), X11 | 52 | + | MOV syscallArgs_a2(X9), X11 |
| 54 | - | MOV syscall15Args_a3(X9), X12 | 53 | + | MOV syscallArgs_a3(X9), X12 |
| 55 | - | MOV syscall15Args_a4(X9), X13 | 54 | + | MOV syscallArgs_a4(X9), X13 |
| 56 | - | MOV syscall15Args_a5(X9), X14 | 55 | + | MOV syscallArgs_a5(X9), X14 |
| 57 | - | MOV syscall15Args_a6(X9), X15 | 56 | + | MOV syscallArgs_a6(X9), X15 |
| 58 | - | MOV syscall15Args_a7(X9), X16 | 57 | + | MOV syscallArgs_a7(X9), X16 |
| 59 | - | MOV syscall15Args_a8(X9), X17 | 58 | + | MOV syscallArgs_a8(X9), X17 |
| 60 | - | 59 | + | ||
| 61 | - | // Spill a9-a15 onto the stack (C ABI) | 60 | + | // Spill a9-a32 onto the stack (C ABI) |
| 62 | - | MOV syscall15Args_a9(X9), X18 | 61 | + | MOV syscallArgs_a9(X9), X18 |
| 63 | MOV X18, 0(SP) | 62 | MOV X18, 0(SP) | ||
| 64 | - | MOV syscall15Args_a10(X9), X18 | 63 | + | MOV syscallArgs_a10(X9), X18 |
| 65 | MOV X18, 8(SP) | 64 | MOV X18, 8(SP) | ||
| 66 | - | MOV syscall15Args_a11(X9), X18 | 65 | + | MOV syscallArgs_a11(X9), X18 |
| 67 | MOV X18, 16(SP) | 66 | MOV X18, 16(SP) | ||
| 68 | - | MOV syscall15Args_a12(X9), X18 | 67 | + | MOV syscallArgs_a12(X9), X18 |
| 69 | MOV X18, 24(SP) | 68 | MOV X18, 24(SP) | ||
| 70 | - | MOV syscall15Args_a13(X9), X18 | 69 | + | MOV syscallArgs_a13(X9), X18 |
| 71 | MOV X18, 32(SP) | 70 | MOV X18, 32(SP) | ||
| 72 | - | MOV syscall15Args_a14(X9), X18 | 71 | + | MOV syscallArgs_a14(X9), X18 |
| 73 | MOV X18, 40(SP) | 72 | MOV X18, 40(SP) | ||
| 74 | - | MOV syscall15Args_a15(X9), X18 | 73 | + | MOV syscallArgs_a15(X9), X18 |
| 75 | MOV X18, 48(SP) | 74 | MOV X18, 48(SP) | ||
| 75 | + | MOV syscallArgs_a16(X9), X18 | |||
| 76 | + | MOV X18, 56(SP) | |||
| 77 | + | MOV syscallArgs_a17(X9), X18 | |||
| 78 | + | MOV X18, 64(SP) | |||
| 79 | + | MOV syscallArgs_a18(X9), X18 | |||
| 80 | + | MOV X18, 72(SP) | |||
| 81 | + | MOV syscallArgs_a19(X9), X18 | |||
| 82 | + | MOV X18, 80(SP) | |||
| 83 | + | MOV syscallArgs_a20(X9), X18 | |||
| 84 | + | MOV X18, 88(SP) | |||
| 85 | + | MOV syscallArgs_a21(X9), X18 | |||
| 86 | + | MOV X18, 96(SP) | |||
| 87 | + | MOV syscallArgs_a22(X9), X18 | |||
| 88 | + | MOV X18, 104(SP) | |||
| 89 | + | MOV syscallArgs_a23(X9), X18 | |||
| 90 | + | MOV X18, 112(SP) | |||
| 91 | + | MOV syscallArgs_a24(X9), X18 | |||
| 92 | + | MOV X18, 120(SP) | |||
| 93 | + | MOV syscallArgs_a25(X9), X18 | |||
| 94 | + | MOV X18, 128(SP) | |||
| 95 | + | MOV syscallArgs_a26(X9), X18 | |||
| 96 | + | MOV X18, 136(SP) | |||
| 97 | + | MOV syscallArgs_a27(X9), X18 | |||
| 98 | + | MOV X18, 144(SP) | |||
| 99 | + | MOV syscallArgs_a28(X9), X18 | |||
| 100 | + | MOV X18, 152(SP) | |||
| 101 | + | MOV syscallArgs_a29(X9), X18 | |||
| 102 | + | MOV X18, 160(SP) | |||
| 103 | + | MOV syscallArgs_a30(X9), X18 | |||
| 104 | + | MOV X18, 168(SP) | |||
| 105 | + | MOV syscallArgs_a31(X9), X18 | |||
| 106 | + | MOV X18, 176(SP) | |||
| 107 | + | MOV syscallArgs_a32(X9), X18 | |||
| 108 | + | MOV X18, 184(SP) | |||
| 76 | 109 | ||||
| 77 | // Call fn | 110 | // Call fn | ||
| 78 | // IMPORTANT: preserve RA across this call (we saved it above) | 111 | // IMPORTANT: preserve RA across this call (we saved it above) | ||
| 79 | - | MOV syscall15Args_fn(X9), X18 | 112 | + | MOV syscallArgs_fn(X9), X18 |
| 80 | CALL X18 | 113 | CALL X18 | ||
| 81 | 114 | ||||
| 82 | - | // Restore args pointer (syscall15Args*) for storing results | 115 | + | // Restore args pointer (syscallArgs*) for storing results |
| 83 | MOV SAVE_ARGP(SP), X9 | 116 | MOV SAVE_ARGP(SP), X9 | ||
| 84 | 117 | ||||
| 85 | // Store results back | 118 | // Store results back | ||
| 86 | - | MOV X10, syscall15Args_a1(X9) | 119 | + | MOV X10, syscallArgs_a1(X9) |
| 87 | - | MOV X11, syscall15Args_a2(X9) | 120 | + | MOV X11, syscallArgs_a2(X9) |
| 121 | + | MOV X0, syscallArgs_a3(X9) // no errno here: clear the input argument | |||
| 88 | 122 | ||||
| 89 | // Store back float return regs if used by your ABI contract | 123 | // Store back float return regs if used by your ABI contract | ||
| 90 | - | MOVD F10, syscall15Args_f1(X9) | 124 | + | MOVD F10, syscallArgs_f1(X9) |
| 91 | - | MOVD F11, syscall15Args_f2(X9) | 125 | + | MOVD F11, syscallArgs_f2(X9) |
| 92 | - | MOVD F12, syscall15Args_f3(X9) | 126 | + | MOVD F12, syscallArgs_f3(X9) |
| 93 | - | MOVD F13, syscall15Args_f4(X9) | 127 | + | MOVD F13, syscallArgs_f4(X9) |
| 94 | 128 | ||||
| 95 | // Restore callee-saved regs and return address | 129 | // Restore callee-saved regs and return address | ||
| 96 | MOV SAVE_X18(SP), X18 | 130 | MOV SAVE_X18(SP), X18 | ||
M sys_s390x.s +70 -36
| @@ -24,19 +24,18 @@ | |||||
| 24 | // | 24 | // | ||
| 25 | // We need space for: | 25 | // We need space for: | ||
| 26 | // - 160 bytes standard frame (with register save area) | 26 | // - 160 bytes standard frame (with register save area) | ||
| 27 | - | // - Stack args a6-a15 (10 * 8 = 80 bytes) | 27 | + | // - Stack args a6-a32 (27 * 8 = 216 bytes) |
| 28 | // - Saved args pointer (8 bytes) | 28 | // - Saved args pointer (8 bytes) | ||
| 29 | - | // - Padding for alignment | 29 | + | // Total: 384 bytes |
| 30 | - | // Total: 264 bytes (rounded to 8-byte alignment) | |||
| 31 | 30 | ||||
| 32 | - | #define STACK_SIZE 264 | 31 | + | #define STACK_SIZE 384 |
| 33 | #define STACK_ARGS 160 | 32 | #define STACK_ARGS 160 | ||
| 34 | - | #define ARGP_SAVE 248 | 33 | + | #define ARGP_SAVE 376 |
| 35 | 34 | ||||
| 36 | - | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | 35 | + | GLOBL ·syscallXABI0(SB), NOPTR|RODATA, $8 |
| 37 | - | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | 36 | + | DATA ·syscallXABI0(SB)/8, $syscallX(SB) |
| 38 | 37 | ||||
| 39 | - | TEXT syscall15X(SB), NOSPLIT, $0 | 38 | + | TEXT syscallX(SB), NOSPLIT, $0 |
| 40 | // On entry, R2 contains the args pointer | 39 | // On entry, R2 contains the args pointer | ||
| 41 | // Save callee-saved registers in caller's frame (per ABI) | 40 | // Save callee-saved registers in caller's frame (per ABI) | ||
| 42 | STMG R6, R15, 48(R15) | 41 | STMG R6, R15, 48(R15) | ||
| @@ -49,61 +48,96 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||||
| 49 | // Save args pointer | 48 | // Save args pointer | ||
| 50 | MOVD R2, ARGP_SAVE(R15) | 49 | MOVD R2, ARGP_SAVE(R15) | ||
| 51 | 50 | ||||
| 52 | - | // R9 := args pointer (syscall15Args*) | 51 | + | // R9 := args pointer (syscallArgs*) |
| 53 | MOVD R2, R9 | 52 | MOVD R2, R9 | ||
| 54 | 53 | ||||
| 55 | // Load float args into F0, F2, F4, F6 (s390x uses even-numbered FPRs) | 54 | // Load float args into F0, F2, F4, F6 (s390x uses even-numbered FPRs) | ||
| 56 | - | FMOVD syscall15Args_f1(R9), F0 | 55 | + | FMOVD syscallArgs_f1(R9), F0 |
| 57 | - | FMOVD syscall15Args_f2(R9), F2 | 56 | + | FMOVD syscallArgs_f2(R9), F2 |
| 58 | - | FMOVD syscall15Args_f3(R9), F4 | 57 | + | FMOVD syscallArgs_f3(R9), F4 |
| 59 | - | FMOVD syscall15Args_f4(R9), F6 | 58 | + | FMOVD syscallArgs_f4(R9), F6 |
| 60 | 59 | ||||
| 61 | // Load integer args into R2-R6 (5 registers) | 60 | // Load integer args into R2-R6 (5 registers) | ||
| 62 | - | MOVD syscall15Args_a1(R9), R2 | 61 | + | MOVD syscallArgs_a1(R9), R2 |
| 63 | - | MOVD syscall15Args_a2(R9), R3 | 62 | + | MOVD syscallArgs_a2(R9), R3 |
| 64 | - | MOVD syscall15Args_a3(R9), R4 | 63 | + | MOVD syscallArgs_a3(R9), R4 |
| 65 | - | MOVD syscall15Args_a4(R9), R5 | 64 | + | MOVD syscallArgs_a4(R9), R5 |
| 66 | - | MOVD syscall15Args_a5(R9), R6 | 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 | MOVD ARGP_SAVE(R15), R9 // reload args pointer | 68 | MOVD ARGP_SAVE(R15), R9 // reload args pointer | ||
| 70 | - | MOVD syscall15Args_a6(R9), R1 | 69 | + | MOVD syscallArgs_a6(R9), R1 |
| 71 | MOVD R1, (STACK_ARGS+0*8)(R15) | 70 | MOVD R1, (STACK_ARGS+0*8)(R15) | ||
| 72 | - | MOVD syscall15Args_a7(R9), R1 | 71 | + | MOVD syscallArgs_a7(R9), R1 |
| 73 | MOVD R1, (STACK_ARGS+1*8)(R15) | 72 | MOVD R1, (STACK_ARGS+1*8)(R15) | ||
| 74 | - | MOVD syscall15Args_a8(R9), R1 | 73 | + | MOVD syscallArgs_a8(R9), R1 |
| 75 | MOVD R1, (STACK_ARGS+2*8)(R15) | 74 | MOVD R1, (STACK_ARGS+2*8)(R15) | ||
| 76 | - | MOVD syscall15Args_a9(R9), R1 | 75 | + | MOVD syscallArgs_a9(R9), R1 |
| 77 | MOVD R1, (STACK_ARGS+3*8)(R15) | 76 | MOVD R1, (STACK_ARGS+3*8)(R15) | ||
| 78 | - | MOVD syscall15Args_a10(R9), R1 | 77 | + | MOVD syscallArgs_a10(R9), R1 |
| 79 | MOVD R1, (STACK_ARGS+4*8)(R15) | 78 | MOVD R1, (STACK_ARGS+4*8)(R15) | ||
| 80 | - | MOVD syscall15Args_a11(R9), R1 | 79 | + | MOVD syscallArgs_a11(R9), R1 |
| 81 | MOVD R1, (STACK_ARGS+5*8)(R15) | 80 | MOVD R1, (STACK_ARGS+5*8)(R15) | ||
| 82 | - | MOVD syscall15Args_a12(R9), R1 | 81 | + | MOVD syscallArgs_a12(R9), R1 |
| 83 | MOVD R1, (STACK_ARGS+6*8)(R15) | 82 | MOVD R1, (STACK_ARGS+6*8)(R15) | ||
| 84 | - | MOVD syscall15Args_a13(R9), R1 | 83 | + | MOVD syscallArgs_a13(R9), R1 |
| 85 | MOVD R1, (STACK_ARGS+7*8)(R15) | 84 | MOVD R1, (STACK_ARGS+7*8)(R15) | ||
| 86 | - | MOVD syscall15Args_a14(R9), R1 | 85 | + | MOVD syscallArgs_a14(R9), R1 |
| 87 | MOVD R1, (STACK_ARGS+8*8)(R15) | 86 | MOVD R1, (STACK_ARGS+8*8)(R15) | ||
| 88 | - | MOVD syscall15Args_a15(R9), R1 | 87 | + | MOVD syscallArgs_a15(R9), R1 |
| 89 | MOVD R1, (STACK_ARGS+9*8)(R15) | 88 | MOVD R1, (STACK_ARGS+9*8)(R15) | ||
| 89 | + | MOVD syscallArgs_a16(R9), R1 | |||
| 90 | + | MOVD R1, (STACK_ARGS+10*8)(R15) | |||
| 91 | + | MOVD syscallArgs_a17(R9), R1 | |||
| 92 | + | MOVD R1, (STACK_ARGS+11*8)(R15) | |||
| 93 | + | MOVD syscallArgs_a18(R9), R1 | |||
| 94 | + | MOVD R1, (STACK_ARGS+12*8)(R15) | |||
| 95 | + | MOVD syscallArgs_a19(R9), R1 | |||
| 96 | + | MOVD R1, (STACK_ARGS+13*8)(R15) | |||
| 97 | + | MOVD syscallArgs_a20(R9), R1 | |||
| 98 | + | MOVD R1, (STACK_ARGS+14*8)(R15) | |||
| 99 | + | MOVD syscallArgs_a21(R9), R1 | |||
| 100 | + | MOVD R1, (STACK_ARGS+15*8)(R15) | |||
| 101 | + | MOVD syscallArgs_a22(R9), R1 | |||
| 102 | + | MOVD R1, (STACK_ARGS+16*8)(R15) | |||
| 103 | + | MOVD syscallArgs_a23(R9), R1 | |||
| 104 | + | MOVD R1, (STACK_ARGS+17*8)(R15) | |||
| 105 | + | MOVD syscallArgs_a24(R9), R1 | |||
| 106 | + | MOVD R1, (STACK_ARGS+18*8)(R15) | |||
| 107 | + | MOVD syscallArgs_a25(R9), R1 | |||
| 108 | + | MOVD R1, (STACK_ARGS+19*8)(R15) | |||
| 109 | + | MOVD syscallArgs_a26(R9), R1 | |||
| 110 | + | MOVD R1, (STACK_ARGS+20*8)(R15) | |||
| 111 | + | MOVD syscallArgs_a27(R9), R1 | |||
| 112 | + | MOVD R1, (STACK_ARGS+21*8)(R15) | |||
| 113 | + | MOVD syscallArgs_a28(R9), R1 | |||
| 114 | + | MOVD R1, (STACK_ARGS+22*8)(R15) | |||
| 115 | + | MOVD syscallArgs_a29(R9), R1 | |||
| 116 | + | MOVD R1, (STACK_ARGS+23*8)(R15) | |||
| 117 | + | MOVD syscallArgs_a30(R9), R1 | |||
| 118 | + | MOVD R1, (STACK_ARGS+24*8)(R15) | |||
| 119 | + | MOVD syscallArgs_a31(R9), R1 | |||
| 120 | + | MOVD R1, (STACK_ARGS+25*8)(R15) | |||
| 121 | + | MOVD syscallArgs_a32(R9), R1 | |||
| 122 | + | MOVD R1, (STACK_ARGS+26*8)(R15) | |||
| 90 | 123 | ||||
| 91 | // Call function | 124 | // Call function | ||
| 92 | - | MOVD syscall15Args_fn(R9), R1 | 125 | + | MOVD syscallArgs_fn(R9), R1 |
| 93 | BL (R1) | 126 | BL (R1) | ||
| 94 | 127 | ||||
| 95 | // Restore args pointer for storing results | 128 | // Restore args pointer for storing results | ||
| 96 | MOVD ARGP_SAVE(R15), R9 | 129 | MOVD ARGP_SAVE(R15), R9 | ||
| 97 | 130 | ||||
| 98 | // Store integer results back (R2, R3) | 131 | // Store integer results back (R2, R3) | ||
| 99 | - | MOVD R2, syscall15Args_a1(R9) | 132 | + | MOVD R2, syscallArgs_a1(R9) |
| 100 | - | MOVD R3, syscall15Args_a2(R9) | 133 | + | MOVD R3, syscallArgs_a2(R9) |
| 134 | + | MOVD $0, syscallArgs_a3(R9) // no errno here: clear the input argument | |||
| 101 | 135 | ||||
| 102 | // Store float return values (F0, F2, F4, F6) | 136 | // Store float return values (F0, F2, F4, F6) | ||
| 103 | - | FMOVD F0, syscall15Args_f1(R9) | 137 | + | FMOVD F0, syscallArgs_f1(R9) |
| 104 | - | FMOVD F2, syscall15Args_f2(R9) | 138 | + | FMOVD F2, syscallArgs_f2(R9) |
| 105 | - | FMOVD F4, syscall15Args_f3(R9) | 139 | + | FMOVD F4, syscallArgs_f3(R9) |
| 106 | - | FMOVD F6, syscall15Args_f4(R9) | 140 | + | FMOVD F6, syscallArgs_f4(R9) |
| 107 | 141 | ||||
| 108 | // Deallocate stack frame | 142 | // Deallocate stack frame | ||
| 109 | ADD $STACK_SIZE, R15 | 143 | ADD $STACK_SIZE, R15 | ||
M sys_unix_386.s +82 -74
| @@ -29,9 +29,10 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 29 | // 0-15: saved callee-saved registers (BX, SI, DI, BP) | 29 | // 0-15: saved callee-saved registers (BX, SI, DI, BP) | ||
| 30 | // 16-19: saved callback index | 30 | // 16-19: saved callback index | ||
| 31 | // 20-23: saved return address | 31 | // 20-23: saved return address | ||
| 32 | - | // 24-35: callbackArgs struct (12 bytes) | 32 | + | // 24-47: callbackArgs struct (24 bytes: index, args, result[4], |
| 33 | - | // 36-291: copy of C arguments (256 bytes for 64 args, matching callbackMaxFrame) | 33 | + | // matching Go's callbackArgs with room for 64-bit returns) |
| 34 | - | // Total: 292 bytes, round up to 304 for alignment | 34 | + | // 48-303: copy of C arguments (256 bytes for 64 args, matching callbackMaxFrame) |
| 35 | + | // Total: 304 bytes, already 16-byte aligned | |||
| 35 | SUBL $304, SP | 36 | SUBL $304, SP | ||
| 36 | 37 | ||||
| 37 | // Save callee-saved registers | 38 | // Save callee-saved registers | ||
| @@ -46,148 +47,152 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 46 | 47 | ||||
| 47 | // Copy C arguments from original stack location to our frame | 48 | // Copy C arguments from original stack location to our frame | ||
| 48 | // Original args start at 304+4(SP) = 308(SP) (past our frame + original return addr) | 49 | // Original args start at 304+4(SP) = 308(SP) (past our frame + original return addr) | ||
| 49 | - | // Copy to our frame at 36(SP) | 50 | + | // Copy to our frame at 48(SP) |
| 50 | // Copy 64 arguments (256 bytes, matching callbackMaxFrame = 64 * ptrSize) | 51 | // Copy 64 arguments (256 bytes, matching callbackMaxFrame = 64 * ptrSize) | ||
| 51 | MOVL 308(SP), AX | 52 | MOVL 308(SP), AX | ||
| 52 | - | MOVL AX, 36(SP) | 53 | + | MOVL AX, 48(SP) |
| 53 | MOVL 312(SP), AX | 54 | MOVL 312(SP), AX | ||
| 54 | - | MOVL AX, 40(SP) | 55 | + | MOVL AX, 52(SP) |
| 55 | MOVL 316(SP), AX | 56 | MOVL 316(SP), AX | ||
| 56 | - | MOVL AX, 44(SP) | 57 | + | MOVL AX, 56(SP) |
| 57 | MOVL 320(SP), AX | 58 | MOVL 320(SP), AX | ||
| 58 | - | MOVL AX, 48(SP) | 59 | + | MOVL AX, 60(SP) |
| 59 | MOVL 324(SP), AX | 60 | MOVL 324(SP), AX | ||
| 60 | - | MOVL AX, 52(SP) | 61 | + | MOVL AX, 64(SP) |
| 61 | MOVL 328(SP), AX | 62 | MOVL 328(SP), AX | ||
| 62 | - | MOVL AX, 56(SP) | 63 | + | MOVL AX, 68(SP) |
| 63 | MOVL 332(SP), AX | 64 | MOVL 332(SP), AX | ||
| 64 | - | MOVL AX, 60(SP) | 65 | + | MOVL AX, 72(SP) |
| 65 | MOVL 336(SP), AX | 66 | MOVL 336(SP), AX | ||
| 66 | - | MOVL AX, 64(SP) | 67 | + | MOVL AX, 76(SP) |
| 67 | MOVL 340(SP), AX | 68 | MOVL 340(SP), AX | ||
| 68 | - | MOVL AX, 68(SP) | 69 | + | MOVL AX, 80(SP) |
| 69 | MOVL 344(SP), AX | 70 | MOVL 344(SP), AX | ||
| 70 | - | MOVL AX, 72(SP) | 71 | + | MOVL AX, 84(SP) |
| 71 | MOVL 348(SP), AX | 72 | MOVL 348(SP), AX | ||
| 72 | - | MOVL AX, 76(SP) | 73 | + | MOVL AX, 88(SP) |
| 73 | MOVL 352(SP), AX | 74 | MOVL 352(SP), AX | ||
| 74 | - | MOVL AX, 80(SP) | 75 | + | MOVL AX, 92(SP) |
| 75 | MOVL 356(SP), AX | 76 | MOVL 356(SP), AX | ||
| 76 | - | MOVL AX, 84(SP) | 77 | + | MOVL AX, 96(SP) |
| 77 | MOVL 360(SP), AX | 78 | MOVL 360(SP), AX | ||
| 78 | - | MOVL AX, 88(SP) | 79 | + | MOVL AX, 100(SP) |
| 79 | MOVL 364(SP), AX | 80 | MOVL 364(SP), AX | ||
| 80 | - | MOVL AX, 92(SP) | 81 | + | MOVL AX, 104(SP) |
| 81 | MOVL 368(SP), AX | 82 | MOVL 368(SP), AX | ||
| 82 | - | MOVL AX, 96(SP) | 83 | + | MOVL AX, 108(SP) |
| 83 | MOVL 372(SP), AX | 84 | MOVL 372(SP), AX | ||
| 84 | - | MOVL AX, 100(SP) | 85 | + | MOVL AX, 112(SP) |
| 85 | MOVL 376(SP), AX | 86 | MOVL 376(SP), AX | ||
| 86 | - | MOVL AX, 104(SP) | 87 | + | MOVL AX, 116(SP) |
| 87 | MOVL 380(SP), AX | 88 | MOVL 380(SP), AX | ||
| 88 | - | MOVL AX, 108(SP) | 89 | + | MOVL AX, 120(SP) |
| 89 | MOVL 384(SP), AX | 90 | MOVL 384(SP), AX | ||
| 90 | - | MOVL AX, 112(SP) | 91 | + | MOVL AX, 124(SP) |
| 91 | MOVL 388(SP), AX | 92 | MOVL 388(SP), AX | ||
| 92 | - | MOVL AX, 116(SP) | 93 | + | MOVL AX, 128(SP) |
| 93 | MOVL 392(SP), AX | 94 | MOVL 392(SP), AX | ||
| 94 | - | MOVL AX, 120(SP) | 95 | + | MOVL AX, 132(SP) |
| 95 | MOVL 396(SP), AX | 96 | MOVL 396(SP), AX | ||
| 96 | - | MOVL AX, 124(SP) | 97 | + | MOVL AX, 136(SP) |
| 97 | MOVL 400(SP), AX | 98 | MOVL 400(SP), AX | ||
| 98 | - | MOVL AX, 128(SP) | 99 | + | MOVL AX, 140(SP) |
| 99 | MOVL 404(SP), AX | 100 | MOVL 404(SP), AX | ||
| 100 | - | MOVL AX, 132(SP) | 101 | + | MOVL AX, 144(SP) |
| 101 | MOVL 408(SP), AX | 102 | MOVL 408(SP), AX | ||
| 102 | - | MOVL AX, 136(SP) | 103 | + | MOVL AX, 148(SP) |
| 103 | MOVL 412(SP), AX | 104 | MOVL 412(SP), AX | ||
| 104 | - | MOVL AX, 140(SP) | 105 | + | MOVL AX, 152(SP) |
| 105 | MOVL 416(SP), AX | 106 | MOVL 416(SP), AX | ||
| 106 | - | MOVL AX, 144(SP) | 107 | + | MOVL AX, 156(SP) |
| 107 | MOVL 420(SP), AX | 108 | MOVL 420(SP), AX | ||
| 108 | - | MOVL AX, 148(SP) | 109 | + | MOVL AX, 160(SP) |
| 109 | MOVL 424(SP), AX | 110 | MOVL 424(SP), AX | ||
| 110 | - | MOVL AX, 152(SP) | 111 | + | MOVL AX, 164(SP) |
| 111 | MOVL 428(SP), AX | 112 | MOVL 428(SP), AX | ||
| 112 | - | MOVL AX, 156(SP) | 113 | + | MOVL AX, 168(SP) |
| 113 | MOVL 432(SP), AX | 114 | MOVL 432(SP), AX | ||
| 114 | - | MOVL AX, 160(SP) | 115 | + | MOVL AX, 172(SP) |
| 115 | MOVL 436(SP), AX | 116 | MOVL 436(SP), AX | ||
| 116 | - | MOVL AX, 164(SP) | 117 | + | MOVL AX, 176(SP) |
| 117 | MOVL 440(SP), AX | 118 | MOVL 440(SP), AX | ||
| 118 | - | MOVL AX, 168(SP) | 119 | + | MOVL AX, 180(SP) |
| 119 | MOVL 444(SP), AX | 120 | MOVL 444(SP), AX | ||
| 120 | - | MOVL AX, 172(SP) | 121 | + | MOVL AX, 184(SP) |
| 121 | MOVL 448(SP), AX | 122 | MOVL 448(SP), AX | ||
| 122 | - | MOVL AX, 176(SP) | 123 | + | MOVL AX, 188(SP) |
| 123 | MOVL 452(SP), AX | 124 | MOVL 452(SP), AX | ||
| 124 | - | MOVL AX, 180(SP) | 125 | + | MOVL AX, 192(SP) |
| 125 | MOVL 456(SP), AX | 126 | MOVL 456(SP), AX | ||
| 126 | - | MOVL AX, 184(SP) | 127 | + | MOVL AX, 196(SP) |
| 127 | MOVL 460(SP), AX | 128 | MOVL 460(SP), AX | ||
| 128 | - | MOVL AX, 188(SP) | 129 | + | MOVL AX, 200(SP) |
| 129 | MOVL 464(SP), AX | 130 | MOVL 464(SP), AX | ||
| 130 | - | MOVL AX, 192(SP) | 131 | + | MOVL AX, 204(SP) |
| 131 | MOVL 468(SP), AX | 132 | MOVL 468(SP), AX | ||
| 132 | - | MOVL AX, 196(SP) | 133 | + | MOVL AX, 208(SP) |
| 133 | MOVL 472(SP), AX | 134 | MOVL 472(SP), AX | ||
| 134 | - | MOVL AX, 200(SP) | 135 | + | MOVL AX, 212(SP) |
| 135 | MOVL 476(SP), AX | 136 | MOVL 476(SP), AX | ||
| 136 | - | MOVL AX, 204(SP) | 137 | + | MOVL AX, 216(SP) |
| 137 | MOVL 480(SP), AX | 138 | MOVL 480(SP), AX | ||
| 138 | - | MOVL AX, 208(SP) | 139 | + | MOVL AX, 220(SP) |
| 139 | MOVL 484(SP), AX | 140 | MOVL 484(SP), AX | ||
| 140 | - | MOVL AX, 212(SP) | 141 | + | MOVL AX, 224(SP) |
| 141 | MOVL 488(SP), AX | 142 | MOVL 488(SP), AX | ||
| 142 | - | MOVL AX, 216(SP) | 143 | + | MOVL AX, 228(SP) |
| 143 | MOVL 492(SP), AX | 144 | MOVL 492(SP), AX | ||
| 144 | - | MOVL AX, 220(SP) | 145 | + | MOVL AX, 232(SP) |
| 145 | MOVL 496(SP), AX | 146 | MOVL 496(SP), AX | ||
| 146 | - | MOVL AX, 224(SP) | 147 | + | MOVL AX, 236(SP) |
| 147 | MOVL 500(SP), AX | 148 | MOVL 500(SP), AX | ||
| 148 | - | MOVL AX, 228(SP) | 149 | + | MOVL AX, 240(SP) |
| 149 | MOVL 504(SP), AX | 150 | MOVL 504(SP), AX | ||
| 150 | - | MOVL AX, 232(SP) | 151 | + | MOVL AX, 244(SP) |
| 151 | MOVL 508(SP), AX | 152 | MOVL 508(SP), AX | ||
| 152 | - | MOVL AX, 236(SP) | 153 | + | MOVL AX, 248(SP) |
| 153 | MOVL 512(SP), AX | 154 | MOVL 512(SP), AX | ||
| 154 | - | MOVL AX, 240(SP) | 155 | + | MOVL AX, 252(SP) |
| 155 | MOVL 516(SP), AX | 156 | MOVL 516(SP), AX | ||
| 156 | - | MOVL AX, 244(SP) | 157 | + | MOVL AX, 256(SP) |
| 157 | MOVL 520(SP), AX | 158 | MOVL 520(SP), AX | ||
| 158 | - | MOVL AX, 248(SP) | 159 | + | MOVL AX, 260(SP) |
| 159 | MOVL 524(SP), AX | 160 | MOVL 524(SP), AX | ||
| 160 | - | MOVL AX, 252(SP) | 161 | + | MOVL AX, 264(SP) |
| 161 | MOVL 528(SP), AX | 162 | MOVL 528(SP), AX | ||
| 162 | - | MOVL AX, 256(SP) | 163 | + | MOVL AX, 268(SP) |
| 163 | MOVL 532(SP), AX | 164 | MOVL 532(SP), AX | ||
| 164 | - | MOVL AX, 260(SP) | 165 | + | MOVL AX, 272(SP) |
| 165 | MOVL 536(SP), AX | 166 | MOVL 536(SP), AX | ||
| 166 | - | MOVL AX, 264(SP) | 167 | + | MOVL AX, 276(SP) |
| 167 | MOVL 540(SP), AX | 168 | MOVL 540(SP), AX | ||
| 168 | - | MOVL AX, 268(SP) | 169 | + | MOVL AX, 280(SP) |
| 169 | MOVL 544(SP), AX | 170 | MOVL 544(SP), AX | ||
| 170 | - | MOVL AX, 272(SP) | 171 | + | MOVL AX, 284(SP) |
| 171 | MOVL 548(SP), AX | 172 | MOVL 548(SP), AX | ||
| 172 | - | MOVL AX, 276(SP) | 173 | + | MOVL AX, 288(SP) |
| 173 | MOVL 552(SP), AX | 174 | MOVL 552(SP), AX | ||
| 174 | - | MOVL AX, 280(SP) | 175 | + | MOVL AX, 292(SP) |
| 175 | MOVL 556(SP), AX | 176 | MOVL 556(SP), AX | ||
| 176 | - | MOVL AX, 284(SP) | 177 | + | MOVL AX, 296(SP) |
| 177 | MOVL 560(SP), AX | 178 | MOVL 560(SP), AX | ||
| 178 | - | MOVL AX, 288(SP) | 179 | + | MOVL AX, 300(SP) |
| 179 | 180 | ||||
| 180 | // Set up callbackArgs struct at 24(SP) | 181 | // Set up callbackArgs struct at 24(SP) | ||
| 181 | // struct callbackArgs { | 182 | // struct callbackArgs { | ||
| 182 | - | // index uintptr // offset 0 | 183 | + | // index uintptr // offset 0 |
| 183 | - | // args *byte // offset 4 | 184 | + | // args unsafe.Pointer // offset 4 |
| 184 | - | // result uintptr // offset 8 | 185 | + | // result [4]uintptr // offset 8: result[0] and result[1] hold |
| 186 | + | // a 64-bit return, result[2:] are unused | |||
| 185 | // } | 187 | // } | ||
| 186 | MOVL 16(SP), AX // callback index | 188 | MOVL 16(SP), AX // callback index | ||
| 187 | MOVL AX, 24(SP) // callbackArgs.index | 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 | MOVL AX, 28(SP) // callbackArgs.args | 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 | // Call crosscall2(fn, frame, 0, ctxt) | 197 | // Call crosscall2(fn, frame, 0, ctxt) | ||
| 193 | // crosscall2 expects arguments on stack: | 198 | // crosscall2 expects arguments on stack: | ||
| @@ -209,8 +214,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 209 | 214 | ||||
| 210 | ADDL $16, SP | 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 | MOVL 32(SP), AX | 220 | MOVL 32(SP), AX | ||
| 221 | + | MOVL 36(SP), DX | |||
| 214 | 222 | ||||
| 215 | // Restore callee-saved registers | 223 | // Restore callee-saved registers | ||
| 216 | MOVL 0(SP), BX | 224 | MOVL 0(SP), BX | ||
M sys_unix_arm.s +11 -1
| @@ -33,8 +33,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 33 | MOVW R2, 136(R13) | 33 | MOVW R2, 136(R13) | ||
| 34 | MOVW R3, 140(R13) | 34 | MOVW R3, 140(R13) | ||
| 35 | 35 | ||||
| 36 | + | // Skip floating point registers if runtime.goarmsoftfp!=0. | |||
| 37 | + | MOVB runtime·goarmsoftfp(SB), R11 | |||
| 38 | + | CMP $0, R11 | |||
| 39 | + | BNE skipfpsave | |||
| 36 | // Save floating point registers F0-F7 at SP+64 (frame[0..15]) | 40 | // Save floating point registers F0-F7 at SP+64 (frame[0..15]) | ||
| 37 | - | // Note: We always save these since we target hard-float ABI. | |||
| 38 | MOVD F0, 64(R13) | 41 | MOVD F0, 64(R13) | ||
| 39 | MOVD F1, 72(R13) | 42 | MOVD F1, 72(R13) | ||
| 40 | MOVD F2, 80(R13) | 43 | MOVD F2, 80(R13) | ||
| @@ -44,6 +47,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 44 | MOVD F6, 112(R13) | 47 | MOVD F6, 112(R13) | ||
| 45 | MOVD F7, 120(R13) | 48 | MOVD F7, 120(R13) | ||
| 46 | 49 | ||||
| 50 | + | skipfpsave: | |||
| 47 | // Set up callbackArgs at SP+48 | 51 | // Set up callbackArgs at SP+48 | ||
| 48 | MOVW 36(R13), R4 | 52 | MOVW 36(R13), R4 | ||
| 49 | MOVW R4, 48(R13) | 53 | MOVW R4, 48(R13) | ||
| @@ -51,6 +55,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 51 | MOVW R4, 52(R13) | 55 | MOVW R4, 52(R13) | ||
| 52 | MOVW $0, R4 | 56 | MOVW $0, R4 | ||
| 53 | MOVW R4, 56(R13) | 57 | MOVW R4, 56(R13) | ||
| 58 | + | MOVW R4, 60(R13) // high word of a 64-bit return | |||
| 54 | 59 | ||||
| 55 | // Call crosscall2(fn, frame, 0, ctxt) | 60 | // Call crosscall2(fn, frame, 0, ctxt) | ||
| 56 | MOVW ·callbackWrap_call(SB), R0 | 61 | MOVW ·callbackWrap_call(SB), R0 | ||
| @@ -63,7 +68,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 63 | 68 | ||||
| 64 | // Get result | 69 | // Get result | ||
| 65 | MOVW 56(R13), R0 | 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 | // Restore float registers | 76 | // Restore float registers | ||
| 68 | MOVD 64(R13), F0 | 77 | MOVD 64(R13), F0 | ||
| 69 | MOVD 72(R13), F1 | 78 | MOVD 72(R13), F1 | ||
| @@ -74,6 +83,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 74 | MOVD 112(R13), F6 | 83 | MOVD 112(R13), F6 | ||
| 75 | MOVD 120(R13), F7 | 84 | MOVD 120(R13), F7 | ||
| 76 | 85 | ||||
| 86 | + | skipfprest: | |||
| 77 | // Restore callee-saved registers | 87 | // Restore callee-saved registers | ||
| 78 | MOVW 0(R13), R4 | 88 | MOVW 0(R13), R4 | ||
| 79 | MOVW 4(R13), R5 | 89 | MOVW 4(R13), R5 | ||
M sys_unix_s390x.s +10 -8
| @@ -18,22 +18,24 @@ | |||||
| 18 | // | 18 | // | ||
| 19 | // S390X uses R2-R6 for integer arguments (5 registers) and F0,F2,F4,F6 for floats (4 registers). | 19 | // S390X uses R2-R6 for integer arguments (5 registers) and F0,F2,F4,F6 for floats (4 registers). | ||
| 20 | // | 20 | // | ||
| 21 | - | // Our frame layout (total 264 bytes, 8-byte aligned): | 21 | + | // Our frame layout (total 200 bytes, 8-byte aligned). To stay under the |
| 22 | + | // 800-byte nosplit limit on Go 1.27 the callbackArgs struct is stored in the | |||
| 23 | + | // caller's free linkage slot (old_R15+16..48) instead of our own frame: | |||
| 22 | // 0(R15) - back chain | 24 | // 0(R15) - back chain | ||
| 23 | // 48(R15) - saved R6-R15 (done by STMG) | 25 | // 48(R15) - saved R6-R15 (done by STMG) | ||
| 24 | - | // 160(R15) - callbackArgs struct (32 bytes: index, args, result, stackArgs) | 26 | + | // 128(R15) - args array start |
| 25 | - | // 192(R15) - args array start | |||
| 26 | // | 27 | // | ||
| 27 | // Args array layout: | 28 | // Args array layout: | ||
| 28 | // - floats F0,F2,F4,F6 (32 bytes) | 29 | // - floats F0,F2,F4,F6 (32 bytes) | ||
| 29 | // - ints R2-R6 (40 bytes) | 30 | // - ints R2-R6 (40 bytes) | ||
| 30 | - | // Total args array: 72 bytes, ends at 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 | // Stack args in caller's frame start at old_R15+160 | 34 | // Stack args in caller's frame start at old_R15+160 | ||
| 33 | 35 | ||||
| 34 | - | #define FRAME_SIZE 264 | 36 | + | #define FRAME_SIZE 200 |
| 35 | - | #define CB_ARGS 160 | 37 | + | #define ARGS_ARRAY 128 |
| 36 | - | #define ARGS_ARRAY 192 | 38 | + | #define CB_ARGS (FRAME_SIZE+16) |
| 37 | #define FLOAT_OFF 0 | 39 | #define FLOAT_OFF 0 | ||
| 38 | #define INT_OFF 32 | 40 | #define INT_OFF 32 | ||
| 39 | 41 | ||||
| @@ -71,7 +73,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 71 | MOVD R5, (ARGS_ARRAY+INT_OFF+3*8)(R15) | 73 | MOVD R5, (ARGS_ARRAY+INT_OFF+3*8)(R15) | ||
| 72 | 74 | ||||
| 73 | // R6 (5th int arg) was saved at 48(old_R15) by STMG | 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 | MOVD (48+FRAME_SIZE)(R15), R1 | 77 | MOVD (48+FRAME_SIZE)(R15), R1 | ||
| 76 | MOVD R1, (ARGS_ARRAY+INT_OFF+4*8)(R15) | 78 | MOVD R1, (ARGS_ARRAY+INT_OFF+4*8)(R15) | ||
| 77 | 79 | ||||
M syscall.go +56 -65
| @@ -1,66 +1,65 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 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 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| 8 | - | // CDecl marks a function as being called using the __cdecl calling convention as defined in | 8 | + | import ( |
| 9 | - | // the [MSDocs] when passed to NewCallback. It must be the first argument to the function. | 9 | + | "runtime" |
| 10 | - | // This is only useful on 386 Windows, but it is safe to use on other platforms. | 10 | + | "structs" |
| 11 | - | // | 11 | + | "unsafe" |
| 12 | - | // [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 | 12 | + | ) |
| 13 | - | type CDecl struct{} | |||
| 14 | 13 | ||||
| 15 | const ( | 14 | const ( | ||
| 16 | - | maxArgs = 15 | 15 | + | maxArgs = 32 |
| 17 | ) | 16 | ) | ||
| 18 | 17 | ||||
| 19 | - | type syscall15Args struct { | 18 | + | // syscallArgs is the argument block handed to the syscall15 trampoline. On |
| 20 | - | _ hostLayout | 19 | + | // platforms that go through internal/cgo it is passed to C as a |
| 21 | - | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr | 20 | + | // struct syscallArgs, so its layout must match the C ABI. |
| 22 | - | f1, f2, f3, f4, f5, f6, f7, f8 uintptr | 21 | + | // |
| 23 | - | arm64_r8 uintptr | 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) { | 34 | + | func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs { |
| 27 | - | s.fn = fn | 35 | + | s := thePool.Get().(*syscallArgs) |
| 28 | - | s.a1 = ints[0] | 36 | + | *s = syscallArgs{ |
| 29 | - | s.a2 = ints[1] | 37 | + | fn: fn, |
| 30 | - | s.a3 = ints[2] | 38 | + | a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3], |
| 31 | - | s.a4 = ints[3] | 39 | + | a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7], |
| 32 | - | s.a5 = ints[4] | 40 | + | a9: sysargs[8], a10: sysargs[9], a11: sysargs[10], a12: sysargs[11], |
| 33 | - | s.a6 = ints[5] | 41 | + | a13: sysargs[12], a14: sysargs[13], a15: sysargs[14], a16: sysargs[15], |
| 34 | - | s.a7 = ints[6] | 42 | + | a17: sysargs[16], a18: sysargs[17], a19: sysargs[18], a20: sysargs[19], |
| 35 | - | s.a8 = ints[7] | 43 | + | a21: sysargs[20], a22: sysargs[21], a23: sysargs[22], a24: sysargs[23], |
| 36 | - | s.a9 = ints[8] | 44 | + | a25: sysargs[24], a26: sysargs[25], a27: sysargs[26], a28: sysargs[27], |
| 37 | - | s.a10 = ints[9] | 45 | + | a29: sysargs[28], a30: sysargs[29], a31: sysargs[30], a32: sysargs[31], |
| 38 | - | s.a11 = ints[10] | 46 | + | f1: floats[0], f2: floats[1], f3: floats[2], f4: floats[3], |
| 39 | - | s.a12 = ints[11] | 47 | + | f5: floats[4], f6: floats[5], f7: floats[6], f8: floats[7], |
| 40 | - | s.a13 = ints[12] | 48 | + | arm64_r8: r8, |
| 41 | - | s.a14 = ints[13] | 49 | + | } |
| 42 | - | s.a15 = ints[14] | 50 | + | runtime_cgocall(syscallXABI0, unsafe.Pointer(s)) |
| 43 | - | s.f1 = floats[0] | 51 | + | return s |
| 44 | - | s.f2 = floats[1] | |||
| 45 | - | s.f3 = floats[2] | |||
| 46 | - | s.f4 = floats[3] | |||
| 47 | - | s.f5 = floats[4] | |||
| 48 | - | s.f6 = floats[5] | |||
| 49 | - | s.f7 = floats[6] | |||
| 50 | - | s.f8 = floats[7] | |||
| 51 | - | s.arm64_r8 = r8 | |||
| 52 | } | 52 | } | ||
| 53 | 53 | ||||
| 54 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | 54 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | ||
| 55 | - | // There is an internal maximum number of arguments that SyscallN can take. | 55 | + | // There is an internal maximum number of arguments that SyscallN can take. It panics |
| 56 | - | // On 386 and arm, SyscallN supports at most 15 uintptr arguments. | 56 | + | // when the maximum is exceeded. It returns the result and GetLastError on Windows or the errno on |
| 57 | - | // It panics when the maximum is exceeded. It returns the result and the libc error code if there is one. | 57 | + | // platforms that capture it, otherwise 0. |
| 58 | // | 58 | // | ||
| 59 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | 59 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | ||
| 60 | // especially point 4. | 60 | // especially point 4. | ||
| 61 | // | 61 | // | ||
| 62 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | 62 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | ||
| 63 | - | // Use RegisterFunc instead when the target function mixes integer and floating-point arguments. | |||
| 64 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | 63 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | ||
| 65 | // for an explanation of why that is. | 64 | // for an explanation of why that is. | ||
| 66 | // | 65 | // | ||
| @@ -79,30 +78,22 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||||
| 79 | if len(args) > maxArgs { | 78 | if len(args) > maxArgs { | ||
| 80 | panic("purego: too many arguments to SyscallN") | 79 | panic("purego: too many arguments to SyscallN") | ||
| 81 | } | 80 | } | ||
| 82 | - | // add padding so there is no out-of-bounds slicing | 81 | + | |
| 82 | + | // Add padding so there is no out-of-bounds slicing. | |||
| 83 | var tmp [maxArgs]uintptr | 83 | var tmp [maxArgs]uintptr | ||
| 84 | - | copy(tmp[:], args) | 84 | + | n := copy(tmp[:], args) |
| 85 | - | return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) | 85 | + | return syscallPadded(fn, &tmp, n) |
| 86 | } | 86 | } | ||
| 87 | 87 | ||||
| 88 | - | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | 88 | + | // syscallPadded calls fn with the first n values of tmp as arguments. |
| 89 | - | // It is designed for method-like calls where the first argument is always a receiver pointer. | 89 | + | func syscallPadded(fn uintptr, tmp *[maxArgs]uintptr, n int) (r1, r2, err uintptr) { |
| 90 | - | // It shares SyscallN's argument-placement limits and mixed integer/float restriction. | 90 | + | // Windows uses syscall.SyscallN in syscall_windows.go. |
| 91 | - | // On 386 and arm, the total count including self is limited to 15 uintptr arguments. | 91 | + | if runtime.GOOS == "windows" { |
| 92 | - | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | 92 | + | return syscall_syscallN(fn, tmp[:n]...) |
| 93 | - | // | |||
| 94 | - | //go:uintptrescapes | |||
| 95 | - | func SyscallSelf(fn, self uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||
| 96 | - | if fn == 0 { | |||
| 97 | - | panic("purego: fn is nil") | |||
| 98 | - | } | |||
| 99 | - | if len(args) > maxArgs-1 { | |||
| 100 | - | panic("purego: too many arguments to SyscallSelf") | |||
| 101 | } | 93 | } | ||
| 102 | - | // Use a stack-allocated array to prepend self without heap allocation. | 94 | + | var floats [maxArgs]uintptr |
| 103 | - | // self goes in tmp[0], remaining args follow. | 95 | + | copy(floats[:], tmp[:]) |
| 104 | - | var tmp [maxArgs]uintptr | 96 | + | s := syscall_SyscallN(fn, tmp[:], floats[:], 0) |
| 105 | - | tmp[0] = self | 97 | + | defer thePool.Put(s) |
| 106 | - | copy(tmp[1:], args) | 98 | + | return s.a1, s.a2, s.a3 |
| 107 | - | return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) | |||
| 108 | } | 99 | } | ||
M syscall_32bit.go +53 -67
| @@ -5,69 +5,63 @@ | |||||
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| 8 | - | // CDecl marks a function as being called using the __cdecl calling convention as defined in | 8 | + | import ( |
| 9 | - | // the [MSDocs] when passed to NewCallback. It must be the first argument to the function. | 9 | + | "runtime" |
| 10 | - | // This is only useful on 386 Windows, but it is safe to use on other platforms. | 10 | + | "structs" |
| 11 | - | // | 11 | + | "unsafe" |
| 12 | - | // [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 | 12 | + | ) |
| 13 | - | type CDecl struct{} | |||
| 14 | 13 | ||||
| 15 | const ( | 14 | const ( | ||
| 16 | - | maxArgs = 15 | 15 | + | maxArgs = 32 |
| 17 | ) | 16 | ) | ||
| 18 | 17 | ||||
| 19 | - | type syscall15Args struct { | 18 | + | // syscallArgs is the argument block handed to the syscall15 trampoline. On |
| 20 | - | _ hostLayout | 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 | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr | 28 | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr | ||
| 22 | a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32 uintptr | 29 | a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32 uintptr | ||
| 23 | f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16 uintptr | 30 | f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16 uintptr | ||
| 24 | arm64_r8 uintptr | 31 | arm64_r8 uintptr | ||
| 25 | } | 32 | } | ||
| 26 | 33 | ||||
| 27 | - | func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uintptr) { | 34 | + | func syscall_SyscallN(fn uintptr, sysargs []uintptr, floats []uintptr, r8 uintptr) *syscallArgs { |
| 28 | - | s.fn = fn | 35 | + | s := thePool.Get().(*syscallArgs) |
| 29 | - | s.a1 = ints[0] | 36 | + | *s = syscallArgs{ |
| 30 | - | s.a2 = ints[1] | 37 | + | fn: fn, |
| 31 | - | s.a3 = ints[2] | 38 | + | a1: sysargs[0], a2: sysargs[1], a3: sysargs[2], a4: sysargs[3], |
| 32 | - | s.a4 = ints[3] | 39 | + | a5: sysargs[4], a6: sysargs[5], a7: sysargs[6], a8: sysargs[7], |
| 33 | - | s.a5 = ints[4] | 40 | + | a9: sysargs[8], a10: sysargs[9], a11: sysargs[10], a12: sysargs[11], |
| 34 | - | s.a6 = ints[5] | 41 | + | a13: sysargs[12], a14: sysargs[13], a15: sysargs[14], a16: sysargs[15], |
| 35 | - | s.a7 = ints[6] | 42 | + | a17: sysargs[16], a18: sysargs[17], a19: sysargs[18], a20: sysargs[19], |
| 36 | - | s.a8 = ints[7] | 43 | + | a21: sysargs[20], a22: sysargs[21], a23: sysargs[22], a24: sysargs[23], |
| 37 | - | s.a9 = ints[8] | 44 | + | a25: sysargs[24], a26: sysargs[25], a27: sysargs[26], a28: sysargs[27], |
| 38 | - | s.a10 = ints[9] | 45 | + | a29: sysargs[28], a30: sysargs[29], a31: sysargs[30], a32: sysargs[31], |
| 39 | - | s.a11 = ints[10] | 46 | + | f1: floats[0], f2: floats[1], f3: floats[2], f4: floats[3], |
| 40 | - | s.a12 = ints[11] | 47 | + | f5: floats[4], f6: floats[5], f7: floats[6], f8: floats[7], |
| 41 | - | s.a13 = ints[12] | 48 | + | f9: floats[8], f10: floats[9], f11: floats[10], f12: floats[11], |
| 42 | - | s.a14 = ints[13] | 49 | + | f13: floats[12], f14: floats[13], f15: floats[14], f16: floats[15], |
| 43 | - | s.a15 = ints[14] | 50 | + | arm64_r8: r8, |
| 44 | - | s.f1 = floats[0] | 51 | + | } |
| 45 | - | s.f2 = floats[1] | 52 | + | runtime_cgocall(syscallXABI0, unsafe.Pointer(s)) |
| 46 | - | s.f3 = floats[2] | 53 | + | return s |
| 47 | - | s.f4 = floats[3] | |||
| 48 | - | s.f5 = floats[4] | |||
| 49 | - | s.f6 = floats[5] | |||
| 50 | - | s.f7 = floats[6] | |||
| 51 | - | s.f8 = floats[7] | |||
| 52 | - | s.f9 = floats[8] | |||
| 53 | - | s.f10 = floats[9] | |||
| 54 | - | s.f11 = floats[10] | |||
| 55 | - | s.f12 = floats[11] | |||
| 56 | - | s.f13 = floats[12] | |||
| 57 | - | s.f14 = floats[13] | |||
| 58 | - | s.f15 = floats[14] | |||
| 59 | - | s.arm64_r8 = r8 | |||
| 60 | } | 54 | } | ||
| 61 | 55 | ||||
| 62 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | 56 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | ||
| 63 | - | // On 386 and arm, SyscallN supports at most 15 uintptr arguments. | 57 | + | // There is an internal maximum number of arguments that SyscallN can take. It panics |
| 64 | - | // It panics when the maximum is exceeded. It returns the result and the libc error code if there is one. | 58 | + | // when the maximum is exceeded. It returns the result and GetLastError on Windows or the errno on |
| 59 | + | // platforms that capture it, otherwise 0. | |||
| 65 | // | 60 | // | ||
| 66 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | 61 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | ||
| 67 | // especially point 4. | 62 | // especially point 4. | ||
| 68 | // | 63 | // | ||
| 69 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | 64 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | ||
| 70 | - | // Use RegisterFunc instead when the target function mixes integer and floating-point arguments. | |||
| 71 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | 65 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | ||
| 72 | // for an explanation of why that is. | 66 | // for an explanation of why that is. | ||
| 73 | // | 67 | // | ||
| @@ -86,30 +80,22 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||||
| 86 | if len(args) > maxArgs { | 80 | if len(args) > maxArgs { | ||
| 87 | panic("purego: too many arguments to SyscallN") | 81 | panic("purego: too many arguments to SyscallN") | ||
| 88 | } | 82 | } | ||
| 89 | - | // add padding so there is no out-of-bounds slicing | 83 | + | |
| 84 | + | // Add padding so there is no out-of-bounds slicing. | |||
| 90 | var tmp [maxArgs]uintptr | 85 | var tmp [maxArgs]uintptr | ||
| 91 | - | copy(tmp[:], args) | 86 | + | n := copy(tmp[:], args) |
| 92 | - | return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) | 87 | + | return syscallPadded(fn, &tmp, n) |
| 93 | } | 88 | } | ||
| 94 | 89 | ||||
| 95 | - | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | 90 | + | // syscallPadded calls fn with the first n values of tmp as arguments. |
| 96 | - | // It is designed for method-like calls where the first argument is always a receiver pointer. | 91 | + | func syscallPadded(fn uintptr, tmp *[maxArgs]uintptr, n int) (r1, r2, err uintptr) { |
| 97 | - | // It shares SyscallN's argument-placement limits and mixed integer/float restriction. | 92 | + | // Windows uses syscall.SyscallN in syscall_windows.go. |
| 98 | - | // On 386 and arm, the total count including self is limited to 15 uintptr arguments. | 93 | + | if runtime.GOOS == "windows" { |
| 99 | - | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | 94 | + | return syscall_syscallN(fn, tmp[:n]...) |
| 100 | - | // | |||
| 101 | - | //go:uintptrescapes | |||
| 102 | - | func SyscallSelf(fn, self uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||
| 103 | - | if fn == 0 { | |||
| 104 | - | panic("purego: fn is nil") | |||
| 105 | - | } | |||
| 106 | - | if len(args) > maxArgs-1 { | |||
| 107 | - | panic("purego: too many arguments to SyscallSelf") | |||
| 108 | } | 95 | } | ||
| 109 | - | // Use a stack-allocated array to prepend self without heap allocation. | 96 | + | var floats [16]uintptr |
| 110 | - | // self goes in tmp[0], remaining args follow. | 97 | + | copy(floats[:], tmp[:16]) |
| 111 | - | var tmp [maxArgs]uintptr | 98 | + | s := syscall_SyscallN(fn, tmp[:], floats[:], 0) |
| 112 | - | tmp[0] = self | 99 | + | defer thePool.Put(s) |
| 113 | - | copy(tmp[1:], args) | 100 | + | return s.a1, s.a2, s.a3 |
| 114 | - | return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) | |||
| 115 | } | 101 | } | ||
D syscall_32bit_internal_test.go +0 -16
| @@ -1,16 +0,0 @@ | |||||
| 1 | - | //go:build (386 || arm) && (freebsd || linux || netbsd || windows) | |||
| 2 | - | ||||
| 3 | - | package purego | |||
| 4 | - | ||||
| 5 | - | import "testing" | |||
| 6 | - | ||||
| 7 | - | func TestSyscall15ArgsSetDoesNotPanicWithMaxArgs(t *testing.T) { | |||
| 8 | - | defer func() { | |||
| 9 | - | if r := recover(); r != nil { | |||
| 10 | - | t.Fatalf("Set panicked: %v", r) | |||
| 11 | - | } | |||
| 12 | - | }() | |||
| 13 | - | ||||
| 14 | - | var s syscall15Args | |||
| 15 | - | s.Set(1, make([]uintptr, maxArgs), make([]uintptr, maxArgs), 8) | |||
| 16 | - | } | |||
D syscall_32bit_test.go +0 -36
| @@ -1,36 +0,0 @@ | |||||
| 1 | - | //go:build (386 || arm) && (freebsd || linux || netbsd || windows) | |||
| 2 | - | ||||
| 3 | - | package purego_test | |||
| 4 | - | ||||
| 5 | - | import ( | |||
| 6 | - | "fmt" | |||
| 7 | - | "testing" | |||
| 8 | - | ||||
| 9 | - | "github.com/bnema/purego" | |||
| 10 | - | ) | |||
| 11 | - | ||||
| 12 | - | func TestSyscallN32BitPanicsAt16Args(t *testing.T) { | |||
| 13 | - | defer func() { | |||
| 14 | - | r := recover() | |||
| 15 | - | if r == nil { | |||
| 16 | - | t.Fatal("expected panic") | |||
| 17 | - | } | |||
| 18 | - | if got := fmt.Sprint(r); got != "purego: too many arguments to SyscallN" { | |||
| 19 | - | t.Fatalf("panic = %q, want %q", got, "purego: too many arguments to SyscallN") | |||
| 20 | - | } | |||
| 21 | - | }() | |||
| 22 | - | purego.SyscallN(uintptr(1), make([]uintptr, 16)...) | |||
| 23 | - | } | |||
| 24 | - | ||||
| 25 | - | func TestSyscallSelf32BitPanicsAt16Args(t *testing.T) { | |||
| 26 | - | defer func() { | |||
| 27 | - | r := recover() | |||
| 28 | - | if r == nil { | |||
| 29 | - | t.Fatal("expected panic") | |||
| 30 | - | } | |||
| 31 | - | if got := fmt.Sprint(r); got != "purego: too many arguments to SyscallSelf" { | |||
| 32 | - | t.Fatalf("panic = %q, want %q", got, "purego: too many arguments to SyscallSelf") | |||
| 33 | - | } | |||
| 34 | - | }() | |||
| 35 | - | purego.SyscallSelf(uintptr(1), 1, make([]uintptr, 15)...) | |||
| 36 | - | } | |||
M syscall_bench_test.go +11 -11
| @@ -37,7 +37,7 @@ func BenchmarkCallingMethods(b *testing.B) { | |||||
| 37 | 37 | ||||
| 38 | // Build C library for benchmarking | 38 | // Build C library for benchmarking | ||
| 39 | libFileName := filepath.Join(b.TempDir(), "libbenchmark.so") | 39 | libFileName := filepath.Join(b.TempDir(), "libbenchmark.so") | ||
| 40 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "benchmarktest", "benchmark.c")); err != nil { | 40 | + | if err := buildSharedLib(b, "CC", libFileName, filepath.Join("testdata", "benchmarktest", "benchmark.c")); err != nil { |
| 41 | b.Fatalf("Failed to build C library: %v", err) | 41 | b.Fatalf("Failed to build C library: %v", err) | ||
| 42 | } | 42 | } | ||
| 43 | b.Cleanup(func() { | 43 | b.Cleanup(func() { | ||
| @@ -116,7 +116,7 @@ func BenchmarkCallingMethods(b *testing.B) { | |||||
| 116 | args := int64sToUintptrs(tc.args) | 116 | args := int64sToUintptrs(tc.args) | ||
| 117 | var result uintptr | 117 | var result uintptr | ||
| 118 | b.ResetTimer() | 118 | b.ResetTimer() | ||
| 119 | - | for i := 0; i < b.N; i++ { | 119 | + | for range b.N { |
| 120 | result, _, _ = purego.SyscallN(tc.goFnPtr, args...) | 120 | result, _, _ = purego.SyscallN(tc.goFnPtr, args...) | ||
| 121 | } | 121 | } | ||
| 122 | b.StopTimer() | 122 | b.StopTimer() | ||
| @@ -135,7 +135,7 @@ func BenchmarkCallingMethods(b *testing.B) { | |||||
| 135 | args := int64sToUintptrs(tc.args) | 135 | args := int64sToUintptrs(tc.args) | ||
| 136 | var result uintptr | 136 | var result uintptr | ||
| 137 | b.ResetTimer() | 137 | b.ResetTimer() | ||
| 138 | - | for i := 0; i < b.N; i++ { | 138 | + | for range b.N { |
| 139 | result, _, _ = purego.SyscallN(tc.cFnPtr, args...) | 139 | result, _, _ = purego.SyscallN(tc.cFnPtr, args...) | ||
| 140 | } | 140 | } | ||
| 141 | b.StopTimer() | 141 | b.StopTimer() | ||
| @@ -165,7 +165,7 @@ func BenchmarkCallingMethods(b *testing.B) { | |||||
| 165 | 165 | ||||
| 166 | var result uintptr | 166 | var result uintptr | ||
| 167 | b.ResetTimer() | 167 | b.ResetTimer() | ||
| 168 | - | for i := 0; i < b.N; i++ { | 168 | + | for range b.N { |
| 169 | result, _, _ = purego.SyscallN(tc.cCallbackPtr, callbackArgs...) | 169 | result, _, _ = purego.SyscallN(tc.cCallbackPtr, callbackArgs...) | ||
| 170 | } | 170 | } | ||
| 171 | b.StopTimer() | 171 | b.StopTimer() | ||
| @@ -205,40 +205,40 @@ func callRegisterFunc(registerFn any, n int, args []int64, iterations int) int64 | |||||
| 205 | switch n { | 205 | switch n { | ||
| 206 | case 1: | 206 | case 1: | ||
| 207 | f := registerFn.(*func(int64) int64) | 207 | f := registerFn.(*func(int64) int64) | ||
| 208 | - | for i := 0; i < iterations; i++ { | 208 | + | for range iterations { |
| 209 | result = (*f)(args[0]) | 209 | result = (*f)(args[0]) | ||
| 210 | } | 210 | } | ||
| 211 | case 2: | 211 | case 2: | ||
| 212 | f := registerFn.(*func(int64, int64) int64) | 212 | f := registerFn.(*func(int64, int64) int64) | ||
| 213 | - | for i := 0; i < iterations; i++ { | 213 | + | for range iterations { |
| 214 | result = (*f)(args[0], args[1]) | 214 | result = (*f)(args[0], args[1]) | ||
| 215 | } | 215 | } | ||
| 216 | case 3: | 216 | case 3: | ||
| 217 | f := registerFn.(*func(int64, int64, int64) int64) | 217 | f := registerFn.(*func(int64, int64, int64) int64) | ||
| 218 | - | for i := 0; i < iterations; i++ { | 218 | + | for range iterations { |
| 219 | result = (*f)(args[0], args[1], args[2]) | 219 | result = (*f)(args[0], args[1], args[2]) | ||
| 220 | } | 220 | } | ||
| 221 | case 5: | 221 | case 5: | ||
| 222 | f := registerFn.(*func(int64, int64, int64, int64, int64) int64) | 222 | f := registerFn.(*func(int64, int64, int64, int64, int64) int64) | ||
| 223 | - | for i := 0; i < iterations; i++ { | 223 | + | for range iterations { |
| 224 | result = (*f)(args[0], args[1], args[2], args[3], args[4]) | 224 | result = (*f)(args[0], args[1], args[2], args[3], args[4]) | ||
| 225 | } | 225 | } | ||
| 226 | case 10: | 226 | case 10: | ||
| 227 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | 227 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | ||
| 228 | - | for i := 0; i < iterations; i++ { | 228 | + | for range iterations { |
| 229 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | 229 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | ||
| 230 | args[5], args[6], args[7], args[8], args[9]) | 230 | args[5], args[6], args[7], args[8], args[9]) | ||
| 231 | } | 231 | } | ||
| 232 | case 14: | 232 | case 14: | ||
| 233 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | 233 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | ||
| 234 | - | for i := 0; i < iterations; i++ { | 234 | + | for range iterations { |
| 235 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | 235 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | ||
| 236 | args[5], args[6], args[7], args[8], args[9], | 236 | args[5], args[6], args[7], args[8], args[9], | ||
| 237 | args[10], args[11], args[12], args[13]) | 237 | args[10], args[11], args[12], args[13]) | ||
| 238 | } | 238 | } | ||
| 239 | case 15: | 239 | case 15: | ||
| 240 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | 240 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | ||
| 241 | - | for i := 0; i < iterations; i++ { | 241 | + | for range iterations { |
| 242 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | 242 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | ||
| 243 | args[5], args[6], args[7], args[8], args[9], | 243 | args[5], args[6], args[7], args[8], args[9], | ||
| 244 | args[10], args[11], args[12], args[13], args[14]) | 244 | args[10], args[11], args[12], args[13], args[14]) | ||
M syscall_cgo_linux.go +5 -6
| @@ -9,13 +9,12 @@ import ( | |||||
| 9 | "github.com/bnema/purego/internal/cgo" | 9 | "github.com/bnema/purego/internal/cgo" | ||
| 10 | ) | 10 | ) | ||
| 11 | 11 | ||||
| 12 | - | var syscall15XABI0 = uintptr(cgo.Syscall15XABI0) | 12 | + | var syscallXABI0 = uintptr(cgo.SyscallXABI0) |
| 13 | - | ||||
| 14 | - | //go:nosplit | |||
| 15 | - | func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { | |||
| 16 | - | return cgo.Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) | |||
| 17 | - | } | |||
| 18 | 13 | ||||
| 19 | func NewCallback(_ any) uintptr { | 14 | func NewCallback(_ any) uintptr { | ||
| 20 | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64/s390x") | 15 | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64/s390x") | ||
| 21 | } | 16 | } | ||
| 17 | + | ||||
| 18 | + | func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||
| 19 | + | panic("purego: syscall_syscallN is only supported on windows") | |||
| 20 | + | } | |||
M syscall_linux_test.go +7 -7
| @@ -10,7 +10,7 @@ import ( | |||||
| 10 | "os" | 10 | "os" | ||
| 11 | "path/filepath" | 11 | "path/filepath" | ||
| 12 | "runtime" | 12 | "runtime" | ||
| 13 | - | "sort" | 13 | + | "slices" |
| 14 | "strings" | 14 | "strings" | ||
| 15 | "syscall" | 15 | "syscall" | ||
| 16 | "testing" | 16 | "testing" | ||
| @@ -119,8 +119,8 @@ func compareStatus(filter, expect string) error { | |||||
| 119 | // "... : bad file descriptor. | 119 | // "... : bad file descriptor. | ||
| 120 | continue | 120 | continue | ||
| 121 | } | 121 | } | ||
| 122 | - | lines := strings.Split(string(d), "\n") | 122 | + | lines := strings.SplitSeq(string(d), "\n") |
| 123 | - | for _, line := range lines { | 123 | + | for line := range lines { |
| 124 | // Different kernel vintages pad differently. | 124 | // Different kernel vintages pad differently. | ||
| 125 | line = strings.TrimSpace(line) | 125 | line = strings.TrimSpace(line) | ||
| 126 | if strings.HasPrefix(line, "Pid:\t") { | 126 | if strings.HasPrefix(line, "Pid:\t") { | ||
| @@ -149,7 +149,7 @@ func compareStatus(filter, expect string) error { | |||||
| 149 | // https://github.com/golang/go/issues/46145 | 149 | // https://github.com/golang/go/issues/46145 | ||
| 150 | // Containers don't reliably output this line in sorted order so manually sort and compare that. | 150 | // Containers don't reliably output this line in sorted order so manually sort and compare that. | ||
| 151 | a := strings.Split(line[8:], " ") | 151 | a := strings.Split(line[8:], " ") | ||
| 152 | - | sort.Slice(a, func(i, j int) bool { return a[i] < a[j] }) | 152 | + | slices.Sort(a) |
| 153 | got := strings.Join(a, " ") | 153 | got := strings.Join(a, " ") | ||
| 154 | if got == expected[8:] { | 154 | if got == expected[8:] { | ||
| 155 | foundAThread = true | 155 | foundAThread = true | ||
| @@ -173,7 +173,7 @@ func compareStatus(filter, expect string) error { | |||||
| 173 | func TestDlopenThenAllThreadsSyscall(t *testing.T) { | 173 | func TestDlopenThenAllThreadsSyscall(t *testing.T) { | ||
| 174 | // Step 1: Build and load a shared C library that calls back into Go. | 174 | // Step 1: Build and load a shared C library that calls back into Go. | ||
| 175 | libFileName := filepath.Join(t.TempDir(), "libcbtest.so") | 175 | libFileName := filepath.Join(t.TempDir(), "libcbtest.so") | ||
| 176 | - | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil { | 176 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil { |
| 177 | t.Fatal(err) | 177 | t.Fatal(err) | ||
| 178 | } | 178 | } | ||
| 179 | defer os.Remove(libFileName) | 179 | defer os.Remove(libFileName) | ||
| @@ -192,12 +192,12 @@ func TestDlopenThenAllThreadsSyscall(t *testing.T) { | |||||
| 192 | } | 192 | } | ||
| 193 | 193 | ||||
| 194 | cb := purego.NewCallback(goFunc) | 194 | cb := purego.NewCallback(goFunc) | ||
| 195 | - | for i := 0; i < 10; i++ { | 195 | + | for range 10 { |
| 196 | callCallback(cb, "hello") | 196 | callCallback(cb, "hello") | ||
| 197 | } | 197 | } | ||
| 198 | 198 | ||||
| 199 | // Step 2: Generate thread churn + AllThreadsSyscall (via Setuid). | 199 | // Step 2: Generate thread churn + AllThreadsSyscall (via Setuid). | ||
| 200 | - | for i := 0; i < 20; i++ { | 200 | + | for range 20 { |
| 201 | c := make(chan struct{}) | 201 | c := make(chan struct{}) | ||
| 202 | go func() { | 202 | go func() { | ||
| 203 | runtime.LockOSThread() | 203 | runtime.LockOSThread() | ||
M syscall_notstackargs.go +18 -1
| @@ -17,7 +17,6 @@ import "unsafe" | |||||
| 17 | // the runtime's own callback mechanism, so this type is compiled but unused, | 17 | // the runtime's own callback mechanism, so this type is compiled but unused, | ||
| 18 | // serving only as a stub to satisfy cross-platform compilation. | 18 | // serving only as a stub to satisfy cross-platform compilation. | ||
| 19 | type callbackArgs struct { | 19 | type callbackArgs struct { | ||
| 20 | - | _ hostLayout | |||
| 21 | index uintptr | 20 | index uintptr | ||
| 22 | // args points to the argument block. | 21 | // args points to the argument block. | ||
| 23 | // | 22 | // | ||
| @@ -36,3 +35,21 @@ type callbackArgs struct { | |||||
| 36 | func (c *callbackArgs) stackFrame() unsafe.Pointer { | 35 | func (c *callbackArgs) stackFrame() unsafe.Pointer { | ||
| 37 | return nil | 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 | package purego_test | 4 | package purego_test | ||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | - | "fmt" | |||
| 8 | "os" | 7 | "os" | ||
| 8 | + | "path/filepath" | |||
| 9 | "runtime" | 9 | "runtime" | ||
| 10 | "testing" | 10 | "testing" | ||
| 11 | - | "unsafe" | |||
| 12 | 11 | ||||
| 13 | "github.com/bnema/purego" | 12 | "github.com/bnema/purego" | ||
| 14 | "github.com/bnema/purego/internal/load" | 13 | "github.com/bnema/purego/internal/load" | ||
| @@ -26,24 +25,54 @@ func TestOS(t *testing.T) { | |||||
| 26 | } | 25 | } | ||
| 27 | } | 26 | } | ||
| 28 | 27 | ||||
| 28 | + | // errnoIsCaptured reports whether SyscallN returns the libc errno as its third | |||
| 29 | + | // value on this platform. | |||
| 30 | + | func errnoIsCaptured() bool { | |||
| 31 | + | switch runtime.GOOS { | |||
| 32 | + | case "darwin": | |||
| 33 | + | return true | |||
| 34 | + | case "linux": | |||
| 35 | + | switch runtime.GOARCH { | |||
| 36 | + | case "mips", "mipsle", "mips64", "mips64le", "ppc64": | |||
| 37 | + | return true | |||
| 38 | + | } | |||
| 39 | + | } | |||
| 40 | + | return false | |||
| 41 | + | } | |||
| 42 | + | ||||
| 29 | func TestErrno(t *testing.T) { | 43 | func TestErrno(t *testing.T) { | ||
| 30 | - | if runtime.GOOS != "darwin" { | 44 | + | if !errnoIsCaptured() { |
| 31 | t.Skip("platform does not support returning errno from syscall") | 45 | t.Skip("platform does not support returning errno from syscall") | ||
| 32 | } | 46 | } | ||
| 33 | 47 | ||||
| 34 | - | libc, err := load.OpenLibrary("/usr/lib/libSystem.B.dylib") | 48 | + | libFileName := filepath.Join(t.TempDir(), "liberrnotest.so") |
| 49 | + | if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "liberrnotest", "errno_test.c")); err != nil { | |||
| 50 | + | t.Fatal(err) | |||
| 51 | + | } | |||
| 52 | + | defer os.Remove(libFileName) | |||
| 53 | + | ||||
| 54 | + | lib, err := load.OpenLibrary(libFileName) | |||
| 35 | if err != nil { | 55 | if err != nil { | ||
| 36 | t.Fatal(err) | 56 | t.Fatal(err) | ||
| 37 | } | 57 | } | ||
| 38 | 58 | ||||
| 39 | - | openSym, err := load.OpenSymbol(libc, "open") | 59 | + | setErrno, err := load.OpenSymbol(lib, "setErrno") |
| 40 | if err != nil { | 60 | if err != nil { | ||
| 41 | t.Fatal(err) | 61 | t.Fatal(err) | ||
| 42 | } | 62 | } | ||
| 43 | 63 | ||||
| 44 | - | r1, _, errno := purego.SyscallN(openSym, uintptr(unsafe.Pointer(&[]byte("_file_that_does_not_exist_\x00")[0])), uintptr(os.O_RDWR)) | 64 | + | r1, _, errno := purego.SyscallN(setErrno) |
| 45 | if int32(r1) != -1 { | 65 | if int32(r1) != -1 { | ||
| 46 | - | t.Errorf("open returned %d, wanted -1", r1) | 66 | + | t.Errorf("setErrno returned %d, wanted -1", r1) |
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | libcName, err := getSystemLibrary() | |||
| 70 | + | if err != nil { | |||
| 71 | + | t.Fatal(err) | |||
| 72 | + | } | |||
| 73 | + | libc, err := load.OpenLibrary(libcName) | |||
| 74 | + | if err != nil { | |||
| 75 | + | t.Fatal(err) | |||
| 47 | } | 76 | } | ||
| 48 | 77 | ||||
| 49 | var strerror func(int32) string | 78 | var strerror func(int32) string | ||
| @@ -55,79 +84,3 @@ func TestErrno(t *testing.T) { | |||||
| 55 | t.Errorf("strerror returned %q, wanted \"%s\"", got, expected) | 84 | t.Errorf("strerror returned %q, wanted \"%s\"", got, expected) | ||
| 56 | } | 85 | } | ||
| 57 | } | 86 | } | ||
| 58 | - | ||||
| 59 | - | func TestSyscallSelf(t *testing.T) { | |||
| 60 | - | // Test that SyscallSelf(fn, self, args...) produces the same result as | |||
| 61 | - | // SyscallN(fn, self, args...) for various argument counts. | |||
| 62 | - | ||||
| 63 | - | // sum2 takes (self, a1) and returns self + a1 | |||
| 64 | - | sum2 := purego.NewCallback(func(self, a1 uintptr) uintptr { | |||
| 65 | - | return self + a1 | |||
| 66 | - | }) | |||
| 67 | - | // sum4 takes (self, a1, a2, a3) and returns self + a1 + a2 + a3 | |||
| 68 | - | sum4 := purego.NewCallback(func(self, a1, a2, a3 uintptr) uintptr { | |||
| 69 | - | return self + a1 + a2 + a3 | |||
| 70 | - | }) | |||
| 71 | - | ||||
| 72 | - | tests := []struct { | |||
| 73 | - | name string | |||
| 74 | - | fn uintptr | |||
| 75 | - | self uintptr | |||
| 76 | - | args []uintptr | |||
| 77 | - | want uintptr | |||
| 78 | - | }{ | |||
| 79 | - | {"no extra args", sum2, 10, []uintptr{20}, 30}, | |||
| 80 | - | {"zero self", sum2, 0, []uintptr{42}, 42}, | |||
| 81 | - | {"multiple args", sum4, 1, []uintptr{2, 3, 4}, 10}, | |||
| 82 | - | {"no variadic args", | |||
| 83 | - | purego.NewCallback(func(self uintptr) uintptr { return self * 3 }), | |||
| 84 | - | 7, nil, 21}, | |||
| 85 | - | } | |||
| 86 | - | ||||
| 87 | - | for _, tc := range tests { | |||
| 88 | - | t.Run(tc.name, func(t *testing.T) { | |||
| 89 | - | r1, _, _ := purego.SyscallSelf(tc.fn, tc.self, tc.args...) | |||
| 90 | - | if r1 != tc.want { | |||
| 91 | - | t.Errorf("SyscallSelf returned %d, want %d", r1, tc.want) | |||
| 92 | - | } | |||
| 93 | - | ||||
| 94 | - | // Verify it matches SyscallN with manually prepended self | |||
| 95 | - | allArgs := make([]uintptr, 0, 1+len(tc.args)) | |||
| 96 | - | allArgs = append(allArgs, tc.self) | |||
| 97 | - | allArgs = append(allArgs, tc.args...) | |||
| 98 | - | r1n, _, _ := purego.SyscallN(tc.fn, allArgs...) | |||
| 99 | - | if r1 != r1n { | |||
| 100 | - | t.Errorf("SyscallSelf result %d differs from SyscallN result %d", r1, r1n) | |||
| 101 | - | } | |||
| 102 | - | }) | |||
| 103 | - | } | |||
| 104 | - | } | |||
| 105 | - | ||||
| 106 | - | func TestSyscallSelfPanics(t *testing.T) { | |||
| 107 | - | t.Run("nil fn", func(t *testing.T) { | |||
| 108 | - | defer func() { | |||
| 109 | - | if r := recover(); r == nil { | |||
| 110 | - | t.Error("expected panic for nil fn") | |||
| 111 | - | } | |||
| 112 | - | }() | |||
| 113 | - | purego.SyscallSelf(0, 1) | |||
| 114 | - | }) | |||
| 115 | - | ||||
| 116 | - | t.Run("too many args", func(t *testing.T) { | |||
| 117 | - | defer func() { | |||
| 118 | - | r := recover() | |||
| 119 | - | if r == nil { | |||
| 120 | - | t.Error("expected panic for too many args") | |||
| 121 | - | } | |||
| 122 | - | msg := fmt.Sprint(r) | |||
| 123 | - | if msg != "purego: too many arguments to SyscallSelf" { | |||
| 124 | - | t.Errorf("unexpected panic message: %s", msg) | |||
| 125 | - | } | |||
| 126 | - | }() | |||
| 127 | - | // Create a valid callback so we don't panic on nil fn | |||
| 128 | - | fn := purego.NewCallback(func(uintptr) uintptr { return 0 }) | |||
| 129 | - | // Pass more args than maxArgs-1 allows (maxArgs is 15 on 64-bit) | |||
| 130 | - | args := make([]uintptr, 15) | |||
| 131 | - | purego.SyscallSelf(fn, 1, args...) | |||
| 132 | - | }) | |||
| 133 | - | } | |||
M syscall_unix.go +105 -62
| @@ -1,8 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | // TODO: remove s390x cgo dependency once golang/go#77449 is resolved | 4 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (s390x && (cgo || go1.27)))) || netbsd |
| 5 | - | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (cgo && s390x))) || netbsd | |||
| 6 | 5 | ||||
| 7 | package purego | 6 | package purego | ||
| 8 | 7 | ||||
| @@ -10,6 +9,7 @@ import ( | |||||
| 10 | "encoding/json" | 9 | "encoding/json" | ||
| 11 | "errors" | 10 | "errors" | ||
| 12 | "fmt" | 11 | "fmt" | ||
| 12 | + | "math" | |||
| 13 | "os" | 13 | "os" | ||
| 14 | "reflect" | 14 | "reflect" | ||
| 15 | "runtime" | 15 | "runtime" | ||
| @@ -22,31 +22,31 @@ import ( | |||||
| 22 | internalstrings "github.com/bnema/purego/internal/strings" | 22 | internalstrings "github.com/bnema/purego/internal/strings" | ||
| 23 | ) | 23 | ) | ||
| 24 | 24 | ||||
| 25 | - | var syscall15XABI0 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) { | 27 | + | func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { |
| 28 | - | args := &syscall15Args{ | 28 | + | panic("purego: syscall_syscallN is only supported on windows") |
| 29 | - | fn: fn, | |||
| 30 | - | a1: a1, a2: a2, a3: a3, a4: a4, a5: a5, a6: a6, a7: a7, a8: a8, | |||
| 31 | - | a9: a9, a10: a10, a11: a11, a12: a12, a13: a13, a14: a14, a15: a15, | |||
| 32 | - | f1: a1, f2: a2, f3: a3, f4: a4, f5: a5, f6: a6, f7: a7, f8: a8, | |||
| 33 | - | } | |||
| 34 | - | ||||
| 35 | - | runtime_cgocall(syscall15XABI0, unsafe.Pointer(args)) | |||
| 36 | - | return args.a1, args.a2, args.a3 | |||
| 37 | } | 29 | } | ||
| 38 | 30 | ||||
| 39 | // NewCallback converts a Go function to a function pointer conforming to the C calling convention. | 31 | // NewCallback converts a Go function to a function pointer conforming to the C calling convention. | ||
| 40 | // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a | 32 | // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a | ||
| 41 | // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size | 33 | // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size | ||
| 42 | // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated | 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 | // provides similar functionality to windows.NewCallback it is distinct. | 36 | // provides similar functionality to windows.NewCallback it is distinct. | ||
| 37 | + | // | |||
| 38 | + | // Every call to NewCallback creates a new callback even for the same function value, so passing a Go callback to C | |||
| 39 | + | // inside a loop (e.g. a qsort comparator) keeps consuming callbacks and eventually panics once they are exhausted. | |||
| 40 | + | // The same happens when a func value is passed to a C function, as [RegisterFunc] creates a new callback for each | |||
| 41 | + | // call. Create the callback once with NewCallback and reuse the returned pointer instead. | |||
| 45 | func NewCallback(fn any) uintptr { | 42 | func NewCallback(fn any) uintptr { | ||
| 46 | ty := reflect.TypeOf(fn) | 43 | ty := reflect.TypeOf(fn) | ||
| 47 | - | for i := 0; i < ty.NumIn(); i++ { | 44 | + | if ty == nil || ty.Kind() != reflect.Func { |
| 45 | + | panic("purego: the type must be a function but was not") | |||
| 46 | + | } | |||
| 47 | + | for i := range ty.NumIn() { | |||
| 48 | in := ty.In(i) | 48 | in := ty.In(i) | ||
| 49 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | 49 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { |
| 50 | continue | 50 | continue | ||
| 51 | } | 51 | } | ||
| 52 | if i != 0 { | 52 | if i != 0 { | ||
| @@ -101,7 +101,6 @@ func UnrefCallback(cb uintptr) error { | |||||
| 101 | delete(cbs.knownIdx, cb) | 101 | delete(cbs.knownIdx, cb) | ||
| 102 | cbs.holes[idx] = struct{}{} | 102 | cbs.holes[idx] = struct{}{} | ||
| 103 | cbs.funcs[idx] = reflect.Value{} | 103 | cbs.funcs[idx] = reflect.Value{} | ||
| 104 | - | cbs.argPools[idx] = nil | |||
| 105 | recordCallbackLedger("release", idx, cb, key, val, false, "") | 104 | recordCallbackLedger("release", idx, cb, key, val, false, "") | ||
| 106 | return nil | 105 | return nil | ||
| 107 | } | 106 | } | ||
| @@ -134,7 +133,6 @@ func UnrefCallbackFnPtr(fnPtr any) error { | |||||
| 134 | cbs.fnPtrKeys[idx] = 0 | 133 | cbs.fnPtrKeys[idx] = 0 | ||
| 135 | cbs.holes[idx] = struct{}{} | 134 | cbs.holes[idx] = struct{}{} | ||
| 136 | cbs.funcs[idx] = reflect.Value{} | 135 | cbs.funcs[idx] = reflect.Value{} | ||
| 137 | - | cbs.argPools[idx] = nil | |||
| 138 | recordCallbackLedger("release-fnptr", idx, addr, val.Pointer(), callbackVal, false, "") | 136 | recordCallbackLedger("release-fnptr", idx, addr, val.Pointer(), callbackVal, false, "") | ||
| 139 | return nil | 137 | return nil | ||
| 140 | } | 138 | } | ||
| @@ -148,7 +146,6 @@ var cbs = struct { | |||||
| 148 | numFn int // the highest allocated callback index + 1 | 146 | numFn int // the highest allocated callback index + 1 | ||
| 149 | holes map[int]struct{} // reusable callback slots | 147 | holes map[int]struct{} // reusable callback slots | ||
| 150 | funcs [maxCB]reflect.Value // the saved callbacks | 148 | funcs [maxCB]reflect.Value // the saved callbacks | ||
| 151 | - | argPools [maxCB]*sync.Pool // pre-allocated argument buffers per callback | |||
| 152 | knownIdx map[uintptr]int // callback address -> slot index | 149 | knownIdx map[uintptr]int // callback address -> slot index | ||
| 153 | knownFnPtr map[uintptr]uintptr // function pointer variable address -> callback address | 150 | knownFnPtr map[uintptr]uintptr // function pointer variable address -> callback address | ||
| 154 | fnPtrKeys [maxCB]uintptr // slot index -> function pointer variable address | 151 | fnPtrKeys [maxCB]uintptr // slot index -> function pointer variable address | ||
| @@ -342,10 +339,6 @@ func traceCallbackAllocation(event string, val reflect.Value, remaining int) { | |||||
| 342 | if traceFile == "" { | 339 | if traceFile == "" { | ||
| 343 | traceFile = fmt.Sprintf("/tmp/purego-callback-trace-%d.log", os.Getpid()) | 340 | traceFile = fmt.Sprintf("/tmp/purego-callback-trace-%d.log", os.Getpid()) | ||
| 344 | } | 341 | } | ||
| 345 | - | // traceCallbackAllocation intentionally accepts the opt-in | |||
| 346 | - | // PUREGO_CALLBACK_TRACE_FILE path. Users must set PUREGO_CALLBACK_TRACE=1 to | |||
| 347 | - | // enable tracing, and callers who override the path control their debugging | |||
| 348 | - | // environment. This documents the deliberate gosec G304 tradeoff for audits. | |||
| 349 | if f, err := os.OpenFile(traceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); err == nil { | 342 | if f, err := os.OpenFile(traceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); err == nil { | ||
| 350 | _, _ = f.WriteString(message) | 343 | _, _ = f.WriteString(message) | ||
| 351 | _ = f.Close() | 344 | _ = f.Close() | ||
| @@ -361,14 +354,14 @@ func compileCallback(fn any) uintptr { | |||||
| 361 | panic("purego: function must not be nil") | 354 | panic("purego: function must not be nil") | ||
| 362 | } | 355 | } | ||
| 363 | ty := val.Type() | 356 | ty := val.Type() | ||
| 364 | - | for i := 0; i < ty.NumIn(); i++ { | 357 | + | for i := range ty.NumIn() { |
| 365 | in := ty.In(i) | 358 | in := ty.In(i) | ||
| 366 | switch in.Kind() { | 359 | switch in.Kind() { | ||
| 367 | case reflect.Struct: | 360 | case reflect.Struct: | ||
| 368 | - | if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) { | 361 | + | if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) { |
| 369 | continue | 362 | continue | ||
| 370 | } | 363 | } | ||
| 371 | - | ensureStructSupported() | 364 | + | ensureCallbackStructSupported() |
| 372 | checkStructFieldsSupported(in) | 365 | checkStructFieldsSupported(in) | ||
| 373 | continue | 366 | continue | ||
| 374 | case reflect.Interface, reflect.Func, reflect.Slice, | 367 | case reflect.Interface, reflect.Func, reflect.Slice, | ||
| @@ -381,9 +374,13 @@ output: | |||||
| 381 | switch { | 374 | switch { | ||
| 382 | case ty.NumOut() == 1: | 375 | case ty.NumOut() == 1: | ||
| 383 | switch ty.Out(0).Kind() { | 376 | switch ty.Out(0).Kind() { | ||
| 377 | + | case reflect.Struct: | |||
| 378 | + | ensureCallbackStructSupported() | |||
| 379 | + | checkStructFieldsSupported(ty.Out(0)) | |||
| 380 | + | break output | |||
| 384 | case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | 381 | case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | ||
| 385 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, | 382 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, | ||
| 386 | - | reflect.Bool, reflect.UnsafePointer, reflect.Struct: | 383 | + | reflect.Bool, reflect.UnsafePointer: |
| 387 | break output | 384 | break output | ||
| 388 | } | 385 | } | ||
| 389 | panic("purego: unsupported return type: " + ty.String()) | 386 | panic("purego: unsupported return type: " + ty.String()) | ||
| @@ -414,12 +411,6 @@ output: | |||||
| 414 | cbs.numFn++ | 411 | cbs.numFn++ | ||
| 415 | } | 412 | } | ||
| 416 | cbs.funcs[index] = val | 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 | addr := callbackasmAddr(index) | 414 | addr := callbackasmAddr(index) | ||
| 424 | cbs.knownIdx[addr] = index | 415 | cbs.knownIdx[addr] = index | ||
| 425 | if reused { | 416 | if reused { | ||
| @@ -449,26 +440,23 @@ var callbackWrap_call = callbackWrap | |||||
| 449 | // callbackWrap is called by assembly code which determines which Go function to call. | 440 | // callbackWrap is called by assembly code which determines which Go function to call. | ||
| 450 | // This function takes the arguments and passes them to the Go function and returns the result. | 441 | // This function takes the arguments and passes them to the Go function and returns the result. | ||
| 451 | func callbackWrap(a *callbackArgs) { | 442 | func callbackWrap(a *callbackArgs) { | ||
| 452 | - | cbs.lock.RLock() | 443 | + | cbs.lock.Lock() |
| 453 | fn := cbs.funcs[a.index] | 444 | fn := cbs.funcs[a.index] | ||
| 454 | - | pool := cbs.argPools[a.index] | 445 | + | cbs.lock.Unlock() |
| 455 | - | cbs.lock.RUnlock() | |||
| 456 | fnType := fn.Type() | 446 | fnType := fn.Type() | ||
| 457 | - | args := pool.Get().([]reflect.Value) | 447 | + | args := make([]reflect.Value, fnType.NumIn()) |
| 458 | - | defer func() { | |||
| 459 | - | for i := range args { | |||
| 460 | - | args[i] = reflect.Value{} | |||
| 461 | - | } | |||
| 462 | - | pool.Put(args) | |||
| 463 | - | }() | |||
| 464 | frame := (*[callbackMaxFrame]uintptr)(a.args) | 448 | frame := (*[callbackMaxFrame]uintptr)(a.args) | ||
| 465 | // stackFrame points to stack-passed arguments. On most architectures this is | 449 | // stackFrame points to stack-passed arguments. On most architectures this is | ||
| 466 | // contiguous with frame (after register args), but on ppc64le it's separate. | 450 | // contiguous with frame (after register args), but on ppc64le it's separate. | ||
| 467 | var stackFrame *[callbackMaxFrame]uintptr | 451 | var stackFrame *[callbackMaxFrame]uintptr | ||
| 452 | + | var intFrame *[callbackMaxFrame]uintptr | |||
| 468 | if sf := a.stackFrame(); sf != nil { | 453 | if sf := a.stackFrame(); sf != nil { | ||
| 469 | // Only ppc64le uses separate stackArgs pointer due to NOSPLIT constraints | 454 | // Only ppc64le uses separate stackArgs pointer due to NOSPLIT constraints | ||
| 470 | stackFrame = (*[callbackMaxFrame]uintptr)(sf) | 455 | stackFrame = (*[callbackMaxFrame]uintptr)(sf) | ||
| 471 | } | 456 | } | ||
| 457 | + | if intf := a.intFrame(); intf != nil { | |||
| 458 | + | intFrame = (*[callbackMaxFrame]uintptr)(intf) | |||
| 459 | + | } | |||
| 472 | // floatsN and intsN track the number of register slots used, not argument count. | 460 | // floatsN and intsN track the number of register slots used, not argument count. | ||
| 473 | // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers). | 461 | // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers). | ||
| 474 | var floatsN int | 462 | var floatsN int | ||
| @@ -476,7 +464,7 @@ func callbackWrap(a *callbackArgs) { | |||||
| 476 | // On amd64/loong64/ppc64le/riscv64/s390x, when returning a struct larger than | 464 | // On amd64/loong64/ppc64le/riscv64/s390x, when returning a struct larger than | ||
| 477 | // maxRegAllocStructSize, the caller passes a hidden pointer in the first integer | 465 | // maxRegAllocStructSize, the caller passes a hidden pointer in the first integer | ||
| 478 | // register. Skip it to avoid misreading it as the first function argument. | 466 | // register. Skip it to avoid misreading it as the first function argument. | ||
| 479 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && | 467 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && |
| 480 | fnType.NumOut() == 1 && fnType.Out(0).Kind() == reflect.Struct && | 468 | fnType.NumOut() == 1 && fnType.Out(0).Kind() == reflect.Struct && | ||
| 481 | fnType.Out(0).Size() > maxRegAllocStructSize { | 469 | fnType.Out(0).Size() > maxRegAllocStructSize { | ||
| 482 | intsN = 1 | 470 | intsN = 1 | ||
| @@ -494,33 +482,62 @@ func callbackWrap(a *callbackArgs) { | |||||
| 494 | stackByteOffset := uintptr(0) | 482 | stackByteOffset := uintptr(0) | ||
| 495 | for i := range args { | 483 | for i := range args { | ||
| 496 | // slots is the number of pointer-sized slots the argument takes | 484 | // slots is the number of pointer-sized slots the argument takes | ||
| 497 | - | var slots int | |||
| 498 | inType := fnType.In(i) | 485 | inType := fnType.In(i) | ||
| 486 | + | slots := int((inType.Size() + ptrSize - 1) / ptrSize) | |||
| 499 | switch inType.Kind() { | 487 | switch inType.Kind() { | ||
| 500 | case reflect.Float32, reflect.Float64: | 488 | case reflect.Float32, reflect.Float64: | ||
| 501 | - | slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize) | 489 | + | if isARMSoftFloat() { |
| 490 | + | // we should restore from integer slot, can skip unnecessary branching here | |||
| 491 | + | if isARMPaddingNeeded(inType, -1, intsN) { | |||
| 492 | + | intsN++ | |||
| 493 | + | } | |||
| 494 | + | if intsN+slots <= numOfIntegerRegisters() { | |||
| 495 | + | // the integers begin after the floats in frame | |||
| 496 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[intsN+numOfFloatRegisters()])).Elem() | |||
| 497 | + | intsN += slots | |||
| 498 | + | continue | |||
| 499 | + | } | |||
| 500 | + | if isARMPaddingNeeded(inType, -1, stackSlot) { | |||
| 501 | + | stackSlot++ | |||
| 502 | + | } | |||
| 503 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |||
| 504 | + | stackSlot += slots | |||
| 505 | + | intsN += slots | |||
| 506 | + | continue | |||
| 507 | + | } | |||
| 508 | + | ||||
| 502 | if floatsN+slots > numOfFloatRegisters() { | 509 | if floatsN+slots > numOfFloatRegisters() { | ||
| 503 | - | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | 510 | + | if isDarwin && runtime.GOARCH == "arm64" { |
| 504 | // Darwin ARM64: read from packed stack with proper alignment | 511 | // Darwin ARM64: read from packed stack with proper alignment | ||
| 505 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | 512 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | ||
| 506 | } else if stackFrame != nil { | 513 | } else if stackFrame != nil { | ||
| 507 | // ppc64le/s390x: stack args are in separate stackFrame | 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 | // s390x big-endian: sub-8-byte values are right-justified | 519 | // s390x big-endian: sub-8-byte values are right-justified | ||
| 510 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | 520 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | ||
| 511 | - | } else { | 521 | + | default: |
| 512 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | 522 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | ||
| 513 | } | 523 | } | ||
| 514 | stackSlot += slots | 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 | } else { | 529 | } else { | ||
| 516 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | 530 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | ||
| 517 | stackSlot += slots | 531 | stackSlot += slots | ||
| 518 | } | 532 | } | ||
| 519 | } else { | 533 | } else { | ||
| 520 | - | if runtime.GOARCH == "s390x" { | 534 | + | switch runtime.GOARCH { |
| 535 | + | case "ppc64le": | |||
| 536 | + | args[i] = callbackFloatFromDoubleSlot(unsafe.Pointer(&frame[floatsN]), inType) | |||
| 537 | + | case "s390x": | |||
| 521 | // s390x big-endian: float32 is right-justified in 8-byte FPR slot | 538 | // s390x big-endian: float32 is right-justified in 8-byte FPR slot | ||
| 522 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType) | 539 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType) | ||
| 523 | - | } else { | 540 | + | default: |
| 524 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | 541 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | ||
| 525 | } | 542 | } | ||
| 526 | } | 543 | } | ||
| @@ -550,7 +567,7 @@ func callbackWrap(a *callbackArgs) { | |||||
| 550 | args[i] = reflect.ValueOf(internalstrings.GoString(ptr)) | 567 | args[i] = reflect.ValueOf(internalstrings.GoString(ptr)) | ||
| 551 | continue | 568 | continue | ||
| 552 | case reflect.Struct: | 569 | case reflect.Struct: | ||
| 553 | - | if i == 0 && inType.AssignableTo(reflect.TypeOf(CDecl{})) { | 570 | + | if i == 0 && inType.AssignableTo(reflect.TypeFor[CDecl]()) { |
| 554 | args[i] = reflect.Zero(inType) | 571 | args[i] = reflect.Zero(inType) | ||
| 555 | continue | 572 | continue | ||
| 556 | } | 573 | } | ||
| @@ -561,9 +578,11 @@ func callbackWrap(a *callbackArgs) { | |||||
| 561 | args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset) | 578 | args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset) | ||
| 562 | continue | 579 | continue | ||
| 563 | default: | 580 | default: | ||
| 564 | - | slots = int((inType.Size() + ptrSize - 1) / ptrSize) | 581 | + | if isARMPaddingNeeded(inType, -1, intsN) { |
| 582 | + | intsN++ | |||
| 583 | + | } | |||
| 565 | if intsN+slots > numOfIntegerRegisters() { | 584 | if intsN+slots > numOfIntegerRegisters() { | ||
| 566 | - | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | 585 | + | if isDarwin && runtime.GOARCH == "arm64" { |
| 567 | // Darwin ARM64: read from packed stack with proper alignment | 586 | // Darwin ARM64: read from packed stack with proper alignment | ||
| 568 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | 587 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | ||
| 569 | } else if stackFrame != nil { | 588 | } else if stackFrame != nil { | ||
| @@ -575,18 +594,26 @@ func callbackWrap(a *callbackArgs) { | |||||
| 575 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | 594 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | ||
| 576 | } | 595 | } | ||
| 577 | stackSlot += slots | 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 | } else { | 601 | } else { | ||
| 579 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | 602 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | ||
| 580 | stackSlot += slots | 603 | stackSlot += slots | ||
| 581 | } | 604 | } | ||
| 582 | } else { | 605 | } else { | ||
| 583 | - | // the integers begin after the floats in frame | 606 | + | if intFrame != nil { |
| 584 | - | pos := intsN + numOfFloatRegisters() | 607 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&intFrame[intsN])).Elem() |
| 585 | - | if runtime.GOARCH == "s390x" { | |||
| 586 | - | // s390x big-endian: sub-8-byte values are right-justified in GPR slot | |||
| 587 | - | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[pos]), inType) | |||
| 588 | } else { | 608 | } else { | ||
| 589 | - | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | 609 | + | // the integers begin after the floats in frame |
| 610 | + | pos := intsN + numOfFloatRegisters() | |||
| 611 | + | if runtime.GOARCH == "s390x" { | |||
| 612 | + | // s390x big-endian: sub-8-byte values are right-justified in GPR slot | |||
| 613 | + | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[pos]), inType) | |||
| 614 | + | } else { | |||
| 615 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | |||
| 616 | + | } | |||
| 590 | } | 617 | } | ||
| 591 | } | 618 | } | ||
| 592 | intsN += slots | 619 | intsN += slots | ||
| @@ -595,9 +622,13 @@ func callbackWrap(a *callbackArgs) { | |||||
| 595 | ret := fn.Call(args) | 622 | ret := fn.Call(args) | ||
| 596 | if len(ret) > 0 { | 623 | if len(ret) > 0 { | ||
| 597 | switch k := ret[0].Kind(); k { | 624 | switch k := ret[0].Kind(); k { | ||
| 598 | - | case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr: | 625 | + | case reflect.Uint64: |
| 626 | + | a.setUint64Result(ret[0].Uint()) | |||
| 627 | + | case reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr: | |||
| 599 | a.result[0] = uintptr(ret[0].Uint()) | 628 | a.result[0] = uintptr(ret[0].Uint()) | ||
| 600 | - | case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: | 629 | + | case reflect.Int64: |
| 630 | + | a.setInt64Result(ret[0].Int()) | |||
| 631 | + | case reflect.Int, reflect.Int32, reflect.Int16, reflect.Int8: | |||
| 601 | a.result[0] = uintptr(ret[0].Int()) | 632 | a.result[0] = uintptr(ret[0].Int()) | ||
| 602 | case reflect.Bool: | 633 | case reflect.Bool: | ||
| 603 | if ret[0].Bool() { | 634 | if ret[0].Bool() { | ||
| @@ -640,6 +671,18 @@ func callbackArgFromStack(argsBase unsafe.Pointer, stackSlot int, stackByteOffse | |||||
| 640 | return reflect.NewAt(inType, ptr).Elem() | 671 | return reflect.NewAt(inType, ptr).Elem() | ||
| 641 | } | 672 | } | ||
| 642 | 673 | ||||
| 674 | + | // callbackFloatFromDoubleSlot reads a floating-point callback argument from an | |||
| 675 | + | // 8-byte register or stack slot on ppc64le, where a single-precision value is | |||
| 676 | + | // held in double-precision format. | |||
| 677 | + | func callbackFloatFromDoubleSlot(slotPtr unsafe.Pointer, inType reflect.Type) reflect.Value { | |||
| 678 | + | if inType.Kind() != reflect.Float32 { | |||
| 679 | + | return reflect.NewAt(inType, slotPtr).Elem() | |||
| 680 | + | } | |||
| 681 | + | v := reflect.New(inType).Elem() | |||
| 682 | + | v.SetFloat(math.Float64frombits(*(*uint64)(slotPtr))) | |||
| 683 | + | return v | |||
| 684 | + | } | |||
| 685 | + | ||||
| 643 | // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures. | 686 | // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures. | ||
| 644 | // On s390x: | 687 | // On s390x: | ||
| 645 | // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size) | 688 | // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size) | ||
M syscall_windows.go +14 -6
| @@ -11,10 +11,10 @@ import ( | |||||
| 11 | "unsafe" | 11 | "unsafe" | ||
| 12 | ) | 12 | ) | ||
| 13 | 13 | ||||
| 14 | - | var syscall15XABI0 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) { | 16 | + | func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { |
| 17 | - | r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) | 17 | + | r1, r2, errno := syscall.SyscallN(fn, args...) |
| 18 | return r1, r2, uintptr(errno) | 18 | return r1, r2, uintptr(errno) | ||
| 19 | } | 19 | } | ||
| 20 | 20 | ||||
| @@ -23,14 +23,22 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a | |||||
| 23 | // function with one uintptr-sized result. The function must not have arguments with size larger than the | 23 | // function with one uintptr-sized result. The function must not have arguments with size larger than the | ||
| 24 | // size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory | 24 | // size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory | ||
| 25 | // allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024 | 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 | // differently. | 27 | // differently. | ||
| 28 | + | // | |||
| 29 | + | // Every call to NewCallback creates a new callback even for the same function value, so passing a Go callback to C | |||
| 30 | + | // inside a loop (e.g. a qsort comparator) keeps consuming callbacks and eventually panics once they are exhausted. | |||
| 31 | + | // The same happens when a func value is passed to a C function, as [RegisterFunc] creates a new callback for each | |||
| 32 | + | // call. Create the callback once with NewCallback and reuse the returned pointer instead. | |||
| 28 | func NewCallback(fn any) uintptr { | 33 | func NewCallback(fn any) uintptr { | ||
| 29 | isCDecl := false | 34 | isCDecl := false | ||
| 30 | ty := reflect.TypeOf(fn) | 35 | ty := reflect.TypeOf(fn) | ||
| 31 | - | for i := 0; i < ty.NumIn(); i++ { | 36 | + | if ty == nil || ty.Kind() != reflect.Func { |
| 37 | + | panic("purego: the type must be a function but was not") | |||
| 38 | + | } | |||
| 39 | + | for i := range ty.NumIn() { | |||
| 32 | in := ty.In(i) | 40 | in := ty.In(i) | ||
| 33 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | 41 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { |
| 34 | continue | 42 | continue | ||
| 35 | } | 43 | } | ||
| 36 | if i != 0 { | 44 | if i != 0 { | ||
M testdata/abitest/abi_test.c +82 -0
| @@ -129,3 +129,85 @@ void stack_25_int64_exceeds(char *buf, size_t bufsize, int64_t a1, int64_t a2, i | |||||
| 129 | snprintf(buf, bufsize, "%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64, | 129 | snprintf(buf, bufsize, "%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64, | ||
| 130 | a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25); | 130 | a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25); | ||
| 131 | } | 131 | } | ||
| 132 | + | ||||
| 133 | + | uintptr_t stack_20_uintptr( | |||
| 134 | + | uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, | |||
| 135 | + | uintptr_t a6, uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, | |||
| 136 | + | uintptr_t a11, uintptr_t a12, uintptr_t a13, uintptr_t a14, uintptr_t a15, | |||
| 137 | + | uintptr_t a16, uintptr_t a17, uintptr_t a18, uintptr_t a19, uintptr_t a20 | |||
| 138 | + | ) { | |||
| 139 | + | return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + | |||
| 140 | + | a11 + a12 + a13 + a14 + a15 + a16 + a17 + a18 + a19 + a20; | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | uintptr_t stack_32_uintptr( | |||
| 144 | + | uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, uintptr_t a7, uintptr_t a8, | |||
| 145 | + | uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, uintptr_t a13, uintptr_t a14, uintptr_t a15, uintptr_t a16, | |||
| 146 | + | uintptr_t a17, uintptr_t a18, uintptr_t a19, uintptr_t a20, uintptr_t a21, uintptr_t a22, uintptr_t a23, uintptr_t a24, | |||
| 147 | + | uintptr_t a25, uintptr_t a26, uintptr_t a27, uintptr_t a28, uintptr_t a29, uintptr_t a30, uintptr_t a31, uintptr_t a32 | |||
| 148 | + | ) { | |||
| 149 | + | return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + | |||
| 150 | + | a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16 + | |||
| 151 | + | a17 + a18 + a19 + a20 + a21 + a22 + a23 + a24 + | |||
| 152 | + | a25 + a26 + a27 + a28 + a29 + a30 + a31 + a32; | |||
| 153 | + | } | |||
| 154 | + | ||||
| 155 | + | double stack_32_mixed_int_float( | |||
| 156 | + | uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, uintptr_t a7, uintptr_t a8, | |||
| 157 | + | uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, uintptr_t a13, uintptr_t a14, uintptr_t a15, uintptr_t a16, | |||
| 158 | + | double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, | |||
| 159 | + | double f9, double f10, double f11, double f12, double f13, double f14, double f15, double f16 | |||
| 160 | + | ) { | |||
| 161 | + | return (double)a1 * 1 + (double)a2 * 2 + (double)a3 * 3 + (double)a4 * 4 + | |||
| 162 | + | (double)a5 * 5 + (double)a6 * 6 + (double)a7 * 7 + (double)a8 * 8 + | |||
| 163 | + | (double)a9 * 9 + (double)a10 * 10 + (double)a11 * 11 + (double)a12 * 12 + | |||
| 164 | + | (double)a13 * 13 + (double)a14 * 14 + (double)a15 * 15 + (double)a16 * 16 + | |||
| 165 | + | f1 * 17 + f2 * 18 + f3 * 19 + f4 * 20 + | |||
| 166 | + | f5 * 21 + f6 * 22 + f7 * 23 + f8 * 24 + | |||
| 167 | + | f9 * 25 + f10 * 26 + f11 * 27 + f12 * 28 + | |||
| 168 | + | f13 * 29 + f14 * 30 + f15 * 31 + f16 * 32; | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | int64_t arm_int64_unaligned_in_registers(uintptr_t a1, int64_t a2) { | |||
| 172 | + | return (int64_t)a1 * 123 + a2; | |||
| 173 | + | } | |||
| 174 | + | ||||
| 175 | + | int64_t arm_int64_unaligned_on_stack(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, int64_t a6) { | |||
| 176 | + | return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + | |||
| 177 | + | (int64_t)a5 * 5 + a6; | |||
| 178 | + | } | |||
| 179 | + | ||||
| 180 | + | int64_t arm_int64_unaligned_on_stack_after_floats( | |||
| 181 | + | uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, /* these args are going to integer registers */ | |||
| 182 | + | float f1, float f2, float f3, float f4, /* these args are going to float registers */ | |||
| 183 | + | float f5, float f6, float f7, float f8, | |||
| 184 | + | float f9, float f10, float f11, float f12, | |||
| 185 | + | float f13, float f14, float f15, float f16, | |||
| 186 | + | float f17, /* this is a first arg on stack */ | |||
| 187 | + | int64_t a5 /* this arg must be padded */ | |||
| 188 | + | ) { | |||
| 189 | + | return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + a5; | |||
| 190 | + | } | |||
| 191 | + | ||||
| 192 | + | double arm_float64_unaligned_in_registers(uintptr_t a1, double a2) { | |||
| 193 | + | return (double)a1 * 123.5 + a2; | |||
| 194 | + | } | |||
| 195 | + | ||||
| 196 | + | double arm_float64_unaligned_on_stack(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, double a6) { | |||
| 197 | + | return (double)a1 * 1 + (double)a2 * 2 + (double)a3 * 3 + (double)a4 * 4 + | |||
| 198 | + | (double)a5 * 5 + a6; | |||
| 199 | + | } | |||
| 200 | + | ||||
| 201 | + | typedef int32_t (*AddFunc)(int32_t, int32_t); | |||
| 202 | + | ||||
| 203 | + | int32_t returned_add(int32_t a, int32_t b) { | |||
| 204 | + | return a + b; | |||
| 205 | + | } | |||
| 206 | + | ||||
| 207 | + | AddFunc return_func_ptr(void) { | |||
| 208 | + | return returned_add; | |||
| 209 | + | } | |||
| 210 | + | ||||
| 211 | + | AddFunc return_null_func_ptr(void) { | |||
| 212 | + | return NULL; | |||
| 213 | + | } | |||
A testdata/liberrnotest/errno_test.c +10 -0
| @@ -0,0 +1,10 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | #include <errno.h> | |||
| 5 | + | ||||
| 6 | + | // setErrno fails the way a C library call does: it returns -1 and sets errno. | |||
| 7 | + | int setErrno(void) { | |||
| 8 | + | errno = ENOENT; | |||
| 9 | + | return -1; | |||
| 10 | + | } | |||
A testdata/slicetest/slice_test.c +34 -0
| @@ -0,0 +1,34 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | #include <stddef.h> | |||
| 5 | + | #include <string.h> | |||
| 6 | + | ||||
| 7 | + | static char buf[256]; | |||
| 8 | + | ||||
| 9 | + | const char *join_strings(const char **strs) { | |||
| 10 | + | buf[0] = '\0'; | |||
| 11 | + | if (strs == NULL) { | |||
| 12 | + | return "(null)"; | |||
| 13 | + | } | |||
| 14 | + | for (size_t i = 0; strs[i] != NULL; i++) { | |||
| 15 | + | if (i > 0) { | |||
| 16 | + | strcat(buf, ","); | |||
| 17 | + | } | |||
| 18 | + | strcat(buf, strs[i]); | |||
| 19 | + | } | |||
| 20 | + | return buf; | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | static const char *items[] = {"alpha", "beta", "gamma", NULL}; | |||
| 24 | + | ||||
| 25 | + | const char **get_strings(int empty) { | |||
| 26 | + | if (empty) { | |||
| 27 | + | return NULL; | |||
| 28 | + | } | |||
| 29 | + | return items; | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | float sum_floats4(const float v[4]) { | |||
| 33 | + | return v[0] + v[1] + v[2] + v[3]; | |||
| 34 | + | } | |||
M testdata/structtest/struct_test.c +33 -38
| @@ -5,16 +5,11 @@ | |||||
| 5 | #include <stdio.h> | 5 | #include <stdio.h> | ||
| 6 | #include <stdlib.h> | 6 | #include <stdlib.h> | ||
| 7 | 7 | ||||
| 8 | - | #if defined(__x86_64__) || defined(__aarch64__) | |||
| 9 | - | typedef int64_t GoInt; | |||
| 10 | - | typedef uint64_t GoUint; | |||
| 11 | - | #endif | |||
| 12 | - | ||||
| 13 | // Empty is empty | 8 | // Empty is empty | ||
| 14 | struct Empty {}; | 9 | struct Empty {}; | ||
| 15 | 10 | ||||
| 16 | // NoStruct tests that an empty struct doesn't cause issues | 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 | return 0xdeadbeef; | 13 | return 0xdeadbeef; | ||
| 19 | } | 14 | } | ||
| 20 | 15 | ||||
| @@ -22,28 +17,28 @@ struct EmptyEmpty { | |||||
| 22 | struct Empty x; | 17 | struct Empty x; | ||
| 23 | }; | 18 | }; | ||
| 24 | 19 | ||||
| 25 | - | unsigned long EmptyEmpty(struct EmptyEmpty e) { | 20 | + | uint64_t EmptyEmpty(struct EmptyEmpty e) { |
| 26 | return 0xdeadbeef; | 21 | return 0xdeadbeef; | ||
| 27 | } | 22 | } | ||
| 28 | 23 | ||||
| 29 | - | unsigned long EmptyEmptyWithReg(unsigned int x, struct EmptyEmpty e, unsigned int y) { | 24 | + | uint64_t EmptyEmptyWithReg(unsigned int x, struct EmptyEmpty e, unsigned int y) { |
| 30 | return (x << 16) | y; | 25 | return (x << 16) | y; | ||
| 31 | } | 26 | } | ||
| 32 | 27 | ||||
| 33 | // GreaterThan16Bytes is 24 bytes on 64 bit systems | 28 | // GreaterThan16Bytes is 24 bytes on 64 bit systems | ||
| 34 | struct GreaterThan16Bytes { | 29 | struct GreaterThan16Bytes { | ||
| 35 | - | long *x, *y, *z; | 30 | + | int64_t *x, *y, *z; |
| 36 | }; | 31 | }; | ||
| 37 | 32 | ||||
| 38 | // GreaterThan16Bytes is a basic test for structs bigger than 16 bytes | 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 | return *g.x + *g.y + *g.z; | 35 | return *g.x + *g.y + *g.z; | ||
| 41 | } | 36 | } | ||
| 42 | 37 | ||||
| 43 | // AfterRegisters tests to make sure that structs placed on the stack work properly | 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) { | 39 | + | uint64_t AfterRegisters(intptr_t a, intptr_t b, intptr_t c, intptr_t d, intptr_t e, intptr_t f, intptr_t g, intptr_t h, struct GreaterThan16Bytes bytes) { |
| 45 | - | long registers = a + b + c + d + e + f + g + h; | 40 | + | intptr_t registers = a + b + c + d + e + f + g + h; |
| 46 | - | long stack = *bytes.x + *bytes.y + *bytes.z; | 41 | + | int64_t stack = *bytes.x + *bytes.y + *bytes.z; |
| 47 | if (registers != stack) { | 42 | if (registers != stack) { | ||
| 48 | return 0xbadbad; | 43 | return 0xbadbad; | ||
| 49 | } | 44 | } | ||
| @@ -53,25 +48,25 @@ unsigned long AfterRegisters(long a, long b, long c, long d, long e, long f, lon | |||||
| 53 | return stack; | 48 | return stack; | ||
| 54 | } | 49 | } | ||
| 55 | 50 | ||||
| 56 | - | unsigned long BeforeRegisters(struct GreaterThan16Bytes bytes, long a, long b) { | 51 | + | uint64_t BeforeRegisters(struct GreaterThan16Bytes bytes, int64_t a, int64_t b) { |
| 57 | return *bytes.x + *bytes.y + *bytes.z + a + b; | 52 | return *bytes.x + *bytes.y + *bytes.z + a + b; | ||
| 58 | } | 53 | } | ||
| 59 | 54 | ||||
| 60 | struct GreaterThan16BytesStruct { | 55 | struct GreaterThan16BytesStruct { | ||
| 61 | struct { | 56 | struct { | ||
| 62 | - | long *x, *y, *z; | 57 | + | int64_t *x, *y, *z; |
| 63 | } a ; | 58 | } a ; | ||
| 64 | }; | 59 | }; | ||
| 65 | 60 | ||||
| 66 | - | unsigned long GreaterThan16BytesStruct(struct GreaterThan16BytesStruct g) { | 61 | + | uint64_t GreaterThan16BytesStruct(struct GreaterThan16BytesStruct g) { |
| 67 | return *(g.a.x) + *(g.a.y) + *(g.a.z); | 62 | return *(g.a.x) + *(g.a.y) + *(g.a.z); | ||
| 68 | } | 63 | } | ||
| 69 | 64 | ||||
| 70 | struct IntLessThan16Bytes { | 65 | struct IntLessThan16Bytes { | ||
| 71 | - | long x, y; | 66 | + | int64_t x, y; |
| 72 | }; | 67 | }; | ||
| 73 | 68 | ||||
| 74 | - | unsigned long IntLessThan16Bytes(struct IntLessThan16Bytes l) { | 69 | + | uint64_t IntLessThan16Bytes(struct IntLessThan16Bytes l) { |
| 75 | return l.x + l.y; | 70 | return l.x + l.y; | ||
| 76 | } | 71 | } | ||
| 77 | 72 | ||||
| @@ -201,23 +196,23 @@ struct Short { | |||||
| 201 | unsigned short a, b, c, d; | 196 | unsigned short a, b, c, d; | ||
| 202 | }; | 197 | }; | ||
| 203 | 198 | ||||
| 204 | - | unsigned long Short(struct Short s) { | 199 | + | uint64_t Short(struct Short s) { |
| 205 | - | return (long)s.a << 48 | (long)s.b << 32 | (long)s.c << 16 | (long)s.d << 0; | 200 | + | return (uint64_t)s.a << 48 | (uint64_t)s.b << 32 | (uint64_t)s.c << 16 | (uint64_t)s.d << 0; |
| 206 | } | 201 | } | ||
| 207 | 202 | ||||
| 208 | struct Int { | 203 | struct Int { | ||
| 209 | unsigned int a, b; | 204 | unsigned int a, b; | ||
| 210 | }; | 205 | }; | ||
| 211 | 206 | ||||
| 212 | - | unsigned long Int(struct Int i) { | 207 | + | uint64_t Int(struct Int i) { |
| 213 | - | return (long)i.a << 32 | (long)i.b << 0; | 208 | + | return (uint64_t)i.a << 32 | (uint64_t)i.b << 0; |
| 214 | } | 209 | } | ||
| 215 | 210 | ||||
| 216 | struct Long { | 211 | struct Long { | ||
| 217 | - | unsigned long a; | 212 | + | uint64_t a; |
| 218 | }; | 213 | }; | ||
| 219 | 214 | ||||
| 220 | - | unsigned long Long(struct Long l) { | 215 | + | uint64_t Long(struct Long l) { |
| 221 | return l.a; | 216 | return l.a; | ||
| 222 | } | 217 | } | ||
| 223 | 218 | ||||
| @@ -327,31 +322,31 @@ struct Content { | |||||
| 327 | struct { double width, height; } size; | 322 | struct { double width, height; } size; | ||
| 328 | }; | 323 | }; | ||
| 329 | 324 | ||||
| 330 | - | unsigned long InitWithContentRect(int *win, struct Content c, int style, int backing, _Bool flag) { | 325 | + | uint64_t InitWithContentRect(int *win, struct Content c, int style, int backing, _Bool flag) { |
| 331 | if (win == 0) | 326 | if (win == 0) | ||
| 332 | return 0xBAD; | 327 | return 0xBAD; | ||
| 333 | if (!flag) | 328 | if (!flag) | ||
| 334 | return 0xF1A6; // FLAG | 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 | struct GoInt4 { | 333 | struct GoInt4 { | ||
| 339 | - | GoInt a, b, c, d; | 334 | + | intptr_t a, b, c, d; |
| 340 | }; | 335 | }; | ||
| 341 | 336 | ||||
| 342 | - | GoInt GoInt4(struct GoInt4 g) { | 337 | + | intptr_t GoInt4(struct GoInt4 g) { |
| 343 | return g.a + g.b - g.c + g.d; | 338 | return g.a + g.b - g.c + g.d; | ||
| 344 | } | 339 | } | ||
| 345 | 340 | ||||
| 346 | struct GoUint4 { | 341 | struct GoUint4 { | ||
| 347 | - | GoUint a, b, c, d; | 342 | + | uintptr_t a, b, c, d; |
| 348 | }; | 343 | }; | ||
| 349 | 344 | ||||
| 350 | - | GoUint GoUint4(struct GoUint4 g) { | 345 | + | uintptr_t GoUint4(struct GoUint4 g) { |
| 351 | return g.a + g.b + g.c + g.d; | 346 | return g.a + g.b + g.c + g.d; | ||
| 352 | } | 347 | } | ||
| 353 | 348 | ||||
| 354 | - | GoUint TakeGoUintAndReturn(GoUint a) { | 349 | + | uintptr_t TakeGoUintAndReturn(uintptr_t a) { |
| 355 | return a; | 350 | return a; | ||
| 356 | } | 351 | } | ||
| 357 | 352 | ||||
| @@ -395,7 +390,7 @@ uintptr_t AddPointers(struct TwoPointers wrapper) { | |||||
| 395 | // Identity functions for round-trip testing of struct arguments | 390 | // Identity functions for round-trip testing of struct arguments | ||
| 396 | 391 | ||||
| 397 | struct OneInt64 { | 392 | struct OneInt64 { | ||
| 398 | - | long long a; | 393 | + | int64_t a; |
| 399 | }; | 394 | }; | ||
| 400 | 395 | ||||
| 401 | struct OneInt64 IdentityOneInt64(struct OneInt64 s) { | 396 | struct OneInt64 IdentityOneInt64(struct OneInt64 s) { | ||
| @@ -423,7 +418,7 @@ struct FloatAndInt IdentityFloatAndInt(struct FloatAndInt s) { | |||||
| 423 | } | 418 | } | ||
| 424 | 419 | ||||
| 425 | struct ThreeInt64 { | 420 | struct ThreeInt64 { | ||
| 426 | - | long long a, b, c; | 421 | + | int64_t a, b, c; |
| 427 | }; | 422 | }; | ||
| 428 | 423 | ||||
| 429 | struct ThreeInt64 IdentityThreeInt64(struct ThreeInt64 s) { | 424 | struct ThreeInt64 IdentityThreeInt64(struct ThreeInt64 s) { | ||
| @@ -431,16 +426,16 @@ struct ThreeInt64 IdentityThreeInt64(struct ThreeInt64 s) { | |||||
| 431 | } | 426 | } | ||
| 432 | 427 | ||||
| 433 | struct PtrInt64Ptr { | 428 | struct PtrInt64Ptr { | ||
| 434 | - | long long *a; | 429 | + | int64_t *a; |
| 435 | - | long long b; | 430 | + | int64_t b; |
| 436 | - | long long *c; | 431 | + | int64_t *c; |
| 437 | }; | 432 | }; | ||
| 438 | 433 | ||||
| 439 | struct PtrInt64Ptr IdentityPtrInt64Ptr(struct PtrInt64Ptr s) { | 434 | struct PtrInt64Ptr IdentityPtrInt64Ptr(struct PtrInt64Ptr s) { | ||
| 440 | return s; | 435 | return s; | ||
| 441 | } | 436 | } | ||
| 442 | 437 | ||||
| 443 | - | struct IntLessThan16Bytes IdentityTwoInt64AfterPrims(long long x, double y, struct IntLessThan16Bytes s) { | 438 | + | struct IntLessThan16Bytes IdentityTwoInt64AfterPrims(int64_t x, double y, struct IntLessThan16Bytes s) { |
| 444 | return s; | 439 | return s; | ||
| 445 | } | 440 | } | ||
| 446 | 441 | ||||
| @@ -449,7 +444,7 @@ struct FloatLessThan16Bytes IdentityTwoFloat32AfterFloats(double x, double y, st | |||||
| 449 | } | 444 | } | ||
| 450 | 445 | ||||
| 451 | struct Mixed5Args { | 446 | struct Mixed5Args { | ||
| 452 | - | long long *a; | 447 | + | int64_t *a; |
| 453 | int32_t b; | 448 | int32_t b; | ||
| 454 | float c; | 449 | float c; | ||
| 455 | int32_t d; | 450 | int32_t d; | ||
M wincallback.go +8 -8
| @@ -35,7 +35,7 @@ func genasm386() { | |||||
| 35 | 35 | ||||
| 36 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | 36 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | ||
| 37 | `) | 37 | `) | ||
| 38 | - | for i := 0; i < maxCallback; i++ { | 38 | + | for i := range maxCallback { |
| 39 | fmt.Fprintf(&buf, "\tMOVL $%d, CX\n", i) | 39 | fmt.Fprintf(&buf, "\tMOVL $%d, CX\n", i) | ||
| 40 | buf.WriteString("\tJMP callbackasm1(SB)\n") | 40 | buf.WriteString("\tJMP callbackasm1(SB)\n") | ||
| 41 | } | 41 | } | ||
| @@ -63,7 +63,7 @@ func genasmAmd64() { | |||||
| 63 | 63 | ||||
| 64 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | 64 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | ||
| 65 | `) | 65 | `) | ||
| 66 | - | for i := 0; i < maxCallback; i++ { | 66 | + | for range maxCallback { |
| 67 | buf.WriteString("\tCALL callbackasm1(SB)\n") | 67 | buf.WriteString("\tCALL callbackasm1(SB)\n") | ||
| 68 | } | 68 | } | ||
| 69 | if err := os.WriteFile("zcallback_amd64.s", buf.Bytes(), 0644); err != nil { | 69 | if err := os.WriteFile("zcallback_amd64.s", buf.Bytes(), 0644); err != nil { | ||
| @@ -90,7 +90,7 @@ func genasmArm() { | |||||
| 90 | 90 | ||||
| 91 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | 91 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | ||
| 92 | `) | 92 | `) | ||
| 93 | - | for i := 0; i < maxCallback; i++ { | 93 | + | for i := range maxCallback { |
| 94 | fmt.Fprintf(&buf, "\tMOVW $%d, R12\n", i) | 94 | fmt.Fprintf(&buf, "\tMOVW $%d, R12\n", i) | ||
| 95 | buf.WriteString("\tB callbackasm1(SB)\n") | 95 | buf.WriteString("\tB callbackasm1(SB)\n") | ||
| 96 | } | 96 | } | ||
| @@ -118,7 +118,7 @@ func genasmArm64() { | |||||
| 118 | 118 | ||||
| 119 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | 119 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | ||
| 120 | `) | 120 | `) | ||
| 121 | - | for i := 0; i < maxCallback; i++ { | 121 | + | for i := range maxCallback { |
| 122 | fmt.Fprintf(&buf, "\tMOVD $%d, R12\n", i) | 122 | fmt.Fprintf(&buf, "\tMOVD $%d, R12\n", i) | ||
| 123 | buf.WriteString("\tB callbackasm1(SB)\n") | 123 | buf.WriteString("\tB callbackasm1(SB)\n") | ||
| 124 | } | 124 | } | ||
| @@ -146,7 +146,7 @@ func genasmLoong64() { | |||||
| 146 | 146 | ||||
| 147 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | 147 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | ||
| 148 | `) | 148 | `) | ||
| 149 | - | for i := 0; i < maxCallback; i++ { | 149 | + | for i := range maxCallback { |
| 150 | fmt.Fprintf(&buf, "\tMOVV $%d, R12\n", i) | 150 | fmt.Fprintf(&buf, "\tMOVV $%d, R12\n", i) | ||
| 151 | buf.WriteString("\tJMP callbackasm1(SB)\n") | 151 | buf.WriteString("\tJMP callbackasm1(SB)\n") | ||
| 152 | } | 152 | } | ||
| @@ -174,7 +174,7 @@ func genasmPpc64le() { | |||||
| 174 | 174 | ||||
| 175 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | 175 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | ||
| 176 | `) | 176 | `) | ||
| 177 | - | for i := 0; i < maxCallback; i++ { | 177 | + | for i := range maxCallback { |
| 178 | fmt.Fprintf(&buf, "\tMOVD $%d, R11\n", i) | 178 | fmt.Fprintf(&buf, "\tMOVD $%d, R11\n", i) | ||
| 179 | buf.WriteString("\tBR callbackasm1(SB)\n") | 179 | buf.WriteString("\tBR callbackasm1(SB)\n") | ||
| 180 | } | 180 | } | ||
| @@ -206,7 +206,7 @@ func genasmRiscv64() { | |||||
| 206 | 206 | ||||
| 207 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | 207 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | ||
| 208 | `) | 208 | `) | ||
| 209 | - | for i := 0; i < maxCallback; i++ { | 209 | + | for i := range maxCallback { |
| 210 | space := " " | 210 | space := " " | ||
| 211 | if i <= 32 { | 211 | if i <= 32 { | ||
| 212 | fmt.Fprintf(&buf, "\tPCALIGN $8\n") | 212 | fmt.Fprintf(&buf, "\tPCALIGN $8\n") | ||
| @@ -240,7 +240,7 @@ func genasmS390x() { | |||||
| 240 | 240 | ||||
| 241 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | 241 | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | ||
| 242 | `) | 242 | `) | ||
| 243 | - | for i := 0; i < maxCallback; i++ { | 243 | + | for i := range maxCallback { |
| 244 | fmt.Fprintf(&buf, "\tMOVD $%d, R0\n", i) | 244 | fmt.Fprintf(&buf, "\tMOVD $%d, R0\n", i) | ||
| 245 | buf.WriteString("\tBR callbackasm1(SB)\n") | 245 | buf.WriteString("\tBR callbackasm1(SB)\n") | ||
| 246 | } | 246 | } | ||