detached f9d41fc

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 UnverifiedB5690EEEBB952194 The 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:
146146 if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }}
147147 run: |
148148 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
150152
151153 # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2
152154 # which has a bug where inter-thread signal delivery (tgkill) hangs,
@@ -177,6 +179,17 @@ jobs:
177179 go env -u CC
178180 go env -u CXX
179181
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+
180193 - name: go test race (no Cgo)
181194 if: runner.os == 'macOS'
182195 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 update148 sudo apt-get update
149- sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross149+ 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.2153 # 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 CC179 go env -u CC
178 go env -u CXX180 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 (
2121 align8ByteSize = 8 // 8-byte alignment boundary
2222 )
2323
24+func isARMSoftFloat() bool {
25+ return runtime.GOARCH == "arm" && *(*uint8)(unsafe.Pointer(&runtime_goarmsoftfp)) != 0
26+}
27+
2428 var thePool = sync.Pool{New: func() any {
2529 return new(syscallArgs)
2630 }}
@@ -183,6 +187,19 @@ func RegisterFunc(fptr any, cfn uintptr) {
183187 }
184188 case reflect.Float32, reflect.Float64:
185189 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+
186203 if isARMFloatPaddingNeeded(arg, floats, stack) {
187204 usesSlots++
188205 }
@@ -402,6 +419,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
402419 // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
403420 // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64.
404421 // 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
405423 switch runtime.GOARCH {
406424 case "386":
407425 v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
@@ -410,13 +428,22 @@ func RegisterFunc(fptr any, cfn uintptr) {
410428 case "s390x":
411429 // S390X is big-endian: float32 in upper 32 bits of 64-bit register
412430 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+ }
413437 default:
414438 v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
415439 }
416440 case reflect.Float64:
417441 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
418442 // 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 {
420447 v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
421448 } else {
422449 v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
@@ -483,6 +510,13 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
483510 case "s390x":
484511 // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register.
485512 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+ }
486520 default:
487521 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
488522 }
@@ -492,7 +526,14 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
492526 // if floats are spilled onto stack on ARM than we must follow AAPCS C.7
493527 addFloat(0)
494528 }
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 {
496537 addFloat(uintptr(bits))
497538 addFloat(uintptr(bits >> 32))
498539 } else {
@@ -603,6 +644,7 @@ func numOfFloatRegisters() int {
603644 case "s390x":
604645 return 4
605646 case "arm":
647+ // 8 doubles (16 words) are always reserved by asm trampolines, even if softfloat is used
606648 return 16
607649 case "386":
608650 // i386 SysV ABI passes all arguments on the stack, including floats
@@ -21,6 +21,10 @@ const (
21 align8ByteSize = 8 // 8-byte alignment boundary21 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 register429 // 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.7526 // 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 4645 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 16648 return 16
607 case "386":649 case "386":
608 // i386 SysV ABI passes all arguments on the stack, including floats650 // 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) {
461461 },
462462 want: strconv.FormatInt(12*1+34*2+56*3+78*4+math.MaxInt32+1500, 10),
463463 },
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+ },
464488 }
465489
466490 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 (
1111
1212 //go:linkname runtime_cgocall runtime.cgocall
1313 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.cgocall12 //go:linkname runtime_cgocall runtime.cgocall
13 func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go13 func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go
14+
15+// from runtime/runtime2.go, exported via go:linkname for usage with cgo assembly
16+// pulled in as struct{} for proper linking, see https://github.com/golang/go/issues/72032
17+//
18+//go:linkname runtime_goarmsoftfp runtime.goarmsoftfp
19+var runtime_goarmsoftfp struct{}
M internal/fakecgo/asm_arm.s +9 -1
@@ -21,8 +21,11 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
2121 // starting at 4(R13).
2222 MOVW.W R14, -4(R13)
2323
24+ // Skip floating point registers if runtime.goarmsoftfp!=0.
25+ MOVB runtime·goarmsoftfp(SB), R11
26+ CMP $0, R11
27+ BNE skipfpsave
2428 // Save VFP callee-saved registers D8-D15 (same as S16-S31).
25- // Note: We always save these since we target hard-float ABI.
2629 MOVD F8, (13*4+8*1)(R13)
2730 MOVD F9, (13*4+8*2)(R13)
2831 MOVD F10, (13*4+8*3)(R13)
@@ -32,9 +35,13 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
3235 MOVD F14, (13*4+8*7)(R13)
3336 MOVD F15, (13*4+8*8)(R13)
3437
38+skipfpsave:
3539 // We set up the arguments to cgocallback when saving registers above.
3640 BL runtime·cgocallback(SB)
3741
42+ MOVB runtime·goarmsoftfp(SB), R11
43+ CMP $0, R11
44+ BNE skipfprest
3845 MOVD (13*4+8*1)(R13), F8
3946 MOVD (13*4+8*2)(R13), F9
4047 MOVD (13*4+8*3)(R13), F10
@@ -44,6 +51,7 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
4451 MOVD (13*4+8*7)(R13), F14
4552 MOVD (13*4+8*8)(R13), F15
4653
54+skipfprest:
4755 MOVW.P 4(R13), R14
4856 MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12]
4957 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), F845 MOVD (13*4+8*1)(R13), F8
39 MOVD (13*4+8*2)(R13), F946 MOVD (13*4+8*2)(R13), F9
40 MOVD (13*4+8*3)(R13), F1047 MOVD (13*4+8*3)(R13), F10
@@ -44,6 +51,7 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0
44 MOVD (13*4+8*7)(R13), F1451 MOVD (13*4+8*7)(R13), F14
45 MOVD (13*4+8*8)(R13), F1552 MOVD (13*4+8*8)(R13), F15
46 53
54+skipfprest:
47 MOVW.P 4(R13), R1455 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), R1357 ADD $(8*9), R13
M internal/fakecgo/trampolines_arm.s +9 -0
@@ -70,6 +70,10 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
7070 MOVW g, 32(R13) // R10
7171 MOVW R11, 36(R13)
7272
73+ // Skip floating point registers if runtime.goarmsoftfp!=0.
74+ MOVB runtime·goarmsoftfp(SB), R11
75+ CMP $0, R11
76+ BNE skipfpsave
7377 MOVD F8, 40(R13)
7478 MOVD F9, 48(R13)
7579 MOVD F10, 56(R13)
@@ -79,10 +83,14 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
7983 MOVD F14, 88(R13)
8084 MOVD F15, 96(R13)
8185
86+skipfpsave:
8287 MOVW ·threadentry_call(SB), R12
8388 MOVW (R12), R12
8489 CALL (R12)
8590
91+ MOVB runtime·goarmsoftfp(SB), R11
92+ CMP $0, R11
93+ BNE skipfprest
8694 MOVD 40(R13), F8
8795 MOVD 48(R13), F9
8896 MOVD 56(R13), F10
@@ -92,6 +100,7 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
92100 MOVD 88(R13), F14
93101 MOVD 96(R13), F15
94102
103+skipfprest:
95104 MOVW 8(R13), R4
96105 MOVW 12(R13), R5
97106 MOVW 16(R13), R6
@@ -70,6 +70,10 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
70 MOVW g, 32(R13) // R1070 MOVW g, 32(R13) // R10
71 MOVW R11, 36(R13)71 MOVW R11, 36(R13)
72 72
73+ // Skip floating point registers if runtime.goarmsoftfp!=0.
74+ MOVB runtime·goarmsoftfp(SB), R11
75+ CMP $0, R11
76+ BNE skipfpsave
73 MOVD F8, 40(R13)77 MOVD F8, 40(R13)
74 MOVD F9, 48(R13)78 MOVD F9, 48(R13)
75 MOVD F10, 56(R13)79 MOVD F10, 56(R13)
@@ -79,10 +83,14 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
79 MOVD F14, 88(R13)83 MOVD F14, 88(R13)
80 MOVD F15, 96(R13)84 MOVD F15, 96(R13)
81 85
86+skipfpsave:
82 MOVW ·threadentry_call(SB), R1287 MOVW ·threadentry_call(SB), R12
83 MOVW (R12), R1288 MOVW (R12), R12
84 CALL (R12)89 CALL (R12)
85 90
91+ MOVB runtime·goarmsoftfp(SB), R11
92+ CMP $0, R11
93+ BNE skipfprest
86 MOVD 40(R13), F894 MOVD 40(R13), F8
87 MOVD 48(R13), F995 MOVD 48(R13), F9
88 MOVD 56(R13), F1096 MOVD 56(R13), F10
@@ -92,6 +100,7 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
92 MOVD 88(R13), F14100 MOVD 88(R13), F14
93 MOVD 96(R13), F15101 MOVD 96(R13), F15
94 102
103+skipfprest:
95 MOVW 8(R13), R4104 MOVW 8(R13), R4
96 MOVW 12(R13), R5105 MOVW 12(R13), R5
97 MOVW 16(R13), R6106 MOVW 16(R13), R6
M sys_arm.s +9 -0
@@ -41,6 +41,10 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0
4141 MOVW syscallArgs_fn(R8), R5
4242 MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 56
4343
44+ // Skip floating point registers if runtime.goarmsoftfp!=0.
45+ MOVB runtime·goarmsoftfp(SB), R11
46+ CMP $0, R11
47+ BNE skipfpload
4448 // Load floating point arguments
4549 // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 2
4650 MOVD syscallArgs_f1(R8), F0 // f1+f2 -> D0
@@ -52,6 +56,7 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0
5256 MOVD syscallArgs_f13(R8), F6 // f13+f14 -> D6
5357 MOVD syscallArgs_f15(R8), F7 // f15+f16 -> D7
5458
59+skipfpload:
5560 // Load integer arguments into registers (R0-R3 for ARM EABI)
5661 MOVW syscallArgs_a1(R8), R0 // a1
5762 MOVW syscallArgs_a2(R8), R1 // a2
@@ -131,12 +136,16 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0
131136 MOVW R0, syscallArgs_a1(R8)
132137 MOVW R1, syscallArgs_a2(R8)
133138
139+ MOVB runtime·goarmsoftfp(SB), R11
140+ CMP $0, R11
141+ BNE skipfpsave
134142 // save f0-f3 (each float64 spans 2 uintptr slots on ARM32)
135143 MOVD F0, syscallArgs_f1(R8)
136144 MOVD F1, syscallArgs_f3(R8)
137145 MOVD F2, syscallArgs_f5(R8)
138146 MOVD F3, syscallArgs_f7(R8)
139147
148+skipfpsave:
140149 // Restore callee-saved registers and return
141150 MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11]
142151 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), R541 MOVW syscallArgs_fn(R8), R5
42 MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 5642 MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 56
43 43
44+ // Skip floating point registers if runtime.goarmsoftfp!=0.
45+ MOVB runtime·goarmsoftfp(SB), R11
46+ CMP $0, R11
47+ BNE skipfpload
44 // Load floating point arguments48 // Load floating point arguments
45 // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 249 // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 2
46 MOVD syscallArgs_f1(R8), F0 // f1+f2 -> D050 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 -> D656 MOVD syscallArgs_f13(R8), F6 // f13+f14 -> D6
53 MOVD syscallArgs_f15(R8), F7 // f15+f16 -> D757 MOVD syscallArgs_f15(R8), F7 // f15+f16 -> D7
54 58
59+skipfpload:
55 // Load integer arguments into registers (R0-R3 for ARM EABI)60 // Load integer arguments into registers (R0-R3 for ARM EABI)
56 MOVW syscallArgs_a1(R8), R0 // a161 MOVW syscallArgs_a1(R8), R0 // a1
57 MOVW syscallArgs_a2(R8), R1 // a262 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 return149 // 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
3333 MOVW R2, 136(R13)
3434 MOVW R3, 140(R13)
3535
36+ // Skip floating point registers if runtime.goarmsoftfp!=0.
37+ MOVB runtime·goarmsoftfp(SB), R11
38+ CMP $0, R11
39+ BNE skipfpsave
3640 // Save floating point registers F0-F7 at SP+64 (frame[0..15])
37- // Note: We always save these since we target hard-float ABI.
3841 MOVD F0, 64(R13)
3942 MOVD F1, 72(R13)
4043 MOVD F2, 80(R13)
@@ -44,6 +47,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
4447 MOVD F6, 112(R13)
4548 MOVD F7, 120(R13)
4649
50+skipfpsave:
4751 // Set up callbackArgs at SP+48
4852 MOVW 36(R13), R4
4953 MOVW R4, 48(R13)
@@ -66,6 +70,9 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
6670 MOVW 56(R13), R0
6771 MOVW 60(R13), R1 // high word of a 64-bit return
6872
73+ MOVB runtime·goarmsoftfp(SB), R11
74+ CMP $0, R11
75+ BNE skipfprest
6976 // Restore float registers
7077 MOVD 64(R13), F0
7178 MOVD 72(R13), F1
@@ -76,6 +83,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
7683 MOVD 112(R13), F6
7784 MOVD 120(R13), F7
7885
86+skipfprest:
7987 // Restore callee-saved registers
8088 MOVW 0(R13), R4
8189 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+4851 // Set up callbackArgs at SP+48
48 MOVW 36(R13), R452 MOVW 36(R13), R4
49 MOVW R4, 48(R13)53 MOVW R4, 48(R13)
@@ -66,6 +70,9 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
66 MOVW 56(R13), R070 MOVW 56(R13), R0
67 MOVW 60(R13), R1 // high word of a 64-bit return71 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 registers76 // Restore float registers
70 MOVD 64(R13), F077 MOVD 64(R13), F0
71 MOVD 72(R13), F178 MOVD 72(R13), F1
@@ -76,6 +83,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
76 MOVD 112(R13), F683 MOVD 112(R13), F6
77 MOVD 120(R13), F784 MOVD 120(R13), F7
78 85
86+skipfprest:
79 // Restore callee-saved registers87 // Restore callee-saved registers
80 MOVW 0(R13), R488 MOVW 0(R13), R4
81 MOVW 4(R13), R589 MOVW 4(R13), R5
M syscall_unix.go +20 -0
@@ -166,6 +166,26 @@ func callbackWrap(a *callbackArgs) {
166166 slots := int((inType.Size() + ptrSize - 1) / ptrSize)
167167 switch inType.Kind() {
168168 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+
169189 if floatsN+slots > numOfFloatRegisters() {
170190 if isDarwin && runtime.GOARCH == "arm64" {
171191 // 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 alignment191 // 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(
188188 ) {
189189 return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + a5;
190190 }
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+}

Keyboard shortcuts

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

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

Branches 1

Tags 102

↑↓ moveenter openesc close