detached 1a5155f

purego: extend struct argument support to Linux amd64/arm64 (#361)

This change removes the Darwin-only restriction on struct arguments and enables
struct-by-value support on Linux for both amd64 and arm64 architectures.

Closes #236
Closes #412
commit
1a5155f4f6ffe0cd9807167734bc7d1684de32d2
author
Travis Cline · · 2026-02-12 04:10 UTC
committer
GitHub · · 2026-02-12 04:10 UTC
parent
071d22a
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
7 files · +186 -35
M func.go +14 -8
@@ -64,7 +64,7 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) {
6464 // int64 <=> int64_t
6565 // float32 <=> float
6666 // float64 <=> double
67-// struct <=> struct (WIP - darwin only)
67+// struct <=> struct (darwin amd64/arm64, linux amd64/arm64)
6868 // func <=> C function
6969 // unsafe.Pointer, *T <=> void*
7070 // []T => void*
@@ -173,9 +173,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
173173 stack++
174174 }
175175 case reflect.Struct:
176- if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
177- panic("purego: struct arguments are only supported on darwin amd64 & arm64")
178- }
176+ ensureStructSupportedForRegisterFunc()
179177 if arg.Size() == 0 {
180178 continue
181179 }
@@ -194,9 +192,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
194192 }
195193 }
196194 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
197- if runtime.GOOS != "darwin" {
198- panic("purego: struct return values only supported on darwin arm64 & amd64")
199- }
195+ ensureStructSupportedForRegisterFunc()
200196 outType := ty.Out(0)
201197 checkStructFieldsSupported(outType)
202198 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
@@ -477,13 +473,23 @@ func checkStructFieldsSupported(ty reflect.Type) {
477473 switch f.Kind() {
478474 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
479475 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
480- reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32:
476+ reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32,
477+ reflect.Bool:
481478 default:
482479 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
483480 }
484481 }
485482 }
486483
484+func ensureStructSupportedForRegisterFunc() {
485+ if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
486+ panic("purego: struct arguments are only supported on amd64 and arm64")
487+ }
488+ if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
489+ panic("purego: struct arguments are only supported on darwin and linux")
490+ }
491+}
492+
487493 func roundUpTo8(val uintptr) uintptr {
488494 return (val + align8ByteMask) &^ align8ByteMask
489495 }
@@ -64,7 +64,7 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) {
64 // int64 <=> int64_t64 // int64 <=> int64_t
65 // float32 <=> float65 // float32 <=> float
66 // float64 <=> double66 // float64 <=> double
67-// struct <=> struct (WIP - darwin only)67+// struct <=> struct (darwin amd64/arm64, linux amd64/arm64)
68 // func <=> C function68 // func <=> C function
69 // unsafe.Pointer, *T <=> void*69 // unsafe.Pointer, *T <=> void*
70 // []T => void*70 // []T => void*
@@ -173,9 +173,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
173 stack++173 stack++
174 }174 }
175 case reflect.Struct:175 case reflect.Struct:
176- if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {176+ ensureStructSupportedForRegisterFunc()
177- panic("purego: struct arguments are only supported on darwin amd64 & arm64")
178- }
179 if arg.Size() == 0 {177 if arg.Size() == 0 {
180 continue178 continue
181 }179 }
@@ -194,9 +192,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
194 }192 }
195 }193 }
196 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {194 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
197- if runtime.GOOS != "darwin" {195+ ensureStructSupportedForRegisterFunc()
198- panic("purego: struct return values only supported on darwin arm64 & amd64")
199- }
200 outType := ty.Out(0)196 outType := ty.Out(0)
201 checkStructFieldsSupported(outType)197 checkStructFieldsSupported(outType)
202 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {198 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
@@ -477,13 +473,23 @@ func checkStructFieldsSupported(ty reflect.Type) {
477 switch f.Kind() {473 switch f.Kind() {
478 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,474 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
479 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,475 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
480- reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32:476+ reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32,
477+ reflect.Bool:
481 default:478 default:
482 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))479 panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
483 }480 }
484 }481 }
485 }482 }
486 483
484+func ensureStructSupportedForRegisterFunc() {
485+ if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
486+ panic("purego: struct arguments are only supported on amd64 and arm64")
487+ }
488+ if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
489+ panic("purego: struct arguments are only supported on darwin and linux")
490+ }
491+}
492+
487 func roundUpTo8(val uintptr) uintptr {493 func roundUpTo8(val uintptr) uintptr {
488 return (val + align8ByteMask) &^ align8ByteMask494 return (val + align8ByteMask) &^ align8ByteMask
489 }495 }
M struct_amd64.go +20 -21
@@ -169,9 +169,10 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
169169 }
170170 shift += 8
171171 class |= _INTEGER
172- case reflect.Pointer:
173- ok = false
174- return
172+ case reflect.Pointer, reflect.UnsafePointer:
173+ val = uint64(f.Pointer())
174+ shift = 64
175+ class = _INTEGER
175176 case reflect.Int8:
176177 val |= uint64(f.Int()&0xFF) << shift
177178 shift += 8
@@ -238,24 +239,22 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
238239 }
239240
240241 func placeStack(v reflect.Value, addStack func(uintptr)) {
241- for i := 0; i < v.Type().NumField(); i++ {
242- f := v.Field(i)
243- switch f.Kind() {
244- case reflect.Pointer:
245- addStack(f.Pointer())
246- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
247- addStack(uintptr(f.Int()))
248- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
249- addStack(uintptr(f.Uint()))
250- case reflect.Float32:
251- addStack(uintptr(math.Float32bits(float32(f.Float()))))
252- case reflect.Float64:
253- addStack(uintptr(math.Float64bits(f.Float())))
254- case reflect.Struct:
255- placeStack(f, addStack)
256- default:
257- panic("purego: unsupported kind " + f.Kind().String())
258- }
242+ // Copy the struct as a contiguous block of memory in eightbyte (8-byte)
243+ // chunks. The x86-64 ABI requires structs passed on the stack to be
244+ // laid out exactly as in memory, including padding and field packing
245+ // within eightbytes. Decomposing field-by-field would place each field
246+ // as a separate stack slot, breaking structs with mixed-type fields
247+ // that share an eightbyte (e.g. int32 + float32).
248+ if !v.CanAddr() {
249+ tmp := reflect.New(v.Type()).Elem()
250+ tmp.Set(v)
251+ v = tmp
252+ }
253+ ptr := v.Addr().UnsafePointer()
254+ size := v.Type().Size()
255+ for off := uintptr(0); off < size; off += 8 {
256+ chunk := *(*uintptr)(unsafe.Add(ptr, off))
257+ addStack(chunk)
259258 }
260259 }
261260
@@ -169,9 +169,10 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
169 }169 }
170 shift += 8170 shift += 8
171 class |= _INTEGER171 class |= _INTEGER
172- case reflect.Pointer:172+ case reflect.Pointer, reflect.UnsafePointer:
173- ok = false173+ val = uint64(f.Pointer())
174- return174+ shift = 64
175+ class = _INTEGER
175 case reflect.Int8:176 case reflect.Int8:
176 val |= uint64(f.Int()&0xFF) << shift177 val |= uint64(f.Int()&0xFF) << shift
177 shift += 8178 shift += 8
@@ -238,24 +239,22 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
238 }239 }
239 240
240 func placeStack(v reflect.Value, addStack func(uintptr)) {241 func placeStack(v reflect.Value, addStack func(uintptr)) {
241- for i := 0; i < v.Type().NumField(); i++ {242+ // Copy the struct as a contiguous block of memory in eightbyte (8-byte)
242- f := v.Field(i)243+ // chunks. The x86-64 ABI requires structs passed on the stack to be
243- switch f.Kind() {244+ // laid out exactly as in memory, including padding and field packing
244- case reflect.Pointer:245+ // within eightbytes. Decomposing field-by-field would place each field
245- addStack(f.Pointer())246+ // as a separate stack slot, breaking structs with mixed-type fields
246- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:247+ // that share an eightbyte (e.g. int32 + float32).
247- addStack(uintptr(f.Int()))248+ if !v.CanAddr() {
248- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:249+ tmp := reflect.New(v.Type()).Elem()
249- addStack(uintptr(f.Uint()))250+ tmp.Set(v)
250- case reflect.Float32:251+ v = tmp
251- addStack(uintptr(math.Float32bits(float32(f.Float()))))252+ }
252- case reflect.Float64:253+ ptr := v.Addr().UnsafePointer()
253- addStack(uintptr(math.Float64bits(f.Float())))254+ size := v.Type().Size()
254- case reflect.Struct:255+ for off := uintptr(0); off < size; off += 8 {
255- placeStack(f, addStack)256+ chunk := *(*uintptr)(unsafe.Add(ptr, off))
256- default:257+ addStack(chunk)
257- panic("purego: unsupported kind " + f.Kind().String())
258- }
259 }258 }
260 }259 }
261 260
M struct_arm64.go +1 -1
@@ -190,7 +190,7 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui
190190 shift = 0
191191 flushed = true
192192 class = _NO_CLASS
193- case reflect.Ptr:
193+ case reflect.Ptr, reflect.UnsafePointer:
194194 addInt(f.Pointer())
195195 shift = 0
196196 flushed = true
@@ -190,7 +190,7 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui
190 shift = 0190 shift = 0
191 flushed = true191 flushed = true
192 class = _NO_CLASS192 class = _NO_CLASS
193- case reflect.Ptr:193+ case reflect.Ptr, reflect.UnsafePointer:
194 addInt(f.Pointer())194 addInt(f.Pointer())
195 shift = 0195 shift = 0
196 flushed = true196 flushed = true
M struct_loong64.go +1 -1
@@ -156,7 +156,7 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
156156 shift = 0
157157 flushed = true
158158 class = _NO_CLASS
159- case reflect.Ptr:
159+ case reflect.Ptr, reflect.UnsafePointer:
160160 addInt(f.Pointer())
161161 shift = 0
162162 flushed = true
@@ -156,7 +156,7 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
156 shift = 0156 shift = 0
157 flushed = true157 flushed = true
158 class = _NO_CLASS158 class = _NO_CLASS
159- case reflect.Ptr:159+ case reflect.Ptr, reflect.UnsafePointer:
160 addInt(f.Pointer())160 addInt(f.Pointer())
161 shift = 0161 shift = 0
162 flushed = true162 flushed = true
M struct_test.go +93 -3
@@ -1,7 +1,7 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
33
4-//go:build darwin && (arm64 || amd64)
4+//go:build (darwin || linux) && (amd64 || arm64)
55
66 package purego_test
77
@@ -373,8 +373,8 @@ func TestRegisterFunc_structArgs(t *testing.T) {
373373 }
374374 var Array4CharsFn func(chars Array4Chars) int32
375375 purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars")
376- const expectedSum = 1 + 2 + 4 + 8
377- if ret := Array4CharsFn(Array4Chars{a: [...]int8{1, 2, 4, 8}}); ret != expectedSum {
376+ const expectedSum = 10 + 20 + 30 + 63
377+ if ret := Array4CharsFn(Array4Chars{a: [...]int8{10, 20, 30, 63}}); ret != expectedSum {
378378 t.Fatalf("Array4CharsFn returned %d wanted %d", ret, expectedSum)
379379 }
380380 }
@@ -499,6 +499,37 @@ func TestRegisterFunc_structArgs(t *testing.T) {
499499 t.Fatalf("FourInt32s returned %d wanted %d", result, want)
500500 }
501501 }
502+ {
503+ type PointerWrapper struct {
504+ ctx unsafe.Pointer
505+ }
506+ var ExtractPointer func(wrapper PointerWrapper) uintptr
507+ purego.RegisterLibFunc(&ExtractPointer, lib, "ExtractPointer")
508+
509+ var v int
510+ ptr := unsafe.Pointer(&v)
511+ expected := uintptr(ptr)
512+ result := ExtractPointer(PointerWrapper{ctx: ptr})
513+ if result != expected {
514+ t.Fatalf("ExtractPointer returned %#x wanted %#x", result, expected)
515+ }
516+ }
517+ {
518+ type TwoPointers struct {
519+ ptr1, ptr2 unsafe.Pointer
520+ }
521+ var AddPointers func(wrapper TwoPointers) uintptr
522+ purego.RegisterLibFunc(&AddPointers, lib, "AddPointers")
523+
524+ var v1, v2 int
525+ ptr1 := unsafe.Pointer(&v1)
526+ ptr2 := unsafe.Pointer(&v2)
527+ expected := uintptr(ptr1) + uintptr(ptr2)
528+ result := AddPointers(TwoPointers{ptr1, ptr2})
529+ if result != expected {
530+ t.Fatalf("AddPointers returned %#x wanted %#x", result, expected)
531+ }
532+ }
502533 }
503534
504535 func TestRegisterFunc_structReturns(t *testing.T) {
@@ -757,6 +788,65 @@ func TestRegisterFunc_structReturns(t *testing.T) {
757788 t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected)
758789 }
759790 }
791+ {
792+ type Mixed5 struct {
793+ a *int64
794+ b int32
795+ c float32
796+ d int32
797+ }
798+ var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5
799+ purego.RegisterLibFunc(&ReturnMixed5, lib, "ReturnMixed5")
800+ ptr := new(int64)
801+ expected := Mixed5{ptr, 1, 7.2, 9}
802+ if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected {
803+ t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected)
804+ }
805+ runtime.KeepAlive(ptr)
806+ }
807+ {
808+ type Mixed5 struct {
809+ a *int64
810+ b int32
811+ c float32
812+ d int32
813+ }
814+ var IdentityMixed5 func(m Mixed5) Mixed5
815+ purego.RegisterLibFunc(&IdentityMixed5, lib, "IdentityMixed5")
816+ ptr := new(int64)
817+ expected := Mixed5{ptr, 1, 7.2, 9}
818+ if ret := IdentityMixed5(expected); ret != expected {
819+ t.Fatalf("IdentityMixed5 returned %+v wanted %+v", ret, expected)
820+ }
821+ runtime.KeepAlive(ptr)
822+ }
823+ {
824+ type SmallBool struct {
825+ a bool
826+ b int32
827+ c int64
828+ }
829+ var ReturnSmallBool func(a bool, b int32, c int64) SmallBool
830+ purego.RegisterLibFunc(&ReturnSmallBool, lib, "ReturnSmallBool")
831+ expected := SmallBool{true, 42, 123456789}
832+ if ret := ReturnSmallBool(true, 42, 123456789); ret != expected {
833+ t.Fatalf("ReturnSmallBool returned %+v wanted %+v", ret, expected)
834+ }
835+ }
836+ {
837+ type LargeBool struct {
838+ a bool
839+ b int32
840+ c int64
841+ d int64
842+ }
843+ var ReturnLargeBool func(a bool, b int32, c int64, d int64) LargeBool
844+ purego.RegisterLibFunc(&ReturnLargeBool, lib, "ReturnLargeBool")
845+ expected := LargeBool{false, -99, 987654321, 111222333444}
846+ if ret := ReturnLargeBool(false, -99, 987654321, 111222333444); ret != expected {
847+ t.Fatalf("ReturnLargeBool returned %+v wanted %+v", ret, expected)
848+ }
849+ }
760850 {
761851 type Ptr1 struct {
762852 a *int64
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3 3
4-//go:build darwin && (arm64 || amd64)4+//go:build (darwin || linux) && (amd64 || arm64)
5 5
6 package purego_test6 package purego_test
7 7
@@ -373,8 +373,8 @@ func TestRegisterFunc_structArgs(t *testing.T) {
373 }373 }
374 var Array4CharsFn func(chars Array4Chars) int32374 var Array4CharsFn func(chars Array4Chars) int32
375 purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars")375 purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars")
376- const expectedSum = 1 + 2 + 4 + 8376+ const expectedSum = 10 + 20 + 30 + 63
377- if ret := Array4CharsFn(Array4Chars{a: [...]int8{1, 2, 4, 8}}); ret != expectedSum {377+ if ret := Array4CharsFn(Array4Chars{a: [...]int8{10, 20, 30, 63}}); ret != expectedSum {
378 t.Fatalf("Array4CharsFn returned %d wanted %d", ret, expectedSum)378 t.Fatalf("Array4CharsFn returned %d wanted %d", ret, expectedSum)
379 }379 }
380 }380 }
@@ -499,6 +499,37 @@ func TestRegisterFunc_structArgs(t *testing.T) {
499 t.Fatalf("FourInt32s returned %d wanted %d", result, want)499 t.Fatalf("FourInt32s returned %d wanted %d", result, want)
500 }500 }
501 }501 }
502+ {
503+ type PointerWrapper struct {
504+ ctx unsafe.Pointer
505+ }
506+ var ExtractPointer func(wrapper PointerWrapper) uintptr
507+ purego.RegisterLibFunc(&ExtractPointer, lib, "ExtractPointer")
508+
509+ var v int
510+ ptr := unsafe.Pointer(&v)
511+ expected := uintptr(ptr)
512+ result := ExtractPointer(PointerWrapper{ctx: ptr})
513+ if result != expected {
514+ t.Fatalf("ExtractPointer returned %#x wanted %#x", result, expected)
515+ }
516+ }
517+ {
518+ type TwoPointers struct {
519+ ptr1, ptr2 unsafe.Pointer
520+ }
521+ var AddPointers func(wrapper TwoPointers) uintptr
522+ purego.RegisterLibFunc(&AddPointers, lib, "AddPointers")
523+
524+ var v1, v2 int
525+ ptr1 := unsafe.Pointer(&v1)
526+ ptr2 := unsafe.Pointer(&v2)
527+ expected := uintptr(ptr1) + uintptr(ptr2)
528+ result := AddPointers(TwoPointers{ptr1, ptr2})
529+ if result != expected {
530+ t.Fatalf("AddPointers returned %#x wanted %#x", result, expected)
531+ }
532+ }
502 }533 }
503 534
504 func TestRegisterFunc_structReturns(t *testing.T) {535 func TestRegisterFunc_structReturns(t *testing.T) {
@@ -757,6 +788,65 @@ func TestRegisterFunc_structReturns(t *testing.T) {
757 t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected)788 t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected)
758 }789 }
759 }790 }
791+ {
792+ type Mixed5 struct {
793+ a *int64
794+ b int32
795+ c float32
796+ d int32
797+ }
798+ var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5
799+ purego.RegisterLibFunc(&ReturnMixed5, lib, "ReturnMixed5")
800+ ptr := new(int64)
801+ expected := Mixed5{ptr, 1, 7.2, 9}
802+ if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected {
803+ t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected)
804+ }
805+ runtime.KeepAlive(ptr)
806+ }
807+ {
808+ type Mixed5 struct {
809+ a *int64
810+ b int32
811+ c float32
812+ d int32
813+ }
814+ var IdentityMixed5 func(m Mixed5) Mixed5
815+ purego.RegisterLibFunc(&IdentityMixed5, lib, "IdentityMixed5")
816+ ptr := new(int64)
817+ expected := Mixed5{ptr, 1, 7.2, 9}
818+ if ret := IdentityMixed5(expected); ret != expected {
819+ t.Fatalf("IdentityMixed5 returned %+v wanted %+v", ret, expected)
820+ }
821+ runtime.KeepAlive(ptr)
822+ }
823+ {
824+ type SmallBool struct {
825+ a bool
826+ b int32
827+ c int64
828+ }
829+ var ReturnSmallBool func(a bool, b int32, c int64) SmallBool
830+ purego.RegisterLibFunc(&ReturnSmallBool, lib, "ReturnSmallBool")
831+ expected := SmallBool{true, 42, 123456789}
832+ if ret := ReturnSmallBool(true, 42, 123456789); ret != expected {
833+ t.Fatalf("ReturnSmallBool returned %+v wanted %+v", ret, expected)
834+ }
835+ }
836+ {
837+ type LargeBool struct {
838+ a bool
839+ b int32
840+ c int64
841+ d int64
842+ }
843+ var ReturnLargeBool func(a bool, b int32, c int64, d int64) LargeBool
844+ purego.RegisterLibFunc(&ReturnLargeBool, lib, "ReturnLargeBool")
845+ expected := LargeBool{false, -99, 987654321, 111222333444}
846+ if ret := ReturnLargeBool(false, -99, 987654321, 111222333444); ret != expected {
847+ t.Fatalf("ReturnLargeBool returned %+v wanted %+v", ret, expected)
848+ }
849+ }
760 {850 {
761 type Ptr1 struct {851 type Ptr1 struct {
762 a *int64852 a *int64
M testdata/structtest/struct_test.c +18 -1
@@ -280,7 +280,7 @@ unsigned int Array2UnsignedShorts(struct Array2UnsignedShort a) {
280280 }
281281
282282 struct Array4Chars {
283- char a[4];
283+ signed char a[4];
284284 };
285285
286286 int Array4Chars(struct Array4Chars a) {
@@ -374,3 +374,20 @@ struct FourInt32s {
374374 int32_t FourInt32s(struct FourInt32s s) {
375375 return s.f0 + s.f1 + s.f2 + s.f3;
376376 }
377+
378+struct PointerWrapper {
379+ void* ctx;
380+};
381+
382+uintptr_t ExtractPointer(struct PointerWrapper wrapper) {
383+ return (uintptr_t)wrapper.ctx;
384+}
385+
386+struct TwoPointers {
387+ void* ptr1;
388+ void* ptr2;
389+};
390+
391+uintptr_t AddPointers(struct TwoPointers wrapper) {
392+ return (uintptr_t)wrapper.ptr1 + (uintptr_t)wrapper.ptr2;
393+}
@@ -280,7 +280,7 @@ unsigned int Array2UnsignedShorts(struct Array2UnsignedShort a) {
280 }280 }
281 281
282 struct Array4Chars {282 struct Array4Chars {
283- char a[4];283+ signed char a[4];
284 };284 };
285 285
286 int Array4Chars(struct Array4Chars a) {286 int Array4Chars(struct Array4Chars a) {
@@ -374,3 +374,20 @@ struct FourInt32s {
374 int32_t FourInt32s(struct FourInt32s s) {374 int32_t FourInt32s(struct FourInt32s s) {
375 return s.f0 + s.f1 + s.f2 + s.f3;375 return s.f0 + s.f1 + s.f2 + s.f3;
376 }376 }
377+
378+struct PointerWrapper {
379+ void* ctx;
380+};
381+
382+uintptr_t ExtractPointer(struct PointerWrapper wrapper) {
383+ return (uintptr_t)wrapper.ctx;
384+}
385+
386+struct TwoPointers {
387+ void* ptr1;
388+ void* ptr2;
389+};
390+
391+uintptr_t AddPointers(struct TwoPointers wrapper) {
392+ return (uintptr_t)wrapper.ptr1 + (uintptr_t)wrapper.ptr2;
393+}
M testdata/structtest/structreturn_test.c +39 -0
@@ -227,6 +227,45 @@ struct Mixed4 ReturnMixed4(double a, uint32_t b, float c) {
227227 return s;
228228 }
229229
230+struct Mixed5{
231+ int64_t *a;
232+ int32_t b;
233+ float c;
234+ int32_t d;
235+};
236+
237+struct Mixed5 ReturnMixed5(int64_t *a, int32_t b, float c, int32_t d) {
238+ struct Mixed5 s = {a, b, c, d};
239+ return s;
240+}
241+
242+struct Mixed5 IdentityMixed5(struct Mixed5 m) {
243+ return m;
244+}
245+
246+struct SmallBool{
247+ _Bool a;
248+ int32_t b;
249+ int64_t c;
250+};
251+
252+struct SmallBool ReturnSmallBool(_Bool a, int32_t b, int64_t c) {
253+ struct SmallBool s = {a, b, c};
254+ return s;
255+}
256+
257+struct LargeBool{
258+ _Bool a;
259+ int32_t b;
260+ int64_t c;
261+ int64_t d;
262+};
263+
264+struct LargeBool ReturnLargeBool(_Bool a, int32_t b, int64_t c, int64_t d) {
265+ struct LargeBool s = {a, b, c, d};
266+ return s;
267+}
268+
230269 struct Ptr1{
231270 int64_t *a;
232271 void *b;
@@ -227,6 +227,45 @@ struct Mixed4 ReturnMixed4(double a, uint32_t b, float c) {
227 return s;227 return s;
228 }228 }
229 229
230+struct Mixed5{
231+ int64_t *a;
232+ int32_t b;
233+ float c;
234+ int32_t d;
235+};
236+
237+struct Mixed5 ReturnMixed5(int64_t *a, int32_t b, float c, int32_t d) {
238+ struct Mixed5 s = {a, b, c, d};
239+ return s;
240+}
241+
242+struct Mixed5 IdentityMixed5(struct Mixed5 m) {
243+ return m;
244+}
245+
246+struct SmallBool{
247+ _Bool a;
248+ int32_t b;
249+ int64_t c;
250+};
251+
252+struct SmallBool ReturnSmallBool(_Bool a, int32_t b, int64_t c) {
253+ struct SmallBool s = {a, b, c};
254+ return s;
255+}
256+
257+struct LargeBool{
258+ _Bool a;
259+ int32_t b;
260+ int64_t c;
261+ int64_t d;
262+};
263+
264+struct LargeBool ReturnLargeBool(_Bool a, int32_t b, int64_t c, int64_t d) {
265+ struct LargeBool s = {a, b, c, d};
266+ return s;
267+}
268+
230 struct Ptr1{269 struct Ptr1{
231 int64_t *a;270 int64_t *a;
232 void *b;271 void *b;

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