detached d888bce

purego: Windows support for RegisterLibFunc and RegisterFunc (#131)

* purego: Windows support for RegisterLibFunc and RegisterFunc

It implements these two functions to make the code between the two more similiar. It also updates the libc example to work on Windows. This PR does not add support for Windows 386 or 32bit arm.

Closes #130
commit
d888bce98506955251c6b11981556f2cf8ac2ff0
author
TotallyGamerJet · · 2023-04-15 14:55 UTC
committer
GitHub · · 2023-04-15 14:55 UTC
parent
ec16513
signature
GPG Unverified4AEE18F83AFDEB23 The key is unknown
refs
tag: v0.4.0-alpha.4
15 files · +287 -174
M .github/workflows/test.yml +2 -0
@@ -76,6 +76,8 @@ jobs:
7676 env CGO_ENABLED=0 go test -shuffle=on -v -count=10 ./...
7777 env CGO_ENABLED=1 go test -shuffle=on -v -count=10 ./...
7878
79+ # TODO: add Windows 386 tests
80+
7981 - name: go test race
8082 if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }}
8183 run: |
@@ -76,6 +76,8 @@ jobs:
76 env CGO_ENABLED=0 go test -shuffle=on -v -count=10 ./...76 env CGO_ENABLED=0 go test -shuffle=on -v -count=10 ./...
77 env CGO_ENABLED=1 go test -shuffle=on -v -count=10 ./...77 env CGO_ENABLED=1 go test -shuffle=on -v -count=10 ./...
78 78
79+ # TODO: add Windows 386 tests
80+
79 - name: go test race81 - name: go test race
80 if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }}82 if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }}
81 run: |83 run: |
M README.md +2 -0
@@ -19,6 +19,8 @@ born to bring that same vision to the other platforms supported by Ebitengine.
1919
2020 ## Example
2121
22+This example only works on macOS and Linux. For a complete example look at [libc](https://github.com/ebitengine/purego/tree/main/examples/libc) which supports Windows.
23+
2224 ```go
2325 package main
2426
@@ -19,6 +19,8 @@ born to bring that same vision to the other platforms supported by Ebitengine.
19 19
20 ## Example20 ## Example
21 21
22+This example only works on macOS and Linux. For a complete example look at [libc](https://github.com/ebitengine/purego/tree/main/examples/libc) which supports Windows.
23+
22 ```go24 ```go
23 package main25 package main
24 26
M dlfcn.go +9 -0
@@ -63,6 +63,15 @@ func Dlclose(handle uintptr) error {
6363 return nil
6464 }
6565
66+//go:linkname openLibrary openLibrary
67+func openLibrary(name string) (uintptr, error) {
68+ return Dlopen(name, RTLD_NOW|RTLD_GLOBAL)
69+}
70+
71+func loadSymbol(handle uintptr, name string) (uintptr, error) {
72+ return Dlsym(handle, name)
73+}
74+
6675 // these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go
6776 // the indirection is necessary because a function is actually a pointer to the pointer to the code.
6877 // sadly, I do not know of anyway to remove the assembly stubs entirely because //go:linkname doesn't
@@ -63,6 +63,15 @@ func Dlclose(handle uintptr) error {
63 return nil63 return nil
64 }64 }
65 65
66+//go:linkname openLibrary openLibrary
67+func openLibrary(name string) (uintptr, error) {
68+ return Dlopen(name, RTLD_NOW|RTLD_GLOBAL)
69+}
70+
71+func loadSymbol(handle uintptr, name string) (uintptr, error) {
72+ return Dlsym(handle, name)
73+}
74+
66 // these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go75 // these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go
67 // the indirection is necessary because a function is actually a pointer to the pointer to the code.76 // the indirection is necessary because a function is actually a pointer to the pointer to the code.
68 // sadly, I do not know of anyway to remove the assembly stubs entirely because //go:linkname doesn't77 // sadly, I do not know of anyway to remove the assembly stubs entirely because //go:linkname doesn't
M dlfcn_test.go +0 -78
@@ -14,88 +14,10 @@ import (
1414 "runtime"
1515 "strings"
1616 "testing"
17- "unsafe"
1817
1918 "github.com/ebitengine/purego"
2019 )
2120
22-func getSystemLibrary() (string, error) {
23- switch runtime.GOOS {
24- case "darwin":
25- return "/usr/lib/libSystem.B.dylib", nil
26- case "linux":
27- return "libc.so.6", nil
28- default:
29- return "", fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)
30- }
31-}
32-
33-func TestRegisterFunc(t *testing.T) {
34- library, err := getSystemLibrary()
35- if err != nil {
36- t.Errorf("couldn't get system library: %s", err)
37- }
38- libc, err := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL)
39- if err != nil {
40- t.Errorf("failed to dlopen: %s", err)
41- }
42- var puts func(string)
43- purego.RegisterLibFunc(&puts, libc, "puts")
44- puts("Calling C from from Go without Cgo!")
45-}
46-
47-func ExampleNewCallback() {
48- if runtime.GOOS == "linux" {
49- // TODO: enable once callbacks are working properly on Linux
50- fmt.Println("1 2 3 4 5 6 7 8 9\n45")
51- return
52- }
53-
54- cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int {
55- fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9)
56- return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9
57- })
58-
59- var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int
60- purego.RegisterFunc(&fn, cb)
61-
62- ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9)
63- fmt.Println(ret)
64-
65- //Output: 1 2 3 4 5 6 7 8 9
66- // 45
67-}
68-
69-func Test_qsort(t *testing.T) {
70- if runtime.GOOS == "linux" {
71- // TODO: enable once callbacks are working properly on Linux
72- t.SkipNow()
73- }
74-
75- library, err := getSystemLibrary()
76- if err != nil {
77- t.Errorf("couldn't get system library: %s", err)
78- }
79- libc, err := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL)
80- if err != nil {
81- t.Errorf("failed to dlopen: %s", err)
82- }
83-
84- var data = []int{88, 56, 100, 2, 25}
85- var sorted = []int{2, 25, 56, 88, 100}
86- compare := func(a, b *int) int {
87- return *a - *b
88- }
89- var qsort func(data []int, nitms uintptr, size uintptr, compar func(a, b *int) int)
90- purego.RegisterLibFunc(&qsort, libc, "qsort")
91- qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare)
92- for i := range data {
93- if data[i] != sorted[i] {
94- t.Errorf("got %d wanted %d at %d", data[i], sorted[i], i)
95- }
96- }
97-}
98-
9921 func TestNestedDlopenCall(t *testing.T) {
10022 libFileName := filepath.Join(t.TempDir(), "libdlnested.so")
10123 t.Logf("Build %v", libFileName)
@@ -14,88 +14,10 @@ import (
14 "runtime"14 "runtime"
15 "strings"15 "strings"
16 "testing"16 "testing"
17- "unsafe"
18 17
19 "github.com/ebitengine/purego"18 "github.com/ebitengine/purego"
20 )19 )
21 20
22-func getSystemLibrary() (string, error) {
23- switch runtime.GOOS {
24- case "darwin":
25- return "/usr/lib/libSystem.B.dylib", nil
26- case "linux":
27- return "libc.so.6", nil
28- default:
29- return "", fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)
30- }
31-}
32-
33-func TestRegisterFunc(t *testing.T) {
34- library, err := getSystemLibrary()
35- if err != nil {
36- t.Errorf("couldn't get system library: %s", err)
37- }
38- libc, err := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL)
39- if err != nil {
40- t.Errorf("failed to dlopen: %s", err)
41- }
42- var puts func(string)
43- purego.RegisterLibFunc(&puts, libc, "puts")
44- puts("Calling C from from Go without Cgo!")
45-}
46-
47-func ExampleNewCallback() {
48- if runtime.GOOS == "linux" {
49- // TODO: enable once callbacks are working properly on Linux
50- fmt.Println("1 2 3 4 5 6 7 8 9\n45")
51- return
52- }
53-
54- cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int {
55- fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9)
56- return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9
57- })
58-
59- var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int
60- purego.RegisterFunc(&fn, cb)
61-
62- ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9)
63- fmt.Println(ret)
64-
65- //Output: 1 2 3 4 5 6 7 8 9
66- // 45
67-}
68-
69-func Test_qsort(t *testing.T) {
70- if runtime.GOOS == "linux" {
71- // TODO: enable once callbacks are working properly on Linux
72- t.SkipNow()
73- }
74-
75- library, err := getSystemLibrary()
76- if err != nil {
77- t.Errorf("couldn't get system library: %s", err)
78- }
79- libc, err := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL)
80- if err != nil {
81- t.Errorf("failed to dlopen: %s", err)
82- }
83-
84- var data = []int{88, 56, 100, 2, 25}
85- var sorted = []int{2, 25, 56, 88, 100}
86- compare := func(a, b *int) int {
87- return *a - *b
88- }
89- var qsort func(data []int, nitms uintptr, size uintptr, compar func(a, b *int) int)
90- purego.RegisterLibFunc(&qsort, libc, "qsort")
91- qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare)
92- for i := range data {
93- if data[i] != sorted[i] {
94- t.Errorf("got %d wanted %d at %d", data[i], sorted[i], i)
95- }
96- }
97-}
98-
99 func TestNestedDlopenCall(t *testing.T) {21 func TestNestedDlopenCall(t *testing.T) {
100 libFileName := filepath.Join(t.TempDir(), "libdlnested.so")22 libFileName := filepath.Join(t.TempDir(), "libdlnested.so")
101 t.Logf("Build %v", libFileName)23 t.Logf("Build %v", libFileName)
M examples/libc/main.go +4 -2
@@ -1,7 +1,7 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
4-//go:build darwin || linux
4+//go:build darwin || linux || windows
55
66 package main
77
@@ -18,13 +18,15 @@ func getSystemLibrary() string {
1818 return "/usr/lib/libSystem.B.dylib"
1919 case "linux":
2020 return "libc.so.6"
21+ case "windows":
22+ return "ucrtbase.dll"
2123 default:
2224 panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS))
2325 }
2426 }
2527
2628 func main() {
27- libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL)
29+ libc, err := openLibrary(getSystemLibrary())
2830 if err != nil {
2931 panic(err)
3032 }
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4-//go:build darwin || linux4+//go:build darwin || linux || windows
5 5
6 package main6 package main
7 7
@@ -18,13 +18,15 @@ func getSystemLibrary() string {
18 return "/usr/lib/libSystem.B.dylib"18 return "/usr/lib/libSystem.B.dylib"
19 case "linux":19 case "linux":
20 return "libc.so.6"20 return "libc.so.6"
21+ case "windows":
22+ return "ucrtbase.dll"
21 default:23 default:
22 panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS))24 panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS))
23 }25 }
24 }26 }
25 27
26 func main() {28 func main() {
27- libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL)29+ libc, err := openLibrary(getSystemLibrary())
28 if err != nil {30 if err != nil {
29 panic(err)31 panic(err)
30 }32 }
A examples/libc/main_unix.go +12 -0
@@ -0,0 +1,12 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3+
4+//go:build darwin || linux
5+
6+package main
7+
8+import "github.com/ebitengine/purego"
9+
10+func openLibrary(name string) (uintptr, error) {
11+ return purego.Dlopen(name, purego.RTLD_NOW|purego.RTLD_GLOBAL)
12+}
@@ -0,0 +1,12 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3+
4+//go:build darwin || linux
5+
6+package main
7+
8+import "github.com/ebitengine/purego"
9+
10+func openLibrary(name string) (uintptr, error) {
11+ return purego.Dlopen(name, purego.RTLD_NOW|purego.RTLD_GLOBAL)
12+}
A examples/libc/main_windows.go +11 -0
@@ -0,0 +1,11 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3+
4+package main
5+
6+import "golang.org/x/sys/windows"
7+
8+func openLibrary(name string) (uintptr, error) {
9+ handle, err := windows.LoadLibrary(name)
10+ return uintptr(handle), err
11+}
@@ -0,0 +1,11 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3+
4+package main
5+
6+import "golang.org/x/sys/windows"
7+
8+func openLibrary(name string) (uintptr, error) {
9+ handle, err := windows.LoadLibrary(name)
10+ return uintptr(handle), err
11+}
M func.go +59 -37
@@ -1,7 +1,7 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
4-//go:build darwin || linux
4+//go:build darwin || linux || windows
55
66 package purego
77
@@ -15,11 +15,9 @@ import (
1515 )
1616
1717 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
18-// It panics if Dlsym fails.
19-//
20-// Windows does not support this function.
18+// It panics if it can't find the name symbol.
2119 func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
22- sym, err := Dlsym(handle, name)
20+ sym, err := loadSymbol(handle, name)
2321 if err != nil {
2422 panic(err)
2523 }
@@ -67,8 +65,6 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
6765 // There are some limitations when using RegisterFunc on Linux. First, there is no support for function arguments.
6866 // Second, float32 and float64 arguments and return values do not work when CGO_ENABLED=1. Otherwise, Linux
6967 // has the same feature parity as Darwin.
70-//
71-// Windows does not support this function.
7268 func RegisterFunc(fptr interface{}, cfn uintptr) {
7369 fn := reflect.ValueOf(fptr).Elem()
7470 ty := fn.Type()
@@ -132,18 +128,43 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
132128 var numInts int
133129 var numFloats int
134130 var numStack int
135- addStack := func(x uintptr) {
136- stack[numStack] = x
137- numStack++
138- }
139- addInt := func(x uintptr) {
140- if numInts >= numOfIntegerRegisters() {
141- addStack(x)
142- } else {
143- sysargs[numInts] = x
144- numInts++
131+ var addStack, addInt, addFloat func(x uintptr)
132+ if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
133+ // Windows arm64 uses the same calling convention as macOS and Linux
134+ addStack = func(x uintptr) {
135+ stack[numStack] = x
136+ numStack++
137+ }
138+ addInt = func(x uintptr) {
139+ if numInts >= numOfIntegerRegisters() {
140+ addStack(x)
141+ } else {
142+ sysargs[numInts] = x
143+ numInts++
144+ }
145+ }
146+ addFloat = func(x uintptr) {
147+ if numFloats < len(floats) {
148+ floats[numFloats] = x
149+ numFloats++
150+ } else {
151+ addStack(x)
152+ }
153+ }
154+ } else {
155+ // On Windows amd64 the arguments are passed in the numbered registered.
156+ // So the first int is in the first integer register and the first float
157+ // is in the second floating register if there is already a first int.
158+ // This is in contrast to how macOS and Linux pass arguments which
159+ // tries to use as many registers as possible in the calling convention.
160+ addStack = func(x uintptr) {
161+ sysargs[numStack] = x
162+ numStack++
145163 }
164+ addInt = addStack
165+ addFloat = addStack
146166 }
167+
147168 var keepAlive []interface{}
148169 defer func() {
149170 runtime.KeepAlive(keepAlive)
@@ -171,33 +192,29 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
171192 addInt(0)
172193 }
173194 case reflect.Float32:
174- if numFloats < len(floats) {
175- floats[numFloats] = uintptr(math.Float32bits(float32(v.Float())))
176- numFloats++
177- } else {
178- addStack(uintptr(math.Float32bits(float32(v.Float()))))
179- }
195+ addFloat(uintptr(math.Float32bits(float32(v.Float()))))
180196 case reflect.Float64:
181- if numFloats < len(floats) {
182- floats[numFloats] = uintptr(math.Float64bits(v.Float()))
183- numFloats++
184- } else {
185- addStack(uintptr(math.Float64bits(v.Float())))
186- }
197+ addFloat(uintptr(math.Float64bits(v.Float())))
187198 default:
188199 panic("purego: unsupported kind: " + v.Kind().String())
189200 }
190201 }
191202 // TODO: support structs
192- syscall := syscall9Args{
193- cfn,
194- sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
195- floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
196- 0, 0, 0,
203+ var r1, r2 uintptr
204+ if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
205+ // Use the normal arm64 calling convention even on Windows
206+ syscall := syscall9Args{
207+ cfn,
208+ sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
209+ floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
210+ 0, 0, 0,
211+ }
212+ runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
213+ r1, r2 = syscall.r1, syscall.r2
214+ } else {
215+ // This is a fallback for amd64, 386, and arm. Note this may not support floats
216+ r1, r2, _ = syscall_syscall9X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8])
197217 }
198- runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
199- r1, r2 := syscall.r1, syscall.r2
200-
201218 if ty.NumOut() == 0 {
202219 return nil
203220 }
@@ -240,6 +257,11 @@ func numOfIntegerRegisters() int {
240257 return 8
241258 case "amd64":
242259 return 6
260+ // TODO: figure out why 386 tests are not working
261+ /*case "386":
262+ return 0
263+ case "arm":
264+ return 4*/
243265 default:
244266 panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")
245267 }
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4-//go:build darwin || linux4+//go:build darwin || linux || windows
5 5
6 package purego6 package purego
7 7
@@ -15,11 +15,9 @@ import (
15 )15 )
16 16
17 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).17 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
18-// It panics if Dlsym fails.18+// It panics if it can't find the name symbol.
19-//
20-// Windows does not support this function.
21 func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {19 func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
22- sym, err := Dlsym(handle, name)20+ sym, err := loadSymbol(handle, name)
23 if err != nil {21 if err != nil {
24 panic(err)22 panic(err)
25 }23 }
@@ -67,8 +65,6 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
67 // There are some limitations when using RegisterFunc on Linux. First, there is no support for function arguments.65 // There are some limitations when using RegisterFunc on Linux. First, there is no support for function arguments.
68 // Second, float32 and float64 arguments and return values do not work when CGO_ENABLED=1. Otherwise, Linux66 // Second, float32 and float64 arguments and return values do not work when CGO_ENABLED=1. Otherwise, Linux
69 // has the same feature parity as Darwin.67 // has the same feature parity as Darwin.
70-//
71-// Windows does not support this function.
72 func RegisterFunc(fptr interface{}, cfn uintptr) {68 func RegisterFunc(fptr interface{}, cfn uintptr) {
73 fn := reflect.ValueOf(fptr).Elem()69 fn := reflect.ValueOf(fptr).Elem()
74 ty := fn.Type()70 ty := fn.Type()
@@ -132,18 +128,43 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
132 var numInts int128 var numInts int
133 var numFloats int129 var numFloats int
134 var numStack int130 var numStack int
135- addStack := func(x uintptr) {131+ var addStack, addInt, addFloat func(x uintptr)
136- stack[numStack] = x132+ if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
137- numStack++133+ // Windows arm64 uses the same calling convention as macOS and Linux
138- }134+ addStack = func(x uintptr) {
139- addInt := func(x uintptr) {135+ stack[numStack] = x
140- if numInts >= numOfIntegerRegisters() {136+ numStack++
141- addStack(x)137+ }
142- } else {138+ addInt = func(x uintptr) {
143- sysargs[numInts] = x139+ if numInts >= numOfIntegerRegisters() {
144- numInts++140+ addStack(x)
141+ } else {
142+ sysargs[numInts] = x
143+ numInts++
144+ }
145+ }
146+ addFloat = func(x uintptr) {
147+ if numFloats < len(floats) {
148+ floats[numFloats] = x
149+ numFloats++
150+ } else {
151+ addStack(x)
152+ }
153+ }
154+ } else {
155+ // On Windows amd64 the arguments are passed in the numbered registered.
156+ // So the first int is in the first integer register and the first float
157+ // is in the second floating register if there is already a first int.
158+ // This is in contrast to how macOS and Linux pass arguments which
159+ // tries to use as many registers as possible in the calling convention.
160+ addStack = func(x uintptr) {
161+ sysargs[numStack] = x
162+ numStack++
145 }163 }
164+ addInt = addStack
165+ addFloat = addStack
146 }166 }
167+
147 var keepAlive []interface{}168 var keepAlive []interface{}
148 defer func() {169 defer func() {
149 runtime.KeepAlive(keepAlive)170 runtime.KeepAlive(keepAlive)
@@ -171,33 +192,29 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
171 addInt(0)192 addInt(0)
172 }193 }
173 case reflect.Float32:194 case reflect.Float32:
174- if numFloats < len(floats) {195+ addFloat(uintptr(math.Float32bits(float32(v.Float()))))
175- floats[numFloats] = uintptr(math.Float32bits(float32(v.Float())))
176- numFloats++
177- } else {
178- addStack(uintptr(math.Float32bits(float32(v.Float()))))
179- }
180 case reflect.Float64:196 case reflect.Float64:
181- if numFloats < len(floats) {197+ addFloat(uintptr(math.Float64bits(v.Float())))
182- floats[numFloats] = uintptr(math.Float64bits(v.Float()))
183- numFloats++
184- } else {
185- addStack(uintptr(math.Float64bits(v.Float())))
186- }
187 default:198 default:
188 panic("purego: unsupported kind: " + v.Kind().String())199 panic("purego: unsupported kind: " + v.Kind().String())
189 }200 }
190 }201 }
191 // TODO: support structs202 // TODO: support structs
192- syscall := syscall9Args{203+ var r1, r2 uintptr
193- cfn,204+ if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
194- sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],205+ // Use the normal arm64 calling convention even on Windows
195- floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],206+ syscall := syscall9Args{
196- 0, 0, 0,207+ cfn,
208+ sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
209+ floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
210+ 0, 0, 0,
211+ }
212+ runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
213+ r1, r2 = syscall.r1, syscall.r2
214+ } else {
215+ // This is a fallback for amd64, 386, and arm. Note this may not support floats
216+ r1, r2, _ = syscall_syscall9X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8])
197 }217 }
198- runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
199- r1, r2 := syscall.r1, syscall.r2
200-
201 if ty.NumOut() == 0 {218 if ty.NumOut() == 0 {
202 return nil219 return nil
203 }220 }
@@ -240,6 +257,11 @@ func numOfIntegerRegisters() int {
240 return 8257 return 8
241 case "amd64":258 case "amd64":
242 return 6259 return 6
260+ // TODO: figure out why 386 tests are not working
261+ /*case "386":
262+ return 0
263+ case "arm":
264+ return 4*/
243 default:265 default:
244 panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")266 panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")
245 }267 }
A func_test.go +97 -0
@@ -0,0 +1,97 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3+
4+package purego_test
5+
6+import (
7+ "fmt"
8+ "runtime"
9+ "testing"
10+ "unsafe"
11+
12+ "github.com/ebitengine/purego"
13+)
14+
15+// This is an internal OS-dependent function for getting the handle to a library
16+//
17+//go:linkname openLibrary openLibrary
18+func openLibrary(name string) (uintptr, error)
19+
20+func getSystemLibrary() (string, error) {
21+ switch runtime.GOOS {
22+ case "darwin":
23+ return "/usr/lib/libSystem.B.dylib", nil
24+ case "linux":
25+ return "libc.so.6", nil
26+ case "windows":
27+ return "ucrtbase.dll", nil
28+ default:
29+ return "", fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)
30+ }
31+}
32+
33+func TestRegisterFunc(t *testing.T) {
34+ library, err := getSystemLibrary()
35+ if err != nil {
36+ t.Errorf("couldn't get system library: %s", err)
37+ }
38+ libc, err := openLibrary(library)
39+ if err != nil {
40+ t.Errorf("failed to dlopen: %s", err)
41+ }
42+ var puts func(string)
43+ purego.RegisterLibFunc(&puts, libc, "puts")
44+ puts("Calling C from from Go without Cgo!")
45+}
46+
47+func ExampleNewCallback() {
48+ if runtime.GOOS == "linux" {
49+ // TODO: enable once callbacks are working properly on Linux
50+ fmt.Println("1 2 3 4 5 6 7 8 9\n45")
51+ return
52+ }
53+
54+ cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int {
55+ fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9)
56+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9
57+ })
58+
59+ var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int
60+ purego.RegisterFunc(&fn, cb)
61+
62+ ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9)
63+ fmt.Println(ret)
64+
65+ // Output: 1 2 3 4 5 6 7 8 9
66+ // 45
67+}
68+
69+func Test_qsort(t *testing.T) {
70+ if runtime.GOOS == "linux" {
71+ // TODO: enable once callbacks are working properly on Linux
72+ t.Skip("callbacks are not supported on Linux")
73+ }
74+
75+ library, err := getSystemLibrary()
76+ if err != nil {
77+ t.Errorf("couldn't get system library: %s", err)
78+ }
79+ libc, err := openLibrary(library)
80+ if err != nil {
81+ t.Errorf("failed to dlopen: %s", err)
82+ }
83+
84+ data := []int{88, 56, 100, 2, 25}
85+ sorted := []int{2, 25, 56, 88, 100}
86+ compare := func(a, b *int) int {
87+ return *a - *b
88+ }
89+ var qsort func(data []int, nitms uintptr, size uintptr, compar func(a, b *int) int)
90+ purego.RegisterLibFunc(&qsort, libc, "qsort")
91+ qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare)
92+ for i := range data {
93+ if data[i] != sorted[i] {
94+ t.Errorf("got %d wanted %d at %d", data[i], sorted[i], i)
95+ }
96+ }
97+}
@@ -0,0 +1,97 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3+
4+package purego_test
5+
6+import (
7+ "fmt"
8+ "runtime"
9+ "testing"
10+ "unsafe"
11+
12+ "github.com/ebitengine/purego"
13+)
14+
15+// This is an internal OS-dependent function for getting the handle to a library
16+//
17+//go:linkname openLibrary openLibrary
18+func openLibrary(name string) (uintptr, error)
19+
20+func getSystemLibrary() (string, error) {
21+ switch runtime.GOOS {
22+ case "darwin":
23+ return "/usr/lib/libSystem.B.dylib", nil
24+ case "linux":
25+ return "libc.so.6", nil
26+ case "windows":
27+ return "ucrtbase.dll", nil
28+ default:
29+ return "", fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)
30+ }
31+}
32+
33+func TestRegisterFunc(t *testing.T) {
34+ library, err := getSystemLibrary()
35+ if err != nil {
36+ t.Errorf("couldn't get system library: %s", err)
37+ }
38+ libc, err := openLibrary(library)
39+ if err != nil {
40+ t.Errorf("failed to dlopen: %s", err)
41+ }
42+ var puts func(string)
43+ purego.RegisterLibFunc(&puts, libc, "puts")
44+ puts("Calling C from from Go without Cgo!")
45+}
46+
47+func ExampleNewCallback() {
48+ if runtime.GOOS == "linux" {
49+ // TODO: enable once callbacks are working properly on Linux
50+ fmt.Println("1 2 3 4 5 6 7 8 9\n45")
51+ return
52+ }
53+
54+ cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int {
55+ fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9)
56+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9
57+ })
58+
59+ var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int
60+ purego.RegisterFunc(&fn, cb)
61+
62+ ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9)
63+ fmt.Println(ret)
64+
65+ // Output: 1 2 3 4 5 6 7 8 9
66+ // 45
67+}
68+
69+func Test_qsort(t *testing.T) {
70+ if runtime.GOOS == "linux" {
71+ // TODO: enable once callbacks are working properly on Linux
72+ t.Skip("callbacks are not supported on Linux")
73+ }
74+
75+ library, err := getSystemLibrary()
76+ if err != nil {
77+ t.Errorf("couldn't get system library: %s", err)
78+ }
79+ libc, err := openLibrary(library)
80+ if err != nil {
81+ t.Errorf("failed to dlopen: %s", err)
82+ }
83+
84+ data := []int{88, 56, 100, 2, 25}
85+ sorted := []int{2, 25, 56, 88, 100}
86+ compare := func(a, b *int) int {
87+ return *a - *b
88+ }
89+ var qsort func(data []int, nitms uintptr, size uintptr, compar func(a, b *int) int)
90+ purego.RegisterLibFunc(&qsort, libc, "qsort")
91+ qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare)
92+ for i := range data {
93+ if data[i] != sorted[i] {
94+ t.Errorf("got %d wanted %d at %d", data[i], sorted[i], i)
95+ }
96+ }
97+}
M go.mod +2 -0
@@ -1,3 +1,5 @@
11 module github.com/ebitengine/purego
22
33 go 1.18
4+
5+require golang.org/x/sys v0.7.0
@@ -1,3 +1,5 @@
1 module github.com/ebitengine/purego1 module github.com/ebitengine/purego
2 2
3 go 1.183 go 1.18
4+
5+require golang.org/x/sys v0.7.0
A go.sum +2 -0
@@ -0,0 +1,2 @@
1+golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
2+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -0,0 +1,2 @@
1+golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
2+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
M go_runtime.go +1 -1
@@ -1,7 +1,7 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
4-//go:build darwin || linux
4+//go:build darwin || linux || windows
55
66 package purego
77
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4-//go:build darwin || linux4+//go:build darwin || linux || windows
5 5
6 package purego6 package purego
7 7
M sys_arm64.s +1 -56
@@ -1,7 +1,7 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
4-//go:build darwin || (!cgo && linux)
4+//go:build darwin || (!cgo && linux) || windows
55
66 #include "textflag.h"
77 #include "go_asm.h"
@@ -62,58 +62,3 @@ TEXT syscall9X(SB), NOSPLIT, $0
6262 MOVD R0, syscall9Args_r1(R2) // save r1
6363 FMOVD F0, syscall9Args_r2(R2) // save r2
6464 RET
65-
66-TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
67- NO_LOCAL_POINTERS
68-
69- // On entry, the trampoline in zcallback_darwin_arm64.s left
70- // the callback index in R12 (which is volatile in the C ABI).
71-
72- // Save callback register arguments R0-R7 and F0-F7.
73- // We do this at the top of the frame so they're contiguous with stack arguments.
74- SUB $(16*8), RSP, R14
75- FMOVD F0, (0*8)(R14)
76- FMOVD F1, (1*8)(R14)
77- FMOVD F2, (2*8)(R14)
78- FMOVD F3, (3*8)(R14)
79- FMOVD F4, (4*8)(R14)
80- FMOVD F5, (5*8)(R14)
81- FMOVD F6, (6*8)(R14)
82- FMOVD F7, (7*8)(R14)
83- STP (R0, R1), (8*8)(R14)
84- STP (R2, R3), (10*8)(R14)
85- STP (R4, R5), (12*8)(R14)
86- STP (R6, R7), (14*8)(R14)
87-
88- // Adjust SP by frame size.
89- // crosscall2 clobbers FP in the frame record so only save/restore SP.
90- SUB $(28*8), RSP
91- MOVD R30, (RSP)
92-
93- // Create a struct callbackArgs on our stack.
94- ADD $(callbackArgs__size + 3*8), RSP, R13
95- MOVD R12, callbackArgs_index(R13) // callback index
96- MOVD R14, R0
97- MOVD R0, callbackArgs_args(R13) // address of args vector
98- MOVD $0, R0
99- MOVD R0, callbackArgs_result(R13) // result
100-
101- // Move parameters into registers
102- // Get the ABIInternal function pointer
103- // without <ABIInternal> by using a closure.
104- MOVD ·callbackWrap_call(SB), R0
105- MOVD (R0), R0 // fn unsafe.Pointer
106- MOVD R13, R1 // frame (&callbackArgs{...})
107- MOVD $0, R3 // ctxt uintptr
108-
109- BL crosscall2(SB)
110-
111- // Get callback result.
112- ADD $(callbackArgs__size + 3*8), RSP, R13
113- MOVD callbackArgs_result(R13), R0
114-
115- // Restore SP
116- MOVD (RSP), R30
117- ADD $(28*8), RSP
118-
119- RET
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4-//go:build darwin || (!cgo && linux)4+//go:build darwin || (!cgo && linux) || windows
5 5
6 #include "textflag.h"6 #include "textflag.h"
7 #include "go_asm.h"7 #include "go_asm.h"
@@ -62,58 +62,3 @@ TEXT syscall9X(SB), NOSPLIT, $0
62 MOVD R0, syscall9Args_r1(R2) // save r162 MOVD R0, syscall9Args_r1(R2) // save r1
63 FMOVD F0, syscall9Args_r2(R2) // save r263 FMOVD F0, syscall9Args_r2(R2) // save r2
64 RET64 RET
65-
66-TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
67- NO_LOCAL_POINTERS
68-
69- // On entry, the trampoline in zcallback_darwin_arm64.s left
70- // the callback index in R12 (which is volatile in the C ABI).
71-
72- // Save callback register arguments R0-R7 and F0-F7.
73- // We do this at the top of the frame so they're contiguous with stack arguments.
74- SUB $(16*8), RSP, R14
75- FMOVD F0, (0*8)(R14)
76- FMOVD F1, (1*8)(R14)
77- FMOVD F2, (2*8)(R14)
78- FMOVD F3, (3*8)(R14)
79- FMOVD F4, (4*8)(R14)
80- FMOVD F5, (5*8)(R14)
81- FMOVD F6, (6*8)(R14)
82- FMOVD F7, (7*8)(R14)
83- STP (R0, R1), (8*8)(R14)
84- STP (R2, R3), (10*8)(R14)
85- STP (R4, R5), (12*8)(R14)
86- STP (R6, R7), (14*8)(R14)
87-
88- // Adjust SP by frame size.
89- // crosscall2 clobbers FP in the frame record so only save/restore SP.
90- SUB $(28*8), RSP
91- MOVD R30, (RSP)
92-
93- // Create a struct callbackArgs on our stack.
94- ADD $(callbackArgs__size + 3*8), RSP, R13
95- MOVD R12, callbackArgs_index(R13) // callback index
96- MOVD R14, R0
97- MOVD R0, callbackArgs_args(R13) // address of args vector
98- MOVD $0, R0
99- MOVD R0, callbackArgs_result(R13) // result
100-
101- // Move parameters into registers
102- // Get the ABIInternal function pointer
103- // without <ABIInternal> by using a closure.
104- MOVD ·callbackWrap_call(SB), R0
105- MOVD (R0), R0 // fn unsafe.Pointer
106- MOVD R13, R1 // frame (&callbackArgs{...})
107- MOVD $0, R3 // ctxt uintptr
108-
109- BL crosscall2(SB)
110-
111- // Get callback result.
112- ADD $(callbackArgs__size + 3*8), RSP, R13
113- MOVD callbackArgs_result(R13), R0
114-
115- // Restore SP
116- MOVD (RSP), R30
117- ADD $(28*8), RSP
118-
119- RET
A sys_unix_arm64.s +64 -0
@@ -0,0 +1,64 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3+
4+//go:build darwin || (!cgo && linux)
5+
6+#include "textflag.h"
7+#include "go_asm.h"
8+#include "funcdata.h"
9+#include "internal/abi/abi_arm64.h"
10+
11+TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
12+ NO_LOCAL_POINTERS
13+
14+ // On entry, the trampoline in zcallback_darwin_arm64.s left
15+ // the callback index in R12 (which is volatile in the C ABI).
16+
17+ // Save callback register arguments R0-R7 and F0-F7.
18+ // We do this at the top of the frame so they're contiguous with stack arguments.
19+ SUB $(16*8), RSP, R14
20+ FMOVD F0, (0*8)(R14)
21+ FMOVD F1, (1*8)(R14)
22+ FMOVD F2, (2*8)(R14)
23+ FMOVD F3, (3*8)(R14)
24+ FMOVD F4, (4*8)(R14)
25+ FMOVD F5, (5*8)(R14)
26+ FMOVD F6, (6*8)(R14)
27+ FMOVD F7, (7*8)(R14)
28+ STP (R0, R1), (8*8)(R14)
29+ STP (R2, R3), (10*8)(R14)
30+ STP (R4, R5), (12*8)(R14)
31+ STP (R6, R7), (14*8)(R14)
32+
33+ // Adjust SP by frame size.
34+ // crosscall2 clobbers FP in the frame record so only save/restore SP.
35+ SUB $(28*8), RSP
36+ MOVD R30, (RSP)
37+
38+ // Create a struct callbackArgs on our stack.
39+ ADD $(callbackArgs__size + 3*8), RSP, R13
40+ MOVD R12, callbackArgs_index(R13) // callback index
41+ MOVD R14, R0
42+ MOVD R0, callbackArgs_args(R13) // address of args vector
43+ MOVD $0, R0
44+ MOVD R0, callbackArgs_result(R13) // result
45+
46+ // Move parameters into registers
47+ // Get the ABIInternal function pointer
48+ // without <ABIInternal> by using a closure.
49+ MOVD ·callbackWrap_call(SB), R0
50+ MOVD (R0), R0 // fn unsafe.Pointer
51+ MOVD R13, R1 // frame (&callbackArgs{...})
52+ MOVD $0, R3 // ctxt uintptr
53+
54+ BL crosscall2(SB)
55+
56+ // Get callback result.
57+ ADD $(callbackArgs__size + 3*8), RSP, R13
58+ MOVD callbackArgs_result(R13), R0
59+
60+ // Restore SP
61+ MOVD (RSP), R30
62+ ADD $(28*8), RSP
63+
64+ RET
@@ -0,0 +1,64 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3+
4+//go:build darwin || (!cgo && linux)
5+
6+#include "textflag.h"
7+#include "go_asm.h"
8+#include "funcdata.h"
9+#include "internal/abi/abi_arm64.h"
10+
11+TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
12+ NO_LOCAL_POINTERS
13+
14+ // On entry, the trampoline in zcallback_darwin_arm64.s left
15+ // the callback index in R12 (which is volatile in the C ABI).
16+
17+ // Save callback register arguments R0-R7 and F0-F7.
18+ // We do this at the top of the frame so they're contiguous with stack arguments.
19+ SUB $(16*8), RSP, R14
20+ FMOVD F0, (0*8)(R14)
21+ FMOVD F1, (1*8)(R14)
22+ FMOVD F2, (2*8)(R14)
23+ FMOVD F3, (3*8)(R14)
24+ FMOVD F4, (4*8)(R14)
25+ FMOVD F5, (5*8)(R14)
26+ FMOVD F6, (6*8)(R14)
27+ FMOVD F7, (7*8)(R14)
28+ STP (R0, R1), (8*8)(R14)
29+ STP (R2, R3), (10*8)(R14)
30+ STP (R4, R5), (12*8)(R14)
31+ STP (R6, R7), (14*8)(R14)
32+
33+ // Adjust SP by frame size.
34+ // crosscall2 clobbers FP in the frame record so only save/restore SP.
35+ SUB $(28*8), RSP
36+ MOVD R30, (RSP)
37+
38+ // Create a struct callbackArgs on our stack.
39+ ADD $(callbackArgs__size + 3*8), RSP, R13
40+ MOVD R12, callbackArgs_index(R13) // callback index
41+ MOVD R14, R0
42+ MOVD R0, callbackArgs_args(R13) // address of args vector
43+ MOVD $0, R0
44+ MOVD R0, callbackArgs_result(R13) // result
45+
46+ // Move parameters into registers
47+ // Get the ABIInternal function pointer
48+ // without <ABIInternal> by using a closure.
49+ MOVD ·callbackWrap_call(SB), R0
50+ MOVD (R0), R0 // fn unsafe.Pointer
51+ MOVD R13, R1 // frame (&callbackArgs{...})
52+ MOVD $0, R3 // ctxt uintptr
53+
54+ BL crosscall2(SB)
55+
56+ // Get callback result.
57+ ADD $(callbackArgs__size + 3*8), RSP, R13
58+ MOVD callbackArgs_result(R13), R0
59+
60+ // Restore SP
61+ MOVD (RSP), R30
62+ ADD $(28*8), RSP
63+
64+ RET
M syscall_windows.go +21 -0
@@ -5,8 +5,19 @@ package purego
55
66 import (
77 "syscall"
8+ _ "unsafe" // only for go:linkname
9+
10+ "golang.org/x/sys/windows"
811 )
912
13+var syscall9XABI0 uintptr
14+
15+type syscall9Args struct {
16+ fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr
17+ f1, f2, f3, f4, f5, f6, f7, f8 uintptr
18+ r1, r2, err uintptr
19+}
20+
1021 func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
1122 r1, r2, errno := syscall.Syscall9(fn, 9, a1, a2, a3, a4, a5, a6, a7, a8, a9)
1223 return r1, r2, uintptr(errno)
@@ -22,3 +33,13 @@ func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2,
2233 func NewCallback(fn interface{}) uintptr {
2334 return syscall.NewCallback(fn)
2435 }
36+
37+//go:linkname openLibrary openLibrary
38+func openLibrary(name string) (uintptr, error) {
39+ handle, err := windows.LoadLibrary(name)
40+ return uintptr(handle), err
41+}
42+
43+func loadSymbol(handle uintptr, name string) (uintptr, error) {
44+ return windows.GetProcAddress(windows.Handle(handle), name)
45+}
@@ -5,8 +5,19 @@ package purego
5 5
6 import (6 import (
7 "syscall"7 "syscall"
8+ _ "unsafe" // only for go:linkname
9+
10+ "golang.org/x/sys/windows"
8 )11 )
9 12
13+var syscall9XABI0 uintptr
14+
15+type syscall9Args struct {
16+ fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr
17+ f1, f2, f3, f4, f5, f6, f7, f8 uintptr
18+ r1, r2, err uintptr
19+}
20+
10 func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {21 func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
11 r1, r2, errno := syscall.Syscall9(fn, 9, a1, a2, a3, a4, a5, a6, a7, a8, a9)22 r1, r2, errno := syscall.Syscall9(fn, 9, a1, a2, a3, a4, a5, a6, a7, a8, a9)
12 return r1, r2, uintptr(errno)23 return r1, r2, uintptr(errno)
@@ -22,3 +33,13 @@ func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2,
22 func NewCallback(fn interface{}) uintptr {33 func NewCallback(fn interface{}) uintptr {
23 return syscall.NewCallback(fn)34 return syscall.NewCallback(fn)
24 }35 }
36+
37+//go:linkname openLibrary openLibrary
38+func openLibrary(name string) (uintptr, error) {
39+ handle, err := windows.LoadLibrary(name)
40+ return uintptr(handle), err
41+}
42+
43+func loadSymbol(handle uintptr, name string) (uintptr, error) {
44+ return windows.GetProcAddress(windows.Handle(handle), name)
45+}

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