Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 78 additions & 10 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
var ints int
var floats int
floatArgRegs := numOfFloatRegisters()
ptrSize := unsafe.Sizeof(uintptr(0))
var stack int
for i := 0; i < ty.NumIn(); i++ {
arg := ty.In(i)
Expand All @@ -164,16 +165,25 @@ 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 := max(1, int(arg.Size()/ptrSize))
if isARMPaddingNeeded(arg, ints, stack) {
usesSlots++
}

if ints < numOfIntegerRegisters() {
ints++
ints += usesSlots
} else {
stack++
stack += usesSlots
}
case reflect.Float32, reflect.Float64:
usesSlots := max(1, int(arg.Size()/ptrSize))
if isARMFloatPaddingNeeded(arg, floats, stack) {
usesSlots++
}
if floats < floatArgRegs {
floats++
} else {
stack++
stack += usesSlots
}
case reflect.Struct:
ensureStructSupported()
Expand Down Expand Up @@ -347,9 +357,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)
Expand Down Expand Up @@ -415,9 +438,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()))
Comment thread
xakep666 marked this conversation as resolved.
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
Expand All @@ -442,12 +481,16 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
addFloat(uintptr(math.Float32bits(float32(v.Float()))))
}
case reflect.Float64:
bits := math.Float64bits(v.Float())
if isARMFloatPaddingNeeded(v.Type(), *numFloats, *numStack) {
// if floats are spilled onto stack on ARM than we must follow AAPCS C.7
addFloat(0)
}
if is32bit {
bits := math.Float64bits(v.Float())
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)
Expand Down Expand Up @@ -603,7 +646,6 @@ func estimateStackBytes(ty reflect.Type) int {
} else if !usesInt && numFloats < numOfFloatRegisters() {
numFloats++
} else {
// Goes to stack - accumulate total bytes
stackBytes += size
}
}
Expand All @@ -613,3 +655,29 @@ func estimateStackBytes(ty reflect.Type) int {
}
return stackBytes
}

func isARMPaddingNeeded(ty reflect.Type, numInts, numStack 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
if runtime.GOARCH != "arm" || ty.Size() != 8 {
return false
}
if numInts >= 0 && numInts < numOfIntegerRegisters() {
return numInts%2 != 0
}
return numStack%2 != 0
}

func isARMFloatPaddingNeeded(ty reflect.Type, numFloats, numStack int) bool {
if runtime.GOARCH != "arm" || ty.Size() != 8 {
return false
}
if numFloats >= 0 && numFloats < numOfFloatRegisters() {
// float registers are 64bit so alignment never needed for args in registers
return false
}
// Only check if AAPCS C.7 is applicable here
return numStack%2 != 0
}
62 changes: 61 additions & 1 deletion func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"bytes"
"errors"
"fmt"
"math"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -402,14 +404,72 @@ 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 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),
},
Comment thread
xakep666 marked this conversation as resolved.
{
// check if unaligned 64bit argument and 64bit returned value is properly passed via stack when it's occupied by floats
// arm-specific but must work everywhere
name: "arm_int64_unaligned_on_stack_after_floats",
fn: new(func(
uintptr, uintptr, uintptr, uintptr,
float32, float32, float32, float32,
float32, float32, float32, float32,
float32, float32, float32, float32,
float32, float32, float32, float32,
float32, int64,
) int64),
cFn: "arm_int64_unaligned_on_stack_after_floats",
call: func(f any) string {
fn := *(f).(*func(
a1, a2, a3, a4 uintptr,
f1, f2, f3, f4 float32,
f5, f6, f7, f8 float32,
f9, f10, f11, f12 float32,
f13, f14, f15, f16 float32,
f17 float32, a5 int64,
) int64)
return strconv.FormatInt(fn(
12, 34, 56, 78,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, math.MaxInt32+1500,
), 10)
},
want: strconv.FormatInt(12*1+34*2+56*3+78*4+math.MaxInt32+1500, 10),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.name == "20_int32" && runtime.GOARCH == "ppc64le" {
t.Skip("ppc64le retains the 15-argument limit")
}
if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") {
if (tt.name == "10_float32" || tt.name == "arm_int64_unaligned_on_stack_after_floats") &&
(runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") {
t.Skip("float32 stack arguments not yet supported on this platform")
}
// Struct tests require Darwin ARM64 or AMD64
Expand Down
2 changes: 2 additions & 0 deletions sys_unix_arm.s
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,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
Expand All @@ -63,6 +64,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0

// Get result
MOVW 56(R13), R0
MOVW 60(R13), R1 // high word of a 64-bit return

// Restore float registers
MOVD 64(R13), F0
Expand Down
14 changes: 14 additions & 0 deletions syscall_notstackargs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
8 changes: 8 additions & 0 deletions syscall_stackargs_ppc64le.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
8 changes: 8 additions & 0 deletions syscall_stackargs_s390x.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
23 changes: 18 additions & 5 deletions syscall_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,10 @@ 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 floatsN+slots > numOfFloatRegisters() {
if isDarwin && runtime.GOARCH == "arm64" {
// Darwin ARM64: read from packed stack with proper alignment
Expand All @@ -183,6 +182,10 @@ func callbackWrap(a *callbackArgs) {
args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
}
stackSlot += slots
} else if isARMFloatPaddingNeeded(inType, -1, 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
Expand Down Expand Up @@ -211,7 +214,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, -1, intsN) {
intsN++
}
Comment thread
hajimehoshi marked this conversation as resolved.
if intsN+slots > numOfIntegerRegisters() {
if isDarwin && runtime.GOARCH == "arm64" {
// Darwin ARM64: read from packed stack with proper alignment
Expand All @@ -225,6 +230,10 @@ func callbackWrap(a *callbackArgs) {
args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
}
stackSlot += slots
} else if isARMPaddingNeeded(inType, -1, 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
Expand All @@ -249,9 +258,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() {
Expand Down
21 changes: 21 additions & 0 deletions testdata/abitest/abi_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,24 @@ 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;
}

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;
}

int64_t arm_int64_unaligned_on_stack_after_floats(
uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, /* these args are going to integer registers */
float f1, float f2, float f3, float f4, /* these args are going to float registers */
float f5, float f6, float f7, float f8,
float f9, float f10, float f11, float f12,
float f13, float f14, float f15, float f16,
float f17, /* this is a first arg on stack */
int64_t a5 /* this arg must be padded */
) {
return (int64_t)a1 * 1 + (int64_t)a2 * 2 + (int64_t)a3 * 3 + (int64_t)a4 * 4 + a5;
}