purego: fix arm64 argument corruption on alignment flush (#360)
Fix a critical bug in ARM64 struct argument packing where the register
value (val) and class were not being reset after flushing due to
alignment requirements.
When packing struct fields into registers, if a field's alignment
causes shift >= 64, the current register is flushed. However, the
code was not resetting 'val' and 'class' after the flush, causing
subsequent fields to be ORed with stale data from previous fields.
Example bug with FourInt32s{1, 2, 3, 4}:
- Fields 0,1 packed: val = 0x0000000200000001
- Flush at field 2 due to shift >= 64
- BUG: val still contains 0x0000000200000001
- Field 2 packs: val |= 3 becomes 0x0000000200000003 (should be 0x03)
- Field 3 packs: val |= (4<<32) becomes 0x0000000400000003
- Result: field 3 = 6 instead of 4 (bit 1 from field 1 leaked)
This fix ensures val and class are properly reset after each flush,
preventing data corruption between register boundaries.
Closes #359- commit
- 25e25ad2aab189c2641445bb02cafe47ebbe73d9
- author
- Travis Cline · · 2025-10-31 18:09 UTC
- committer
- GitHub · · 2025-10-31 18:09 UTC
- parent
- 1bb4460
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown - refs
- tag: v0.10.0-alpha.3
3 files · +31 -3
M struct_arm64.go +2 -0
| @@ -117,6 +117,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr | |||
| 117 | 117 | } else { | |
| 118 | 118 | addInt(uintptr(val)) | |
| 119 | 119 | } | |
| 120 | + | val = 0 | |
| 121 | + | class = _NO_CLASS | |
| 120 | 122 | } | |
| 121 | 123 | switch f.Type().Kind() { | |
| 122 | 124 | case reflect.Struct: | |
| @@ -117,6 +117,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr | |||||
| 117 | } else { | 117 | } else { | ||
| 118 | addInt(uintptr(val)) | 118 | addInt(uintptr(val)) | ||
| 119 | } | 119 | } | ||
| 120 | + | val = 0 | |||
| 121 | + | class = _NO_CLASS | |||
| 120 | } | 122 | } | ||
| 121 | switch f.Type().Kind() { | 123 | switch f.Type().Kind() { | ||
| 122 | case reflect.Struct: | 124 | case reflect.Struct: | ||
M struct_test.go +15 -2
| @@ -373,8 +373,9 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 373 | 373 | } | |
| 374 | 374 | var Array4CharsFn func(chars Array4Chars) int32 | |
| 375 | 375 | purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars") | |
| 376 | - | if ret := Array4CharsFn(Array4Chars{a: [...]int8{100, -127, 4, -100}}); ret != expectedSigned { | |
| 377 | - | t.Fatalf("Array4CharsFn returned %#x wanted %#x", ret, expectedSigned) | |
| 376 | + | const expectedSum = 1 + 2 + 4 + 8 | |
| 377 | + | if ret := Array4CharsFn(Array4Chars{a: [...]int8{1, 2, 4, 8}}); ret != expectedSum { | |
| 378 | + | t.Fatalf("Array4CharsFn returned %d wanted %d", ret, expectedSum) | |
| 378 | 379 | } | |
| 379 | 380 | } | |
| 380 | 381 | { | |
| @@ -486,6 +487,18 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 486 | 487 | t.Fatalf("FloatAndBool(y: false) = %d, want 0", ret) | |
| 487 | 488 | } | |
| 488 | 489 | } | |
| 490 | + | { | |
| 491 | + | type FourInt32s struct { | |
| 492 | + | f0, f1, f2, f3 int32 | |
| 493 | + | } | |
| 494 | + | var FourInt32sFn func(FourInt32s) int32 | |
| 495 | + | purego.RegisterLibFunc(&FourInt32sFn, lib, "FourInt32s") | |
| 496 | + | result := FourInt32sFn(FourInt32s{100, -127, 4, -100}) | |
| 497 | + | const want = 100 - 127 + 4 - 100 | |
| 498 | + | if result != want { | |
| 499 | + | t.Fatalf("FourInt32s returned %d wanted %d", result, want) | |
| 500 | + | } | |
| 501 | + | } | |
| 489 | 502 | } | |
| 490 | 503 | ||
| 491 | 504 | func TestRegisterFunc_structReturns(t *testing.T) { | |
| @@ -373,8 +373,9 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 373 | } | 373 | } | ||
| 374 | var Array4CharsFn func(chars Array4Chars) int32 | 374 | var Array4CharsFn func(chars Array4Chars) int32 | ||
| 375 | purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars") | 375 | purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars") | ||
| 376 | - | if ret := Array4CharsFn(Array4Chars{a: [...]int8{100, -127, 4, -100}}); ret != expectedSigned { | 376 | + | const expectedSum = 1 + 2 + 4 + 8 |
| 377 | - | t.Fatalf("Array4CharsFn returned %#x wanted %#x", ret, expectedSigned) | 377 | + | if ret := Array4CharsFn(Array4Chars{a: [...]int8{1, 2, 4, 8}}); ret != expectedSum { |
| 378 | + | t.Fatalf("Array4CharsFn returned %d wanted %d", ret, expectedSum) | |||
| 378 | } | 379 | } | ||
| 379 | } | 380 | } | ||
| 380 | { | 381 | { | ||
| @@ -486,6 +487,18 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 486 | t.Fatalf("FloatAndBool(y: false) = %d, want 0", ret) | 487 | t.Fatalf("FloatAndBool(y: false) = %d, want 0", ret) | ||
| 487 | } | 488 | } | ||
| 488 | } | 489 | } | ||
| 490 | + | { | |||
| 491 | + | type FourInt32s struct { | |||
| 492 | + | f0, f1, f2, f3 int32 | |||
| 493 | + | } | |||
| 494 | + | var FourInt32sFn func(FourInt32s) int32 | |||
| 495 | + | purego.RegisterLibFunc(&FourInt32sFn, lib, "FourInt32s") | |||
| 496 | + | result := FourInt32sFn(FourInt32s{100, -127, 4, -100}) | |||
| 497 | + | const want = 100 - 127 + 4 - 100 | |||
| 498 | + | if result != want { | |||
| 499 | + | t.Fatalf("FourInt32s returned %d wanted %d", result, want) | |||
| 500 | + | } | |||
| 501 | + | } | |||
| 489 | } | 502 | } | ||
| 490 | 503 | ||||
| 491 | func TestRegisterFunc_structReturns(t *testing.T) { | 504 | func TestRegisterFunc_structReturns(t *testing.T) { | ||
M testdata/structtest/struct_test.c +14 -1
| @@ -1,7 +1,9 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | #include "stdint.h" | |
| 4 | + | #include <stdint.h> | |
| 5 | + | #include <stdio.h> | |
| 6 | + | #include <stdlib.h> | |
| 5 | 7 | ||
| 6 | 8 | #if defined(__x86_64__) || defined(__aarch64__) | |
| 7 | 9 | typedef int64_t GoInt; | |
| @@ -361,3 +363,14 @@ struct FloatAndBool { | |||
| 361 | 363 | int FloatAndBool(struct FloatAndBool f) { | |
| 362 | 364 | return f.has_value; | |
| 363 | 365 | } | |
| 366 | + | ||
| 367 | + | struct FourInt32s { | |
| 368 | + | int32_t f0; | |
| 369 | + | int32_t f1; | |
| 370 | + | int32_t f2; | |
| 371 | + | int32_t f3; | |
| 372 | + | }; | |
| 373 | + | ||
| 374 | + | int32_t FourInt32s(struct FourInt32s s) { | |
| 375 | + | return s.f0 + s.f1 + s.f2 + s.f3; | |
| 376 | + | } | |
| @@ -1,7 +1,9 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | #include "stdint.h" | 4 | + | #include <stdint.h> |
| 5 | + | #include <stdio.h> | |||
| 6 | + | #include <stdlib.h> | |||
| 5 | 7 | ||||
| 6 | #if defined(__x86_64__) || defined(__aarch64__) | 8 | #if defined(__x86_64__) || defined(__aarch64__) | ||
| 7 | typedef int64_t GoInt; | 9 | typedef int64_t GoInt; | ||
| @@ -361,3 +363,14 @@ struct FloatAndBool { | |||||
| 361 | int FloatAndBool(struct FloatAndBool f) { | 363 | int FloatAndBool(struct FloatAndBool f) { | ||
| 362 | return f.has_value; | 364 | return f.has_value; | ||
| 363 | } | 365 | } | ||
| 366 | + | ||||
| 367 | + | struct FourInt32s { | |||
| 368 | + | int32_t f0; | |||
| 369 | + | int32_t f1; | |||
| 370 | + | int32_t f2; | |||
| 371 | + | int32_t f3; | |||
| 372 | + | }; | |||
| 373 | + | ||||
| 374 | + | int32_t FourInt32s(struct FourInt32s s) { | |||
| 375 | + | return s.f0 + s.f1 + s.f2 + s.f3; | |||
| 376 | + | } | |||