detached 61308c8

purego: properly handle 64-bit integers on 32-bit arm (#491)

1. Obey 8-byte alignment on stack and even-register placement required by AAPCS.
2. Use two registers (R0, R1) to make function return value. This will work for '386' architecture too.
3. Add test cases.

Closes #489

Co-authored-by: Hirador <63920290+Hirador@users.noreply.github.com>
commit
61308c82fac659d9ba60b435697dc08c6cf9eba2
author
Vladimir Stolyarov · · 2026-08-11 13:13 UTC
committer
GitHub · · 2026-08-11 13:13 UTC
parent
ecac7f7
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
8 files · +216 -16
M func.go +84 -10
@@ -144,6 +144,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
144144 var ints int
145145 var floats int
146146 floatArgRegs := numOfFloatRegisters()
147+ ptrSize := unsafe.Sizeof(uintptr(0))
147148 var stack int
148149 for i := 0; i < ty.NumIn(); i++ {
149150 arg := ty.In(i)
@@ -161,19 +162,34 @@ func RegisterFunc(fptr any, cfn uintptr) {
161162 panic("purego: CDecl must be the first argument")
162163 }
163164 }
164- case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
165- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer,
165+ case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
166+ reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Pointer, reflect.UnsafePointer,
166167 reflect.Slice, reflect.Bool:
167168 if ints < numOfIntegerRegisters() {
168169 ints++
169170 } else {
170171 stack++
171172 }
173+ case reflect.Uint64, reflect.Int64:
174+ usesSlots := max(1, int(arg.Size()/ptrSize))
175+ if isARMPaddingNeeded(arg, ints, stack) {
176+ usesSlots++
177+ }
178+
179+ if ints < numOfIntegerRegisters() {
180+ ints += usesSlots
181+ } else {
182+ stack += usesSlots
183+ }
172184 case reflect.Float32, reflect.Float64:
185+ usesSlots := max(1, int(arg.Size()/ptrSize))
186+ if isARMFloatPaddingNeeded(arg, floats, stack) {
187+ usesSlots++
188+ }
173189 if floats < floatArgRegs {
174190 floats++
175191 } else {
176- stack++
192+ stack += usesSlots
177193 }
178194 case reflect.Struct:
179195 ensureStructSupported()
@@ -347,9 +363,22 @@ func RegisterFunc(fptr any, cfn uintptr) {
347363 outType := ty.Out(0)
348364 v := reflect.New(outType).Elem()
349365 switch outType.Kind() {
350- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
366+ case reflect.Uint64:
367+ if is32bit {
368+ // high-word is recorded at a2 for 32-bit platforms and 64-bit returns
369+ v.SetUint(uint64(syscall.a1) | (uint64(syscall.a2) << 32))
370+ } else {
371+ v.SetUint(uint64(syscall.a1))
372+ }
373+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
351374 v.SetUint(uint64(syscall.a1))
352- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
375+ case reflect.Int64:
376+ if is32bit {
377+ v.SetInt(int64(syscall.a1) | (int64(syscall.a2) << 32))
378+ } else {
379+ v.SetInt(int64(syscall.a1))
380+ }
381+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
353382 v.SetInt(int64(syscall.a1))
354383 case reflect.Bool:
355384 v.SetBool(byte(syscall.a1) != 0)
@@ -415,9 +444,25 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
415444 ptr := strings.CString(v.String())
416445 keepAlive = append(keepAlive, ptr)
417446 addInt(uintptr(unsafe.Pointer(ptr)))
418- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
447+ case reflect.Uint64:
448+ if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
449+ addInt(0)
450+ }
451+ addInt(uintptr(v.Uint()))
452+ if is32bit {
453+ addInt(uintptr(v.Uint() >> 32)) // on 32bit we must add high word too
454+ }
455+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
419456 addInt(uintptr(v.Uint()))
420- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
457+ case reflect.Int64:
458+ if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
459+ addInt(0)
460+ }
461+ addInt(uintptr(v.Int()))
462+ if is32bit {
463+ addInt(uintptr(v.Int() >> 32))
464+ }
465+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
421466 addInt(uintptr(v.Int()))
422467 case reflect.Pointer, reflect.UnsafePointer, reflect.Slice:
423468 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
@@ -442,12 +487,16 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
442487 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
443488 }
444489 case reflect.Float64:
490+ bits := math.Float64bits(v.Float())
491+ if isARMFloatPaddingNeeded(v.Type(), *numFloats, *numStack) {
492+ // if floats are spilled onto stack on ARM than we must follow AAPCS C.7
493+ addFloat(0)
494+ }
445495 if is32bit {
446- bits := math.Float64bits(v.Float())
447496 addFloat(uintptr(bits))
448497 addFloat(uintptr(bits >> 32))
449498 } else {
450- addFloat(uintptr(math.Float64bits(v.Float())))
499+ addFloat(uintptr(bits))
451500 }
452501 case reflect.Struct:
453502 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
@@ -603,7 +652,6 @@ func estimateStackBytes(ty reflect.Type) int {
603652 } else if !usesInt && numFloats < numOfFloatRegisters() {
604653 numFloats++
605654 } else {
606- // Goes to stack - accumulate total bytes
607655 stackBytes += size
608656 }
609657 }
@@ -613,3 +661,29 @@ func estimateStackBytes(ty reflect.Type) int {
613661 }
614662 return stackBytes
615663 }
664+
665+func isARMPaddingNeeded(ty reflect.Type, numInts, numStack int) bool {
666+ // ARM EABI (AAPCS): 8-byte-aligned types (int64/uint64) start on an
667+ // even core register (C.3); if they then spill, the stack slot is
668+ // 8-byte aligned too (C.7).
669+ // https://github.com/ARM-software/abi-aa/blob/main/aapcs32/aapcs32.rst#6111handling-values-larger-than-32-bits
670+ if runtime.GOARCH != "arm" || ty.Size() != 8 {
671+ return false
672+ }
673+ if numInts >= 0 && numInts < numOfIntegerRegisters() {
674+ return numInts%2 != 0
675+ }
676+ return numStack%2 != 0
677+}
678+
679+func isARMFloatPaddingNeeded(ty reflect.Type, numFloats, numStack int) bool {
680+ if runtime.GOARCH != "arm" || ty.Size() != 8 {
681+ return false
682+ }
683+ if numFloats >= 0 && numFloats < numOfFloatRegisters() {
684+ // float registers are 64bit so alignment never needed for args in registers
685+ return false
686+ }
687+ // Only check if AAPCS C.7 is applicable here
688+ return numStack%2 != 0
689+}
@@ -144,6 +144,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
144 var ints int144 var ints int
145 var floats int145 var floats int
146 floatArgRegs := numOfFloatRegisters()146 floatArgRegs := numOfFloatRegisters()
147+ ptrSize := unsafe.Sizeof(uintptr(0))
147 var stack int148 var stack int
148 for i := 0; i < ty.NumIn(); i++ {149 for i := 0; i < ty.NumIn(); i++ {
149 arg := ty.In(i)150 arg := ty.In(i)
@@ -161,19 +162,34 @@ func RegisterFunc(fptr any, cfn uintptr) {
161 panic("purego: CDecl must be the first argument")162 panic("purego: CDecl must be the first argument")
162 }163 }
163 }164 }
164- case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,165+ case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
165- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Pointer, reflect.UnsafePointer,166+ reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Pointer, reflect.UnsafePointer,
166 reflect.Slice, reflect.Bool:167 reflect.Slice, reflect.Bool:
167 if ints < numOfIntegerRegisters() {168 if ints < numOfIntegerRegisters() {
168 ints++169 ints++
169 } else {170 } else {
170 stack++171 stack++
171 }172 }
173+ case reflect.Uint64, reflect.Int64:
174+ usesSlots := max(1, int(arg.Size()/ptrSize))
175+ if isARMPaddingNeeded(arg, ints, stack) {
176+ usesSlots++
177+ }
178+
179+ if ints < numOfIntegerRegisters() {
180+ ints += usesSlots
181+ } else {
182+ stack += usesSlots
183+ }
172 case reflect.Float32, reflect.Float64:184 case reflect.Float32, reflect.Float64:
185+ usesSlots := max(1, int(arg.Size()/ptrSize))
186+ if isARMFloatPaddingNeeded(arg, floats, stack) {
187+ usesSlots++
188+ }
173 if floats < floatArgRegs {189 if floats < floatArgRegs {
174 floats++190 floats++
175 } else {191 } else {
176- stack++192+ stack += usesSlots
177 }193 }
178 case reflect.Struct:194 case reflect.Struct:
179 ensureStructSupported()195 ensureStructSupported()
@@ -347,9 +363,22 @@ func RegisterFunc(fptr any, cfn uintptr) {
347 outType := ty.Out(0)363 outType := ty.Out(0)
348 v := reflect.New(outType).Elem()364 v := reflect.New(outType).Elem()
349 switch outType.Kind() {365 switch outType.Kind() {
350- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:366+ case reflect.Uint64:
367+ if is32bit {
368+ // high-word is recorded at a2 for 32-bit platforms and 64-bit returns
369+ v.SetUint(uint64(syscall.a1) | (uint64(syscall.a2) << 32))
370+ } else {
371+ v.SetUint(uint64(syscall.a1))
372+ }
373+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
351 v.SetUint(uint64(syscall.a1))374 v.SetUint(uint64(syscall.a1))
352- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:375+ case reflect.Int64:
376+ if is32bit {
377+ v.SetInt(int64(syscall.a1) | (int64(syscall.a2) << 32))
378+ } else {
379+ v.SetInt(int64(syscall.a1))
380+ }
381+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
353 v.SetInt(int64(syscall.a1))382 v.SetInt(int64(syscall.a1))
354 case reflect.Bool:383 case reflect.Bool:
355 v.SetBool(byte(syscall.a1) != 0)384 v.SetBool(byte(syscall.a1) != 0)
@@ -415,9 +444,25 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
415 ptr := strings.CString(v.String())444 ptr := strings.CString(v.String())
416 keepAlive = append(keepAlive, ptr)445 keepAlive = append(keepAlive, ptr)
417 addInt(uintptr(unsafe.Pointer(ptr)))446 addInt(uintptr(unsafe.Pointer(ptr)))
418- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:447+ case reflect.Uint64:
448+ if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
449+ addInt(0)
450+ }
451+ addInt(uintptr(v.Uint()))
452+ if is32bit {
453+ addInt(uintptr(v.Uint() >> 32)) // on 32bit we must add high word too
454+ }
455+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
419 addInt(uintptr(v.Uint()))456 addInt(uintptr(v.Uint()))
420- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:457+ case reflect.Int64:
458+ if isARMPaddingNeeded(v.Type(), *numInts, *numStack) {
459+ addInt(0)
460+ }
461+ addInt(uintptr(v.Int()))
462+ if is32bit {
463+ addInt(uintptr(v.Int() >> 32))
464+ }
465+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
421 addInt(uintptr(v.Int()))466 addInt(uintptr(v.Int()))
422 case reflect.Pointer, reflect.UnsafePointer, reflect.Slice:467 case reflect.Pointer, reflect.UnsafePointer, reflect.Slice:
423 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable468 // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
@@ -442,12 +487,16 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
442 addFloat(uintptr(math.Float32bits(float32(v.Float()))))487 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
443 }488 }
444 case reflect.Float64:489 case reflect.Float64:
490+ bits := math.Float64bits(v.Float())
491+ if isARMFloatPaddingNeeded(v.Type(), *numFloats, *numStack) {
492+ // if floats are spilled onto stack on ARM than we must follow AAPCS C.7
493+ addFloat(0)
494+ }
445 if is32bit {495 if is32bit {
446- bits := math.Float64bits(v.Float())
447 addFloat(uintptr(bits))496 addFloat(uintptr(bits))
448 addFloat(uintptr(bits >> 32))497 addFloat(uintptr(bits >> 32))
449 } else {498 } else {
450- addFloat(uintptr(math.Float64bits(v.Float())))499+ addFloat(uintptr(bits))
451 }500 }
452 case reflect.Struct:501 case reflect.Struct:
453 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)502 keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
@@ -603,7 +652,6 @@ func estimateStackBytes(ty reflect.Type) int {
603 } else if !usesInt && numFloats < numOfFloatRegisters() {652 } else if !usesInt && numFloats < numOfFloatRegisters() {
604 numFloats++653 numFloats++
605 } else {654 } else {
606- // Goes to stack - accumulate total bytes
607 stackBytes += size655 stackBytes += size
608 }656 }
609 }657 }
@@ -613,3 +661,29 @@ func estimateStackBytes(ty reflect.Type) int {
613 }661 }
614 return stackBytes662 return stackBytes
615 }663 }
664+
665+func isARMPaddingNeeded(ty reflect.Type, numInts, numStack int) bool {
666+ // ARM EABI (AAPCS): 8-byte-aligned types (int64/uint64) start on an
667+ // even core register (C.3); if they then spill, the stack slot is
668+ // 8-byte aligned too (C.7).
669+ // https://github.com/ARM-software/abi-aa/blob/main/aapcs32/aapcs32.rst#6111handling-values-larger-than-32-bits
670+ if runtime.GOARCH != "arm" || ty.Size() != 8 {
671+ return false
672+ }
673+ if numInts >= 0 && numInts < numOfIntegerRegisters() {
674+ return numInts%2 != 0
675+ }
676+ return numStack%2 != 0
677+}
678+
679+func isARMFloatPaddingNeeded(ty reflect.Type, numFloats, numStack int) bool {
680+ if runtime.GOARCH != "arm" || ty.Size() != 8 {
681+ return false
682+ }
683+ if numFloats >= 0 && numFloats < numOfFloatRegisters() {
684+ // float registers are 64bit so alignment never needed for args in registers
685+ return false
686+ }
687+ // Only check if AAPCS C.7 is applicable here
688+ return numStack%2 != 0
689+}
M func_test.go +61 -1
@@ -7,11 +7,13 @@ import (
77 "bytes"
88 "errors"
99 "fmt"
10+ "math"
1011 "os"
1112 "os/exec"
1213 "path/filepath"
1314 "reflect"
1415 "runtime"
16+ "strconv"
1517 "strings"
1618 "sync"
1719 "testing"
@@ -402,6 +404,63 @@ func TestABI_ArgumentPassing(t *testing.T) {
402404 },
403405 want: "1:2:3:4:5:6:7:8:9:10.0",
404406 },
407+ {
408+ // check if unaligned 64bit argument and 64bit returned value is properly passed via registers
409+ // arm-specific but must work everywhere
410+ name: "arm_int64_unaligned_in_registers",
411+ fn: new(func(uintptr, int64) int64),
412+ cFn: "arm_int64_unaligned_in_registers",
413+ call: func(f any) string {
414+ fn := *(f).(*func(x uintptr, y int64) int64)
415+ return strconv.FormatInt(fn(456, math.MaxInt32+1500), 10)
416+ },
417+ want: strconv.FormatInt(456*123+math.MaxInt32+1500, 10),
418+ },
419+ {
420+ // check if unaligned 64bit argument and 64bit returned value is properly passed via stack
421+ // arm-specific but must work everywhere
422+ name: "arm_int64_unaligned_on_stack",
423+ fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, int64) int64),
424+ cFn: "arm_int64_unaligned_on_stack",
425+ call: func(f any) string {
426+ fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 int64) int64)
427+ return strconv.FormatInt(fn(12, 34, 56, 78, 90, math.MaxInt32+1500), 10)
428+ },
429+ want: strconv.FormatInt(12*1+34*2+56*3+78*4+90*5+math.MaxInt32+1500, 10),
430+ },
431+ {
432+ // check if unaligned 64bit argument and 64bit returned value is properly passed via stack when it's occupied by floats
433+ // arm-specific but must work everywhere
434+ name: "arm_int64_unaligned_on_stack_after_floats",
435+ fn: new(func(
436+ uintptr, uintptr, uintptr, uintptr,
437+ float32, float32, float32, float32,
438+ float32, float32, float32, float32,
439+ float32, float32, float32, float32,
440+ float32, float32, float32, float32,
441+ float32, int64,
442+ ) int64),
443+ cFn: "arm_int64_unaligned_on_stack_after_floats",
444+ call: func(f any) string {
445+ fn := *(f).(*func(
446+ a1, a2, a3, a4 uintptr,
447+ f1, f2, f3, f4 float32,
448+ f5, f6, f7, f8 float32,
449+ f9, f10, f11, f12 float32,
450+ f13, f14, f15, f16 float32,
451+ f17 float32, a5 int64,
452+ ) int64)
453+ return strconv.FormatInt(fn(
454+ 12, 34, 56, 78,
455+ 0, 0, 0, 0,
456+ 0, 0, 0, 0,
457+ 0, 0, 0, 0,
458+ 0, 0, 0, 0,
459+ 0, math.MaxInt32+1500,
460+ ), 10)
461+ },
462+ want: strconv.FormatInt(12*1+34*2+56*3+78*4+math.MaxInt32+1500, 10),
463+ },
405464 }
406465
407466 for _, tt := range tests {
@@ -409,7 +468,8 @@ func TestABI_ArgumentPassing(t *testing.T) {
409468 if tt.name == "20_int32" && runtime.GOARCH == "ppc64le" {
410469 t.Skip("ppc64le retains the 15-argument limit")
411470 }
412- if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") {
471+ if (tt.name == "10_float32" || tt.name == "arm_int64_unaligned_on_stack_after_floats") &&
472+ (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") {
413473 t.Skip("float32 stack arguments not yet supported on this platform")
414474 }
415475 // Struct tests require Darwin ARM64 or AMD64
@@ -7,11 +7,13 @@ import (
7 "bytes"7 "bytes"
8 "errors"8 "errors"
9 "fmt"9 "fmt"
10+ "math"
10 "os"11 "os"
11 "os/exec"12 "os/exec"
12 "path/filepath"13 "path/filepath"
13 "reflect"14 "reflect"
14 "runtime"15 "runtime"
16+ "strconv"
15 "strings"17 "strings"
16 "sync"18 "sync"
17 "testing"19 "testing"
@@ -402,6 +404,63 @@ func TestABI_ArgumentPassing(t *testing.T) {
402 },404 },
403 want: "1:2:3:4:5:6:7:8:9:10.0",405 want: "1:2:3:4:5:6:7:8:9:10.0",
404 },406 },
407+ {
408+ // check if unaligned 64bit argument and 64bit returned value is properly passed via registers
409+ // arm-specific but must work everywhere
410+ name: "arm_int64_unaligned_in_registers",
411+ fn: new(func(uintptr, int64) int64),
412+ cFn: "arm_int64_unaligned_in_registers",
413+ call: func(f any) string {
414+ fn := *(f).(*func(x uintptr, y int64) int64)
415+ return strconv.FormatInt(fn(456, math.MaxInt32+1500), 10)
416+ },
417+ want: strconv.FormatInt(456*123+math.MaxInt32+1500, 10),
418+ },
419+ {
420+ // check if unaligned 64bit argument and 64bit returned value is properly passed via stack
421+ // arm-specific but must work everywhere
422+ name: "arm_int64_unaligned_on_stack",
423+ fn: new(func(uintptr, uintptr, uintptr, uintptr, uintptr, int64) int64),
424+ cFn: "arm_int64_unaligned_on_stack",
425+ call: func(f any) string {
426+ fn := *(f).(*func(a1, a2, a3, a4, a5 uintptr, a6 int64) int64)
427+ return strconv.FormatInt(fn(12, 34, 56, 78, 90, math.MaxInt32+1500), 10)
428+ },
429+ want: strconv.FormatInt(12*1+34*2+56*3+78*4+90*5+math.MaxInt32+1500, 10),
430+ },
431+ {
432+ // check if unaligned 64bit argument and 64bit returned value is properly passed via stack when it's occupied by floats
433+ // arm-specific but must work everywhere
434+ name: "arm_int64_unaligned_on_stack_after_floats",
435+ fn: new(func(
436+ uintptr, uintptr, uintptr, uintptr,
437+ float32, float32, float32, float32,
438+ float32, float32, float32, float32,
439+ float32, float32, float32, float32,
440+ float32, float32, float32, float32,
441+ float32, int64,
442+ ) int64),
443+ cFn: "arm_int64_unaligned_on_stack_after_floats",
444+ call: func(f any) string {
445+ fn := *(f).(*func(
446+ a1, a2, a3, a4 uintptr,
447+ f1, f2, f3, f4 float32,
448+ f5, f6, f7, f8 float32,
449+ f9, f10, f11, f12 float32,
450+ f13, f14, f15, f16 float32,
451+ f17 float32, a5 int64,
452+ ) int64)
453+ return strconv.FormatInt(fn(
454+ 12, 34, 56, 78,
455+ 0, 0, 0, 0,
456+ 0, 0, 0, 0,
457+ 0, 0, 0, 0,
458+ 0, 0, 0, 0,
459+ 0, math.MaxInt32+1500,
460+ ), 10)
461+ },
462+ want: strconv.FormatInt(12*1+34*2+56*3+78*4+math.MaxInt32+1500, 10),
463+ },
405 }464 }
406 465
407 for _, tt := range tests {466 for _, tt := range tests {
@@ -409,7 +468,8 @@ func TestABI_ArgumentPassing(t *testing.T) {
409 if tt.name == "20_int32" && runtime.GOARCH == "ppc64le" {468 if tt.name == "20_int32" && runtime.GOARCH == "ppc64le" {
410 t.Skip("ppc64le retains the 15-argument limit")469 t.Skip("ppc64le retains the 15-argument limit")
411 }470 }
412- if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") {471+ if (tt.name == "10_float32" || tt.name == "arm_int64_unaligned_on_stack_after_floats") &&
472+ (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") {
413 t.Skip("float32 stack arguments not yet supported on this platform")473 t.Skip("float32 stack arguments not yet supported on this platform")
414 }474 }
415 // Struct tests require Darwin ARM64 or AMD64475 // Struct tests require Darwin ARM64 or AMD64
M sys_unix_arm.s +2 -0
@@ -51,6 +51,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
5151 MOVW R4, 52(R13)
5252 MOVW $0, R4
5353 MOVW R4, 56(R13)
54+ MOVW R4, 60(R13) // high word of a 64-bit return
5455
5556 // Call crosscall2(fn, frame, 0, ctxt)
5657 MOVW ·callbackWrap_call(SB), R0
@@ -63,6 +64,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
6364
6465 // Get result
6566 MOVW 56(R13), R0
67+ MOVW 60(R13), R1 // high word of a 64-bit return
6668
6769 // Restore float registers
6870 MOVD 64(R13), F0
@@ -51,6 +51,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
51 MOVW R4, 52(R13)51 MOVW R4, 52(R13)
52 MOVW $0, R452 MOVW $0, R4
53 MOVW R4, 56(R13)53 MOVW R4, 56(R13)
54+ MOVW R4, 60(R13) // high word of a 64-bit return
54 55
55 // Call crosscall2(fn, frame, 0, ctxt)56 // Call crosscall2(fn, frame, 0, ctxt)
56 MOVW ·callbackWrap_call(SB), R057 MOVW ·callbackWrap_call(SB), R0
@@ -63,6 +64,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
63 64
64 // Get result65 // Get result
65 MOVW 56(R13), R066 MOVW 56(R13), R0
67+ MOVW 60(R13), R1 // high word of a 64-bit return
66 68
67 // Restore float registers69 // Restore float registers
68 MOVD 64(R13), F070 MOVD 64(R13), F0
M syscall_notstackargs.go +14 -0
@@ -39,3 +39,17 @@ func (c *callbackArgs) stackFrame() unsafe.Pointer {
3939 func (c *callbackArgs) intFrame() unsafe.Pointer {
4040 return nil
4141 }
42+
43+func (c *callbackArgs) setInt64Result(result int64) {
44+ c.result[0] = uintptr(result)
45+ if unsafe.Sizeof(uintptr(0)) == 4 {
46+ c.result[1] = uintptr(result >> 32)
47+ }
48+}
49+
50+func (c *callbackArgs) setUint64Result(result uint64) {
51+ c.result[0] = uintptr(result)
52+ if unsafe.Sizeof(uintptr(0)) == 4 {
53+ c.result[1] = uintptr(result >> 32)
54+ }
55+}
@@ -39,3 +39,17 @@ func (c *callbackArgs) stackFrame() unsafe.Pointer {
39 func (c *callbackArgs) intFrame() unsafe.Pointer {39 func (c *callbackArgs) intFrame() unsafe.Pointer {
40 return nil40 return nil
41 }41 }
42+
43+func (c *callbackArgs) setInt64Result(result int64) {
44+ c.result[0] = uintptr(result)
45+ if unsafe.Sizeof(uintptr(0)) == 4 {
46+ c.result[1] = uintptr(result >> 32)
47+ }
48+}
49+
50+func (c *callbackArgs) setUint64Result(result uint64) {
51+ c.result[0] = uintptr(result)
52+ if unsafe.Sizeof(uintptr(0)) == 4 {
53+ c.result[1] = uintptr(result >> 32)
54+ }
55+}
M syscall_stackargs_ppc64le.go +8 -0
@@ -28,3 +28,11 @@ func (c *callbackArgs) stackFrame() unsafe.Pointer {
2828 func (c *callbackArgs) intFrame() unsafe.Pointer {
2929 return nil
3030 }
31+
32+func (c *callbackArgs) setInt64Result(result int64) {
33+ c.result[0] = uintptr(result)
34+}
35+
36+func (c *callbackArgs) setUint64Result(result uint64) {
37+ c.result[0] = uintptr(result)
38+}
@@ -28,3 +28,11 @@ func (c *callbackArgs) stackFrame() unsafe.Pointer {
28 func (c *callbackArgs) intFrame() unsafe.Pointer {28 func (c *callbackArgs) intFrame() unsafe.Pointer {
29 return nil29 return nil
30 }30 }
31+
32+func (c *callbackArgs) setInt64Result(result int64) {
33+ c.result[0] = uintptr(result)
34+}
35+
36+func (c *callbackArgs) setUint64Result(result uint64) {
37+ c.result[0] = uintptr(result)
38+}
M syscall_stackargs_s390x.go +8 -0
@@ -28,3 +28,11 @@ func (c *callbackArgs) stackFrame() unsafe.Pointer {
2828 func (c *callbackArgs) intFrame() unsafe.Pointer {
2929 return nil
3030 }
31+
32+func (c *callbackArgs) setInt64Result(result int64) {
33+ c.result[0] = uintptr(result)
34+}
35+
36+func (c *callbackArgs) setUint64Result(result uint64) {
37+ c.result[0] = uintptr(result)
38+}
@@ -28,3 +28,11 @@ func (c *callbackArgs) stackFrame() unsafe.Pointer {
28 func (c *callbackArgs) intFrame() unsafe.Pointer {28 func (c *callbackArgs) intFrame() unsafe.Pointer {
29 return nil29 return nil
30 }30 }
31+
32+func (c *callbackArgs) setInt64Result(result int64) {
33+ c.result[0] = uintptr(result)
34+}
35+
36+func (c *callbackArgs) setUint64Result(result uint64) {
37+ c.result[0] = uintptr(result)
38+}
M syscall_unix.go +18 -5
@@ -162,11 +162,10 @@ func callbackWrap(a *callbackArgs) {
162162 stackByteOffset := uintptr(0)
163163 for i := range args {
164164 // slots is the number of pointer-sized slots the argument takes
165- var slots int
166165 inType := fnType.In(i)
166+ slots := int((inType.Size() + ptrSize - 1) / ptrSize)
167167 switch inType.Kind() {
168168 case reflect.Float32, reflect.Float64:
169- slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize)
170169 if floatsN+slots > numOfFloatRegisters() {
171170 if isDarwin && runtime.GOARCH == "arm64" {
172171 // Darwin ARM64: read from packed stack with proper alignment
@@ -183,6 +182,10 @@ func callbackWrap(a *callbackArgs) {
183182 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
184183 }
185184 stackSlot += slots
185+ } else if isARMFloatPaddingNeeded(inType, -1, stackSlot) {
186+ stackSlot++
187+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
188+ stackSlot += slots
186189 } else {
187190 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
188191 stackSlot += slots
@@ -211,7 +214,9 @@ func callbackWrap(a *callbackArgs) {
211214 args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset)
212215 continue
213216 default:
214- slots = int((inType.Size() + ptrSize - 1) / ptrSize)
217+ if isARMPaddingNeeded(inType, -1, intsN) {
218+ intsN++
219+ }
215220 if intsN+slots > numOfIntegerRegisters() {
216221 if isDarwin && runtime.GOARCH == "arm64" {
217222 // Darwin ARM64: read from packed stack with proper alignment
@@ -225,6 +230,10 @@ func callbackWrap(a *callbackArgs) {
225230 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
226231 }
227232 stackSlot += slots
233+ } else if isARMPaddingNeeded(inType, -1, stackSlot) {
234+ stackSlot++
235+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
236+ stackSlot += slots
228237 } else {
229238 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
230239 stackSlot += slots
@@ -249,9 +258,13 @@ func callbackWrap(a *callbackArgs) {
249258 ret := fn.Call(args)
250259 if len(ret) > 0 {
251260 switch k := ret[0].Kind(); k {
252- case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
261+ case reflect.Uint64:
262+ a.setUint64Result(ret[0].Uint())
263+ case reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
253264 a.result[0] = uintptr(ret[0].Uint())
254- case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
265+ case reflect.Int64:
266+ a.setInt64Result(ret[0].Int())
267+ case reflect.Int, reflect.Int32, reflect.Int16, reflect.Int8:
255268 a.result[0] = uintptr(ret[0].Int())
256269 case reflect.Bool:
257270 if ret[0].Bool() {
@@ -162,11 +162,10 @@ func callbackWrap(a *callbackArgs) {
162 stackByteOffset := uintptr(0)162 stackByteOffset := uintptr(0)
163 for i := range args {163 for i := range args {
164 // slots is the number of pointer-sized slots the argument takes164 // slots is the number of pointer-sized slots the argument takes
165- var slots int
166 inType := fnType.In(i)165 inType := fnType.In(i)
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- slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize)
170 if floatsN+slots > numOfFloatRegisters() {169 if floatsN+slots > numOfFloatRegisters() {
171 if isDarwin && runtime.GOARCH == "arm64" {170 if isDarwin && runtime.GOARCH == "arm64" {
172 // Darwin ARM64: read from packed stack with proper alignment171 // Darwin ARM64: read from packed stack with proper alignment
@@ -183,6 +182,10 @@ func callbackWrap(a *callbackArgs) {
183 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()182 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
184 }183 }
185 stackSlot += slots184 stackSlot += slots
185+ } else if isARMFloatPaddingNeeded(inType, -1, stackSlot) {
186+ stackSlot++
187+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
188+ stackSlot += slots
186 } else {189 } else {
187 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()190 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
188 stackSlot += slots191 stackSlot += slots
@@ -211,7 +214,9 @@ func callbackWrap(a *callbackArgs) {
211 args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset)214 args[i] = getCallbackStruct(inType, a.args, &floatsN, &intsN, &stackSlot, &stackByteOffset)
212 continue215 continue
213 default:216 default:
214- slots = int((inType.Size() + ptrSize - 1) / ptrSize)217+ if isARMPaddingNeeded(inType, -1, intsN) {
218+ intsN++
219+ }
215 if intsN+slots > numOfIntegerRegisters() {220 if intsN+slots > numOfIntegerRegisters() {
216 if isDarwin && runtime.GOARCH == "arm64" {221 if isDarwin && runtime.GOARCH == "arm64" {
217 // Darwin ARM64: read from packed stack with proper alignment222 // Darwin ARM64: read from packed stack with proper alignment
@@ -225,6 +230,10 @@ func callbackWrap(a *callbackArgs) {
225 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()230 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
226 }231 }
227 stackSlot += slots232 stackSlot += slots
233+ } else if isARMPaddingNeeded(inType, -1, stackSlot) {
234+ stackSlot++
235+ args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
236+ stackSlot += slots
228 } else {237 } else {
229 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()238 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem()
230 stackSlot += slots239 stackSlot += slots
@@ -249,9 +258,13 @@ func callbackWrap(a *callbackArgs) {
249 ret := fn.Call(args)258 ret := fn.Call(args)
250 if len(ret) > 0 {259 if len(ret) > 0 {
251 switch k := ret[0].Kind(); k {260 switch k := ret[0].Kind(); k {
252- case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:261+ case reflect.Uint64:
262+ a.setUint64Result(ret[0].Uint())
263+ case reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
253 a.result[0] = uintptr(ret[0].Uint())264 a.result[0] = uintptr(ret[0].Uint())
254- case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:265+ case reflect.Int64:
266+ a.setInt64Result(ret[0].Int())
267+ case reflect.Int, reflect.Int32, reflect.Int16, reflect.Int8:
255 a.result[0] = uintptr(ret[0].Int())268 a.result[0] = uintptr(ret[0].Int())
256 case reflect.Bool:269 case reflect.Bool:
257 if ret[0].Bool() {270 if ret[0].Bool() {
M testdata/abitest/abi_test.c +21 -0
@@ -167,3 +167,24 @@ double stack_32_mixed_int_float(
167167 f9 * 25 + f10 * 26 + f11 * 27 + f12 * 28 +
168168 f13 * 29 + f14 * 30 + f15 * 31 + f16 * 32;
169169 }
170+
171+int64_t arm_int64_unaligned_in_registers(uintptr_t a1, int64_t a2) {
172+ return (int64_t)a1 * 123 + a2;
173+}
174+
175+int64_t arm_int64_unaligned_on_stack(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, int64_t a6) {
176+ return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 +
177+ (int64_t)a5 * 5 + a6;
178+}
179+
180+int64_t arm_int64_unaligned_on_stack_after_floats(
181+ uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, /* these args are going to integer registers */
182+ float f1, float f2, float f3, float f4, /* these args are going to float registers */
183+ float f5, float f6, float f7, float f8,
184+ float f9, float f10, float f11, float f12,
185+ float f13, float f14, float f15, float f16,
186+ float f17, /* this is a first arg on stack */
187+ int64_t a5 /* this arg must be padded */
188+) {
189+ return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + a5;
190+}
@@ -167,3 +167,24 @@ double stack_32_mixed_int_float(
167 f9 * 25 + f10 * 26 + f11 * 27 + f12 * 28 +167 f9 * 25 + f10 * 26 + f11 * 27 + f12 * 28 +
168 f13 * 29 + f14 * 30 + f15 * 31 + f16 * 32;168 f13 * 29 + f14 * 30 + f15 * 31 + f16 * 32;
169 }169 }
170+
171+int64_t arm_int64_unaligned_in_registers(uintptr_t a1, int64_t a2) {
172+ return (int64_t)a1 * 123 + a2;
173+}
174+
175+int64_t arm_int64_unaligned_on_stack(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, int64_t a6) {
176+ return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 +
177+ (int64_t)a5 * 5 + a6;
178+}
179+
180+int64_t arm_int64_unaligned_on_stack_after_floats(
181+ uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, /* these args are going to integer registers */
182+ float f1, float f2, float f3, float f4, /* these args are going to float registers */
183+ float f5, float f6, float f7, float f8,
184+ float f9, float f10, float f11, float f12,
185+ float f13, float f14, float f15, float f16,
186+ float f17, /* this is a first arg on stack */
187+ int64_t a5 /* this arg must be padded */
188+) {
189+ return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + a5;
190+}

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