detached 0879f2b

fix: correct loong64 and s390x struct packing

commit
0879f2b44707ee29762604e56f6c803c49da199f
author
brice · · 2026-04-13 10:03 UTC
parent
5e664bd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +63 -12
M struct_loong64.go +6 -11
@@ -19,7 +19,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
1919 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
2020 r1 = syscall.f1
2121 if numFields == 2 {
22- r1 = syscall.f2<<32 | syscall.f1
22+ r1 = packFloat32Pair(syscall.f1, syscall.f2)
2323 }
2424 }
2525 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
@@ -28,10 +28,10 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
2828 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
2929 switch numFields {
3030 case 4:
31- r1 = syscall.f2<<32 | syscall.f1
32- r2 = syscall.f4<<32 | syscall.f3
31+ r1 = packFloat32Pair(syscall.f1, syscall.f2)
32+ r2 = packFloat32Pair(syscall.f3, syscall.f4)
3333 case 3:
34- r1 = syscall.f2<<32 | syscall.f1
34+ r1 = packFloat32Pair(syscall.f1, syscall.f2)
3535 r2 = syscall.f3
3636 case 2:
3737 r1 = syscall.f1
@@ -91,13 +91,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
9191 align := byte(f.Type().Align()*8 - 1)
9292 shift = (shift + align) &^ align
9393 if shift >= 64 {
94- shift = 0
95- flushed = true
96- if class == _FLOAT {
97- addFloat(uintptr(val))
98- } else {
99- addInt(uintptr(val))
100- }
94+ flushPackedWord(&val, &shift, &flushed, class == _FLOAT, addFloat, addInt)
95+ class = _NO_CLASS
10196 }
10297 switch f.Type().Kind() {
10398 case reflect.Struct:
@@ -19,7 +19,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
19 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {19 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
20 r1 = syscall.f120 r1 = syscall.f1
21 if numFields == 2 {21 if numFields == 2 {
22- r1 = syscall.f2<<32 | syscall.f122+ r1 = packFloat32Pair(syscall.f1, syscall.f2)
23 }23 }
24 }24 }
25 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()25 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
@@ -28,10 +28,10 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
28 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {28 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
29 switch numFields {29 switch numFields {
30 case 4:30 case 4:
31- r1 = syscall.f2<<32 | syscall.f131+ r1 = packFloat32Pair(syscall.f1, syscall.f2)
32- r2 = syscall.f4<<32 | syscall.f332+ r2 = packFloat32Pair(syscall.f3, syscall.f4)
33 case 3:33 case 3:
34- r1 = syscall.f2<<32 | syscall.f134+ r1 = packFloat32Pair(syscall.f1, syscall.f2)
35 r2 = syscall.f335 r2 = syscall.f3
36 case 2:36 case 2:
37 r1 = syscall.f137 r1 = syscall.f1
@@ -91,13 +91,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
91 align := byte(f.Type().Align()*8 - 1)91 align := byte(f.Type().Align()*8 - 1)
92 shift = (shift + align) &^ align92 shift = (shift + align) &^ align
93 if shift >= 64 {93 if shift >= 64 {
94- shift = 094+ flushPackedWord(&val, &shift, &flushed, class == _FLOAT, addFloat, addInt)
95- flushed = true95+ class = _NO_CLASS
96- if class == _FLOAT {
97- addFloat(uintptr(val))
98- } else {
99- addInt(uintptr(val))
100- }
101 }96 }
102 switch f.Type().Kind() {97 switch f.Type().Kind() {
103 case reflect.Struct:98 case reflect.Struct:
A struct_loong64_regression_test.go +35 -0
@@ -0,0 +1,35 @@
1+//go:build loong64 && linux
2+
3+package purego
4+
5+import (
6+ "math"
7+ "reflect"
8+ "testing"
9+)
10+
11+func TestLoong64GetStructMasksFloat32Pairs(t *testing.T) {
12+ type pair struct{ X, Y float32 }
13+ sys := syscall15Args{
14+ f1: uintptr(0xDEADBEEF00000000 | uint64(math.Float32bits(1.5))),
15+ f2: uintptr(math.Float32bits(2.5)),
16+ }
17+ got := getStruct(reflect.TypeOf(pair{}), sys).Interface().(pair)
18+ if got.X != 1.5 || got.Y != 2.5 {
19+ t.Fatalf("got %+v, want {1.5 2.5}", got)
20+ }
21+}
22+
23+func TestLoong64PlaceRegistersResetsAfterFlush(t *testing.T) {
24+ type triple struct{ A, B, C uint32 }
25+ var ints []uintptr
26+ placeRegisters(reflect.ValueOf(triple{A: 1, B: 2, C: 3}), func(uintptr) {
27+ t.Fatal("unexpected float register")
28+ }, func(v uintptr) {
29+ ints = append(ints, v)
30+ })
31+ want := []uintptr{0x0000000200000001, 0x0000000000000003}
32+ if len(ints) != len(want) || ints[0] != want[0] || ints[1] != want[1] {
33+ t.Fatalf("placeRegisters() = %#v, want %#v", ints, want)
34+ }
35+}
@@ -0,0 +1,35 @@
1+//go:build loong64 && linux
2+
3+package purego
4+
5+import (
6+ "math"
7+ "reflect"
8+ "testing"
9+)
10+
11+func TestLoong64GetStructMasksFloat32Pairs(t *testing.T) {
12+ type pair struct{ X, Y float32 }
13+ sys := syscall15Args{
14+ f1: uintptr(0xDEADBEEF00000000 | uint64(math.Float32bits(1.5))),
15+ f2: uintptr(math.Float32bits(2.5)),
16+ }
17+ got := getStruct(reflect.TypeOf(pair{}), sys).Interface().(pair)
18+ if got.X != 1.5 || got.Y != 2.5 {
19+ t.Fatalf("got %+v, want {1.5 2.5}", got)
20+ }
21+}
22+
23+func TestLoong64PlaceRegistersResetsAfterFlush(t *testing.T) {
24+ type triple struct{ A, B, C uint32 }
25+ var ints []uintptr
26+ placeRegisters(reflect.ValueOf(triple{A: 1, B: 2, C: 3}), func(uintptr) {
27+ t.Fatal("unexpected float register")
28+ }, func(v uintptr) {
29+ ints = append(ints, v)
30+ })
31+ want := []uintptr{0x0000000200000001, 0x0000000000000003}
32+ if len(ints) != len(want) || ints[0] != want[0] || ints[1] != want[1] {
33+ t.Fatalf("placeRegisters() = %#v, want %#v", ints, want)
34+ }
35+}
M struct_s390x.go +1 -1
@@ -31,7 +31,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value {
3131 // float32 values in FP regs
3232 f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4}
3333 for i := 0; i < numFields; i++ {
34- *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i])
34+ *(*uint32)(unsafe.Pointer(&buf[i*4])) = highFloat32Bits(f[i])
3535 }
3636 } else {
3737 // float64: whole register value is valid
@@ -31,7 +31,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value {
31 // float32 values in FP regs31 // float32 values in FP regs
32 f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4}32 f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4}
33 for i := 0; i < numFields; i++ {33 for i := 0; i < numFields; i++ {
34- *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i])34+ *(*uint32)(unsafe.Pointer(&buf[i*4])) = highFloat32Bits(f[i])
35 }35 }
36 } else {36 } else {
37 // float64: whole register value is valid37 // float64: whole register value is valid
A struct_s390x_regression_test.go +21 -0
@@ -0,0 +1,21 @@
1+//go:build s390x && linux
2+
3+package purego
4+
5+import (
6+ "math"
7+ "reflect"
8+ "testing"
9+)
10+
11+func TestS390xGetStructUsesHighFloat32Bits(t *testing.T) {
12+ type pair struct{ X, Y float32 }
13+ sys := syscall15Args{
14+ f1: uintptr(uint64(math.Float32bits(1.5)) << 32),
15+ f2: uintptr(uint64(math.Float32bits(2.5)) << 32),
16+ }
17+ got := getStruct(reflect.TypeOf(pair{}), sys).Interface().(pair)
18+ if got.X != 1.5 || got.Y != 2.5 {
19+ t.Fatalf("got %+v, want {1.5 2.5}", got)
20+ }
21+}
@@ -0,0 +1,21 @@
1+//go:build s390x && linux
2+
3+package purego
4+
5+import (
6+ "math"
7+ "reflect"
8+ "testing"
9+)
10+
11+func TestS390xGetStructUsesHighFloat32Bits(t *testing.T) {
12+ type pair struct{ X, Y float32 }
13+ sys := syscall15Args{
14+ f1: uintptr(uint64(math.Float32bits(1.5)) << 32),
15+ f2: uintptr(uint64(math.Float32bits(2.5)) << 32),
16+ }
17+ got := getStruct(reflect.TypeOf(pair{}), sys).Interface().(pair)
18+ if got.X != 1.5 || got.Y != 2.5 {
19+ t.Fatalf("got %+v, want {1.5 2.5}", got)
20+ }
21+}

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