Merge branch 'fix/abi-correctness'
16 files · +436 -88
A struct_abi_helpers.go +38 -0
| @@ -0,0 +1,38 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | package purego | |
| 5 | + | ||
| 6 | + | import ( | |
| 7 | + | "math" | |
| 8 | + | "reflect" | |
| 9 | + | "unsafe" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | func packFloat32Pair(low, high uintptr) uintptr { | |
| 13 | + | return uintptr((uint64(high) << 32) | (uint64(low) & math.MaxUint32)) | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | func highFloat32Bits(word uintptr) uint32 { | |
| 17 | + | return uint32(uint64(word) >> 32) | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | func flushPackedWord(val *uint64, shift *byte, flushed *bool, isFloat bool, addFloat, addInt func(uintptr)) { | |
| 21 | + | *flushed = true | |
| 22 | + | if isFloat { | |
| 23 | + | addFloat(uintptr(*val)) | |
| 24 | + | } else { | |
| 25 | + | addInt(uintptr(*val)) | |
| 26 | + | } | |
| 27 | + | *val = 0 | |
| 28 | + | *shift = 0 | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | func stableValuePointer(v reflect.Value) (unsafe.Pointer, any) { | |
| 32 | + | if v.CanAddr() { | |
| 33 | + | return v.Addr().UnsafePointer(), nil | |
| 34 | + | } | |
| 35 | + | tmp := reflect.New(v.Type()) | |
| 36 | + | tmp.Elem().Set(v) | |
| 37 | + | return tmp.UnsafePointer(), tmp.Interface() | |
| 38 | + | } | |
| @@ -0,0 +1,38 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package purego | |||
| 5 | + | ||||
| 6 | + | import ( | |||
| 7 | + | "math" | |||
| 8 | + | "reflect" | |||
| 9 | + | "unsafe" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | func packFloat32Pair(low, high uintptr) uintptr { | |||
| 13 | + | return uintptr((uint64(high) << 32) | (uint64(low) & math.MaxUint32)) | |||
| 14 | + | } | |||
| 15 | + | ||||
| 16 | + | func highFloat32Bits(word uintptr) uint32 { | |||
| 17 | + | return uint32(uint64(word) >> 32) | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | func flushPackedWord(val *uint64, shift *byte, flushed *bool, isFloat bool, addFloat, addInt func(uintptr)) { | |||
| 21 | + | *flushed = true | |||
| 22 | + | if isFloat { | |||
| 23 | + | addFloat(uintptr(*val)) | |||
| 24 | + | } else { | |||
| 25 | + | addInt(uintptr(*val)) | |||
| 26 | + | } | |||
| 27 | + | *val = 0 | |||
| 28 | + | *shift = 0 | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | func stableValuePointer(v reflect.Value) (unsafe.Pointer, any) { | |||
| 32 | + | if v.CanAddr() { | |||
| 33 | + | return v.Addr().UnsafePointer(), nil | |||
| 34 | + | } | |||
| 35 | + | tmp := reflect.New(v.Type()) | |||
| 36 | + | tmp.Elem().Set(v) | |||
| 37 | + | return tmp.UnsafePointer(), tmp.Interface() | |||
| 38 | + | } | |||
A struct_abi_helpers_test.go +105 -0
| @@ -0,0 +1,105 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | package purego | |
| 5 | + | ||
| 6 | + | import ( | |
| 7 | + | "math" | |
| 8 | + | "reflect" | |
| 9 | + | "testing" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | func TestPackFloat32PairMasksLowWord(t *testing.T) { | |
| 13 | + | low := uintptr(0xDEADBEEF00000000 | uint64(math.Float32bits(1.5))) | |
| 14 | + | high := uintptr(math.Float32bits(2.5)) | |
| 15 | + | want := uintptr(uint64(math.Float32bits(2.5))<<32 | uint64(math.Float32bits(1.5))) | |
| 16 | + | if got := packFloat32Pair(low, high); got != want { | |
| 17 | + | t.Fatalf("packFloat32Pair() = %#x, want %#x", got, want) | |
| 18 | + | } | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | func TestHighFloat32Bits(t *testing.T) { | |
| 22 | + | word := uintptr(uint64(math.Float32bits(3.25)) << 32) | |
| 23 | + | if got, want := highFloat32Bits(word), math.Float32bits(3.25); got != want { | |
| 24 | + | t.Fatalf("highFloat32Bits() = %#x, want %#x", got, want) | |
| 25 | + | } | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | func TestFlushPackedWordResetsState(t *testing.T) { | |
| 29 | + | var val uint64 = 0x11223344 | |
| 30 | + | shift := byte(32) | |
| 31 | + | flushed := false | |
| 32 | + | var gotFloat uintptr | |
| 33 | + | flushPackedWord(&val, &shift, &flushed, true, func(v uintptr) { gotFloat = v }, func(uintptr) { | |
| 34 | + | t.Fatal("unexpected integer flush") | |
| 35 | + | }) | |
| 36 | + | if !flushed || val != 0 || shift != 0 { | |
| 37 | + | t.Fatalf("flushPackedWord() left state dirty: flushed=%v val=%#x shift=%d", flushed, val, shift) | |
| 38 | + | } | |
| 39 | + | if gotFloat != 0x11223344 { | |
| 40 | + | t.Fatalf("flushPackedWord() flushed %#x, want %#x", gotFloat, uintptr(0x11223344)) | |
| 41 | + | } | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | func TestFlushPackedWordResetsStateForIntegerBranch(t *testing.T) { | |
| 45 | + | var val uint64 = 0x55667788 | |
| 46 | + | shift := byte(16) | |
| 47 | + | flushed := false | |
| 48 | + | var gotInt uintptr | |
| 49 | + | flushPackedWord(&val, &shift, &flushed, false, func(uintptr) { | |
| 50 | + | t.Fatal("unexpected float flush") | |
| 51 | + | }, func(v uintptr) { gotInt = v }) | |
| 52 | + | if !flushed || val != 0 || shift != 0 { | |
| 53 | + | t.Fatalf("flushPackedWord() left state dirty: flushed=%v val=%#x shift=%d", flushed, val, shift) | |
| 54 | + | } | |
| 55 | + | if gotInt != 0x55667788 { | |
| 56 | + | t.Fatalf("flushPackedWord() flushed %#x, want %#x", gotInt, uintptr(0x55667788)) | |
| 57 | + | } | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | func TestStableValuePointerCopiesUnaddressableValue(t *testing.T) { | |
| 61 | + | type sample struct{ X uint32 } | |
| 62 | + | ptr, keepAlive := stableValuePointer(reflect.ValueOf(sample{X: 7})) | |
| 63 | + | if keepAlive == nil { | |
| 64 | + | t.Fatal("expected keepAlive copy for unaddressable value") | |
| 65 | + | } | |
| 66 | + | if got := *(*uint32)(ptr); got != 7 { | |
| 67 | + | t.Fatalf("stableValuePointer() copied %#x, want %#x", got, uint32(7)) | |
| 68 | + | } | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | func TestStableValuePointerUsesAddressableValue(t *testing.T) { | |
| 72 | + | type sample struct{ X uint32 } | |
| 73 | + | value := sample{X: 9} | |
| 74 | + | ptr, keepAlive := stableValuePointer(reflect.ValueOf(&value).Elem()) | |
| 75 | + | if keepAlive != nil { | |
| 76 | + | t.Fatal("expected nil keepAlive for addressable value") | |
| 77 | + | } | |
| 78 | + | if got := *(*uint32)(ptr); got != 9 { | |
| 79 | + | t.Fatalf("stableValuePointer() pointed to %#x, want %#x", got, uint32(9)) | |
| 80 | + | } | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | func TestIsAllSameFloatRejectsNestedMixedStruct(t *testing.T) { | |
| 84 | + | type inner struct{ X, Y float32 } | |
| 85 | + | type mixed struct { | |
| 86 | + | Inner inner | |
| 87 | + | Tail int32 | |
| 88 | + | } | |
| 89 | + | allFloats, fields := isAllSameFloat(reflect.TypeOf(mixed{})) | |
| 90 | + | if allFloats { | |
| 91 | + | t.Fatalf("isAllSameFloat() = true for mixed nested struct with %d fields", fields) | |
| 92 | + | } | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | func TestIsAllSameFloatAcceptsNestedFloatStruct(t *testing.T) { | |
| 96 | + | type inner struct{ X, Y float32 } | |
| 97 | + | type outer struct { | |
| 98 | + | Left inner | |
| 99 | + | Right inner | |
| 100 | + | } | |
| 101 | + | allFloats, fields := isAllSameFloat(reflect.TypeOf(outer{})) | |
| 102 | + | if !allFloats || fields != 4 { | |
| 103 | + | t.Fatalf("isAllSameFloat() = (%v, %d), want (true, 4)", allFloats, fields) | |
| 104 | + | } | |
| 105 | + | } | |
| @@ -0,0 +1,105 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package purego | |||
| 5 | + | ||||
| 6 | + | import ( | |||
| 7 | + | "math" | |||
| 8 | + | "reflect" | |||
| 9 | + | "testing" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | func TestPackFloat32PairMasksLowWord(t *testing.T) { | |||
| 13 | + | low := uintptr(0xDEADBEEF00000000 | uint64(math.Float32bits(1.5))) | |||
| 14 | + | high := uintptr(math.Float32bits(2.5)) | |||
| 15 | + | want := uintptr(uint64(math.Float32bits(2.5))<<32 | uint64(math.Float32bits(1.5))) | |||
| 16 | + | if got := packFloat32Pair(low, high); got != want { | |||
| 17 | + | t.Fatalf("packFloat32Pair() = %#x, want %#x", got, want) | |||
| 18 | + | } | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | func TestHighFloat32Bits(t *testing.T) { | |||
| 22 | + | word := uintptr(uint64(math.Float32bits(3.25)) << 32) | |||
| 23 | + | if got, want := highFloat32Bits(word), math.Float32bits(3.25); got != want { | |||
| 24 | + | t.Fatalf("highFloat32Bits() = %#x, want %#x", got, want) | |||
| 25 | + | } | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | func TestFlushPackedWordResetsState(t *testing.T) { | |||
| 29 | + | var val uint64 = 0x11223344 | |||
| 30 | + | shift := byte(32) | |||
| 31 | + | flushed := false | |||
| 32 | + | var gotFloat uintptr | |||
| 33 | + | flushPackedWord(&val, &shift, &flushed, true, func(v uintptr) { gotFloat = v }, func(uintptr) { | |||
| 34 | + | t.Fatal("unexpected integer flush") | |||
| 35 | + | }) | |||
| 36 | + | if !flushed || val != 0 || shift != 0 { | |||
| 37 | + | t.Fatalf("flushPackedWord() left state dirty: flushed=%v val=%#x shift=%d", flushed, val, shift) | |||
| 38 | + | } | |||
| 39 | + | if gotFloat != 0x11223344 { | |||
| 40 | + | t.Fatalf("flushPackedWord() flushed %#x, want %#x", gotFloat, uintptr(0x11223344)) | |||
| 41 | + | } | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | func TestFlushPackedWordResetsStateForIntegerBranch(t *testing.T) { | |||
| 45 | + | var val uint64 = 0x55667788 | |||
| 46 | + | shift := byte(16) | |||
| 47 | + | flushed := false | |||
| 48 | + | var gotInt uintptr | |||
| 49 | + | flushPackedWord(&val, &shift, &flushed, false, func(uintptr) { | |||
| 50 | + | t.Fatal("unexpected float flush") | |||
| 51 | + | }, func(v uintptr) { gotInt = v }) | |||
| 52 | + | if !flushed || val != 0 || shift != 0 { | |||
| 53 | + | t.Fatalf("flushPackedWord() left state dirty: flushed=%v val=%#x shift=%d", flushed, val, shift) | |||
| 54 | + | } | |||
| 55 | + | if gotInt != 0x55667788 { | |||
| 56 | + | t.Fatalf("flushPackedWord() flushed %#x, want %#x", gotInt, uintptr(0x55667788)) | |||
| 57 | + | } | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | func TestStableValuePointerCopiesUnaddressableValue(t *testing.T) { | |||
| 61 | + | type sample struct{ X uint32 } | |||
| 62 | + | ptr, keepAlive := stableValuePointer(reflect.ValueOf(sample{X: 7})) | |||
| 63 | + | if keepAlive == nil { | |||
| 64 | + | t.Fatal("expected keepAlive copy for unaddressable value") | |||
| 65 | + | } | |||
| 66 | + | if got := *(*uint32)(ptr); got != 7 { | |||
| 67 | + | t.Fatalf("stableValuePointer() copied %#x, want %#x", got, uint32(7)) | |||
| 68 | + | } | |||
| 69 | + | } | |||
| 70 | + | ||||
| 71 | + | func TestStableValuePointerUsesAddressableValue(t *testing.T) { | |||
| 72 | + | type sample struct{ X uint32 } | |||
| 73 | + | value := sample{X: 9} | |||
| 74 | + | ptr, keepAlive := stableValuePointer(reflect.ValueOf(&value).Elem()) | |||
| 75 | + | if keepAlive != nil { | |||
| 76 | + | t.Fatal("expected nil keepAlive for addressable value") | |||
| 77 | + | } | |||
| 78 | + | if got := *(*uint32)(ptr); got != 9 { | |||
| 79 | + | t.Fatalf("stableValuePointer() pointed to %#x, want %#x", got, uint32(9)) | |||
| 80 | + | } | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | func TestIsAllSameFloatRejectsNestedMixedStruct(t *testing.T) { | |||
| 84 | + | type inner struct{ X, Y float32 } | |||
| 85 | + | type mixed struct { | |||
| 86 | + | Inner inner | |||
| 87 | + | Tail int32 | |||
| 88 | + | } | |||
| 89 | + | allFloats, fields := isAllSameFloat(reflect.TypeOf(mixed{})) | |||
| 90 | + | if allFloats { | |||
| 91 | + | t.Fatalf("isAllSameFloat() = true for mixed nested struct with %d fields", fields) | |||
| 92 | + | } | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | func TestIsAllSameFloatAcceptsNestedFloatStruct(t *testing.T) { | |||
| 96 | + | type inner struct{ X, Y float32 } | |||
| 97 | + | type outer struct { | |||
| 98 | + | Left inner | |||
| 99 | + | Right inner | |||
| 100 | + | } | |||
| 101 | + | allFloats, fields := isAllSameFloat(reflect.TypeOf(outer{})) | |||
| 102 | + | if !allFloats || fields != 4 { | |||
| 103 | + | t.Fatalf("isAllSameFloat() = (%v, %d), want (true, 4)", allFloats, fields) | |||
| 104 | + | } | |||
| 105 | + | } | |||
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 +37 -23
| @@ -229,38 +229,52 @@ 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 | - | if structSize == 0 || t.NumField() > 4 { | |
| 233 | + | if structSize == 0 { | |
| 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 | + | case reflect.Array: | |
| 258 | + | elemKind := ft.Elem().Kind() | |
| 259 | + | if elemKind != reflect.Float32 && elemKind != reflect.Float64 { | |
| 260 | + | return false | |
| 261 | + | } | |
| 262 | + | if kind == 0 { | |
| 263 | + | kind = elemKind | |
| 264 | + | } else if elemKind != kind { | |
| 265 | + | return false | |
| 266 | + | } | |
| 267 | + | members += ft.Len() | |
| 268 | + | if members > 4 { | |
| 269 | + | return false | |
| 270 | + | } | |
| 271 | + | default: | |
| 257 | 272 | return false | |
| 258 | 273 | } | |
| 259 | 274 | } | |
| 260 | 275 | return true | |
| 261 | - | default: | |
| 262 | - | return false | |
| 263 | 276 | } | |
| 277 | + | return walk(t) && members > 0 | |
| 264 | 278 | } | |
| 265 | 279 | ||
| 266 | 280 | // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type | |
| @@ -229,38 +229,52 @@ 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 { |
| 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 | + | case reflect.Array: | |||
| 258 | + | elemKind := ft.Elem().Kind() | |||
| 259 | + | if elemKind != reflect.Float32 && elemKind != reflect.Float64 { | |||
| 260 | + | return false | |||
| 261 | + | } | |||
| 262 | + | if kind == 0 { | |||
| 263 | + | kind = elemKind | |||
| 264 | + | } else if elemKind != kind { | |||
| 265 | + | return false | |||
| 266 | + | } | |||
| 267 | + | members += ft.Len() | |||
| 268 | + | if members > 4 { | |||
| 269 | + | return false | |||
| 270 | + | } | |||
| 271 | + | default: | |||
| 257 | return false | 272 | return false | ||
| 258 | } | 273 | } | ||
| 259 | } | 274 | } | ||
| 260 | return true | 275 | return true | ||
| 261 | - | default: | |||
| 262 | - | return false | |||
| 263 | } | 276 | } | ||
| 277 | + | return walk(t) && members > 0 | |||
| 264 | } | 278 | } | ||
| 265 | 279 | ||||
| 266 | // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type | 280 | // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type | ||
A struct_arm64_regression_test.go +37 -0
| @@ -0,0 +1,37 @@ | |||
| 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 | + | } | |
| 31 | + | ||
| 32 | + | func TestIsHFAAcceptsFloatArrayStruct(t *testing.T) { | |
| 33 | + | type arrayed struct{ A [2]float64 } | |
| 34 | + | if !isHFA(reflect.TypeOf(arrayed{})) { | |
| 35 | + | t.Fatal("expected float array struct to be HFA") | |
| 36 | + | } | |
| 37 | + | } | |
| @@ -0,0 +1,37 @@ | |||||
| 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 | + | } | |||
| 31 | + | ||||
| 32 | + | func TestIsHFAAcceptsFloatArrayStruct(t *testing.T) { | |||
| 33 | + | type arrayed struct{ A [2]float64 } | |||
| 34 | + | if !isHFA(reflect.TypeOf(arrayed{})) { | |||
| 35 | + | t.Fatal("expected float array struct to be HFA") | |||
| 36 | + | } | |||
| 37 | + | } | |||
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 | + | } | |||
M struct_loong64.go +6 -11
| @@ -19,7 +19,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||
| 19 | 19 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 20 | 20 | r1 = syscall.f1 | |
| 21 | 21 | if numFields == 2 { | |
| 22 | - | r1 = syscall.f2<<32 | syscall.f1 | |
| 22 | + | r1 = packFloat32Pair(syscall.f1, syscall.f2) | |
| 23 | 23 | } | |
| 24 | 24 | } | |
| 25 | 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 | 28 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 29 | 29 | switch numFields { | |
| 30 | 30 | 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) | |
| 33 | 33 | case 3: | |
| 34 | - | r1 = syscall.f2<<32 | syscall.f1 | |
| 34 | + | r1 = packFloat32Pair(syscall.f1, syscall.f2) | |
| 35 | 35 | r2 = syscall.f3 | |
| 36 | 36 | case 2: | |
| 37 | 37 | r1 = syscall.f1 | |
| @@ -91,13 +91,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr | |||
| 91 | 91 | align := byte(f.Type().Align()*8 - 1) | |
| 92 | 92 | shift = (shift + align) &^ align | |
| 93 | 93 | 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 | |
| 101 | 96 | } | |
| 102 | 97 | switch f.Type().Kind() { | |
| 103 | 98 | 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.f1 | 20 | r1 = syscall.f1 | ||
| 21 | if numFields == 2 { | 21 | if numFields == 2 { | ||
| 22 | - | r1 = syscall.f2<<32 | syscall.f1 | 22 | + | 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.f1 | 31 | + | r1 = packFloat32Pair(syscall.f1, syscall.f2) |
| 32 | - | r2 = syscall.f4<<32 | syscall.f3 | 32 | + | r2 = packFloat32Pair(syscall.f3, syscall.f4) |
| 33 | case 3: | 33 | case 3: | ||
| 34 | - | r1 = syscall.f2<<32 | syscall.f1 | 34 | + | r1 = packFloat32Pair(syscall.f1, syscall.f2) |
| 35 | r2 = syscall.f3 | 35 | r2 = syscall.f3 | ||
| 36 | case 2: | 36 | case 2: | ||
| 37 | r1 = syscall.f1 | 37 | 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) &^ align | 92 | shift = (shift + align) &^ align | ||
| 93 | if shift >= 64 { | 93 | if shift >= 64 { | ||
| 94 | - | shift = 0 | 94 | + | flushPackedWord(&val, &shift, &flushed, class == _FLOAT, addFloat, addInt) |
| 95 | - | flushed = true | 95 | + | 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 { | |||
| 31 | 31 | // float32 values in FP regs | |
| 32 | 32 | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | |
| 33 | 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 | 36 | } else { | |
| 37 | 37 | // float64: whole register value is valid | |
| @@ -31,7 +31,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | |||||
| 31 | // float32 values in FP regs | 31 | // 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 valid | 37 | // 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 | + | } | |||
M syscall.go +6 -2
| @@ -52,13 +52,15 @@ func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uin | |||
| 52 | 52 | } | |
| 53 | 53 | ||
| 54 | 54 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | |
| 55 | - | // There is an internal maximum number of arguments that SyscallN can take. It panics | |
| 56 | - | // when the maximum is exceeded. It returns the result and the libc error code if there is one. | |
| 55 | + | // There is an internal maximum number of arguments that SyscallN can take. | |
| 56 | + | // On 386 and arm, SyscallN supports at most 15 uintptr arguments. | |
| 57 | + | // It panics when the maximum is exceeded. It returns the result and the libc error code if there is one. | |
| 57 | 58 | // | |
| 58 | 59 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | |
| 59 | 60 | // especially point 4. | |
| 60 | 61 | // | |
| 61 | 62 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | |
| 63 | + | // Use RegisterFunc instead when the target function mixes integer and floating-point arguments. | |
| 62 | 64 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | |
| 63 | 65 | // for an explanation of why that is. | |
| 64 | 66 | // | |
| @@ -85,6 +87,8 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||
| 85 | 87 | ||
| 86 | 88 | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | |
| 87 | 89 | // It is designed for method-like calls where the first argument is always a receiver pointer. | |
| 90 | + | // It shares SyscallN's argument-placement limits and mixed integer/float restriction. | |
| 91 | + | // On 386 and arm, the total count including self is limited to 15 uintptr arguments. | |
| 88 | 92 | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | |
| 89 | 93 | // | |
| 90 | 94 | //go:uintptrescapes | |
| @@ -52,13 +52,15 @@ func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uin | |||||
| 52 | } | 52 | } | ||
| 53 | 53 | ||||
| 54 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | 54 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | ||
| 55 | - | // There is an internal maximum number of arguments that SyscallN can take. It panics | 55 | + | // There is an internal maximum number of arguments that SyscallN can take. |
| 56 | - | // when the maximum is exceeded. It returns the result and the libc error code if there is one. | 56 | + | // On 386 and arm, SyscallN supports at most 15 uintptr arguments. |
| 57 | + | // It panics when the maximum is exceeded. It returns the result and the libc error code if there is one. | |||
| 57 | // | 58 | // | ||
| 58 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | 59 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | ||
| 59 | // especially point 4. | 60 | // especially point 4. | ||
| 60 | // | 61 | // | ||
| 61 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | 62 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | ||
| 63 | + | // Use RegisterFunc instead when the target function mixes integer and floating-point arguments. | |||
| 62 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | 64 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | ||
| 63 | // for an explanation of why that is. | 65 | // for an explanation of why that is. | ||
| 64 | // | 66 | // | ||
| @@ -85,6 +87,8 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||||
| 85 | 87 | ||||
| 86 | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | 88 | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | ||
| 87 | // It is designed for method-like calls where the first argument is always a receiver pointer. | 89 | // It is designed for method-like calls where the first argument is always a receiver pointer. | ||
| 90 | + | // It shares SyscallN's argument-placement limits and mixed integer/float restriction. | |||
| 91 | + | // On 386 and arm, the total count including self is limited to 15 uintptr arguments. | |||
| 88 | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | 92 | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | ||
| 89 | // | 93 | // | ||
| 90 | //go:uintptrescapes | 94 | //go:uintptrescapes | ||
M syscall_32bit.go +6 -21
| @@ -13,7 +13,7 @@ package purego | |||
| 13 | 13 | type CDecl struct{} | |
| 14 | 14 | ||
| 15 | 15 | const ( | |
| 16 | - | maxArgs = 32 | |
| 16 | + | maxArgs = 15 | |
| 17 | 17 | ) | |
| 18 | 18 | ||
| 19 | 19 | type syscall15Args struct { | |
| @@ -41,23 +41,6 @@ func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uin | |||
| 41 | 41 | s.a13 = ints[12] | |
| 42 | 42 | s.a14 = ints[13] | |
| 43 | 43 | s.a15 = ints[14] | |
| 44 | - | s.a16 = ints[15] | |
| 45 | - | s.a17 = ints[16] | |
| 46 | - | s.a18 = ints[17] | |
| 47 | - | s.a19 = ints[18] | |
| 48 | - | s.a20 = ints[19] | |
| 49 | - | s.a21 = ints[20] | |
| 50 | - | s.a22 = ints[21] | |
| 51 | - | s.a23 = ints[22] | |
| 52 | - | s.a24 = ints[23] | |
| 53 | - | s.a25 = ints[24] | |
| 54 | - | s.a26 = ints[25] | |
| 55 | - | s.a27 = ints[26] | |
| 56 | - | s.a28 = ints[27] | |
| 57 | - | s.a29 = ints[28] | |
| 58 | - | s.a30 = ints[29] | |
| 59 | - | s.a31 = ints[30] | |
| 60 | - | s.a32 = ints[31] | |
| 61 | 44 | s.f1 = floats[0] | |
| 62 | 45 | s.f2 = floats[1] | |
| 63 | 46 | s.f3 = floats[2] | |
| @@ -73,18 +56,18 @@ func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uin | |||
| 73 | 56 | s.f13 = floats[12] | |
| 74 | 57 | s.f14 = floats[13] | |
| 75 | 58 | s.f15 = floats[14] | |
| 76 | - | s.f16 = floats[15] | |
| 77 | 59 | s.arm64_r8 = r8 | |
| 78 | 60 | } | |
| 79 | 61 | ||
| 80 | 62 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | |
| 81 | - | // There is an internal maximum number of arguments that SyscallN can take. It panics | |
| 82 | - | // when the maximum is exceeded. It returns the result and the libc error code if there is one. | |
| 63 | + | // On 386 and arm, SyscallN supports at most 15 uintptr arguments. | |
| 64 | + | // It panics when the maximum is exceeded. It returns the result and the libc error code if there is one. | |
| 83 | 65 | // | |
| 84 | 66 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | |
| 85 | 67 | // especially point 4. | |
| 86 | 68 | // | |
| 87 | 69 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | |
| 70 | + | // Use RegisterFunc instead when the target function mixes integer and floating-point arguments. | |
| 88 | 71 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | |
| 89 | 72 | // for an explanation of why that is. | |
| 90 | 73 | // | |
| @@ -111,6 +94,8 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||
| 111 | 94 | ||
| 112 | 95 | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | |
| 113 | 96 | // It is designed for method-like calls where the first argument is always a receiver pointer. | |
| 97 | + | // It shares SyscallN's argument-placement limits and mixed integer/float restriction. | |
| 98 | + | // On 386 and arm, the total count including self is limited to 15 uintptr arguments. | |
| 114 | 99 | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | |
| 115 | 100 | // | |
| 116 | 101 | //go:uintptrescapes | |
| @@ -13,7 +13,7 @@ package purego | |||||
| 13 | type CDecl struct{} | 13 | type CDecl struct{} | ||
| 14 | 14 | ||||
| 15 | const ( | 15 | const ( | ||
| 16 | - | maxArgs = 32 | 16 | + | maxArgs = 15 |
| 17 | ) | 17 | ) | ||
| 18 | 18 | ||||
| 19 | type syscall15Args struct { | 19 | type syscall15Args struct { | ||
| @@ -41,23 +41,6 @@ func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uin | |||||
| 41 | s.a13 = ints[12] | 41 | s.a13 = ints[12] | ||
| 42 | s.a14 = ints[13] | 42 | s.a14 = ints[13] | ||
| 43 | s.a15 = ints[14] | 43 | s.a15 = ints[14] | ||
| 44 | - | s.a16 = ints[15] | |||
| 45 | - | s.a17 = ints[16] | |||
| 46 | - | s.a18 = ints[17] | |||
| 47 | - | s.a19 = ints[18] | |||
| 48 | - | s.a20 = ints[19] | |||
| 49 | - | s.a21 = ints[20] | |||
| 50 | - | s.a22 = ints[21] | |||
| 51 | - | s.a23 = ints[22] | |||
| 52 | - | s.a24 = ints[23] | |||
| 53 | - | s.a25 = ints[24] | |||
| 54 | - | s.a26 = ints[25] | |||
| 55 | - | s.a27 = ints[26] | |||
| 56 | - | s.a28 = ints[27] | |||
| 57 | - | s.a29 = ints[28] | |||
| 58 | - | s.a30 = ints[29] | |||
| 59 | - | s.a31 = ints[30] | |||
| 60 | - | s.a32 = ints[31] | |||
| 61 | s.f1 = floats[0] | 44 | s.f1 = floats[0] | ||
| 62 | s.f2 = floats[1] | 45 | s.f2 = floats[1] | ||
| 63 | s.f3 = floats[2] | 46 | s.f3 = floats[2] | ||
| @@ -73,18 +56,18 @@ func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uin | |||||
| 73 | s.f13 = floats[12] | 56 | s.f13 = floats[12] | ||
| 74 | s.f14 = floats[13] | 57 | s.f14 = floats[13] | ||
| 75 | s.f15 = floats[14] | 58 | s.f15 = floats[14] | ||
| 76 | - | s.f16 = floats[15] | |||
| 77 | s.arm64_r8 = r8 | 59 | s.arm64_r8 = r8 | ||
| 78 | } | 60 | } | ||
| 79 | 61 | ||||
| 80 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | 62 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | ||
| 81 | - | // There is an internal maximum number of arguments that SyscallN can take. It panics | 63 | + | // On 386 and arm, SyscallN supports at most 15 uintptr arguments. |
| 82 | - | // when the maximum is exceeded. It returns the result and the libc error code if there is one. | 64 | + | // It panics when the maximum is exceeded. It returns the result and the libc error code if there is one. |
| 83 | // | 65 | // | ||
| 84 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | 66 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | ||
| 85 | // especially point 4. | 67 | // especially point 4. | ||
| 86 | // | 68 | // | ||
| 87 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | 69 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | ||
| 70 | + | // Use RegisterFunc instead when the target function mixes integer and floating-point arguments. | |||
| 88 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | 71 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | ||
| 89 | // for an explanation of why that is. | 72 | // for an explanation of why that is. | ||
| 90 | // | 73 | // | ||
| @@ -111,6 +94,8 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||||
| 111 | 94 | ||||
| 112 | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | 95 | // SyscallSelf is like SyscallN but efficiently prepends self to args without allocation. | ||
| 113 | // It is designed for method-like calls where the first argument is always a receiver pointer. | 96 | // It is designed for method-like calls where the first argument is always a receiver pointer. | ||
| 97 | + | // It shares SyscallN's argument-placement limits and mixed integer/float restriction. | |||
| 98 | + | // On 386 and arm, the total count including self is limited to 15 uintptr arguments. | |||
| 114 | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | 99 | // This avoids the heap allocation that would occur with SyscallN(fn, append([]uintptr{self}, args...)...). | ||
| 115 | // | 100 | // | ||
| 116 | //go:uintptrescapes | 101 | //go:uintptrescapes | ||
A syscall_32bit_internal_test.go +16 -0
| @@ -0,0 +1,16 @@ | |||
| 1 | + | //go:build (386 || arm) && (freebsd || linux || netbsd || windows) | |
| 2 | + | ||
| 3 | + | package purego | |
| 4 | + | ||
| 5 | + | import "testing" | |
| 6 | + | ||
| 7 | + | func TestSyscall15ArgsSetDoesNotPanicWithMaxArgs(t *testing.T) { | |
| 8 | + | defer func() { | |
| 9 | + | if r := recover(); r != nil { | |
| 10 | + | t.Fatalf("Set panicked: %v", r) | |
| 11 | + | } | |
| 12 | + | }() | |
| 13 | + | ||
| 14 | + | var s syscall15Args | |
| 15 | + | s.Set(1, make([]uintptr, maxArgs), make([]uintptr, maxArgs), 8) | |
| 16 | + | } | |
| @@ -0,0 +1,16 @@ | |||||
| 1 | + | //go:build (386 || arm) && (freebsd || linux || netbsd || windows) | |||
| 2 | + | ||||
| 3 | + | package purego | |||
| 4 | + | ||||
| 5 | + | import "testing" | |||
| 6 | + | ||||
| 7 | + | func TestSyscall15ArgsSetDoesNotPanicWithMaxArgs(t *testing.T) { | |||
| 8 | + | defer func() { | |||
| 9 | + | if r := recover(); r != nil { | |||
| 10 | + | t.Fatalf("Set panicked: %v", r) | |||
| 11 | + | } | |||
| 12 | + | }() | |||
| 13 | + | ||||
| 14 | + | var s syscall15Args | |||
| 15 | + | s.Set(1, make([]uintptr, maxArgs), make([]uintptr, maxArgs), 8) | |||
| 16 | + | } | |||
A syscall_32bit_test.go +36 -0
| @@ -0,0 +1,36 @@ | |||
| 1 | + | //go:build (386 || arm) && (freebsd || linux || netbsd || windows) | |
| 2 | + | ||
| 3 | + | package purego_test | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "fmt" | |
| 7 | + | "testing" | |
| 8 | + | ||
| 9 | + | "github.com/ebitengine/purego" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | func TestSyscallN32BitPanicsAt16Args(t *testing.T) { | |
| 13 | + | defer func() { | |
| 14 | + | r := recover() | |
| 15 | + | if r == nil { | |
| 16 | + | t.Fatal("expected panic") | |
| 17 | + | } | |
| 18 | + | if got := fmt.Sprint(r); got != "purego: too many arguments to SyscallN" { | |
| 19 | + | t.Fatalf("panic = %q, want %q", got, "purego: too many arguments to SyscallN") | |
| 20 | + | } | |
| 21 | + | }() | |
| 22 | + | purego.SyscallN(uintptr(1), make([]uintptr, 16)...) | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | func TestSyscallSelf32BitPanicsAt16Args(t *testing.T) { | |
| 26 | + | defer func() { | |
| 27 | + | r := recover() | |
| 28 | + | if r == nil { | |
| 29 | + | t.Fatal("expected panic") | |
| 30 | + | } | |
| 31 | + | if got := fmt.Sprint(r); got != "purego: too many arguments to SyscallSelf" { | |
| 32 | + | t.Fatalf("panic = %q, want %q", got, "purego: too many arguments to SyscallSelf") | |
| 33 | + | } | |
| 34 | + | }() | |
| 35 | + | purego.SyscallSelf(uintptr(1), 1, make([]uintptr, 15)...) | |
| 36 | + | } | |
| @@ -0,0 +1,36 @@ | |||||
| 1 | + | //go:build (386 || arm) && (freebsd || linux || netbsd || windows) | |||
| 2 | + | ||||
| 3 | + | package purego_test | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "fmt" | |||
| 7 | + | "testing" | |||
| 8 | + | ||||
| 9 | + | "github.com/ebitengine/purego" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | func TestSyscallN32BitPanicsAt16Args(t *testing.T) { | |||
| 13 | + | defer func() { | |||
| 14 | + | r := recover() | |||
| 15 | + | if r == nil { | |||
| 16 | + | t.Fatal("expected panic") | |||
| 17 | + | } | |||
| 18 | + | if got := fmt.Sprint(r); got != "purego: too many arguments to SyscallN" { | |||
| 19 | + | t.Fatalf("panic = %q, want %q", got, "purego: too many arguments to SyscallN") | |||
| 20 | + | } | |||
| 21 | + | }() | |||
| 22 | + | purego.SyscallN(uintptr(1), make([]uintptr, 16)...) | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | func TestSyscallSelf32BitPanicsAt16Args(t *testing.T) { | |||
| 26 | + | defer func() { | |||
| 27 | + | r := recover() | |||
| 28 | + | if r == nil { | |||
| 29 | + | t.Fatal("expected panic") | |||
| 30 | + | } | |||
| 31 | + | if got := fmt.Sprint(r); got != "purego: too many arguments to SyscallSelf" { | |||
| 32 | + | t.Fatalf("panic = %q, want %q", got, "purego: too many arguments to SyscallSelf") | |||
| 33 | + | } | |||
| 34 | + | }() | |||
| 35 | + | purego.SyscallSelf(uintptr(1), 1, make([]uintptr, 15)...) | |||
| 36 | + | } | |||