diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c334cac..984401ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -146,7 +146,9 @@ jobs: if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} run: | sudo apt-get update - sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross + sudo apt-get install -y \ + gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross \ + gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libc6-armel-cross # Install the latest QEMU 10.x package from Debian. Ubuntu 24.04 ships QEMU 8.2 # which has a bug where inter-thread signal delivery (tgkill) hangs, @@ -177,6 +179,17 @@ jobs: go env -u CC go env -u CXX + # Repeat the same for ARMv5 target (soft-float) + sudo ln -sf /usr/arm-linux-gnueabi/lib/ld-linux.so.3 /lib/ld-linux.so.3 + go env -w CC=arm-linux-gnueabi-gcc + go env -w CXX=arm-linux-gnueabi-g++ + env GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=0 go test -c -o=purego-test-nocgo . + env QEMU_LD_PREFIX=/usr/arm-linux-gnueabi qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 + env GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 go test -c -o=purego-test-cgo . + env QEMU_LD_PREFIX=/usr/arm-linux-gnueabi qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 + go env -u CC + go env -u CXX + - name: go test race (no Cgo) if: runner.os == 'macOS' run: | diff --git a/func.go b/func.go index 6ddf3ce3..10c0bc12 100644 --- a/func.go +++ b/func.go @@ -21,6 +21,10 @@ const ( align8ByteSize = 8 // 8-byte alignment boundary ) +func isARMSoftFloat() bool { + return runtime.GOARCH == "arm" && *(*uint8)(unsafe.Pointer(&runtime_goarmsoftfp)) != 0 +} + var thePool = sync.Pool{New: func() any { return new(syscallArgs) }} @@ -164,16 +168,37 @@ func RegisterFunc(fptr any, cfn uintptr) { case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer, reflect.Slice, reflect.Bool: + usesSlots := min(1, int(ty.Size()/unsafe.Sizeof(uintptr(0)))) + if isARMPaddingNeeded(ty, ints+stack) { + usesSlots++ + } + if ints < numOfIntegerRegisters() { - ints++ + ints += usesSlots } else { - stack++ + stack += usesSlots } case reflect.Float32, reflect.Float64: + usesSlots := int(ty.Size() / unsafe.Sizeof(uintptr(0))) + if isARMSoftFloat() { + // float64 for arm with softfloat uses same rules as int64 + if isARMPaddingNeeded(ty, ints+stack) { + usesSlots++ + } + if ints+usesSlots < numOfIntegerRegisters() { + ints += usesSlots + } else { + stack += usesSlots + } + continue + } + if floats < floatArgRegs { floats++ + } else if isARMPaddingNeeded(ty, floats+stack) { + stack += usesSlots + 1 } else { - stack++ + stack += usesSlots } case reflect.Struct: ensureStructSupported() @@ -347,9 +372,22 @@ func RegisterFunc(fptr any, cfn uintptr) { outType := ty.Out(0) v := reflect.New(outType).Elem() switch outType.Kind() { - case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + case reflect.Uint64: + if is32bit { + // high-word is recorded at a2 for 32-bit platforms and 64-bit returns + v.SetUint(uint64(syscall.a1) | (uint64(syscall.a2) << 32)) + } else { + v.SetUint(uint64(syscall.a1)) + } + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: v.SetUint(uint64(syscall.a1)) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflect.Int64: + if is32bit { + v.SetInt(int64(syscall.a1) | (int64(syscall.a2) << 32)) + } else { + v.SetInt(int64(syscall.a1)) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: v.SetInt(int64(syscall.a1)) case reflect.Bool: v.SetBool(byte(syscall.a1) != 0) @@ -373,6 +411,7 @@ func RegisterFunc(fptr any, cfn uintptr) { // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. + // On 32bit ARM with softfloat float32 returned as integer switch runtime.GOARCH { case "386": v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) @@ -381,13 +420,22 @@ func RegisterFunc(fptr any, cfn uintptr) { case "s390x": // S390X is big-endian: float32 in upper 32 bits of 64-bit register v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) + case "arm": + if isARMSoftFloat() { + v.SetFloat(float64(math.Float32frombits(uint32(syscall.a1)))) + } else { + v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) + } default: v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) } case reflect.Float64: // NOTE: syscall.r2 is only the floating return value on 64bit platforms. // On 32bit platforms syscall.r2 is the upper part of a 64bit return. - if is32bit { + if isARMSoftFloat() { + // a1,a2 are populated in this case + v.SetFloat(math.Float64frombits(uint64(syscall.a1) | (uint64(syscall.a2) << 32))) + } else if is32bit { v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) } else { v.SetFloat(math.Float64frombits(uint64(syscall.f1))) @@ -415,9 +463,25 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat ptr := strings.CString(v.String()) keepAlive = append(keepAlive, ptr) addInt(uintptr(unsafe.Pointer(ptr))) - case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + case reflect.Uint64: + if isARMPaddingNeeded(v.Type(), *numInts+*numStack) { + addInt(0) + } + addInt(uintptr(v.Uint())) + if is32bit { + addInt(uintptr(v.Uint() >> 32)) // on 32bit we must add high word too + } + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: addInt(uintptr(v.Uint())) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflect.Int64: + if isARMPaddingNeeded(v.Type(), *numInts+*numStack) { + addInt(0) + } + addInt(uintptr(v.Int())) + if is32bit { + addInt(uintptr(v.Int() >> 32)) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: addInt(uintptr(v.Int())) case reflect.Pointer, reflect.UnsafePointer, reflect.Slice: // There is no need to keepAlive this pointer separately because it is kept alive in the args variable @@ -438,16 +502,30 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat case "s390x": // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register. addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) + case "arm": + if isARMSoftFloat() { + // 32-bit ARM with softfloat: float32 goes as integer + addInt(uintptr(math.Float32bits(float32(v.Float())))) + } else { + addFloat(uintptr(math.Float32bits(float32(v.Float())))) + } default: addFloat(uintptr(math.Float32bits(float32(v.Float())))) } case reflect.Float64: - if is32bit { - bits := math.Float64bits(v.Float()) + bits := math.Float64bits(v.Float()) + if isARMSoftFloat() { + // add as uint64 + if isARMPaddingNeeded(v.Type(), *numInts+*numStack) { + addInt(0) + } + addInt(uintptr(bits)) + addInt(uintptr(bits >> 32)) + } else if is32bit { addFloat(uintptr(bits)) addFloat(uintptr(bits >> 32)) } else { - addFloat(uintptr(math.Float64bits(v.Float()))) + addFloat(uintptr(bits)) } case reflect.Struct: keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) @@ -554,6 +632,7 @@ func numOfFloatRegisters() int { case "s390x": return 4 case "arm": + // 8 doubles (16 words) are always reserved by asm trampolines, even if softfloat is used return 16 case "386": // i386 SysV ABI passes all arguments on the stack, including floats @@ -592,18 +671,25 @@ func estimateStackBytes(ty reflect.Type) int { var numInts, numFloats int var stackBytes int + ptrSize := int(unsafe.Sizeof(uintptr(0))) for i := 0; i < ty.NumIn(); i++ { arg := ty.In(i) size := int(arg.Size()) // Check if this goes to register or stack - usesInt := arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64 + usesInt := (arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64) || isARMSoftFloat() if usesInt && numInts < numOfIntegerRegisters() { + if isARMPaddingNeeded(arg, numInts) { + numInts++ + } numInts++ } else if !usesInt && numFloats < numOfFloatRegisters() { numFloats++ } else { // Goes to stack - accumulate total bytes + if isARMPaddingNeeded(arg, stackBytes/ptrSize) { + stackBytes += ptrSize + } stackBytes += size } } @@ -613,3 +699,11 @@ func estimateStackBytes(ty reflect.Type) int { } return stackBytes } + +func isARMPaddingNeeded(ty reflect.Type, numValues int) bool { + // ARM EABI (AAPCS): 8-byte-aligned types (int64/uint64) start on an + // even core register (C.3); if they then spill, the stack slot is + // 8-byte aligned too (C.7). + // https://github.com/ARM-software/abi-aa/blob/main/aapcs32/aapcs32.rst#6111handling-values-larger-than-32-bits + return runtime.GOARCH == "arm" && ty.Size() == 8 && numValues%2 != 0 +} diff --git a/func_test.go b/func_test.go index 2c0cae4d..45b75773 100644 --- a/func_test.go +++ b/func_test.go @@ -7,11 +7,13 @@ import ( "bytes" "errors" "fmt" + "math" "os" "os/exec" "path/filepath" "reflect" "runtime" + "strconv" "strings" "sync" "testing" @@ -402,6 +404,54 @@ func TestABI_ArgumentPassing(t *testing.T) { }, want: "1:2:3:4:5:6:7:8:9:10.0", }, + { + // check if unaligned 64bit argument and 64bit returned value is properly passed via registers + // arm-specific but must work everywhere + name: "arm_int64_unaligned_in_registers", + fn: new(func(uintptr, int64) int64), + cFn: "arm_int64_unaligned_in_registers", + call: func(f any) string { + fn := *(f).(*func(x uintptr, y int64) int64) + return strconv.FormatInt(fn(456, math.MaxInt32+1500), 10) + }, + want: strconv.FormatInt(456*123+math.MaxInt32+1500, 10), + }, + { + // check if unaligned float 64bit argument and float 64bit returned value is properly passed via registers + // arm-softfloat-specific but must work everywhere + name: "arm_float64_unaligned_in_registers", + fn: new(func(uintptr, float64) float64), + cFn: "arm_float64_unaligned_in_registers", + call: func(f any) string { + fn := *(f).(*func(x uintptr, y float64) float64) + return strconv.FormatFloat(fn(456, math.MaxFloat32+1500), 'b', 10, 64) + }, + want: strconv.FormatFloat(456*123.5+math.MaxFloat32+1500, 'b', 10, 64), + }, + { + // check if unaligned 64bit argument and 64bit returned value is properly passed via stack + // arm-specific but must work everywhere + name: "arm_int64_unaligned_on_stack", + fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, int64) int64), + cFn: "arm_int64_unaligned_on_stack", + call: func(f any) string { + fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 int64) int64) + return strconv.FormatInt(fn(12, 34, 56, 78, 90, math.MaxInt32+1500), 10) + }, + want: strconv.FormatInt(12*1+34*2+56*3+78*4+90*5+math.MaxInt32+1500, 10), + }, + { + // check if unaligned float 64bit argument and float 64bit returned value is properly passed via stack + // arm-softfloat-specific but must work everywhere + name: "arm_float64_unaligned_on_stack", + fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, float64) float64), + cFn: "arm_float64_unaligned_on_stack", + call: func(f any) string { + fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 float64) float64) + return strconv.FormatFloat(fn(12, 34, 56, 78, 90, math.MaxFloat32+1500), 'b', 10, 64) + }, + want: strconv.FormatFloat(12*1+34*2+56*3+78*4+90*5+math.MaxFloat32+1500, 'b', 10, 64), + }, } for _, tt := range tests { diff --git a/go_runtime.go b/go_runtime.go index b327f786..0a332b6e 100644 --- a/go_runtime.go +++ b/go_runtime.go @@ -11,3 +11,9 @@ import ( //go:linkname runtime_cgocall runtime.cgocall func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go + +// from runtime/runtime2.go, exported via go:linkname for usage with cgo assembly +// pulled in as struct{} for proper linking, see https://github.com/golang/go/issues/72032 +// +//go:linkname runtime_goarmsoftfp runtime.goarmsoftfp +var runtime_goarmsoftfp struct{} diff --git a/internal/fakecgo/asm_arm.s b/internal/fakecgo/asm_arm.s index a45f1f83..1ebb7e4f 100644 --- a/internal/fakecgo/asm_arm.s +++ b/internal/fakecgo/asm_arm.s @@ -21,8 +21,11 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 // starting at 4(R13). MOVW.W R14, -4(R13) + // Skip floating point registers if runtime.goarmsoftfp!=0. + MOVB runtime·goarmsoftfp(SB), R11 + CMP $0, R11 + BNE skipfpsave // Save VFP callee-saved registers D8-D15 (same as S16-S31). - // Note: We always save these since we target hard-float ABI. MOVD F8, (13*4+8*1)(R13) MOVD F9, (13*4+8*2)(R13) MOVD F10, (13*4+8*3)(R13) @@ -32,9 +35,13 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 MOVD F14, (13*4+8*7)(R13) MOVD F15, (13*4+8*8)(R13) +skipfpsave: // We set up the arguments to cgocallback when saving registers above. BL runtime·cgocallback(SB) + MOVB runtime·goarmsoftfp(SB), R11 + CMP $0, R11 + BNE skipfprest MOVD (13*4+8*1)(R13), F8 MOVD (13*4+8*2)(R13), F9 MOVD (13*4+8*3)(R13), F10 @@ -44,6 +51,7 @@ TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 MOVD (13*4+8*7)(R13), F14 MOVD (13*4+8*8)(R13), F15 +skipfprest: MOVW.P 4(R13), R14 MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12] ADD $(8*9), R13 diff --git a/internal/fakecgo/trampolines_arm.s b/internal/fakecgo/trampolines_arm.s index c1cd0c92..d679f4c1 100644 --- a/internal/fakecgo/trampolines_arm.s +++ b/internal/fakecgo/trampolines_arm.s @@ -70,6 +70,10 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 MOVW g, 32(R13) // R10 MOVW R11, 36(R13) + // Skip floating point registers if runtime.goarmsoftfp!=0. + MOVB runtime·goarmsoftfp(SB), R11 + CMP $0, R11 + BNE skipfpsave MOVD F8, 40(R13) MOVD F9, 48(R13) MOVD F10, 56(R13) @@ -79,10 +83,14 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 MOVD F14, 88(R13) MOVD F15, 96(R13) +skipfpsave: MOVW ·threadentry_call(SB), R12 MOVW (R12), R12 CALL (R12) + MOVB runtime·goarmsoftfp(SB), R11 + CMP $0, R11 + BNE skipfprest MOVD 40(R13), F8 MOVD 48(R13), F9 MOVD 56(R13), F10 @@ -92,6 +100,7 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $104-0 MOVD 88(R13), F14 MOVD 96(R13), F15 +skipfprest: MOVW 8(R13), R4 MOVW 12(R13), R5 MOVW 16(R13), R6 diff --git a/sys_arm.s b/sys_arm.s index f1ea44a2..fd2dcc22 100644 --- a/sys_arm.s +++ b/sys_arm.s @@ -41,6 +41,10 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 MOVW syscallArgs_fn(R8), R5 MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 56 + // Skip floating point registers if runtime.goarmsoftfp!=0. + MOVB runtime·goarmsoftfp(SB), R11 + CMP $0, R11 + BNE skipfpload // Load floating point arguments // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 2 MOVD syscallArgs_f1(R8), F0 // f1+f2 -> D0 @@ -52,6 +56,7 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 MOVD syscallArgs_f13(R8), F6 // f13+f14 -> D6 MOVD syscallArgs_f15(R8), F7 // f15+f16 -> D7 +skipfpload: // Load integer arguments into registers (R0-R3 for ARM EABI) MOVW syscallArgs_a1(R8), R0 // a1 MOVW syscallArgs_a2(R8), R1 // a2 @@ -131,12 +136,16 @@ TEXT syscallX(SB), NOSPLIT|NOFRAME, $0-0 MOVW R0, syscallArgs_a1(R8) MOVW R1, syscallArgs_a2(R8) + MOVB runtime·goarmsoftfp(SB), R11 + CMP $0, R11 + BNE skipfpsave // save f0-f3 (each float64 spans 2 uintptr slots on ARM32) MOVD F0, syscallArgs_f1(R8) MOVD F1, syscallArgs_f3(R8) MOVD F2, syscallArgs_f5(R8) MOVD F3, syscallArgs_f7(R8) +skipfpsave: // Restore callee-saved registers and return MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11] MOVW.P 4(R13), R15 // pop LR into PC (return) diff --git a/sys_unix_arm.s b/sys_unix_arm.s index a97e9437..0efd59e5 100644 --- a/sys_unix_arm.s +++ b/sys_unix_arm.s @@ -33,8 +33,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 MOVW R2, 136(R13) MOVW R3, 140(R13) + // Skip floating point registers if runtime.goarmsoftfp!=0. + MOVB runtime·goarmsoftfp(SB), R11 + CMP $0, R11 + BNE skipfpsave // Save floating point registers F0-F7 at SP+64 (frame[0..15]) - // Note: We always save these since we target hard-float ABI. MOVD F0, 64(R13) MOVD F1, 72(R13) MOVD F2, 80(R13) @@ -44,6 +47,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 MOVD F6, 112(R13) MOVD F7, 120(R13) +skipfpsave: // Set up callbackArgs at SP+48 MOVW 36(R13), R4 MOVW R4, 48(R13) @@ -51,6 +55,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 MOVW R4, 52(R13) MOVW $0, R4 MOVW R4, 56(R13) + MOVW R4, 60(R13) // high word of a 64-bit return // Call crosscall2(fn, frame, 0, ctxt) MOVW ·callbackWrap_call(SB), R0 @@ -63,7 +68,11 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 // Get result MOVW 56(R13), R0 + MOVW 60(R13), R1 // high word of a 64-bit return + MOVB runtime·goarmsoftfp(SB), R11 + CMP $0, R11 + BNE skipfprest // Restore float registers MOVD 64(R13), F0 MOVD 72(R13), F1 @@ -74,6 +83,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 MOVD 112(R13), F6 MOVD 120(R13), F7 +skipfprest: // Restore callee-saved registers MOVW 0(R13), R4 MOVW 4(R13), R5 diff --git a/syscall_notstackargs.go b/syscall_notstackargs.go index 8acb7ddb..dc206f8f 100644 --- a/syscall_notstackargs.go +++ b/syscall_notstackargs.go @@ -39,3 +39,17 @@ func (c *callbackArgs) stackFrame() unsafe.Pointer { func (c *callbackArgs) intFrame() unsafe.Pointer { return nil } + +func (c *callbackArgs) setInt64Result(result int64) { + c.result[0] = uintptr(result) + if unsafe.Sizeof(uintptr(0)) == 4 { + c.result[1] = uintptr(result >> 32) + } +} + +func (c *callbackArgs) setUint64Result(result uint64) { + c.result[0] = uintptr(result) + if unsafe.Sizeof(uintptr(0)) == 4 { + c.result[1] = uintptr(result >> 32) + } +} diff --git a/syscall_stackargs_ppc64le.go b/syscall_stackargs_ppc64le.go index 9eb41315..3d7468f4 100644 --- a/syscall_stackargs_ppc64le.go +++ b/syscall_stackargs_ppc64le.go @@ -28,3 +28,11 @@ func (c *callbackArgs) stackFrame() unsafe.Pointer { func (c *callbackArgs) intFrame() unsafe.Pointer { return nil } + +func (c *callbackArgs) setInt64Result(result int64) { + c.result[0] = uintptr(result) +} + +func (c *callbackArgs) setUint64Result(result uint64) { + c.result[0] = uintptr(result) +} diff --git a/syscall_stackargs_s390x.go b/syscall_stackargs_s390x.go index 399985a5..c1a79970 100644 --- a/syscall_stackargs_s390x.go +++ b/syscall_stackargs_s390x.go @@ -28,3 +28,11 @@ func (c *callbackArgs) stackFrame() unsafe.Pointer { func (c *callbackArgs) intFrame() unsafe.Pointer { return nil } + +func (c *callbackArgs) setInt64Result(result int64) { + c.result[0] = uintptr(result) +} + +func (c *callbackArgs) setUint64Result(result uint64) { + c.result[0] = uintptr(result) +} diff --git a/syscall_unix.go b/syscall_unix.go index 4cfe9a8f..1f2b9653 100644 --- a/syscall_unix.go +++ b/syscall_unix.go @@ -162,11 +162,30 @@ func callbackWrap(a *callbackArgs) { stackByteOffset := uintptr(0) for i := range args { // slots is the number of pointer-sized slots the argument takes - var slots int inType := fnType.In(i) + slots := int((inType.Size() + ptrSize - 1) / ptrSize) switch inType.Kind() { case reflect.Float32, reflect.Float64: - slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize) + if isARMSoftFloat() { + // we should restore from integer slot, can skip unnecessary branching here + if isARMPaddingNeeded(inType, intsN) { + intsN++ + } + if intsN+slots <= numOfIntegerRegisters() { + // the integers begin after the floats in frame + args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[intsN+numOfFloatRegisters()])).Elem() + intsN += slots + continue + } + if isARMPaddingNeeded(inType, stackSlot) { + stackSlot++ + } + args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() + stackSlot += slots + intsN += slots + continue + } + if floatsN+slots > numOfFloatRegisters() { if isDarwin && runtime.GOARCH == "arm64" { // Darwin ARM64: read from packed stack with proper alignment @@ -211,7 +230,9 @@ func callbackWrap(a *callbackArgs) { args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset) continue default: - slots = int((inType.Size() + ptrSize - 1) / ptrSize) + if isARMPaddingNeeded(inType, intsN) { + intsN++ + } if intsN+slots > numOfIntegerRegisters() { if isDarwin && runtime.GOARCH == "arm64" { // Darwin ARM64: read from packed stack with proper alignment @@ -225,6 +246,10 @@ func callbackWrap(a *callbackArgs) { args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() } stackSlot += slots + } else if isARMPaddingNeeded(inType, stackSlot) { + stackSlot++ + args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() + stackSlot += slots } else { args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() stackSlot += slots @@ -249,9 +274,13 @@ func callbackWrap(a *callbackArgs) { ret := fn.Call(args) if len(ret) > 0 { switch k := ret[0].Kind(); k { - case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr: + case reflect.Uint64: + a.setUint64Result(ret[0].Uint()) + case reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr: a.result[0] = uintptr(ret[0].Uint()) - case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: + case reflect.Int64: + a.setInt64Result(ret[0].Int()) + case reflect.Int, reflect.Int32, reflect.Int16, reflect.Int8: a.result[0] = uintptr(ret[0].Int()) case reflect.Bool: if ret[0].Bool() { diff --git a/testdata/abitest/abi_test.c b/testdata/abitest/abi_test.c index 824e453d..8ad7ceab 100644 --- a/testdata/abitest/abi_test.c +++ b/testdata/abitest/abi_test.c @@ -167,3 +167,21 @@ double stack_32_mixed_int_float( f9 * 25 + f10 * 26 + f11 * 27 + f12 * 28 + f13 * 29 + f14 * 30 + f15 * 31 + f16 * 32; } + +int64_t arm_int64_unaligned_in_registers(uintptr_t a1, int64_t a2) { + return (int64_t)a1 * 123 + a2; +} + +double arm_float64_unaligned_in_registers(uintptr_t a1, double a2) { + return (double)a1 * 123.5 + a2; +} + +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) { + return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + + (int64_t)a5 * 5 + a6; +} + +double arm_float64_unaligned_on_stack(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, double a6) { + return (double)a1 * 1 + (double)a2 * 2 + (double)a3 * 3 + (double)a4 * 4 + + (double)a5 * 5 + a6; +} \ No newline at end of file