fix: harden struct classification across architectures
- commit
- b883928226ea452a87bcf382f30a67ad967190ed
- author
- brice · · 2026-04-13 10:09 UTC
- parent
- 0879f2b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +144 -52
M struct_amd64.go +22 -27
| @@ -23,35 +23,30 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||
| 23 | 23 | // up to 8 bytes is returned in RAX | |
| 24 | 24 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() | |
| 25 | 25 | case outSize <= 16: | |
| 26 | - | r1, r2 := syscall.a1, syscall.a2 | |
| 27 | - | if isAllFloats(outType) { | |
| 28 | - | r1 = syscall.f1 | |
| 29 | - | r2 = syscall.f2 | |
| 30 | - | } else { | |
| 31 | - | // check first 8 bytes if it's floats | |
| 32 | - | hasFirstFloat := false | |
| 33 | - | f1 := outType.Field(0).Type | |
| 34 | - | if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 { | |
| 35 | - | r1 = syscall.f1 | |
| 36 | - | hasFirstFloat = true | |
| 37 | - | } | |
| 38 | - | ||
| 39 | - | // find index of the field that starts the second 8 bytes | |
| 40 | - | var i int | |
| 41 | - | for i = 0; i < outType.NumField(); i++ { | |
| 42 | - | if outType.Field(i).Offset == 8 { | |
| 43 | - | break | |
| 26 | + | var r1, r2 uintptr | |
| 27 | + | var intRegs, floatRegs int | |
| 28 | + | for i := 0; i < int((outSize+7)/8); i++ { | |
| 29 | + | class := classifyEightbyte(outType, uintptr(i)*8, uintptr(i)*8+8) | |
| 30 | + | var reg uintptr | |
| 31 | + | if class == _SSE { | |
| 32 | + | if floatRegs == 0 { | |
| 33 | + | reg = syscall.f1 | |
| 34 | + | } else { | |
| 35 | + | reg = syscall.f2 | |
| 44 | 36 | } | |
| 37 | + | floatRegs++ | |
| 38 | + | } else { | |
| 39 | + | if intRegs == 0 { | |
| 40 | + | reg = syscall.a1 | |
| 41 | + | } else { | |
| 42 | + | reg = syscall.a2 | |
| 43 | + | } | |
| 44 | + | intRegs++ | |
| 45 | 45 | } | |
| 46 | - | ||
| 47 | - | // check last 8 bytes if they are floats | |
| 48 | - | f1 = outType.Field(i).Type | |
| 49 | - | if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() { | |
| 50 | - | r2 = syscall.f1 | |
| 51 | - | } else if hasFirstFloat { | |
| 52 | - | // if the first field was a float then that means the second integer field | |
| 53 | - | // comes from the first integer register | |
| 54 | - | r2 = syscall.a1 | |
| 46 | + | if i == 0 { | |
| 47 | + | r1 = reg | |
| 48 | + | } else { | |
| 49 | + | r2 = reg | |
| 55 | 50 | } | |
| 56 | 51 | } | |
| 57 | 52 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |
| @@ -23,35 +23,30 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||||
| 23 | // up to 8 bytes is returned in RAX | 23 | // up to 8 bytes is returned in RAX | ||
| 24 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() | 24 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() | ||
| 25 | case outSize <= 16: | 25 | case outSize <= 16: | ||
| 26 | - | r1, r2 := syscall.a1, syscall.a2 | 26 | + | var r1, r2 uintptr |
| 27 | - | if isAllFloats(outType) { | 27 | + | var intRegs, floatRegs int |
| 28 | - | r1 = syscall.f1 | 28 | + | for i := 0; i < int((outSize+7)/8); i++ { |
| 29 | - | r2 = syscall.f2 | 29 | + | class := classifyEightbyte(outType, uintptr(i)*8, uintptr(i)*8+8) |
| 30 | - | } else { | 30 | + | var reg uintptr |
| 31 | - | // check first 8 bytes if it's floats | 31 | + | if class == _SSE { |
| 32 | - | hasFirstFloat := false | 32 | + | if floatRegs == 0 { |
| 33 | - | f1 := outType.Field(0).Type | 33 | + | reg = syscall.f1 |
| 34 | - | if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 { | 34 | + | } else { |
| 35 | - | r1 = syscall.f1 | 35 | + | reg = syscall.f2 |
| 36 | - | hasFirstFloat = true | |||
| 37 | - | } | |||
| 38 | - | ||||
| 39 | - | // find index of the field that starts the second 8 bytes | |||
| 40 | - | var i int | |||
| 41 | - | for i = 0; i < outType.NumField(); i++ { | |||
| 42 | - | if outType.Field(i).Offset == 8 { | |||
| 43 | - | break | |||
| 44 | } | 36 | } | ||
| 37 | + | floatRegs++ | |||
| 38 | + | } else { | |||
| 39 | + | if intRegs == 0 { | |||
| 40 | + | reg = syscall.a1 | |||
| 41 | + | } else { | |||
| 42 | + | reg = syscall.a2 | |||
| 43 | + | } | |||
| 44 | + | intRegs++ | |||
| 45 | } | 45 | } | ||
| 46 | - | 46 | + | if i == 0 { | |
| 47 | - | // check last 8 bytes if they are floats | 47 | + | r1 = reg |
| 48 | - | f1 = outType.Field(i).Type | 48 | + | } else { |
| 49 | - | if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() { | 49 | + | r2 = reg |
| 50 | - | r2 = syscall.f1 | |||
| 51 | - | } else if hasFirstFloat { | |||
| 52 | - | // if the first field was a float then that means the second integer field | |||
| 53 | - | // comes from the first integer register | |||
| 54 | - | r2 = syscall.a1 | |||
| 55 | } | 50 | } | ||
| 56 | } | 51 | } | ||
| 57 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | 52 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | ||
A struct_amd64_regression_test.go +21 -0
| @@ -0,0 +1,21 @@ | |||
| 1 | + | //go:build amd64 && (darwin || linux) | |
| 2 | + | ||
| 3 | + | package purego | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "reflect" | |
| 7 | + | "testing" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func TestGetStructHandlesSecondEightbyteWithoutOffset8Field(t *testing.T) { | |
| 11 | + | type awkward struct { | |
| 12 | + | A [3]uint32 | |
| 13 | + | B uint32 | |
| 14 | + | } | |
| 15 | + | sys := syscall15Args{a1: 0x0000000200000001, a2: 0x0000000400000003} | |
| 16 | + | got := getStruct(reflect.TypeOf(awkward{}), sys).Interface().(awkward) | |
| 17 | + | want := awkward{A: [3]uint32{1, 2, 3}, B: 4} | |
| 18 | + | if got != want { | |
| 19 | + | t.Fatalf("got %+v, want %+v", got, want) | |
| 20 | + | } | |
| 21 | + | } | |
| @@ -0,0 +1,21 @@ | |||||
| 1 | + | //go:build amd64 && (darwin || linux) | |||
| 2 | + | ||||
| 3 | + | package purego | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "reflect" | |||
| 7 | + | "testing" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func TestGetStructHandlesSecondEightbyteWithoutOffset8Field(t *testing.T) { | |||
| 11 | + | type awkward struct { | |||
| 12 | + | A [3]uint32 | |||
| 13 | + | B uint32 | |||
| 14 | + | } | |||
| 15 | + | sys := syscall15Args{a1: 0x0000000200000001, a2: 0x0000000400000003} | |||
| 16 | + | got := getStruct(reflect.TypeOf(awkward{}), sys).Interface().(awkward) | |||
| 17 | + | want := awkward{A: [3]uint32{1, 2, 3}, B: 4} | |||
| 18 | + | if got != want { | |||
| 19 | + | t.Fatalf("got %+v, want %+v", got, want) | |||
| 20 | + | } | |||
| 21 | + | } | |||
M struct_arm.go +8 -3
| @@ -5,6 +5,7 @@ package purego | |||
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | 7 | "reflect" | |
| 8 | + | "runtime" | |
| 8 | 9 | "unsafe" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| @@ -16,8 +17,10 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||
| 16 | 17 | ||
| 17 | 18 | // TODO: ARM EABI: small structs are passed in registers or on stack | |
| 18 | 19 | // For simplicity, pass by pointer for now | |
| 19 | - | ptr := v.Addr().UnsafePointer() | |
| 20 | - | keepAlive = append(keepAlive, ptr) | |
| 20 | + | ptr, live := stableValuePointer(v) | |
| 21 | + | if live != nil { | |
| 22 | + | keepAlive = append(keepAlive, live) | |
| 23 | + | } | |
| 21 | 24 | if *numInts < 4 { | |
| 22 | 25 | addInt(uintptr(ptr)) | |
| 23 | 26 | *numInts++ | |
| @@ -25,6 +28,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||
| 25 | 28 | addStack(uintptr(ptr)) | |
| 26 | 29 | *numStack++ | |
| 27 | 30 | } | |
| 31 | + | runtime.KeepAlive(live) | |
| 28 | 32 | return keepAlive | |
| 29 | 33 | } | |
| 30 | 34 | ||
| @@ -52,13 +56,14 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr | |||
| 52 | 56 | if size == 0 { | |
| 53 | 57 | return | |
| 54 | 58 | } | |
| 55 | - | ptr := unsafe.Pointer(v.UnsafeAddr()) | |
| 59 | + | ptr, live := stableValuePointer(v) | |
| 56 | 60 | if size <= 4 { | |
| 57 | 61 | addInt(*(*uintptr)(ptr)) | |
| 58 | 62 | } else if size <= 8 { | |
| 59 | 63 | addInt(*(*uintptr)(ptr)) | |
| 60 | 64 | addInt(*(*uintptr)(unsafe.Add(ptr, 4))) | |
| 61 | 65 | } | |
| 66 | + | runtime.KeepAlive(live) | |
| 62 | 67 | } | |
| 63 | 68 | ||
| 64 | 69 | // shouldBundleStackArgs always returns false on arm | |
| @@ -5,6 +5,7 @@ package purego | |||||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | "reflect" | 7 | "reflect" | ||
| 8 | + | "runtime" | |||
| 8 | "unsafe" | 9 | "unsafe" | ||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| @@ -16,8 +17,10 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||||
| 16 | 17 | ||||
| 17 | // TODO: ARM EABI: small structs are passed in registers or on stack | 18 | // TODO: ARM EABI: small structs are passed in registers or on stack | ||
| 18 | // For simplicity, pass by pointer for now | 19 | // For simplicity, pass by pointer for now | ||
| 19 | - | ptr := v.Addr().UnsafePointer() | 20 | + | ptr, live := stableValuePointer(v) |
| 20 | - | keepAlive = append(keepAlive, ptr) | 21 | + | if live != nil { |
| 22 | + | keepAlive = append(keepAlive, live) | |||
| 23 | + | } | |||
| 21 | if *numInts < 4 { | 24 | if *numInts < 4 { | ||
| 22 | addInt(uintptr(ptr)) | 25 | addInt(uintptr(ptr)) | ||
| 23 | *numInts++ | 26 | *numInts++ | ||
| @@ -25,6 +28,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||||
| 25 | addStack(uintptr(ptr)) | 28 | addStack(uintptr(ptr)) | ||
| 26 | *numStack++ | 29 | *numStack++ | ||
| 27 | } | 30 | } | ||
| 31 | + | runtime.KeepAlive(live) | |||
| 28 | return keepAlive | 32 | return keepAlive | ||
| 29 | } | 33 | } | ||
| 30 | 34 | ||||
| @@ -52,13 +56,14 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr | |||||
| 52 | if size == 0 { | 56 | if size == 0 { | ||
| 53 | return | 57 | return | ||
| 54 | } | 58 | } | ||
| 55 | - | ptr := unsafe.Pointer(v.UnsafeAddr()) | 59 | + | ptr, live := stableValuePointer(v) |
| 56 | if size <= 4 { | 60 | if size <= 4 { | ||
| 57 | addInt(*(*uintptr)(ptr)) | 61 | addInt(*(*uintptr)(ptr)) | ||
| 58 | } else if size <= 8 { | 62 | } else if size <= 8 { | ||
| 59 | addInt(*(*uintptr)(ptr)) | 63 | addInt(*(*uintptr)(ptr)) | ||
| 60 | addInt(*(*uintptr)(unsafe.Add(ptr, 4))) | 64 | addInt(*(*uintptr)(unsafe.Add(ptr, 4))) | ||
| 61 | } | 65 | } | ||
| 66 | + | runtime.KeepAlive(live) | |||
| 62 | } | 67 | } | ||
| 63 | 68 | ||||
| 64 | // shouldBundleStackArgs always returns false on arm | 69 | // shouldBundleStackArgs always returns false on arm | ||
M struct_arm64.go +22 -22
| @@ -229,38 +229,38 @@ func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |||
| 229 | 229 | // | |
| 230 | 230 | // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst | |
| 231 | 231 | func isHFA(t reflect.Type) bool { | |
| 232 | - | // round up struct size to nearest 8 see section B.4 | |
| 233 | 232 | structSize := roundUpTo8(t.Size()) | |
| 234 | 233 | if structSize == 0 || t.NumField() > 4 { | |
| 235 | 234 | return false | |
| 236 | 235 | } | |
| 237 | - | first := t.Field(0) | |
| 238 | - | switch first.Type.Kind() { | |
| 239 | - | case reflect.Float32, reflect.Float64: | |
| 240 | - | firstKind := first.Type.Kind() | |
| 236 | + | var kind reflect.Kind | |
| 237 | + | var members int | |
| 238 | + | var walk func(reflect.Type) bool | |
| 239 | + | walk = func(t reflect.Type) bool { | |
| 241 | 240 | for i := 0; i < t.NumField(); i++ { | |
| 242 | - | if t.Field(i).Type.Kind() != firstKind { | |
| 243 | - | return false | |
| 244 | - | } | |
| 245 | - | } | |
| 246 | - | return true | |
| 247 | - | case reflect.Array: | |
| 248 | - | switch first.Type.Elem().Kind() { | |
| 249 | - | case reflect.Float32, reflect.Float64: | |
| 250 | - | return true | |
| 251 | - | default: | |
| 252 | - | return false | |
| 253 | - | } | |
| 254 | - | case reflect.Struct: | |
| 255 | - | for i := 0; i < first.Type.NumField(); i++ { | |
| 256 | - | if !isHFA(first.Type) { | |
| 241 | + | ft := t.Field(i).Type | |
| 242 | + | switch ft.Kind() { | |
| 243 | + | case reflect.Float32, reflect.Float64: | |
| 244 | + | if kind == 0 { | |
| 245 | + | kind = ft.Kind() | |
| 246 | + | } else if ft.Kind() != kind { | |
| 247 | + | return false | |
| 248 | + | } | |
| 249 | + | members++ | |
| 250 | + | if members > 4 { | |
| 251 | + | return false | |
| 252 | + | } | |
| 253 | + | case reflect.Struct: | |
| 254 | + | if !walk(ft) { | |
| 255 | + | return false | |
| 256 | + | } | |
| 257 | + | default: | |
| 257 | 258 | return false | |
| 258 | 259 | } | |
| 259 | 260 | } | |
| 260 | 261 | return true | |
| 261 | - | default: | |
| 262 | - | return false | |
| 263 | 262 | } | |
| 263 | + | return walk(t) && members > 0 | |
| 264 | 264 | } | |
| 265 | 265 | ||
| 266 | 266 | // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type | |
| @@ -229,38 +229,38 @@ func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |||||
| 229 | // | 229 | // | ||
| 230 | // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst | 230 | // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst | ||
| 231 | func isHFA(t reflect.Type) bool { | 231 | func isHFA(t reflect.Type) bool { | ||
| 232 | - | // round up struct size to nearest 8 see section B.4 | |||
| 233 | structSize := roundUpTo8(t.Size()) | 232 | structSize := roundUpTo8(t.Size()) | ||
| 234 | if structSize == 0 || t.NumField() > 4 { | 233 | if structSize == 0 || t.NumField() > 4 { | ||
| 235 | return false | 234 | return false | ||
| 236 | } | 235 | } | ||
| 237 | - | first := t.Field(0) | 236 | + | var kind reflect.Kind |
| 238 | - | switch first.Type.Kind() { | 237 | + | var members int |
| 239 | - | case reflect.Float32, reflect.Float64: | 238 | + | var walk func(reflect.Type) bool |
| 240 | - | firstKind := first.Type.Kind() | 239 | + | walk = func(t reflect.Type) bool { |
| 241 | for i := 0; i < t.NumField(); i++ { | 240 | for i := 0; i < t.NumField(); i++ { | ||
| 242 | - | if t.Field(i).Type.Kind() != firstKind { | 241 | + | ft := t.Field(i).Type |
| 243 | - | return false | 242 | + | switch ft.Kind() { |
| 244 | - | } | 243 | + | case reflect.Float32, reflect.Float64: |
| 245 | - | } | 244 | + | if kind == 0 { |
| 246 | - | return true | 245 | + | kind = ft.Kind() |
| 247 | - | case reflect.Array: | 246 | + | } else if ft.Kind() != kind { |
| 248 | - | switch first.Type.Elem().Kind() { | 247 | + | return false |
| 249 | - | case reflect.Float32, reflect.Float64: | 248 | + | } |
| 250 | - | return true | 249 | + | members++ |
| 251 | - | default: | 250 | + | if members > 4 { |
| 252 | - | return false | 251 | + | return false |
| 253 | - | } | 252 | + | } |
| 254 | - | case reflect.Struct: | 253 | + | case reflect.Struct: |
| 255 | - | for i := 0; i < first.Type.NumField(); i++ { | 254 | + | if !walk(ft) { |
| 256 | - | if !isHFA(first.Type) { | 255 | + | return false |
| 256 | + | } | |||
| 257 | + | default: | |||
| 257 | return false | 258 | return false | ||
| 258 | } | 259 | } | ||
| 259 | } | 260 | } | ||
| 260 | return true | 261 | return true | ||
| 261 | - | default: | |||
| 262 | - | return false | |||
| 263 | } | 262 | } | ||
| 263 | + | return walk(t) && members > 0 | |||
| 264 | } | 264 | } | ||
| 265 | 265 | ||||
| 266 | // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type | 266 | // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type | ||
A struct_arm64_regression_test.go +30 -0
| @@ -0,0 +1,30 @@ | |||
| 1 | + | //go:build arm64 && (darwin || linux) | |
| 2 | + | ||
| 3 | + | package purego | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "reflect" | |
| 7 | + | "testing" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func TestIsHFARejectsNestedMixedStruct(t *testing.T) { | |
| 11 | + | type inner struct{ X, Y float32 } | |
| 12 | + | type mixed struct { | |
| 13 | + | Inner inner | |
| 14 | + | Tail int32 | |
| 15 | + | } | |
| 16 | + | if isHFA(reflect.TypeOf(mixed{})) { | |
| 17 | + | t.Fatal("expected mixed nested struct to be non-HFA") | |
| 18 | + | } | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | func TestIsHFAAcceptsNestedFloatStructs(t *testing.T) { | |
| 22 | + | type inner struct{ X, Y float32 } | |
| 23 | + | type outer struct { | |
| 24 | + | Left inner | |
| 25 | + | Right inner | |
| 26 | + | } | |
| 27 | + | if !isHFA(reflect.TypeOf(outer{})) { | |
| 28 | + | t.Fatal("expected nested float-only struct to be HFA") | |
| 29 | + | } | |
| 30 | + | } | |
| @@ -0,0 +1,30 @@ | |||||
| 1 | + | //go:build arm64 && (darwin || linux) | |||
| 2 | + | ||||
| 3 | + | package purego | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "reflect" | |||
| 7 | + | "testing" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func TestIsHFARejectsNestedMixedStruct(t *testing.T) { | |||
| 11 | + | type inner struct{ X, Y float32 } | |||
| 12 | + | type mixed struct { | |||
| 13 | + | Inner inner | |||
| 14 | + | Tail int32 | |||
| 15 | + | } | |||
| 16 | + | if isHFA(reflect.TypeOf(mixed{})) { | |||
| 17 | + | t.Fatal("expected mixed nested struct to be non-HFA") | |||
| 18 | + | } | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | func TestIsHFAAcceptsNestedFloatStructs(t *testing.T) { | |||
| 22 | + | type inner struct{ X, Y float32 } | |||
| 23 | + | type outer struct { | |||
| 24 | + | Left inner | |||
| 25 | + | Right inner | |||
| 26 | + | } | |||
| 27 | + | if !isHFA(reflect.TypeOf(outer{})) { | |||
| 28 | + | t.Fatal("expected nested float-only struct to be HFA") | |||
| 29 | + | } | |||
| 30 | + | } | |||
A struct_arm_regression_test.go +41 -0
| @@ -0,0 +1,41 @@ | |||
| 1 | + | //go:build arm && (freebsd || linux || netbsd || windows) | |
| 2 | + | ||
| 3 | + | package purego | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "reflect" | |
| 7 | + | "testing" | |
| 8 | + | "unsafe" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | func TestARMAddStructCopiesUnaddressableValue(t *testing.T) { | |
| 12 | + | type sample struct{ X uint32 } | |
| 13 | + | var numInts, numFloats, numStack int | |
| 14 | + | var ptr uintptr | |
| 15 | + | keepAlive := addStruct(reflect.ValueOf(sample{X: 7}), &numInts, &numFloats, &numStack, func(v uintptr) { | |
| 16 | + | ptr = v | |
| 17 | + | }, func(uintptr) { | |
| 18 | + | t.Fatal("unexpected float register") | |
| 19 | + | }, func(uintptr) { | |
| 20 | + | t.Fatal("unexpected stack slot") | |
| 21 | + | }, nil) | |
| 22 | + | if len(keepAlive) == 0 { | |
| 23 | + | t.Fatal("expected copied keepAlive value") | |
| 24 | + | } | |
| 25 | + | if got := *(*uint32)(unsafe.Pointer(ptr)); got != 7 { | |
| 26 | + | t.Fatalf("copied value = %#x, want %#x", got, uint32(7)) | |
| 27 | + | } | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | func TestARMPlaceRegistersReadsUnaddressableValue(t *testing.T) { | |
| 31 | + | type sample struct{ X uint32 } | |
| 32 | + | var ints []uintptr | |
| 33 | + | placeRegisters(reflect.ValueOf(sample{X: 7}), func(uintptr) { | |
| 34 | + | t.Fatal("unexpected float register") | |
| 35 | + | }, func(v uintptr) { | |
| 36 | + | ints = append(ints, v) | |
| 37 | + | }) | |
| 38 | + | if len(ints) != 1 || ints[0] != 7 { | |
| 39 | + | t.Fatalf("placeRegisters() = %#v, want [7]", ints) | |
| 40 | + | } | |
| 41 | + | } | |
| @@ -0,0 +1,41 @@ | |||||
| 1 | + | //go:build arm && (freebsd || linux || netbsd || windows) | |||
| 2 | + | ||||
| 3 | + | package purego | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "reflect" | |||
| 7 | + | "testing" | |||
| 8 | + | "unsafe" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | func TestARMAddStructCopiesUnaddressableValue(t *testing.T) { | |||
| 12 | + | type sample struct{ X uint32 } | |||
| 13 | + | var numInts, numFloats, numStack int | |||
| 14 | + | var ptr uintptr | |||
| 15 | + | keepAlive := addStruct(reflect.ValueOf(sample{X: 7}), &numInts, &numFloats, &numStack, func(v uintptr) { | |||
| 16 | + | ptr = v | |||
| 17 | + | }, func(uintptr) { | |||
| 18 | + | t.Fatal("unexpected float register") | |||
| 19 | + | }, func(uintptr) { | |||
| 20 | + | t.Fatal("unexpected stack slot") | |||
| 21 | + | }, nil) | |||
| 22 | + | if len(keepAlive) == 0 { | |||
| 23 | + | t.Fatal("expected copied keepAlive value") | |||
| 24 | + | } | |||
| 25 | + | if got := *(*uint32)(unsafe.Pointer(ptr)); got != 7 { | |||
| 26 | + | t.Fatalf("copied value = %#x, want %#x", got, uint32(7)) | |||
| 27 | + | } | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | func TestARMPlaceRegistersReadsUnaddressableValue(t *testing.T) { | |||
| 31 | + | type sample struct{ X uint32 } | |||
| 32 | + | var ints []uintptr | |||
| 33 | + | placeRegisters(reflect.ValueOf(sample{X: 7}), func(uintptr) { | |||
| 34 | + | t.Fatal("unexpected float register") | |||
| 35 | + | }, func(v uintptr) { | |||
| 36 | + | ints = append(ints, v) | |||
| 37 | + | }) | |||
| 38 | + | if len(ints) != 1 || ints[0] != 7 { | |||
| 39 | + | t.Fatalf("placeRegisters() = %#v, want [7]", ints) | |||
| 40 | + | } | |||
| 41 | + | } | |||