PureGo Version
main @ 763bb99
Operating System
Go Version (go version)
go1.26.7 linux/amd64 (finding is in arm64-only code; verified by code inspection + cross-build, see below)
What steps will reproduce the problem?
On arm64, placeRegistersArm64 (struct_arm64.go, the non-darwin path) routes struct members straight to FP or integer registers by kind:
case reflect.Int64, reflect.Int:
addInt(uintptr(f.Int())) // GPR
...
case reflect.Float64:
addFloat(...) // FPR
So a non-HFA mixed struct such as {int64; float64} is sent as x0 + v0. But AAPCS64 says a non-HFA composite of 16 bytes or less is packed into 1–2 general-purpose registers (x0, x1) — and purego's own receive paths already do exactly that:
getCallbackStruct (struct_arm64.go:595-611): "Non-HFA composite ≤ 16 bytes: packed into 1–2 integer registers", reading only f[numOfFloatRegisters()+*intsN ...].
getStruct (struct_arm64.go:38-55): non-isAllSameFloat returns fall back to syscall.a1, syscall.a2.
Three code paths, one of which disagrees — the sender is the odd one out (3-against-1 within the repo, plus the ABI spec).
Repro (arm64 only — this box is amd64/Linux, so no execution trace; the case below is what to run on an M-series Mac, Linux/arm64, or Android/iOS device):
struct Mixed { int64_t i; double d; };
struct Mixed ident_mixed(struct Mixed s) { return s; }
type Mixed struct {
_ structs.HostLayout
I int64
D float64
}
var ident func(Mixed) Mixed
purego.RegisterLibFunc(&ident, lib, "ident_mixed")
got := ident(Mixed{I: 1, D: 2.5})
// want {I:1 D:2.5}; with the bug D arrives in v0 while C reads x1
Additionally a Go→C→Go round trip through NewCallback is self-inconsistent even without C: the sender writes x0/v0 while the receiver reads x0/x1.
What is the expected result?
Mixed non-HFA structs travel packed in x0/x1, matching AAPCS64 and purego's own receive paths, so the round trip above returns {I:1 D:2.5}.
What happens instead?
The float member goes out on v0 while the callee (real C ABI, or purego's own callback receiver) reads x1: the double arrives as garbage/zero and integer register accounting shifts for all following arguments.
Anything else you feel useful to add?
Minimized reproducible case: the ident_mixed C helper plus the Go snippet above. Requires an arm64 host (or device) to execute; on this amd64 box I verified instead that (1) GOOS=linux GOARCH=arm64 and GOOS=darwin GOARCH=arm64 builds pass, (2) the existing test suite passes, and (3) the three in-repo code paths cited above disagree exactly as described.
Related PR: #517.
PureGo Version
main @ 763bb99
Operating System
Go Version (
go version)go1.26.7 linux/amd64 (finding is in arm64-only code; verified by code inspection + cross-build, see below)
What steps will reproduce the problem?
On arm64,
placeRegistersArm64(struct_arm64.go, the non-darwin path) routes struct members straight to FP or integer registers by kind:So a non-HFA mixed struct such as
{int64; float64}is sent asx0+v0. But AAPCS64 says a non-HFA composite of 16 bytes or less is packed into 1–2 general-purpose registers (x0,x1) — and purego's own receive paths already do exactly that:getCallbackStruct(struct_arm64.go:595-611): "Non-HFA composite ≤ 16 bytes: packed into 1–2 integer registers", reading onlyf[numOfFloatRegisters()+*intsN ...].getStruct(struct_arm64.go:38-55): non-isAllSameFloatreturns fall back tosyscall.a1, syscall.a2.Three code paths, one of which disagrees — the sender is the odd one out (3-against-1 within the repo, plus the ABI spec).
Repro (arm64 only — this box is amd64/Linux, so no execution trace; the case below is what to run on an M-series Mac, Linux/arm64, or Android/iOS device):
Additionally a Go→C→Go round trip through
NewCallbackis self-inconsistent even without C: the sender writesx0/v0while the receiver readsx0/x1.What is the expected result?
Mixed non-HFA structs travel packed in
x0/x1, matching AAPCS64 and purego's own receive paths, so the round trip above returns{I:1 D:2.5}.What happens instead?
The float member goes out on
v0while the callee (real C ABI, or purego's own callback receiver) readsx1: the double arrives as garbage/zero and integer register accounting shifts for all following arguments.Anything else you feel useful to add?
Minimized reproducible case: the
ident_mixedC helper plus the Go snippet above. Requires an arm64 host (or device) to execute; on this amd64 box I verified instead that (1)GOOS=linux GOARCH=arm64andGOOS=darwin GOARCH=arm64builds pass, (2) the existing test suite passes, and (3) the three in-repo code paths cited above disagree exactly as described.Related PR: #517.