purego: support arm soft-float abi (#494)
* Pull runtime variable 'goarmsoftfp' to detect if soft-float ABI is used. * Assembly trampolines skips load/store to FP registers if soft-float is detected. This was implemented in exactly same way how it's done in Go runtime. * Apply integer passing rules for float arguments/return values. * Run tests for GOARM=5 (no FPU) in test workflow and add some arm-specific test cases. Closes #490 Co-authored-by: Hirador <63920290+Hirador@users.noreply.github.com>
- commit
- f9d41fc60f1f296d730749174bd3bec93c1ea13c
- author
- Vladimir Stolyarov · · 2026-08-12 18:28 UTC
- committer
- GitHub · · 2026-08-12 18:28 UTC
- parent
- 0960e99
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown - refs
- tag: v0.11.0-alpha.9
10 files · +153 -5
M .github/workflows/test.yml +14 -1
| @@ -146,7 +146,9 @@ jobs: | |||
| 146 | 146 | if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} | |
| 147 | 147 | run: | | |
| 148 | 148 | sudo apt-get update | |
| 149 | - | sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross | |
| 149 | + | sudo apt-get install -y \ | |
| 150 | + | gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross \ | |
| 151 | + | gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libc6-armel-cross | |
| 150 | 152 | ||
| 151 | 153 | # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2 | |
| 152 | 154 | # which has a bug where inter-thread signal delivery (tgkill) hangs, | |
| @@ -177,6 +179,17 @@ jobs: | |||
| 177 | 179 | go env -u CC | |
| 178 | 180 | go env -u CXX | |
| 179 | 181 | ||
| 182 | + | # Repeat the same for ARMv5 target (soft-float) | |
| 183 | + | sudo ln -sf /usr/arm-linux-gnueabi/lib/ld-linux.so.3 /lib/ld-linux.so.3 | |
| 184 | + | go env -w CC=arm-linux-gnueabi-gcc | |
| 185 | + | go env -w CXX=arm-linux-gnueabi-g++ | |
| 186 | + | env GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |
| 187 | + | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabi qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |
| 188 | + | env GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |
| 189 | + | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabi qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |
| 190 | + | go env -u CC | |
| 191 | + | go env -u CXX | |
| 192 | + | ||
| 180 | 193 | - name: go test race (no Cgo) | |
| 181 | 194 | if: runner.os == 'macOS' | |
| 182 | 195 | run: | | |
| @@ -146,7 +146,9 @@ jobs: | |||||
| 146 | if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} | 146 | if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} | ||
| 147 | run: | | 147 | run: | | ||
| 148 | sudo apt-get update | 148 | sudo apt-get update | ||
| 149 | - | sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross | 149 | + | sudo apt-get install -y \ |
| 150 | + | gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross \ | |||
| 151 | + | gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libc6-armel-cross | |||
| 150 | 152 | ||||
| 151 | # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2 | 153 | # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2 | ||
| 152 | # which has a bug where inter-thread signal delivery (tgkill) hangs, | 154 | # which has a bug where inter-thread signal delivery (tgkill) hangs, | ||
| @@ -177,6 +179,17 @@ jobs: | |||||
| 177 | go env -u CC | 179 | go env -u CC | ||
| 178 | go env -u CXX | 180 | go env -u CXX | ||
| 179 | 181 | ||||
| 182 | + | # Repeat the same for ARMv5 target (soft-float) | |||
| 183 | + | sudo ln -sf /usr/arm-linux-gnueabi/lib/ld-linux.so.3 /lib/ld-linux.so.3 | |||
| 184 | + | go env -w CC=arm-linux-gnueabi-gcc | |||
| 185 | + | go env -w CXX=arm-linux-gnueabi-g++ | |||
| 186 | + | env GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |||
| 187 | + | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabi qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |||
| 188 | + | env GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |||
| 189 | + | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabi qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |||
| 190 | + | go env -u CC | |||
| 191 | + | go env -u CXX | |||
| 192 | + | ||||
| 180 | - name: go test race (no Cgo) | 193 | - name: go test race (no Cgo) | ||
| 181 | if: runner.os == 'macOS' | 194 | if: runner.os == 'macOS' | ||
| 182 | run: | | 195 | run: | | ||
M func.go +44 -2
| @@ -21,6 +21,10 @@ const ( | |||
| 21 | 21 | align8ByteSize = 8 // 8-byte alignment boundary | |
| 22 | 22 | ) | |
| 23 | 23 | ||
| 24 | + | func isARMSoftFloat() bool { | |
| 25 | + | return runtime.GOARCH == "arm" && *(*uint8)(unsafe.Pointer(&runtime_goarmsoftfp)) != 0 | |
| 26 | + | } | |
| 27 | + | ||
| 24 | 28 | var thePool = sync.Pool{New: func() any { | |
| 25 | 29 | return new(syscallArgs) | |
| 26 | 30 | }} | |
| @@ -183,6 +187,19 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 183 | 187 | } | |
| 184 | 188 | case reflect.Float32, reflect.Float64: | |
| 185 | 189 | usesSlots := max(1, int(arg.Size()/ptrSize)) | |
| 190 | + | if isARMSoftFloat() { | |
| 191 | + | // float64 for arm with softfloat uses same rules as int64 | |
| 192 | + | if isARMPaddingNeeded(arg, ints, stack) { | |
| 193 | + | usesSlots++ | |
| 194 | + | } | |
| 195 | + | if ints < numOfIntegerRegisters() { | |
| 196 | + | ints += usesSlots | |
| 197 | + | } else { | |
| 198 | + | stack += usesSlots | |
| 199 | + | } | |
| 200 | + | continue | |
| 201 | + | } | |
| 202 | + | ||
| 186 | 203 | if isARMFloatPaddingNeeded(arg, floats, stack) { | |
| 187 | 204 | usesSlots++ | |
| 188 | 205 | } | |
| @@ -402,6 +419,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 402 | 419 | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | |
| 403 | 420 | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | |
| 404 | 421 | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | |
| 422 | + | // On 32bit ARM with softfloat float32 returned as integer | |
| 405 | 423 | switch runtime.GOARCH { | |
| 406 | 424 | case "386": | |
| 407 | 425 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |
| @@ -410,13 +428,22 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 410 | 428 | case "s390x": | |
| 411 | 429 | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | |
| 412 | 430 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | |
| 431 | + | case "arm": | |
| 432 | + | if isARMSoftFloat() { | |
| 433 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.a1)))) | |
| 434 | + | } else { | |
| 435 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |
| 436 | + | } | |
| 413 | 437 | default: | |
| 414 | 438 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |
| 415 | 439 | } | |
| 416 | 440 | case reflect.Float64: | |
| 417 | 441 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |
| 418 | 442 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |
| 419 | - | if is32bit { | |
| 443 | + | if isARMSoftFloat() { | |
| 444 | + | // a1,a2 are populated in this case | |
| 445 | + | v.SetFloat(math.Float64frombits(uint64(syscall.a1) | (uint64(syscall.a2) << 32))) | |
| 446 | + | } else if is32bit { | |
| 420 | 447 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |
| 421 | 448 | } else { | |
| 422 | 449 | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |
| @@ -483,6 +510,13 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||
| 483 | 510 | case "s390x": | |
| 484 | 511 | // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register. | |
| 485 | 512 | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | |
| 513 | + | case "arm": | |
| 514 | + | if isARMSoftFloat() { | |
| 515 | + | // 32-bit ARM with softfloat: float32 goes as integer | |
| 516 | + | addInt(uintptr(math.Float32bits(float32(v.Float())))) | |
| 517 | + | } else { | |
| 518 | + | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |
| 519 | + | } | |
| 486 | 520 | default: | |
| 487 | 521 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |
| 488 | 522 | } | |
| @@ -492,7 +526,14 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||
| 492 | 526 | // if floats are spilled onto stack on ARM than we must follow AAPCS C.7 | |
| 493 | 527 | addFloat(0) | |
| 494 | 528 | } | |
| 495 | - | if is32bit { | |
| 529 | + | if isARMSoftFloat() { | |
| 530 | + | // add as uint64 | |
| 531 | + | if isARMPaddingNeeded(v.Type(), *numInts, *numStack) { | |
| 532 | + | addInt(0) | |
| 533 | + | } | |
| 534 | + | addInt(uintptr(bits)) | |
| 535 | + | addInt(uintptr(bits >> 32)) | |
| 536 | + | } else if is32bit { | |
| 496 | 537 | addFloat(uintptr(bits)) | |
| 497 | 538 | addFloat(uintptr(bits >> 32)) | |
| 498 | 539 | } else { | |
| @@ -603,6 +644,7 @@ func numOfFloatRegisters() int { | |||
| 603 | 644 | case "s390x": | |
| 604 | 645 | return 4 | |
| 605 | 646 | case "arm": | |
| 647 | + | // 8 doubles (16 words) are always reserved by asm trampolines, even if softfloat is used | |
| 606 | 648 | return 16 | |
| 607 | 649 | case "386": | |
| 608 | 650 | // i386 SysV ABI passes all arguments on the stack, including floats | |
| @@ -21,6 +21,10 @@ const ( | |||||
| 21 | align8ByteSize = 8 // 8-byte alignment boundary | 21 | align8ByteSize = 8 // 8-byte alignment boundary | ||
| 22 | ) | 22 | ) | ||
| 23 | 23 | ||||
| 24 | + | func isARMSoftFloat() bool { | |||
| 25 | + | return runtime.GOARCH == "arm" && *(*uint8)(unsafe.Pointer(&runtime_goarmsoftfp)) != 0 | |||
| 26 | + | } | |||
| 27 | + | ||||
| 24 | var thePool = sync.Pool{New: func() any { | 28 | var thePool = sync.Pool{New: func() any { | ||
| 25 | return new(syscallArgs) | 29 | return new(syscallArgs) | ||
| 26 | }} | 30 | }} | ||
| @@ -183,6 +187,19 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 183 | } | 187 | } | ||
| 184 | case reflect.Float32, reflect.Float64: | 188 | case reflect.Float32, reflect.Float64: | ||
| 185 | usesSlots := max(1, int(arg.Size()/ptrSize)) | 189 | usesSlots := max(1, int(arg.Size()/ptrSize)) | ||
| 190 | + | if isARMSoftFloat() { | |||
| 191 | + | // float64 for arm with softfloat uses same rules as int64 | |||
| 192 | + | if isARMPaddingNeeded(arg, ints, stack) { | |||
| 193 | + | usesSlots++ | |||
| 194 | + | } | |||
| 195 | + | if ints < numOfIntegerRegisters() { | |||
| 196 | + | ints += usesSlots | |||
| 197 | + | } else { | |||
| 198 | + | stack += usesSlots | |||
| 199 | + | } | |||
| 200 | + | continue | |||
| 201 | + | } | |||
| 202 | + | ||||
| 186 | if isARMFloatPaddingNeeded(arg, floats, stack) { | 203 | if isARMFloatPaddingNeeded(arg, floats, stack) { | ||
| 187 | usesSlots++ | 204 | usesSlots++ | ||
| 188 | } | 205 | } | ||
| @@ -402,6 +419,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 402 | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | 419 | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | ||
| 403 | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | 420 | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | ||
| 404 | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | 421 | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | ||
| 422 | + | // On 32bit ARM with softfloat float32 returned as integer | |||
| 405 | switch runtime.GOARCH { | 423 | switch runtime.GOARCH { | ||
| 406 | case "386": | 424 | case "386": | ||
| 407 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | 425 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | ||
| @@ -410,13 +428,22 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 410 | case "s390x": | 428 | case "s390x": | ||
| 411 | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | 429 | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | ||
| 412 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | 430 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | ||
| 431 | + | case "arm": | |||
| 432 | + | if isARMSoftFloat() { | |||
| 433 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.a1)))) | |||
| 434 | + | } else { | |||
| 435 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |||
| 436 | + | } | |||
| 413 | default: | 437 | default: | ||
| 414 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | 438 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | ||
| 415 | } | 439 | } | ||
| 416 | case reflect.Float64: | 440 | case reflect.Float64: | ||
| 417 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | 441 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | ||
| 418 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | 442 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | ||
| 419 | - | if is32bit { | 443 | + | if isARMSoftFloat() { |
| 444 | + | // a1,a2 are populated in this case | |||
| 445 | + | v.SetFloat(math.Float64frombits(uint64(syscall.a1) | (uint64(syscall.a2) << 32))) | |||
| 446 | + | } else if is32bit { | |||
| 420 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | 447 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | ||
| 421 | } else { | 448 | } else { | ||
| 422 | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | 449 | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | ||
| @@ -483,6 +510,13 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 483 | case "s390x": | 510 | case "s390x": | ||
| 484 | // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register. | 511 | // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register. | ||
| 485 | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | 512 | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | ||
| 513 | + | case "arm": | |||
| 514 | + | if isARMSoftFloat() { | |||
| 515 | + | // 32-bit ARM with softfloat: float32 goes as integer | |||
| 516 | + | addInt(uintptr(math.Float32bits(float32(v.Float())))) | |||
| 517 | + | } else { | |||
| 518 | + | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |||
| 519 | + | } | |||
| 486 | default: | 520 | default: | ||
| 487 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | 521 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | ||
| 488 | } | 522 | } | ||
| @@ -492,7 +526,14 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 492 | // if floats are spilled onto stack on ARM than we must follow AAPCS C.7 | 526 | // if floats are spilled onto stack on ARM than we must follow AAPCS C.7 | ||
| 493 | addFloat(0) | 527 | addFloat(0) | ||
| 494 | } | 528 | } | ||
| 495 | - | if is32bit { | 529 | + | if isARMSoftFloat() { |
| 530 | + | // add as uint64 | |||
| 531 | + | if isARMPaddingNeeded(v.Type(), *numInts, *numStack) { | |||
| 532 | + | addInt(0) | |||
| 533 | + | } | |||
| 534 | + | addInt(uintptr(bits)) | |||
| 535 | + | addInt(uintptr(bits >> 32)) | |||
| 536 | + | } else if is32bit { | |||
| 496 | addFloat(uintptr(bits)) | 537 | addFloat(uintptr(bits)) | ||
| 497 | addFloat(uintptr(bits >> 32)) | 538 | addFloat(uintptr(bits >> 32)) | ||
| 498 | } else { | 539 | } else { | ||
| @@ -603,6 +644,7 @@ func numOfFloatRegisters() int { | |||||
| 603 | case "s390x": | 644 | case "s390x": | ||
| 604 | return 4 | 645 | return 4 | ||
| 605 | case "arm": | 646 | case "arm": | ||
| 647 | + | // 8 doubles (16 words) are always reserved by asm trampolines, even if softfloat is used | |||
| 606 | return 16 | 648 | return 16 | ||
| 607 | case "386": | 649 | case "386": | ||
| 608 | // i386 SysV ABI passes all arguments on the stack, including floats | 650 | // i386 SysV ABI passes all arguments on the stack, including floats | ||
M func_test.go +24 -0
| @@ -461,6 +461,30 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||
| 461 | 461 | }, | |
| 462 | 462 | want: strconv.FormatInt(12*1+34*2+56*3+78*4+math.MaxInt32+1500, 10), | |
| 463 | 463 | }, | |
| 464 | + | { | |
| 465 | + | // check if unaligned float 64bit argument and float 64bit returned value is properly passed via registers | |
| 466 | + | // arm-softfloat-specific but must work everywhere | |
| 467 | + | name: "arm_float64_unaligned_in_registers", | |
| 468 | + | fn: new(func(uintptr, float64) float64), | |
| 469 | + | cFn: "arm_float64_unaligned_in_registers", | |
| 470 | + | call: func(f any) string { | |
| 471 | + | fn := *(f).(*func(x uintptr, y float64) float64) | |
| 472 | + | return strconv.FormatFloat(fn(456, math.MaxFloat32+1500), 'b', 10, 64) | |
| 473 | + | }, | |
| 474 | + | want: strconv.FormatFloat(456*123.5+math.MaxFloat32+1500, 'b', 10, 64), | |
| 475 | + | }, | |
| 476 | + | { | |
| 477 | + | // check if unaligned float 64bit argument and float 64bit returned value is properly passed via stack | |
| 478 | + | // arm-softfloat-specific but must work everywhere | |
| 479 | + | name: "arm_float64_unaligned_on_stack", | |
| 480 | + | fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, float64) float64), | |
| 481 | + | cFn: "arm_float64_unaligned_on_stack", | |
| 482 | + | call: func(f any) string { | |
| 483 | + | fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 float64) float64) | |
| 484 | + | return strconv.FormatFloat(fn(12, 34, 56, 78, 90, math.MaxFloat32+1500), 'b', 10, 64) | |
| 485 | + | }, | |
| 486 | + | want: strconv.FormatFloat(12*1+34*2+56*3+78*4+90*5+math.MaxFloat32+1500, 'b', 10, 64), | |
| 487 | + | }, | |
| 464 | 488 | } | |
| 465 | 489 | ||
| 466 | 490 | for _, tt := range tests { | |
| @@ -461,6 +461,30 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||||
| 461 | }, | 461 | }, | ||
| 462 | want: strconv.FormatInt(12*1+34*2+56*3+78*4+math.MaxInt32+1500, 10), | 462 | want: strconv.FormatInt(12*1+34*2+56*3+78*4+math.MaxInt32+1500, 10), | ||
| 463 | }, | 463 | }, | ||
| 464 | + | { | |||
| 465 | + | // check if unaligned float 64bit argument and float 64bit returned value is properly passed via registers | |||
| 466 | + | // arm-softfloat-specific but must work everywhere | |||
| 467 | + | name: "arm_float64_unaligned_in_registers", | |||
| 468 | + | fn: new(func(uintptr, float64) float64), | |||
| 469 | + | cFn: "arm_float64_unaligned_in_registers", | |||
| 470 | + | call: func(f any) string { | |||
| 471 | + | fn := *(f).(*func(x uintptr, y float64) float64) | |||
| 472 | + | return strconv.FormatFloat(fn(456, math.MaxFloat32+1500), 'b', 10, 64) | |||
| 473 | + | }, | |||
| 474 | + | want: strconv.FormatFloat(456*123.5+math.MaxFloat32+1500, 'b', 10, 64), | |||
| 475 | + | }, | |||
| 476 | + | { | |||
| 477 | + | // check if unaligned float 64bit argument and float 64bit returned value is properly passed via stack | |||
| 478 | + | // arm-softfloat-specific but must work everywhere | |||
| 479 | + | name: "arm_float64_unaligned_on_stack", | |||
| 480 | + | fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, float64) float64), | |||
| 481 | + | cFn: "arm_float64_unaligned_on_stack", | |||
| 482 | + | call: func(f any) string { | |||
| 483 | + | fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 float64) float64) | |||
| 484 | + | return strconv.FormatFloat(fn(12, 34, 56, 78, 90, math.MaxFloat32+1500), 'b', 10, 64) | |||
| 485 | + | }, | |||
| 486 | + | want: strconv.FormatFloat(12*1+34*2+56*3+78*4+90*5+math.MaxFloat32+1500, 'b', 10, 64), | |||
| 487 | + | }, | |||
| 464 | } | 488 | } | ||
| 465 | 489 | ||||
| 466 | for _, tt := range tests { | 490 | for _, tt := range tests { | ||
M go_runtime.go +6 -0
| @@ -11,3 +11,9 @@ import ( | |||
| 11 | 11 | ||
| 12 | 12 | //go:linkname runtime_cgocall runtime.cgocall | |
| 13 | 13 | func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go | |
| 14 | + | ||
| 15 | + | // from runtime/runtime2.go, exported via go:linkname for usage with cgo assembly | |
| 16 | + | // pulled in as struct{} for proper linking, see https://github.com/golang/go/issues/72032 | |
| 17 | + | // | |
| 18 | + | //go:linkname runtime_goarmsoftfp runtime.goarmsoftfp | |
| 19 | + | var runtime_goarmsoftfp struct{} | |
| @@ -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{} | |||
M internal/fakecgo/asm_arm.s +9 -1
| @@ -21,8 +21,11 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 21 | 21 | // starting at 4(R13). | |
| 22 | 22 | MOVW.W R14, -4(R13) | |
| 23 | 23 | ||
| 24 | + | // Skip floating point registers if runtime.goarmsoftfp!=0. | |
| 25 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 26 | + | CMP $0, R11 | |
| 27 | + | BNE skipfpsave | |
| 24 | 28 | // Save VFP callee-saved registers D8-D15 (same as S16-S31). | |
| 25 | - | // Note: We always save these since we target hard-float ABI. | |
| 26 | 29 | MOVD F8, (13*4+8*1)(R13) | |
| 27 | 30 | MOVD F9, (13*4+8*2)(R13) | |
| 28 | 31 | MOVD F10, (13*4+8*3)(R13) | |
| @@ -32,9 +35,13 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 32 | 35 | MOVD F14, (13*4+8*7)(R13) | |
| 33 | 36 | MOVD F15, (13*4+8*8)(R13) | |
| 34 | 37 | ||
| 38 | + | skipfpsave: | |
| 35 | 39 | // We set up the arguments to cgocallback when saving registers above. | |
| 36 | 40 | BL runtime·cgocallback(SB) | |
| 37 | 41 | ||
| 42 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 43 | + | CMP $0, R11 | |
| 44 | + | BNE skipfprest | |
| 38 | 45 | MOVD (13*4+8*1)(R13), F8 | |
| 39 | 46 | MOVD (13*4+8*2)(R13), F9 | |
| 40 | 47 | MOVD (13*4+8*3)(R13), F10 | |
| @@ -44,6 +51,7 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 44 | 51 | MOVD (13*4+8*7)(R13), F14 | |
| 45 | 52 | MOVD (13*4+8*8)(R13), F15 | |
| 46 | 53 | ||
| 54 | + | skipfprest: | |
| 47 | 55 | MOVW.P 4(R13), R14 | |
| 48 | 56 | MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12] | |
| 49 | 57 | ADD $(8*9), R13 | |
| @@ -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,9 +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 | ||||
| 38 | + | skipfpsave: | |||
| 35 | // We set up the arguments to cgocallback when saving registers above. | 39 | // We set up the arguments to cgocallback when saving registers above. | ||
| 36 | BL runtime·cgocallback(SB) | 40 | BL runtime·cgocallback(SB) | ||
| 37 | 41 | ||||
| 42 | + | MOVB runtime·goarmsoftfp(SB), R11 | |||
| 43 | + | CMP $0, R11 | |||
| 44 | + | BNE skipfprest | |||
| 38 | MOVD (13*4+8*1)(R13), F8 | 45 | MOVD (13*4+8*1)(R13), F8 | ||
| 39 | MOVD (13*4+8*2)(R13), F9 | 46 | MOVD (13*4+8*2)(R13), F9 | ||
| 40 | MOVD (13*4+8*3)(R13), F10 | 47 | MOVD (13*4+8*3)(R13), F10 | ||
| @@ -44,6 +51,7 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||||
| 44 | MOVD (13*4+8*7)(R13), F14 | 51 | MOVD (13*4+8*7)(R13), F14 | ||
| 45 | MOVD (13*4+8*8)(R13), F15 | 52 | MOVD (13*4+8*8)(R13), F15 | ||
| 46 | 53 | ||||
| 54 | + | skipfprest: | |||
| 47 | MOVW.P 4(R13), R14 | 55 | MOVW.P 4(R13), R14 | ||
| 48 | 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] | ||
| 49 | ADD $(8*9), R13 | 57 | ADD $(8*9), R13 | ||
M internal/fakecgo/trampolines_arm.s +9 -0
| @@ -70,6 +70,10 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 | |||
| 70 | 70 | MOVW g, 32(R13) // R10 | |
| 71 | 71 | MOVW R11, 36(R13) | |
| 72 | 72 | ||
| 73 | + | // Skip floating point registers if runtime.goarmsoftfp!=0. | |
| 74 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 75 | + | CMP $0, R11 | |
| 76 | + | BNE skipfpsave | |
| 73 | 77 | MOVD F8, 40(R13) | |
| 74 | 78 | MOVD F9, 48(R13) | |
| 75 | 79 | MOVD F10, 56(R13) | |
| @@ -79,10 +83,14 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 | |||
| 79 | 83 | MOVD F14, 88(R13) | |
| 80 | 84 | MOVD F15, 96(R13) | |
| 81 | 85 | ||
| 86 | + | skipfpsave: | |
| 82 | 87 | MOVW ·threadentry_call(SB), R12 | |
| 83 | 88 | MOVW (R12), R12 | |
| 84 | 89 | CALL (R12) | |
| 85 | 90 | ||
| 91 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 92 | + | CMP $0, R11 | |
| 93 | + | BNE skipfprest | |
| 86 | 94 | MOVD 40(R13), F8 | |
| 87 | 95 | MOVD 48(R13), F9 | |
| 88 | 96 | MOVD 56(R13), F10 | |
| @@ -92,6 +100,7 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 | |||
| 92 | 100 | MOVD 88(R13), F14 | |
| 93 | 101 | MOVD 96(R13), F15 | |
| 94 | 102 | ||
| 103 | + | skipfprest: | |
| 95 | 104 | MOVW 8(R13), R4 | |
| 96 | 105 | MOVW 12(R13), R5 | |
| 97 | 106 | MOVW 16(R13), R6 | |
| @@ -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 | ||
M sys_arm.s +9 -0
| @@ -41,6 +41,10 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 41 | 41 | MOVW syscallArgs_fn(R8), R5 | |
| 42 | 42 | MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 56 | |
| 43 | 43 | ||
| 44 | + | // Skip floating point registers if runtime.goarmsoftfp!=0. | |
| 45 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 46 | + | CMP $0, R11 | |
| 47 | + | BNE skipfpload | |
| 44 | 48 | // Load floating point arguments | |
| 45 | 49 | // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 2 | |
| 46 | 50 | MOVD syscallArgs_f1(R8), F0 // f1+f2 -> D0 | |
| @@ -52,6 +56,7 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 52 | 56 | MOVD syscallArgs_f13(R8), F6 // f13+f14 -> D6 | |
| 53 | 57 | MOVD syscallArgs_f15(R8), F7 // f15+f16 -> D7 | |
| 54 | 58 | ||
| 59 | + | skipfpload: | |
| 55 | 60 | // Load integer arguments into registers (R0-R3 for ARM EABI) | |
| 56 | 61 | MOVW syscallArgs_a1(R8), R0 // a1 | |
| 57 | 62 | MOVW syscallArgs_a2(R8), R1 // a2 | |
| @@ -131,12 +136,16 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 131 | 136 | MOVW R0, syscallArgs_a1(R8) | |
| 132 | 137 | MOVW R1, syscallArgs_a2(R8) | |
| 133 | 138 | ||
| 139 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 140 | + | CMP $0, R11 | |
| 141 | + | BNE skipfpsave | |
| 134 | 142 | // save f0-f3 (each float64 spans 2 uintptr slots on ARM32) | |
| 135 | 143 | MOVD F0, syscallArgs_f1(R8) | |
| 136 | 144 | MOVD F1, syscallArgs_f3(R8) | |
| 137 | 145 | MOVD F2, syscallArgs_f5(R8) | |
| 138 | 146 | MOVD F3, syscallArgs_f7(R8) | |
| 139 | 147 | ||
| 148 | + | skipfpsave: | |
| 140 | 149 | // Restore callee-saved registers and return | |
| 141 | 150 | MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11] | |
| 142 | 151 | MOVW.P 4(R13), R15 // pop LR into PC (return) | |
| @@ -41,6 +41,10 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 | |||||
| 41 | MOVW syscallArgs_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 syscallArgs_f1(R8), F0 // f1+f2 -> D0 | 50 | MOVD syscallArgs_f1(R8), F0 // f1+f2 -> D0 | ||
| @@ -52,6 +56,7 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 | |||||
| 52 | MOVD syscallArgs_f13(R8), F6 // f13+f14 -> D6 | 56 | MOVD syscallArgs_f13(R8), F6 // f13+f14 -> D6 | ||
| 53 | MOVD syscallArgs_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 syscallArgs_a1(R8), R0 // a1 | 61 | MOVW syscallArgs_a1(R8), R0 // a1 | ||
| 57 | MOVW syscallArgs_a2(R8), R1 // a2 | 62 | MOVW syscallArgs_a2(R8), R1 // a2 | ||
| @@ -131,12 +136,16 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 | |||||
| 131 | MOVW R0, syscallArgs_a1(R8) | 136 | MOVW R0, syscallArgs_a1(R8) | ||
| 132 | MOVW R1, syscallArgs_a2(R8) | 137 | MOVW R1, syscallArgs_a2(R8) | ||
| 133 | 138 | ||||
| 139 | + | MOVB runtime·goarmsoftfp(SB), R11 | |||
| 140 | + | CMP $0, R11 | |||
| 141 | + | BNE skipfpsave | |||
| 134 | // save f0-f3 (each float64 spans 2 uintptr slots on ARM32) | 142 | // save f0-f3 (each float64 spans 2 uintptr slots on ARM32) | ||
| 135 | MOVD F0, syscallArgs_f1(R8) | 143 | MOVD F0, syscallArgs_f1(R8) | ||
| 136 | MOVD F1, syscallArgs_f3(R8) | 144 | MOVD F1, syscallArgs_f3(R8) | ||
| 137 | MOVD F2, syscallArgs_f5(R8) | 145 | MOVD F2, syscallArgs_f5(R8) | ||
| 138 | MOVD F3, syscallArgs_f7(R8) | 146 | MOVD F3, syscallArgs_f7(R8) | ||
| 139 | 147 | ||||
| 148 | + | skipfpsave: | |||
| 140 | // Restore callee-saved registers and return | 149 | // Restore callee-saved registers and return | ||
| 141 | MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11] | 150 | MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11] | ||
| 142 | MOVW.P 4(R13), R15 // pop LR into PC (return) | 151 | MOVW.P 4(R13), R15 // pop LR into PC (return) | ||
M sys_unix_arm.s +9 -1
| @@ -33,8 +33,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 33 | 33 | MOVW R2, 136(R13) | |
| 34 | 34 | MOVW R3, 140(R13) | |
| 35 | 35 | ||
| 36 | + | // Skip floating point registers if runtime.goarmsoftfp!=0. | |
| 37 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 38 | + | CMP $0, R11 | |
| 39 | + | BNE skipfpsave | |
| 36 | 40 | // Save floating point registers F0-F7 at SP+64 (frame[0..15]) | |
| 37 | - | // Note: We always save these since we target hard-float ABI. | |
| 38 | 41 | MOVD F0, 64(R13) | |
| 39 | 42 | MOVD F1, 72(R13) | |
| 40 | 43 | MOVD F2, 80(R13) | |
| @@ -44,6 +47,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 44 | 47 | MOVD F6, 112(R13) | |
| 45 | 48 | MOVD F7, 120(R13) | |
| 46 | 49 | ||
| 50 | + | skipfpsave: | |
| 47 | 51 | // Set up callbackArgs at SP+48 | |
| 48 | 52 | MOVW 36(R13), R4 | |
| 49 | 53 | MOVW R4, 48(R13) | |
| @@ -66,6 +70,9 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 66 | 70 | MOVW 56(R13), R0 | |
| 67 | 71 | MOVW 60(R13), R1 // high word of a 64-bit return | |
| 68 | 72 | ||
| 73 | + | MOVB runtime·goarmsoftfp(SB), R11 | |
| 74 | + | CMP $0, R11 | |
| 75 | + | BNE skipfprest | |
| 69 | 76 | // Restore float registers | |
| 70 | 77 | MOVD 64(R13), F0 | |
| 71 | 78 | MOVD 72(R13), F1 | |
| @@ -76,6 +83,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 76 | 83 | MOVD 112(R13), F6 | |
| 77 | 84 | MOVD 120(R13), F7 | |
| 78 | 85 | ||
| 86 | + | skipfprest: | |
| 79 | 87 | // Restore callee-saved registers | |
| 80 | 88 | MOVW 0(R13), R4 | |
| 81 | 89 | MOVW 4(R13), R5 | |
| @@ -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) | ||
| @@ -66,6 +70,9 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 66 | MOVW 56(R13), R0 | 70 | MOVW 56(R13), R0 | ||
| 67 | MOVW 60(R13), R1 // high word of a 64-bit return | 71 | MOVW 60(R13), R1 // high word of a 64-bit return | ||
| 68 | 72 | ||||
| 73 | + | MOVB runtime·goarmsoftfp(SB), R11 | |||
| 74 | + | CMP $0, R11 | |||
| 75 | + | BNE skipfprest | |||
| 69 | // Restore float registers | 76 | // Restore float registers | ||
| 70 | MOVD 64(R13), F0 | 77 | MOVD 64(R13), F0 | ||
| 71 | MOVD 72(R13), F1 | 78 | MOVD 72(R13), F1 | ||
| @@ -76,6 +83,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||||
| 76 | MOVD 112(R13), F6 | 83 | MOVD 112(R13), F6 | ||
| 77 | MOVD 120(R13), F7 | 84 | MOVD 120(R13), F7 | ||
| 78 | 85 | ||||
| 86 | + | skipfprest: | |||
| 79 | // Restore callee-saved registers | 87 | // Restore callee-saved registers | ||
| 80 | MOVW 0(R13), R4 | 88 | MOVW 0(R13), R4 | ||
| 81 | MOVW 4(R13), R5 | 89 | MOVW 4(R13), R5 | ||
M syscall_unix.go +20 -0
| @@ -166,6 +166,26 @@ func callbackWrap(a *callbackArgs) { | |||
| 166 | 166 | slots := int((inType.Size() + ptrSize - 1) / ptrSize) | |
| 167 | 167 | switch inType.Kind() { | |
| 168 | 168 | case reflect.Float32, reflect.Float64: | |
| 169 | + | if isARMSoftFloat() { | |
| 170 | + | // we should restore from integer slot, can skip unnecessary branching here | |
| 171 | + | if isARMPaddingNeeded(inType, -1, intsN) { | |
| 172 | + | intsN++ | |
| 173 | + | } | |
| 174 | + | if intsN+slots <= numOfIntegerRegisters() { | |
| 175 | + | // the integers begin after the floats in frame | |
| 176 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[intsN+numOfFloatRegisters()])).Elem() | |
| 177 | + | intsN += slots | |
| 178 | + | continue | |
| 179 | + | } | |
| 180 | + | if isARMPaddingNeeded(inType, -1, stackSlot) { | |
| 181 | + | stackSlot++ | |
| 182 | + | } | |
| 183 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |
| 184 | + | stackSlot += slots | |
| 185 | + | intsN += slots | |
| 186 | + | continue | |
| 187 | + | } | |
| 188 | + | ||
| 169 | 189 | if floatsN+slots > numOfFloatRegisters() { | |
| 170 | 190 | if isDarwin && runtime.GOARCH == "arm64" { | |
| 171 | 191 | // Darwin ARM64: read from packed stack with proper alignment | |
| @@ -166,6 +166,26 @@ func callbackWrap(a *callbackArgs) { | |||||
| 166 | slots := int((inType.Size() + ptrSize - 1) / ptrSize) | 166 | slots := int((inType.Size() + ptrSize - 1) / ptrSize) | ||
| 167 | switch inType.Kind() { | 167 | switch inType.Kind() { | ||
| 168 | case reflect.Float32, reflect.Float64: | 168 | case reflect.Float32, reflect.Float64: | ||
| 169 | + | if isARMSoftFloat() { | |||
| 170 | + | // we should restore from integer slot, can skip unnecessary branching here | |||
| 171 | + | if isARMPaddingNeeded(inType, -1, intsN) { | |||
| 172 | + | intsN++ | |||
| 173 | + | } | |||
| 174 | + | if intsN+slots <= numOfIntegerRegisters() { | |||
| 175 | + | // the integers begin after the floats in frame | |||
| 176 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[intsN+numOfFloatRegisters()])).Elem() | |||
| 177 | + | intsN += slots | |||
| 178 | + | continue | |||
| 179 | + | } | |||
| 180 | + | if isARMPaddingNeeded(inType, -1, stackSlot) { | |||
| 181 | + | stackSlot++ | |||
| 182 | + | } | |||
| 183 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |||
| 184 | + | stackSlot += slots | |||
| 185 | + | intsN += slots | |||
| 186 | + | continue | |||
| 187 | + | } | |||
| 188 | + | ||||
| 169 | if floatsN+slots > numOfFloatRegisters() { | 189 | if floatsN+slots > numOfFloatRegisters() { | ||
| 170 | if isDarwin && runtime.GOARCH == "arm64" { | 190 | if isDarwin && runtime.GOARCH == "arm64" { | ||
| 171 | // Darwin ARM64: read from packed stack with proper alignment | 191 | // Darwin ARM64: read from packed stack with proper alignment | ||
M testdata/abitest/abi_test.c +9 -0
| @@ -188,3 +188,12 @@ int64_t arm_int64_unaligned_on_stack_after_floats( | |||
| 188 | 188 | ) { | |
| 189 | 189 | return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + a5; | |
| 190 | 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 | + | } | |
| @@ -188,3 +188,12 @@ int64_t arm_int64_unaligned_on_stack_after_floats( | |||||
| 188 | ) { | 188 | ) { | ||
| 189 | return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + a5; | 189 | return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + a5; | ||
| 190 | } | 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 | + | } | |||