detached d0aedd0

all: struct return fixes (#221)

On amd64, there was a crash when calling objc.Send with a struct return type. This was due to calling the wrong objc message send function.

On arm64, if a struct return contained structs that only contained floats it would expect it to be in R8 instead of the expected float registers

Closes #223
commit
d0aedd0f43939888f2fd38fcf8d5c73e0e26a4ff
author
TotallyGamerJet · · 2024-04-04 02:43 UTC
committer
GitHub · · 2024-04-04 02:43 UTC
parent
05c9a4c
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
6 files · +145 -38
M func.go +23 -8
@@ -250,7 +250,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
250250 keepAlive = append(keepAlive, val)
251251 addInt(val.Pointer())
252252 } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
253- if !isAllSameFloat(outType) || outType.NumField() > 4 {
253+ isAllFloats, numFields := isAllSameFloat(outType)
254+ if !isAllFloats || numFields > 4 {
254255 val := reflect.New(outType)
255256 keepAlive = append(keepAlive, val)
256257 syscall.arm64_r8 = val.Pointer()
@@ -351,20 +352,34 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
351352 // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
352353 // if it is bigger than this than enough space must be allocated on the heap and then passed into
353354 // the function as the first parameter on amd64 or in R8 on arm64.
355+//
356+// If you change this make sure to update it in objc_runtime_darwin.go
354357 const maxRegAllocStructSize = 16
355358
356-func isAllSameFloat(ty reflect.Type) bool {
357- first := ty.Field(0).Type.Kind()
359+func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
360+ allFloats = true
361+ root := ty.Field(0).Type
362+ for root.Kind() == reflect.Struct {
363+ root = root.Field(0).Type
364+ }
365+ first := root.Kind()
358366 if first != reflect.Float32 && first != reflect.Float64 {
359- return false
367+ allFloats = false
360368 }
361369 for i := 0; i < ty.NumField(); i++ {
362- f := ty.Field(i)
363- if f.Type.Kind() != first {
364- return false
370+ f := ty.Field(i).Type
371+ if f.Kind() == reflect.Struct {
372+ var structNumFields int
373+ allFloats, structNumFields = isAllSameFloat(f)
374+ numFields += structNumFields
375+ continue
376+ }
377+ numFields++
378+ if f.Kind() != first {
379+ allFloats = false
365380 }
366381 }
367- return true
382+ return allFloats, numFields
368383 }
369384
370385 func checkStructFieldsSupported(ty reflect.Type) {
@@ -250,7 +250,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
250 keepAlive = append(keepAlive, val)250 keepAlive = append(keepAlive, val)
251 addInt(val.Pointer())251 addInt(val.Pointer())
252 } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {252 } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
253- if !isAllSameFloat(outType) || outType.NumField() > 4 {253+ isAllFloats, numFields := isAllSameFloat(outType)
254+ if !isAllFloats || numFields > 4 {
254 val := reflect.New(outType)255 val := reflect.New(outType)
255 keepAlive = append(keepAlive, val)256 keepAlive = append(keepAlive, val)
256 syscall.arm64_r8 = val.Pointer()257 syscall.arm64_r8 = val.Pointer()
@@ -351,20 +352,34 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
351 // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.352 // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
352 // if it is bigger than this than enough space must be allocated on the heap and then passed into353 // if it is bigger than this than enough space must be allocated on the heap and then passed into
353 // the function as the first parameter on amd64 or in R8 on arm64.354 // the function as the first parameter on amd64 or in R8 on arm64.
355+//
356+// If you change this make sure to update it in objc_runtime_darwin.go
354 const maxRegAllocStructSize = 16357 const maxRegAllocStructSize = 16
355 358
356-func isAllSameFloat(ty reflect.Type) bool {359+func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
357- first := ty.Field(0).Type.Kind()360+ allFloats = true
361+ root := ty.Field(0).Type
362+ for root.Kind() == reflect.Struct {
363+ root = root.Field(0).Type
364+ }
365+ first := root.Kind()
358 if first != reflect.Float32 && first != reflect.Float64 {366 if first != reflect.Float32 && first != reflect.Float64 {
359- return false367+ allFloats = false
360 }368 }
361 for i := 0; i < ty.NumField(); i++ {369 for i := 0; i < ty.NumField(); i++ {
362- f := ty.Field(i)370+ f := ty.Field(i).Type
363- if f.Type.Kind() != first {371+ if f.Kind() == reflect.Struct {
364- return false372+ var structNumFields int
373+ allFloats, structNumFields = isAllSameFloat(f)
374+ numFields += structNumFields
375+ continue
376+ }
377+ numFields++
378+ if f.Kind() != first {
379+ allFloats = false
365 }380 }
366 }381 }
367- return true382+ return allFloats, numFields
368 }383 }
369 384
370 func checkStructFieldsSupported(ty reflect.Type) {385 func checkStructFieldsSupported(ty reflect.Type) {
M objc/objc_runtime_darwin.go +54 -24
@@ -11,6 +11,7 @@ import (
1111 "math"
1212 "reflect"
1313 "regexp"
14+ "runtime"
1415 "unicode"
1516 "unsafe"
1617
@@ -20,28 +21,30 @@ import (
2021 // TODO: support try/catch?
2122 // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
2223 var (
23- objc_msgSend_fn uintptr
24- objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID
25- objc_msgSendSuper2_fn uintptr
26- objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID
27- objc_getClass func(name string) Class
28- objc_getProtocol func(name string) *Protocol
29- objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class
30- objc_registerClassPair func(class Class)
31- sel_registerName func(name string) SEL
32- class_getSuperclass func(class Class) Class
33- class_getInstanceVariable func(class Class, name string) Ivar
34- class_getInstanceSize func(class Class) uintptr
35- class_addMethod func(class Class, name SEL, imp IMP, types string) bool
36- class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool
37- class_addProtocol func(class Class, protocol *Protocol) bool
38- ivar_getOffset func(ivar Ivar) uintptr
39- ivar_getName func(ivar Ivar) string
40- object_getClass func(obj ID) Class
41- object_getIvar func(obj ID, ivar Ivar) ID
42- object_setIvar func(obj ID, ivar Ivar, value ID)
43- protocol_getName func(protocol *Protocol) string
44- protocol_isEqual func(p *Protocol, p2 *Protocol) bool
24+ objc_msgSend_fn uintptr
25+ objc_msgSend_stret_fn uintptr
26+ objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID
27+ objc_msgSendSuper2_fn uintptr
28+ objc_msgSendSuper2_stret_fn uintptr
29+ objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID
30+ objc_getClass func(name string) Class
31+ objc_getProtocol func(name string) *Protocol
32+ objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class
33+ objc_registerClassPair func(class Class)
34+ sel_registerName func(name string) SEL
35+ class_getSuperclass func(class Class) Class
36+ class_getInstanceVariable func(class Class, name string) Ivar
37+ class_getInstanceSize func(class Class) uintptr
38+ class_addMethod func(class Class, name SEL, imp IMP, types string) bool
39+ class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool
40+ class_addProtocol func(class Class, protocol *Protocol) bool
41+ ivar_getOffset func(ivar Ivar) uintptr
42+ ivar_getName func(ivar Ivar) string
43+ object_getClass func(obj ID) Class
44+ object_getIvar func(obj ID, ivar Ivar) ID
45+ object_setIvar func(obj ID, ivar Ivar, value ID)
46+ protocol_getName func(protocol *Protocol) string
47+ protocol_isEqual func(p *Protocol, p2 *Protocol) bool
4548 )
4649
4750 func init() {
@@ -53,6 +56,16 @@ func init() {
5356 if err != nil {
5457 panic(fmt.Errorf("objc: %w", err))
5558 }
59+ if runtime.GOARCH == "amd64" {
60+ objc_msgSend_stret_fn, err = purego.Dlsym(objc, "objc_msgSend_stret")
61+ if err != nil {
62+ panic(fmt.Errorf("objc: %w", err))
63+ }
64+ objc_msgSendSuper2_stret_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2_stret")
65+ if err != nil {
66+ panic(fmt.Errorf("objc: %w", err))
67+ }
68+ }
5669 purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn)
5770 objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2")
5871 if err != nil {
@@ -104,12 +117,22 @@ func (id ID) SetIvar(ivar Ivar, value ID) {
104117 object_setIvar(id, ivar, value)
105118 }
106119
120+// keep in sync with func.go
121+const maxRegAllocStructSize = 16
122+
107123 // Send is a convenience method for sending messages to objects that can return any type.
108124 // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
109125 // It is best to cache the result of RegisterName.
110126 func Send[T any](id ID, sel SEL, args ...any) T {
111127 var fn func(id ID, sel SEL, args ...any) T
112- purego.RegisterFunc(&fn, objc_msgSend_fn)
128+ var zero T
129+ if runtime.GOARCH == "amd64" &&
130+ reflect.ValueOf(zero).Kind() == reflect.Struct &&
131+ reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize {
132+ purego.RegisterFunc(&fn, objc_msgSend_stret_fn)
133+ } else {
134+ purego.RegisterFunc(&fn, objc_msgSend_fn)
135+ }
113136 return fn(id, sel, args...)
114137 }
115138
@@ -141,7 +164,14 @@ func SendSuper[T any](id ID, sel SEL, args ...any) T {
141164 superClass: id.Class(),
142165 }
143166 var fn func(objcSuper *objc_super, sel SEL, args ...any) T
144- purego.RegisterFunc(&fn, objc_msgSendSuper2_fn)
167+ var zero T
168+ if runtime.GOARCH == "amd64" &&
169+ reflect.ValueOf(zero).Kind() == reflect.Struct &&
170+ reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize {
171+ purego.RegisterFunc(&fn, objc_msgSendSuper2_stret_fn)
172+ } else {
173+ purego.RegisterFunc(&fn, objc_msgSendSuper2_fn)
174+ }
145175 return fn(super, sel, args...)
146176 }
147177
@@ -11,6 +11,7 @@ import (
11 "math"11 "math"
12 "reflect"12 "reflect"
13 "regexp"13 "regexp"
14+ "runtime"
14 "unicode"15 "unicode"
15 "unsafe"16 "unsafe"
16 17
@@ -20,28 +21,30 @@ import (
20 // TODO: support try/catch?21 // TODO: support try/catch?
21 // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime22 // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
22 var (23 var (
23- objc_msgSend_fn uintptr24+ objc_msgSend_fn uintptr
24- objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID25+ objc_msgSend_stret_fn uintptr
25- objc_msgSendSuper2_fn uintptr26+ objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID
26- objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID27+ objc_msgSendSuper2_fn uintptr
27- objc_getClass func(name string) Class28+ objc_msgSendSuper2_stret_fn uintptr
28- objc_getProtocol func(name string) *Protocol29+ objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID
29- objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class30+ objc_getClass func(name string) Class
30- objc_registerClassPair func(class Class)31+ objc_getProtocol func(name string) *Protocol
31- sel_registerName func(name string) SEL32+ objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class
32- class_getSuperclass func(class Class) Class33+ objc_registerClassPair func(class Class)
33- class_getInstanceVariable func(class Class, name string) Ivar34+ sel_registerName func(name string) SEL
34- class_getInstanceSize func(class Class) uintptr35+ class_getSuperclass func(class Class) Class
35- class_addMethod func(class Class, name SEL, imp IMP, types string) bool36+ class_getInstanceVariable func(class Class, name string) Ivar
36- class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool37+ class_getInstanceSize func(class Class) uintptr
37- class_addProtocol func(class Class, protocol *Protocol) bool38+ class_addMethod func(class Class, name SEL, imp IMP, types string) bool
38- ivar_getOffset func(ivar Ivar) uintptr39+ class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool
39- ivar_getName func(ivar Ivar) string40+ class_addProtocol func(class Class, protocol *Protocol) bool
40- object_getClass func(obj ID) Class41+ ivar_getOffset func(ivar Ivar) uintptr
41- object_getIvar func(obj ID, ivar Ivar) ID42+ ivar_getName func(ivar Ivar) string
42- object_setIvar func(obj ID, ivar Ivar, value ID)43+ object_getClass func(obj ID) Class
43- protocol_getName func(protocol *Protocol) string44+ object_getIvar func(obj ID, ivar Ivar) ID
44- protocol_isEqual func(p *Protocol, p2 *Protocol) bool45+ object_setIvar func(obj ID, ivar Ivar, value ID)
46+ protocol_getName func(protocol *Protocol) string
47+ protocol_isEqual func(p *Protocol, p2 *Protocol) bool
45 )48 )
46 49
47 func init() {50 func init() {
@@ -53,6 +56,16 @@ func init() {
53 if err != nil {56 if err != nil {
54 panic(fmt.Errorf("objc: %w", err))57 panic(fmt.Errorf("objc: %w", err))
55 }58 }
59+ if runtime.GOARCH == "amd64" {
60+ objc_msgSend_stret_fn, err = purego.Dlsym(objc, "objc_msgSend_stret")
61+ if err != nil {
62+ panic(fmt.Errorf("objc: %w", err))
63+ }
64+ objc_msgSendSuper2_stret_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2_stret")
65+ if err != nil {
66+ panic(fmt.Errorf("objc: %w", err))
67+ }
68+ }
56 purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn)69 purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn)
57 objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2")70 objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2")
58 if err != nil {71 if err != nil {
@@ -104,12 +117,22 @@ func (id ID) SetIvar(ivar Ivar, value ID) {
104 object_setIvar(id, ivar, value)117 object_setIvar(id, ivar, value)
105 }118 }
106 119
120+// keep in sync with func.go
121+const maxRegAllocStructSize = 16
122+
107 // Send is a convenience method for sending messages to objects that can return any type.123 // Send is a convenience method for sending messages to objects that can return any type.
108 // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.124 // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
109 // It is best to cache the result of RegisterName.125 // It is best to cache the result of RegisterName.
110 func Send[T any](id ID, sel SEL, args ...any) T {126 func Send[T any](id ID, sel SEL, args ...any) T {
111 var fn func(id ID, sel SEL, args ...any) T127 var fn func(id ID, sel SEL, args ...any) T
112- purego.RegisterFunc(&fn, objc_msgSend_fn)128+ var zero T
129+ if runtime.GOARCH == "amd64" &&
130+ reflect.ValueOf(zero).Kind() == reflect.Struct &&
131+ reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize {
132+ purego.RegisterFunc(&fn, objc_msgSend_stret_fn)
133+ } else {
134+ purego.RegisterFunc(&fn, objc_msgSend_fn)
135+ }
113 return fn(id, sel, args...)136 return fn(id, sel, args...)
114 }137 }
115 138
@@ -141,7 +164,14 @@ func SendSuper[T any](id ID, sel SEL, args ...any) T {
141 superClass: id.Class(),164 superClass: id.Class(),
142 }165 }
143 var fn func(objcSuper *objc_super, sel SEL, args ...any) T166 var fn func(objcSuper *objc_super, sel SEL, args ...any) T
144- purego.RegisterFunc(&fn, objc_msgSendSuper2_fn)167+ var zero T
168+ if runtime.GOARCH == "amd64" &&
169+ reflect.ValueOf(zero).Kind() == reflect.Struct &&
170+ reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize {
171+ purego.RegisterFunc(&fn, objc_msgSendSuper2_stret_fn)
172+ } else {
173+ purego.RegisterFunc(&fn, objc_msgSendSuper2_fn)
174+ }
145 return fn(super, sel, args...)175 return fn(super, sel, args...)
146 }176 }
147 177
M objc/objc_runtime_darwin_test.go +36 -0
@@ -117,3 +117,39 @@ func TestSend(t *testing.T) {
117117 t.Failed()
118118 }
119119 }
120+
121+func ExampleSend() {
122+ type NSRange struct {
123+ Location, Range uint
124+ }
125+ class_NSString := objc.GetClass("NSString")
126+ sel_stringWithUTF8String := objc.RegisterName("stringWithUTF8String:")
127+
128+ fullString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "Hello, World!\x00")
129+ subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00")
130+
131+ r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString)
132+
133+ fmt.Println(r)
134+
135+ // Output: {3 7}
136+}
137+
138+func ExampleSendSuper() {
139+ super := objc.AllocateClassPair(objc.GetClass("NSObject"), "SuperObject2", 0)
140+ super.AddMethod(objc.RegisterName("doSomething"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) int {
141+ return 16
142+ }), "i@:")
143+ super.Register()
144+
145+ child := objc.AllocateClassPair(super, "ChildObject2", 0)
146+ child.AddMethod(objc.RegisterName("doSomething"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) int {
147+ return 24
148+ }), "i@:")
149+ child.Register()
150+
151+ res := objc.SendSuper[int](objc.ID(child).Send(objc.RegisterName("new")), objc.RegisterName("doSomething"))
152+
153+ fmt.Println(res)
154+ // Output: 16
155+}
@@ -117,3 +117,39 @@ func TestSend(t *testing.T) {
117 t.Failed()117 t.Failed()
118 }118 }
119 }119 }
120+
121+func ExampleSend() {
122+ type NSRange struct {
123+ Location, Range uint
124+ }
125+ class_NSString := objc.GetClass("NSString")
126+ sel_stringWithUTF8String := objc.RegisterName("stringWithUTF8String:")
127+
128+ fullString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "Hello, World!\x00")
129+ subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00")
130+
131+ r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString)
132+
133+ fmt.Println(r)
134+
135+ // Output: {3 7}
136+}
137+
138+func ExampleSendSuper() {
139+ super := objc.AllocateClassPair(objc.GetClass("NSObject"), "SuperObject2", 0)
140+ super.AddMethod(objc.RegisterName("doSomething"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) int {
141+ return 16
142+ }), "i@:")
143+ super.Register()
144+
145+ child := objc.AllocateClassPair(super, "ChildObject2", 0)
146+ child.AddMethod(objc.RegisterName("doSomething"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) int {
147+ return 24
148+ }), "i@:")
149+ child.Register()
150+
151+ res := objc.SendSuper[int](objc.ID(child).Send(objc.RegisterName("new")), objc.RegisterName("doSomething"))
152+
153+ fmt.Println(res)
154+ // Output: 16
155+}
M struct_arm64.go +6 -6
@@ -16,17 +16,17 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
1616 return reflect.New(outType).Elem()
1717 case outSize <= 8:
1818 r1 := syscall.a1
19- if isAllSameFloat(outType) {
19+ if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
2020 r1 = syscall.f1
21- if outType.NumField() == 2 {
21+ if numFields == 2 {
2222 r1 = syscall.f2<<32 | syscall.f1
2323 }
2424 }
2525 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
2626 case outSize <= 16:
2727 r1, r2 := syscall.a1, syscall.a2
28- if isAllSameFloat(outType) {
29- switch outType.NumField() {
28+ if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
29+ switch numFields {
3030 case 4:
3131 r1 = syscall.f2<<32 | syscall.f1
3232 r2 = syscall.f4<<32 | syscall.f3
@@ -42,8 +42,8 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
4242 }
4343 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
4444 default:
45- if isAllSameFloat(outType) && outType.NumField() <= 4 {
46- switch outType.NumField() {
45+ if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 {
46+ switch numFields {
4747 case 4:
4848 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
4949 case 3:
@@ -16,17 +16,17 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
16 return reflect.New(outType).Elem()16 return reflect.New(outType).Elem()
17 case outSize <= 8:17 case outSize <= 8:
18 r1 := syscall.a118 r1 := syscall.a1
19- if isAllSameFloat(outType) {19+ if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
20 r1 = syscall.f120 r1 = syscall.f1
21- if outType.NumField() == 2 {21+ if numFields == 2 {
22 r1 = syscall.f2<<32 | syscall.f122 r1 = syscall.f2<<32 | syscall.f1
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()
26 case outSize <= 16:26 case outSize <= 16:
27 r1, r2 := syscall.a1, syscall.a227 r1, r2 := syscall.a1, syscall.a2
28- if isAllSameFloat(outType) {28+ if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
29- switch outType.NumField() {29+ switch numFields {
30 case 4:30 case 4:
31 r1 = syscall.f2<<32 | syscall.f131 r1 = syscall.f2<<32 | syscall.f1
32 r2 = syscall.f4<<32 | syscall.f332 r2 = syscall.f4<<32 | syscall.f3
@@ -42,8 +42,8 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
42 }42 }
43 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()43 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
44 default:44 default:
45- if isAllSameFloat(outType) && outType.NumField() <= 4 {45+ if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 {
46- switch outType.NumField() {46+ switch numFields {
47 case 4:47 case 4:
48 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()48 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
49 case 3:49 case 3:
M struct_test.go +12 -0
@@ -585,6 +585,18 @@ func TestRegisterFunc_structReturns(t *testing.T) {
585585 t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected)
586586 }
587587 }
588+ {
589+ type FourDoublesInternal struct {
590+ f struct{ a, b float64 }
591+ g struct{ c, d float64 }
592+ }
593+ var ReturnFourDoublesInternal func(a, b, c, d float64) FourDoublesInternal
594+ purego.RegisterLibFunc(&ReturnFourDoublesInternal, lib, "ReturnFourDoublesInternal")
595+ expected := FourDoublesInternal{f: struct{ a, b float64 }{a: 1, b: 2}, g: struct{ c, d float64 }{c: 3, d: 4}}
596+ if ret := ReturnFourDoublesInternal(1, 2, 3, 4); ret != expected {
597+ t.Fatalf("ReturnFourDoublesInternal returned %+v wanted %+v", ret, expected)
598+ }
599+ }
588600 {
589601 type FiveDoubles struct{ a, b, c, d, e float64 }
590602 var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles
@@ -585,6 +585,18 @@ func TestRegisterFunc_structReturns(t *testing.T) {
585 t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected)585 t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected)
586 }586 }
587 }587 }
588+ {
589+ type FourDoublesInternal struct {
590+ f struct{ a, b float64 }
591+ g struct{ c, d float64 }
592+ }
593+ var ReturnFourDoublesInternal func(a, b, c, d float64) FourDoublesInternal
594+ purego.RegisterLibFunc(&ReturnFourDoublesInternal, lib, "ReturnFourDoublesInternal")
595+ expected := FourDoublesInternal{f: struct{ a, b float64 }{a: 1, b: 2}, g: struct{ c, d float64 }{c: 3, d: 4}}
596+ if ret := ReturnFourDoublesInternal(1, 2, 3, 4); ret != expected {
597+ t.Fatalf("ReturnFourDoublesInternal returned %+v wanted %+v", ret, expected)
598+ }
599+ }
588 {600 {
589 type FiveDoubles struct{ a, b, c, d, e float64 }601 type FiveDoubles struct{ a, b, c, d, e float64 }
590 var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles602 var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles
M testdata/structtest/structreturn_test.c +14 -0
@@ -129,6 +129,20 @@ struct FourDoubles ReturnFourDoubles(double a, double b, double c, double d) {
129129 return e;
130130 }
131131
132+struct FourDoublesInternal{
133+ struct {
134+ double a, b;
135+ } f;
136+ struct {
137+ double c, d;
138+ } g;
139+};
140+
141+struct FourDoublesInternal ReturnFourDoublesInternal(double a, double b, double c, double d) {
142+ struct FourDoublesInternal e = { {a, b}, {c, d} };
143+ return e;
144+}
145+
132146 struct FiveDoubles{
133147 double a, b, c, d, e;
134148 };
@@ -129,6 +129,20 @@ struct FourDoubles ReturnFourDoubles(double a, double b, double c, double d) {
129 return e;129 return e;
130 }130 }
131 131
132+struct FourDoublesInternal{
133+ struct {
134+ double a, b;
135+ } f;
136+ struct {
137+ double c, d;
138+ } g;
139+};
140+
141+struct FourDoublesInternal ReturnFourDoublesInternal(double a, double b, double c, double d) {
142+ struct FourDoublesInternal e = { {a, b}, {c, d} };
143+ return e;
144+}
145+
132 struct FiveDoubles{146 struct FiveDoubles{
133 double a, b, c, d, e;147 double a, b, c, d, e;
134 };148 };

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