detached 1491ef7

purego: add Objective-C runtime bindings (#11)

commit
1491ef70dbf7295267f2db9ac6dc112232844eb2
author
TotallyGamerJet · · 2022-07-24 15:27 UTC
committer
GitHub · · 2022-07-24 15:27 UTC
parent
0a71637
signature
GPG Unverified4AEE18F83AFDEB23 The key is unknown
16 files · +6673 -24
M README.md +9 -0
@@ -2,3 +2,12 @@
22 [![Go Reference](https://pkg.go.dev/badge/github.com/ebitengine/purego?GOOS=darwin.svg)](https://pkg.go.dev/github.com/ebitengine/purego?GOOS=darwin)
33
44 A library for calling C functions from Go without Cgo.
5+
6+### External Code
7+
8+Purego uses code that originates from the Go runtime. These files are under the BSD-3
9+License that can be found [in the Go Source](https://github.com/golang/go/blob/master/LICENSE).
10+This is a list of the copied files:
11+
12+* `zcallback_darwin_*.s` from package `runtime`
13+* `internal/abi/abi_*.h` from package `runtime/cgo`⏎̸
@@ -2,3 +2,12 @@
2 [![Go Reference](https://pkg.go.dev/badge/github.com/ebitengine/purego?GOOS=darwin.svg)](https://pkg.go.dev/github.com/ebitengine/purego?GOOS=darwin)2 [![Go Reference](https://pkg.go.dev/badge/github.com/ebitengine/purego?GOOS=darwin.svg)](https://pkg.go.dev/github.com/ebitengine/purego?GOOS=darwin)
3 3
4 A library for calling C functions from Go without Cgo.4 A library for calling C functions from Go without Cgo.
5+
6+### External Code
7+
8+Purego uses code that originates from the Go runtime. These files are under the BSD-3
9+License that can be found [in the Go Source](https://github.com/golang/go/blob/master/LICENSE).
10+This is a list of the copied files:
11+
12+* `zcallback_darwin_*.s` from package `runtime`
13+* `internal/abi/abi_*.h` from package `runtime/cgo`⏎̸
M dlfcn.go +5 -17
@@ -1,5 +1,5 @@
11 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
44 //go:build darwin
55 // +build darwin
@@ -9,35 +9,23 @@ package purego
99 import (
1010 "runtime"
1111 "unsafe"
12+
13+ "github.com/ebitengine/purego/internal/strings"
1214 )
1315
1416 const RTLD_GLOBAL = 0x8
1517
1618 const RTLD_DEFAULT = ^uintptr(1)
1719
18-// hasSuffix tests whether the string s ends with suffix.
19-func hasSuffix(s, suffix string) bool {
20- return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
21-}
22-
23-func cString(name string) *byte {
24- if hasSuffix(name, "\x00") {
25- return &(*(*[]byte)(unsafe.Pointer(&name)))[0]
26- }
27- var b = make([]byte, len(name)+1)
28- copy(b, name)
29- return &b[0]
30-}
31-
3220 func Dlopen(name string, mode int) uintptr {
33- bs := cString(name)
21+ bs := strings.CString(name)
3422 ret, _, _ := SyscallN(dlopenABI0, uintptr(unsafe.Pointer(bs)), uintptr(mode), 0)
3523 runtime.KeepAlive(bs)
3624 return ret
3725 }
3826
3927 func Dlsym(handle uintptr, name string) uintptr {
40- bs := cString(name)
28+ bs := strings.CString(name)
4129 ret, _, _ := SyscallN(dlsymABI0, handle, uintptr(unsafe.Pointer(bs)), 0)
4230 runtime.KeepAlive(bs)
4331 return ret
@@ -1,5 +1,5 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4 //go:build darwin4 //go:build darwin
5 // +build darwin5 // +build darwin
@@ -9,35 +9,23 @@ package purego
9 import (9 import (
10 "runtime"10 "runtime"
11 "unsafe"11 "unsafe"
12+
13+ "github.com/ebitengine/purego/internal/strings"
12 )14 )
13 15
14 const RTLD_GLOBAL = 0x816 const RTLD_GLOBAL = 0x8
15 17
16 const RTLD_DEFAULT = ^uintptr(1)18 const RTLD_DEFAULT = ^uintptr(1)
17 19
18-// hasSuffix tests whether the string s ends with suffix.
19-func hasSuffix(s, suffix string) bool {
20- return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
21-}
22-
23-func cString(name string) *byte {
24- if hasSuffix(name, "\x00") {
25- return &(*(*[]byte)(unsafe.Pointer(&name)))[0]
26- }
27- var b = make([]byte, len(name)+1)
28- copy(b, name)
29- return &b[0]
30-}
31-
32 func Dlopen(name string, mode int) uintptr {20 func Dlopen(name string, mode int) uintptr {
33- bs := cString(name)21+ bs := strings.CString(name)
34 ret, _, _ := SyscallN(dlopenABI0, uintptr(unsafe.Pointer(bs)), uintptr(mode), 0)22 ret, _, _ := SyscallN(dlopenABI0, uintptr(unsafe.Pointer(bs)), uintptr(mode), 0)
35 runtime.KeepAlive(bs)23 runtime.KeepAlive(bs)
36 return ret24 return ret
37 }25 }
38 26
39 func Dlsym(handle uintptr, name string) uintptr {27 func Dlsym(handle uintptr, name string) uintptr {
40- bs := cString(name)28+ bs := strings.CString(name)
41 ret, _, _ := SyscallN(dlsymABI0, handle, uintptr(unsafe.Pointer(bs)), 0)29 ret, _, _ := SyscallN(dlsymABI0, handle, uintptr(unsafe.Pointer(bs)), 0)
42 runtime.KeepAlive(bs)30 runtime.KeepAlive(bs)
43 return ret31 return ret
M dlfcn_darwin.go +1 -1
@@ -1,5 +1,5 @@
11 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
44 package purego
55
@@ -1,5 +1,5 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4 package purego4 package purego
5 5
M dlfcn_stubs.s +1 -1
@@ -1,5 +1,5 @@
11 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
44 //go:build darwin
55 // +build darwin
@@ -1,5 +1,5 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4 //go:build darwin4 //go:build darwin
5 // +build darwin5 // +build darwin
M go_runtime.go +1 -1
@@ -1,5 +1,5 @@
11 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
44 //go:build darwin
55 // +build darwin
@@ -1,5 +1,5 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4 //go:build darwin4 //go:build darwin
5 // +build darwin5 // +build darwin
A internal/abi/abi_amd64.h +99 -0
@@ -0,0 +1,99 @@
1+// Copyright 2021 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+// Macros for transitioning from the host ABI to Go ABI0.
6+//
7+// These save the frame pointer, so in general, functions that use
8+// these should have zero frame size to suppress the automatic frame
9+// pointer, though it's harmless to not do this.
10+
11+#ifdef GOOS_windows
12+
13+// REGS_HOST_TO_ABI0_STACK is the stack bytes used by
14+// PUSH_REGS_HOST_TO_ABI0.
15+#define REGS_HOST_TO_ABI0_STACK (28*8 + 8)
16+
17+// PUSH_REGS_HOST_TO_ABI0 prepares for transitioning from
18+// the host ABI to Go ABI0 code. It saves all registers that are
19+// callee-save in the host ABI and caller-save in Go ABI0 and prepares
20+// for entry to Go.
21+//
22+// Save DI SI BP BX R12 R13 R14 R15 X6-X15 registers and the DF flag.
23+// Clear the DF flag for the Go ABI.
24+// MXCSR matches the Go ABI, so we don't have to set that,
25+// and Go doesn't modify it, so we don't have to save it.
26+#define PUSH_REGS_HOST_TO_ABI0() \
27+ PUSHFQ \
28+ CLD \
29+ ADJSP $(REGS_HOST_TO_ABI0_STACK - 8) \
30+ MOVQ DI, (0*0)(SP) \
31+ MOVQ SI, (1*8)(SP) \
32+ MOVQ BP, (2*8)(SP) \
33+ MOVQ BX, (3*8)(SP) \
34+ MOVQ R12, (4*8)(SP) \
35+ MOVQ R13, (5*8)(SP) \
36+ MOVQ R14, (6*8)(SP) \
37+ MOVQ R15, (7*8)(SP) \
38+ MOVUPS X6, (8*8)(SP) \
39+ MOVUPS X7, (10*8)(SP) \
40+ MOVUPS X8, (12*8)(SP) \
41+ MOVUPS X9, (14*8)(SP) \
42+ MOVUPS X10, (16*8)(SP) \
43+ MOVUPS X11, (18*8)(SP) \
44+ MOVUPS X12, (20*8)(SP) \
45+ MOVUPS X13, (22*8)(SP) \
46+ MOVUPS X14, (24*8)(SP) \
47+ MOVUPS X15, (26*8)(SP)
48+
49+#define POP_REGS_HOST_TO_ABI0() \
50+ MOVQ (0*0)(SP), DI \
51+ MOVQ (1*8)(SP), SI \
52+ MOVQ (2*8)(SP), BP \
53+ MOVQ (3*8)(SP), BX \
54+ MOVQ (4*8)(SP), R12 \
55+ MOVQ (5*8)(SP), R13 \
56+ MOVQ (6*8)(SP), R14 \
57+ MOVQ (7*8)(SP), R15 \
58+ MOVUPS (8*8)(SP), X6 \
59+ MOVUPS (10*8)(SP), X7 \
60+ MOVUPS (12*8)(SP), X8 \
61+ MOVUPS (14*8)(SP), X9 \
62+ MOVUPS (16*8)(SP), X10 \
63+ MOVUPS (18*8)(SP), X11 \
64+ MOVUPS (20*8)(SP), X12 \
65+ MOVUPS (22*8)(SP), X13 \
66+ MOVUPS (24*8)(SP), X14 \
67+ MOVUPS (26*8)(SP), X15 \
68+ ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8) \
69+ POPFQ
70+
71+#else
72+// SysV ABI
73+
74+#define REGS_HOST_TO_ABI0_STACK (6*8)
75+
76+// SysV MXCSR matches the Go ABI, so we don't have to set that,
77+// and Go doesn't modify it, so we don't have to save it.
78+// Both SysV and Go require DF to be cleared, so that's already clear.
79+// The SysV and Go frame pointer conventions are compatible.
80+#define PUSH_REGS_HOST_TO_ABI0() \
81+ ADJSP $(REGS_HOST_TO_ABI0_STACK) \
82+ MOVQ BP, (5*8)(SP) \
83+ LEAQ (5*8)(SP), BP \
84+ MOVQ BX, (0*8)(SP) \
85+ MOVQ R12, (1*8)(SP) \
86+ MOVQ R13, (2*8)(SP) \
87+ MOVQ R14, (3*8)(SP) \
88+ MOVQ R15, (4*8)(SP)
89+
90+#define POP_REGS_HOST_TO_ABI0() \
91+ MOVQ (0*8)(SP), BX \
92+ MOVQ (1*8)(SP), R12 \
93+ MOVQ (2*8)(SP), R13 \
94+ MOVQ (3*8)(SP), R14 \
95+ MOVQ (4*8)(SP), R15 \
96+ MOVQ (5*8)(SP), BP \
97+ ADJSP $-(REGS_HOST_TO_ABI0_STACK)
98+
99+#endif
@@ -0,0 +1,99 @@
1+// Copyright 2021 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+// Macros for transitioning from the host ABI to Go ABI0.
6+//
7+// These save the frame pointer, so in general, functions that use
8+// these should have zero frame size to suppress the automatic frame
9+// pointer, though it's harmless to not do this.
10+
11+#ifdef GOOS_windows
12+
13+// REGS_HOST_TO_ABI0_STACK is the stack bytes used by
14+// PUSH_REGS_HOST_TO_ABI0.
15+#define REGS_HOST_TO_ABI0_STACK (28*8 + 8)
16+
17+// PUSH_REGS_HOST_TO_ABI0 prepares for transitioning from
18+// the host ABI to Go ABI0 code. It saves all registers that are
19+// callee-save in the host ABI and caller-save in Go ABI0 and prepares
20+// for entry to Go.
21+//
22+// Save DI SI BP BX R12 R13 R14 R15 X6-X15 registers and the DF flag.
23+// Clear the DF flag for the Go ABI.
24+// MXCSR matches the Go ABI, so we don't have to set that,
25+// and Go doesn't modify it, so we don't have to save it.
26+#define PUSH_REGS_HOST_TO_ABI0() \
27+ PUSHFQ \
28+ CLD \
29+ ADJSP $(REGS_HOST_TO_ABI0_STACK - 8) \
30+ MOVQ DI, (0*0)(SP) \
31+ MOVQ SI, (1*8)(SP) \
32+ MOVQ BP, (2*8)(SP) \
33+ MOVQ BX, (3*8)(SP) \
34+ MOVQ R12, (4*8)(SP) \
35+ MOVQ R13, (5*8)(SP) \
36+ MOVQ R14, (6*8)(SP) \
37+ MOVQ R15, (7*8)(SP) \
38+ MOVUPS X6, (8*8)(SP) \
39+ MOVUPS X7, (10*8)(SP) \
40+ MOVUPS X8, (12*8)(SP) \
41+ MOVUPS X9, (14*8)(SP) \
42+ MOVUPS X10, (16*8)(SP) \
43+ MOVUPS X11, (18*8)(SP) \
44+ MOVUPS X12, (20*8)(SP) \
45+ MOVUPS X13, (22*8)(SP) \
46+ MOVUPS X14, (24*8)(SP) \
47+ MOVUPS X15, (26*8)(SP)
48+
49+#define POP_REGS_HOST_TO_ABI0() \
50+ MOVQ (0*0)(SP), DI \
51+ MOVQ (1*8)(SP), SI \
52+ MOVQ (2*8)(SP), BP \
53+ MOVQ (3*8)(SP), BX \
54+ MOVQ (4*8)(SP), R12 \
55+ MOVQ (5*8)(SP), R13 \
56+ MOVQ (6*8)(SP), R14 \
57+ MOVQ (7*8)(SP), R15 \
58+ MOVUPS (8*8)(SP), X6 \
59+ MOVUPS (10*8)(SP), X7 \
60+ MOVUPS (12*8)(SP), X8 \
61+ MOVUPS (14*8)(SP), X9 \
62+ MOVUPS (16*8)(SP), X10 \
63+ MOVUPS (18*8)(SP), X11 \
64+ MOVUPS (20*8)(SP), X12 \
65+ MOVUPS (22*8)(SP), X13 \
66+ MOVUPS (24*8)(SP), X14 \
67+ MOVUPS (26*8)(SP), X15 \
68+ ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8) \
69+ POPFQ
70+
71+#else
72+// SysV ABI
73+
74+#define REGS_HOST_TO_ABI0_STACK (6*8)
75+
76+// SysV MXCSR matches the Go ABI, so we don't have to set that,
77+// and Go doesn't modify it, so we don't have to save it.
78+// Both SysV and Go require DF to be cleared, so that's already clear.
79+// The SysV and Go frame pointer conventions are compatible.
80+#define PUSH_REGS_HOST_TO_ABI0() \
81+ ADJSP $(REGS_HOST_TO_ABI0_STACK) \
82+ MOVQ BP, (5*8)(SP) \
83+ LEAQ (5*8)(SP), BP \
84+ MOVQ BX, (0*8)(SP) \
85+ MOVQ R12, (1*8)(SP) \
86+ MOVQ R13, (2*8)(SP) \
87+ MOVQ R14, (3*8)(SP) \
88+ MOVQ R15, (4*8)(SP)
89+
90+#define POP_REGS_HOST_TO_ABI0() \
91+ MOVQ (0*8)(SP), BX \
92+ MOVQ (1*8)(SP), R12 \
93+ MOVQ (2*8)(SP), R13 \
94+ MOVQ (3*8)(SP), R14 \
95+ MOVQ (4*8)(SP), R15 \
96+ MOVQ (5*8)(SP), BP \
97+ ADJSP $-(REGS_HOST_TO_ABI0_STACK)
98+
99+#endif
A internal/abi/abi_arm64.h +39 -0
@@ -0,0 +1,39 @@
1+// Copyright 2021 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+// Macros for transitioning from the host ABI to Go ABI0.
6+//
7+// These macros save and restore the callee-saved registers
8+// from the stack, but they don't adjust stack pointer, so
9+// the user should prepare stack space in advance.
10+// SAVE_R19_TO_R28(offset) saves R19 ~ R28 to the stack space
11+// of ((offset)+0*8)(RSP) ~ ((offset)+9*8)(RSP).
12+//
13+// SAVE_F8_TO_F15(offset) saves F8 ~ F15 to the stack space
14+// of ((offset)+0*8)(RSP) ~ ((offset)+7*8)(RSP).
15+//
16+// R29 is not saved because Go will save and restore it.
17+
18+#define SAVE_R19_TO_R28(offset) \
19+ STP (R19, R20), ((offset)+0*8)(RSP) \
20+ STP (R21, R22), ((offset)+2*8)(RSP) \
21+ STP (R23, R24), ((offset)+4*8)(RSP) \
22+ STP (R25, R26), ((offset)+6*8)(RSP) \
23+ STP (R27, g), ((offset)+8*8)(RSP)
24+#define RESTORE_R19_TO_R28(offset) \
25+ LDP ((offset)+0*8)(RSP), (R19, R20) \
26+ LDP ((offset)+2*8)(RSP), (R21, R22) \
27+ LDP ((offset)+4*8)(RSP), (R23, R24) \
28+ LDP ((offset)+6*8)(RSP), (R25, R26) \
29+ LDP ((offset)+8*8)(RSP), (R27, g) /* R28 */
30+#define SAVE_F8_TO_F15(offset) \
31+ FSTPD (F8, F9), ((offset)+0*8)(RSP) \
32+ FSTPD (F10, F11), ((offset)+2*8)(RSP) \
33+ FSTPD (F12, F13), ((offset)+4*8)(RSP) \
34+ FSTPD (F14, F15), ((offset)+6*8)(RSP)
35+#define RESTORE_F8_TO_F15(offset) \
36+ FLDPD ((offset)+0*8)(RSP), (F8, F9) \
37+ FLDPD ((offset)+2*8)(RSP), (F10, F11) \
38+ FLDPD ((offset)+4*8)(RSP), (F12, F13) \
39+ FLDPD ((offset)+6*8)(RSP), (F14, F15)
@@ -0,0 +1,39 @@
1+// Copyright 2021 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+// Macros for transitioning from the host ABI to Go ABI0.
6+//
7+// These macros save and restore the callee-saved registers
8+// from the stack, but they don't adjust stack pointer, so
9+// the user should prepare stack space in advance.
10+// SAVE_R19_TO_R28(offset) saves R19 ~ R28 to the stack space
11+// of ((offset)+0*8)(RSP) ~ ((offset)+9*8)(RSP).
12+//
13+// SAVE_F8_TO_F15(offset) saves F8 ~ F15 to the stack space
14+// of ((offset)+0*8)(RSP) ~ ((offset)+7*8)(RSP).
15+//
16+// R29 is not saved because Go will save and restore it.
17+
18+#define SAVE_R19_TO_R28(offset) \
19+ STP (R19, R20), ((offset)+0*8)(RSP) \
20+ STP (R21, R22), ((offset)+2*8)(RSP) \
21+ STP (R23, R24), ((offset)+4*8)(RSP) \
22+ STP (R25, R26), ((offset)+6*8)(RSP) \
23+ STP (R27, g), ((offset)+8*8)(RSP)
24+#define RESTORE_R19_TO_R28(offset) \
25+ LDP ((offset)+0*8)(RSP), (R19, R20) \
26+ LDP ((offset)+2*8)(RSP), (R21, R22) \
27+ LDP ((offset)+4*8)(RSP), (R23, R24) \
28+ LDP ((offset)+6*8)(RSP), (R25, R26) \
29+ LDP ((offset)+8*8)(RSP), (R27, g) /* R28 */
30+#define SAVE_F8_TO_F15(offset) \
31+ FSTPD (F8, F9), ((offset)+0*8)(RSP) \
32+ FSTPD (F10, F11), ((offset)+2*8)(RSP) \
33+ FSTPD (F12, F13), ((offset)+4*8)(RSP) \
34+ FSTPD (F14, F15), ((offset)+6*8)(RSP)
35+#define RESTORE_F8_TO_F15(offset) \
36+ FLDPD ((offset)+0*8)(RSP), (F8, F9) \
37+ FLDPD ((offset)+2*8)(RSP), (F10, F11) \
38+ FLDPD ((offset)+4*8)(RSP), (F12, F13) \
39+ FLDPD ((offset)+6*8)(RSP), (F14, F15)
A internal/strings/strings.go +21 -0
@@ -0,0 +1,21 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+package strings
5+
6+import "unsafe"
7+
8+// hasSuffix tests whether the string s ends with suffix.
9+func hasSuffix(s, suffix string) bool {
10+ return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
11+}
12+
13+// CString converts a go string to *byte that can be passed to C code.
14+func CString(name string) *byte {
15+ if hasSuffix(name, "\x00") {
16+ return &(*(*[]byte)(unsafe.Pointer(&name)))[0]
17+ }
18+ var b = make([]byte, len(name)+1)
19+ copy(b, name)
20+ return &b[0]
21+}
@@ -0,0 +1,21 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+package strings
5+
6+import "unsafe"
7+
8+// hasSuffix tests whether the string s ends with suffix.
9+func hasSuffix(s, suffix string) bool {
10+ return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
11+}
12+
13+// CString converts a go string to *byte that can be passed to C code.
14+func CString(name string) *byte {
15+ if hasSuffix(name, "\x00") {
16+ return &(*(*[]byte)(unsafe.Pointer(&name)))[0]
17+ }
18+ var b = make([]byte, len(name)+1)
19+ copy(b, name)
20+ return &b[0]
21+}
A objc/objc_runtime_darwin.go +190 -0
@@ -0,0 +1,190 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+// Package objc is a low-level pure Go objective-c runtime. This package is easy to use incorrectly, so it is best
5+// to use a wrapper that provides the functionality you need in a safer way.
6+package objc
7+
8+import (
9+ "fmt"
10+ "reflect"
11+ "runtime"
12+ "unsafe"
13+
14+ "github.com/ebitengine/purego"
15+ "github.com/ebitengine/purego/internal/strings"
16+)
17+
18+//TODO: support try/catch?
19+//https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
20+
21+var (
22+ objc = purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
23+
24+ objc_msgSend = purego.Dlsym(objc, "objc_msgSend")
25+ objc_msgSendSuper2 = purego.Dlsym(objc, "objc_msgSendSuper2")
26+ objc_getClass = purego.Dlsym(objc, "objc_getClass")
27+ objc_allocateClassPair = purego.Dlsym(objc, "objc_allocateClassPair")
28+ objc_registerClassPair = purego.Dlsym(objc, "objc_registerClassPair")
29+ sel_registerName = purego.Dlsym(objc, "sel_registerName")
30+ class_getSuperclass = purego.Dlsym(objc, "class_getSuperclass")
31+ class_addMethod = purego.Dlsym(objc, "class_addMethod")
32+ object_getClass = purego.Dlsym(objc, "object_getClass")
33+)
34+
35+// ID is an opaque pointer to some Objective-C object
36+type ID uintptr
37+
38+// Class returns the class of the object.
39+func (id ID) Class() Class {
40+ ret, _, _ := purego.SyscallN(object_getClass, uintptr(id))
41+ return Class(ret)
42+}
43+
44+// Send is a convenience method for sending messages to objects.
45+func (id ID) Send(sel SEL, args ...interface{}) ID {
46+ tmp := createArgs(id, sel, args...)
47+ ret, _, _ := purego.SyscallN(objc_msgSend, tmp...)
48+ return ID(ret)
49+}
50+
51+// objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword
52+// as the receiver of a message. It specifies the class definition of the particular superclass that should
53+// be messaged.
54+type objc_super struct {
55+ receiver ID
56+ superClass Class
57+}
58+
59+// SendSuper is a convenience method for sending message to object's super
60+func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
61+ var super = &objc_super{
62+ receiver: id,
63+ superClass: id.Class(),
64+ }
65+ tmp := createArgs(0, sel, args...)
66+ tmp[0] = uintptr(unsafe.Pointer(super)) // if createArgs splits the stack the pointer would be wrong
67+ ret, _, _ := purego.SyscallN(objc_msgSendSuper2, tmp...)
68+ return ID(ret)
69+}
70+
71+func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr) {
72+ out = make([]uintptr, 2, len(args)+2)
73+ out[0] = uintptr(cls)
74+ out[1] = uintptr(sel)
75+ for _, a := range args {
76+ switch v := a.(type) {
77+ case ID:
78+ out = append(out, uintptr(v))
79+ case Class:
80+ out = append(out, uintptr(v))
81+ case SEL:
82+ out = append(out, uintptr(v))
83+ case _IMP:
84+ out = append(out, uintptr(v))
85+ case bool:
86+ if v {
87+ out = append(out, uintptr(1))
88+ } else {
89+ out = append(out, uintptr(0))
90+ }
91+ case unsafe.Pointer:
92+ out = append(out, uintptr(v))
93+ case uintptr:
94+ out = append(out, v)
95+ case int:
96+ out = append(out, uintptr(v))
97+ case uint:
98+ out = append(out, uintptr(v))
99+ default:
100+ panic(fmt.Sprintf("objc: unknown type %T", v))
101+ }
102+ }
103+ return out
104+}
105+
106+// SEL is an opaque type that represents a method selector
107+type SEL uintptr
108+
109+// RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector,
110+// and returns the selector value.
111+func RegisterName(name string) SEL {
112+ n := strings.CString(name)
113+ ret, _, _ := purego.SyscallN(sel_registerName, uintptr(unsafe.Pointer(n)))
114+ runtime.KeepAlive(n)
115+ return SEL(ret)
116+}
117+
118+// Class is an opaque type that represents an Objective-C class.
119+type Class uintptr
120+
121+// GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime.
122+func GetClass(name string) Class {
123+ n := strings.CString(name)
124+ ret, _, _ := purego.SyscallN(objc_getClass, uintptr(unsafe.Pointer(n)))
125+ runtime.KeepAlive(n)
126+ return Class(ret)
127+}
128+
129+// AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created
130+func AllocateClassPair(super Class, name string, extraBytes uintptr) Class {
131+ n := strings.CString(name)
132+ ret, _, _ := purego.SyscallN(objc_allocateClassPair, uintptr(super), uintptr(unsafe.Pointer(n)), extraBytes)
133+ runtime.KeepAlive(n)
134+ return Class(ret)
135+}
136+
137+// SuperClass returns the superclass of a class.
138+// You should usually use NSObject‘s superclass method instead of this function.
139+func (c Class) SuperClass() Class {
140+ ret, _, _ := purego.SyscallN(class_getSuperclass, uintptr(c))
141+ return Class(ret)
142+}
143+
144+// AddMethod adds a new method to a class with a given name and implementation.
145+// The types argument is a string containing the mapping of parameters and return type.
146+// Since the function must take at least two arguments—self and _cmd, the second and third
147+// characters must be “@:” (the first character is the return type).
148+func (c Class) AddMethod(name SEL, imp _IMP, types string) bool {
149+ t := strings.CString(types)
150+ ret, _, _ := purego.SyscallN(class_addMethod, uintptr(c), uintptr(name), uintptr(imp), uintptr(unsafe.Pointer(t)))
151+ runtime.KeepAlive(t)
152+ return byte(ret) != 0
153+}
154+
155+// Register registers a class that was allocated using objc_allocateClassPair.
156+// It can now be used to make objects by sending it either alloc and init or new.
157+func (c Class) Register() {
158+ purego.SyscallN(objc_registerClassPair, uintptr(c))
159+}
160+
161+// _IMP is unexported so that the only way to make this type is by providing a Go function and casting
162+// it with the IMP function
163+type _IMP uintptr
164+
165+// IMP takes a Go function that takes (id, SEL) as its first two arguments. It returns an _IMP function
166+// pointer that can be called by Objective-C code. The function pointer is never deallocated.
167+func IMP(fn interface{}) _IMP {
168+ // this is only here so that it is easier to port C code to Go.
169+ // this is not guaranteed to be here forever so make sure to port your callbacks to Go
170+ // If you have a C function pointer cast it to a uintptr before passing it
171+ // to this function.
172+ if x, ok := fn.(uintptr); ok {
173+ return _IMP(x)
174+ }
175+ val := reflect.ValueOf(fn)
176+ if val.Kind() != reflect.Func {
177+ panic("objc: not a function")
178+ }
179+ // IMP is stricter than a normal callback
180+ // id (*IMP)(id, SEL, ...)
181+ switch {
182+ case val.Type().NumIn() < 2:
183+ fallthrough
184+ case val.Type().In(0).Kind() != reflect.Uintptr:
185+ fallthrough
186+ case val.Type().In(1).Kind() != reflect.Uintptr:
187+ panic("objc: IMP must take a (id, SEL) as its first two arguments")
188+ }
189+ return _IMP(purego.NewCallback(fn))
190+}
@@ -0,0 +1,190 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+// Package objc is a low-level pure Go objective-c runtime. This package is easy to use incorrectly, so it is best
5+// to use a wrapper that provides the functionality you need in a safer way.
6+package objc
7+
8+import (
9+ "fmt"
10+ "reflect"
11+ "runtime"
12+ "unsafe"
13+
14+ "github.com/ebitengine/purego"
15+ "github.com/ebitengine/purego/internal/strings"
16+)
17+
18+//TODO: support try/catch?
19+//https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
20+
21+var (
22+ objc = purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
23+
24+ objc_msgSend = purego.Dlsym(objc, "objc_msgSend")
25+ objc_msgSendSuper2 = purego.Dlsym(objc, "objc_msgSendSuper2")
26+ objc_getClass = purego.Dlsym(objc, "objc_getClass")
27+ objc_allocateClassPair = purego.Dlsym(objc, "objc_allocateClassPair")
28+ objc_registerClassPair = purego.Dlsym(objc, "objc_registerClassPair")
29+ sel_registerName = purego.Dlsym(objc, "sel_registerName")
30+ class_getSuperclass = purego.Dlsym(objc, "class_getSuperclass")
31+ class_addMethod = purego.Dlsym(objc, "class_addMethod")
32+ object_getClass = purego.Dlsym(objc, "object_getClass")
33+)
34+
35+// ID is an opaque pointer to some Objective-C object
36+type ID uintptr
37+
38+// Class returns the class of the object.
39+func (id ID) Class() Class {
40+ ret, _, _ := purego.SyscallN(object_getClass, uintptr(id))
41+ return Class(ret)
42+}
43+
44+// Send is a convenience method for sending messages to objects.
45+func (id ID) Send(sel SEL, args ...interface{}) ID {
46+ tmp := createArgs(id, sel, args...)
47+ ret, _, _ := purego.SyscallN(objc_msgSend, tmp...)
48+ return ID(ret)
49+}
50+
51+// objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword
52+// as the receiver of a message. It specifies the class definition of the particular superclass that should
53+// be messaged.
54+type objc_super struct {
55+ receiver ID
56+ superClass Class
57+}
58+
59+// SendSuper is a convenience method for sending message to object's super
60+func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
61+ var super = &objc_super{
62+ receiver: id,
63+ superClass: id.Class(),
64+ }
65+ tmp := createArgs(0, sel, args...)
66+ tmp[0] = uintptr(unsafe.Pointer(super)) // if createArgs splits the stack the pointer would be wrong
67+ ret, _, _ := purego.SyscallN(objc_msgSendSuper2, tmp...)
68+ return ID(ret)
69+}
70+
71+func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr) {
72+ out = make([]uintptr, 2, len(args)+2)
73+ out[0] = uintptr(cls)
74+ out[1] = uintptr(sel)
75+ for _, a := range args {
76+ switch v := a.(type) {
77+ case ID:
78+ out = append(out, uintptr(v))
79+ case Class:
80+ out = append(out, uintptr(v))
81+ case SEL:
82+ out = append(out, uintptr(v))
83+ case _IMP:
84+ out = append(out, uintptr(v))
85+ case bool:
86+ if v {
87+ out = append(out, uintptr(1))
88+ } else {
89+ out = append(out, uintptr(0))
90+ }
91+ case unsafe.Pointer:
92+ out = append(out, uintptr(v))
93+ case uintptr:
94+ out = append(out, v)
95+ case int:
96+ out = append(out, uintptr(v))
97+ case uint:
98+ out = append(out, uintptr(v))
99+ default:
100+ panic(fmt.Sprintf("objc: unknown type %T", v))
101+ }
102+ }
103+ return out
104+}
105+
106+// SEL is an opaque type that represents a method selector
107+type SEL uintptr
108+
109+// RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector,
110+// and returns the selector value.
111+func RegisterName(name string) SEL {
112+ n := strings.CString(name)
113+ ret, _, _ := purego.SyscallN(sel_registerName, uintptr(unsafe.Pointer(n)))
114+ runtime.KeepAlive(n)
115+ return SEL(ret)
116+}
117+
118+// Class is an opaque type that represents an Objective-C class.
119+type Class uintptr
120+
121+// GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime.
122+func GetClass(name string) Class {
123+ n := strings.CString(name)
124+ ret, _, _ := purego.SyscallN(objc_getClass, uintptr(unsafe.Pointer(n)))
125+ runtime.KeepAlive(n)
126+ return Class(ret)
127+}
128+
129+// AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created
130+func AllocateClassPair(super Class, name string, extraBytes uintptr) Class {
131+ n := strings.CString(name)
132+ ret, _, _ := purego.SyscallN(objc_allocateClassPair, uintptr(super), uintptr(unsafe.Pointer(n)), extraBytes)
133+ runtime.KeepAlive(n)
134+ return Class(ret)
135+}
136+
137+// SuperClass returns the superclass of a class.
138+// You should usually use NSObject‘s superclass method instead of this function.
139+func (c Class) SuperClass() Class {
140+ ret, _, _ := purego.SyscallN(class_getSuperclass, uintptr(c))
141+ return Class(ret)
142+}
143+
144+// AddMethod adds a new method to a class with a given name and implementation.
145+// The types argument is a string containing the mapping of parameters and return type.
146+// Since the function must take at least two arguments—self and _cmd, the second and third
147+// characters must be “@:” (the first character is the return type).
148+func (c Class) AddMethod(name SEL, imp _IMP, types string) bool {
149+ t := strings.CString(types)
150+ ret, _, _ := purego.SyscallN(class_addMethod, uintptr(c), uintptr(name), uintptr(imp), uintptr(unsafe.Pointer(t)))
151+ runtime.KeepAlive(t)
152+ return byte(ret) != 0
153+}
154+
155+// Register registers a class that was allocated using objc_allocateClassPair.
156+// It can now be used to make objects by sending it either alloc and init or new.
157+func (c Class) Register() {
158+ purego.SyscallN(objc_registerClassPair, uintptr(c))
159+}
160+
161+// _IMP is unexported so that the only way to make this type is by providing a Go function and casting
162+// it with the IMP function
163+type _IMP uintptr
164+
165+// IMP takes a Go function that takes (id, SEL) as its first two arguments. It returns an _IMP function
166+// pointer that can be called by Objective-C code. The function pointer is never deallocated.
167+func IMP(fn interface{}) _IMP {
168+ // this is only here so that it is easier to port C code to Go.
169+ // this is not guaranteed to be here forever so make sure to port your callbacks to Go
170+ // If you have a C function pointer cast it to a uintptr before passing it
171+ // to this function.
172+ if x, ok := fn.(uintptr); ok {
173+ return _IMP(x)
174+ }
175+ val := reflect.ValueOf(fn)
176+ if val.Kind() != reflect.Func {
177+ panic("objc: not a function")
178+ }
179+ // IMP is stricter than a normal callback
180+ // id (*IMP)(id, SEL, ...)
181+ switch {
182+ case val.Type().NumIn() < 2:
183+ fallthrough
184+ case val.Type().In(0).Kind() != reflect.Uintptr:
185+ fallthrough
186+ case val.Type().In(1).Kind() != reflect.Uintptr:
187+ panic("objc: IMP must take a (id, SEL) as its first two arguments")
188+ }
189+ return _IMP(purego.NewCallback(fn))
190+}
M sys_darwin_amd64.s +65 -1
@@ -1,8 +1,10 @@
11 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
44 #include "textflag.h"
5+#include "internal/abi/abi_amd64.h"
56 #include "go_asm.h"
7+#include "funcdata.h"
68
79 // syscall9X calls a function in libc on behalf of the syscall package.
810 // syscall9X takes a pointer to a struct like:
@@ -69,3 +71,65 @@ TEXT syscall9X(SB), NOSPLIT, $0
6971 MOVQ BP, SP
7072 POPQ BP
7173 RET
74+
75+// runtime·cgocallback expects a call to the ABIInternal function
76+// However, the tag <ABIInternal> is only available in the runtime :(
77+// This is a small wrapper function that moves the parameter from AX to the stack
78+// where the Go function can find it. It then calls callbackWrap
79+TEXT callbackWrapInternal<>(SB), $0-0
80+ MOVQ AX, 0(SP)
81+ CALL ·callbackWrap(SB)
82+ RET
83+
84+TEXT callbackasm1(SB), NOSPLIT, $0
85+ // remove return address from stack, we are not returning to callbackasm, but to its caller.
86+ MOVQ 0(SP), AX
87+ ADDQ $8, SP
88+
89+ // make space for first six arguments below the frame
90+ // TODO: check to make sure that arguments 7 and above are right after these six
91+ ADJSP $6*8, SP
92+ MOVQ DI, 0(SP)
93+ MOVQ SI, 8(SP)
94+ MOVQ DX, 16(SP)
95+ MOVQ CX, 24(SP)
96+ MOVQ R8, 32(SP)
97+ MOVQ R9, 40(SP)
98+ LEAQ (SP), R8 // R8 = address of args vector
99+
100+ // determine index into runtime·cbs table
101+ MOVQ $callbackasm(SB), DX
102+ SUBQ DX, AX
103+ MOVQ $0, DX
104+ MOVQ $5, CX // divide by 5 because each call instruction in ·callbacks is 5 bytes long
105+ DIVL CX
106+ SUBQ $1, AX // subtract 1 because return PC is to the next slot
107+
108+ // Switch from the host ABI to the Go ABI.
109+ PUSH_REGS_HOST_TO_ABI0()
110+
111+ // Create a struct callbackArgs on our stack to be passed as
112+ // the "frame" to cgocallback and on to callbackWrap.
113+ // $24 to make enough room for the arguments to runtime.cgocallback
114+ SUBQ $(24+callbackArgs__size), SP
115+ MOVQ AX, (24+callbackArgs_index)(SP) // callback index
116+ MOVQ R8, (24+callbackArgs_args)(SP) // address of args vector
117+ MOVQ $0, (24+callbackArgs_result)(SP) // result
118+ LEAQ 24(SP), AX // take the address of callbackArgs
119+
120+ // Call cgocallback, which will call callbackWrap(frame).
121+ MOVQ $0, 16(SP) // context
122+ MOVQ AX, 8(SP) // frame (address of callbackArgs)
123+ MOVQ $callbackWrapInternal<>(SB), BX
124+ MOVQ BX, 0(SP) // PC of function value to call (callbackWrap)
125+ CALL runtime·cgocallback(SB) // runtime.cgocallback(fn, frame, ctxt uintptr)
126+
127+ // Get callback result.
128+ MOVQ (24+callbackArgs_result)(SP), AX
129+ ADDQ $(24+callbackArgs__size), SP // remove callbackArgs struct
130+
131+ POP_REGS_HOST_TO_ABI0()
132+
133+ ADJSP $-6*8, SP // remove arguments
134+
135+ RET
@@ -1,8 +1,10 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4 #include "textflag.h"4 #include "textflag.h"
5+#include "internal/abi/abi_amd64.h"
5 #include "go_asm.h"6 #include "go_asm.h"
7+#include "funcdata.h"
6 8
7 // syscall9X calls a function in libc on behalf of the syscall package.9 // syscall9X calls a function in libc on behalf of the syscall package.
8 // syscall9X takes a pointer to a struct like:10 // syscall9X takes a pointer to a struct like:
@@ -69,3 +71,65 @@ TEXT syscall9X(SB), NOSPLIT, $0
69 MOVQ BP, SP71 MOVQ BP, SP
70 POPQ BP72 POPQ BP
71 RET73 RET
74+
75+// runtime·cgocallback expects a call to the ABIInternal function
76+// However, the tag <ABIInternal> is only available in the runtime :(
77+// This is a small wrapper function that moves the parameter from AX to the stack
78+// where the Go function can find it. It then calls callbackWrap
79+TEXT callbackWrapInternal<>(SB), $0-0
80+ MOVQ AX, 0(SP)
81+ CALL ·callbackWrap(SB)
82+ RET
83+
84+TEXT callbackasm1(SB), NOSPLIT, $0
85+ // remove return address from stack, we are not returning to callbackasm, but to its caller.
86+ MOVQ 0(SP), AX
87+ ADDQ $8, SP
88+
89+ // make space for first six arguments below the frame
90+ // TODO: check to make sure that arguments 7 and above are right after these six
91+ ADJSP $6*8, SP
92+ MOVQ DI, 0(SP)
93+ MOVQ SI, 8(SP)
94+ MOVQ DX, 16(SP)
95+ MOVQ CX, 24(SP)
96+ MOVQ R8, 32(SP)
97+ MOVQ R9, 40(SP)
98+ LEAQ (SP), R8 // R8 = address of args vector
99+
100+ // determine index into runtime·cbs table
101+ MOVQ $callbackasm(SB), DX
102+ SUBQ DX, AX
103+ MOVQ $0, DX
104+ MOVQ $5, CX // divide by 5 because each call instruction in ·callbacks is 5 bytes long
105+ DIVL CX
106+ SUBQ $1, AX // subtract 1 because return PC is to the next slot
107+
108+ // Switch from the host ABI to the Go ABI.
109+ PUSH_REGS_HOST_TO_ABI0()
110+
111+ // Create a struct callbackArgs on our stack to be passed as
112+ // the "frame" to cgocallback and on to callbackWrap.
113+ // $24 to make enough room for the arguments to runtime.cgocallback
114+ SUBQ $(24+callbackArgs__size), SP
115+ MOVQ AX, (24+callbackArgs_index)(SP) // callback index
116+ MOVQ R8, (24+callbackArgs_args)(SP) // address of args vector
117+ MOVQ $0, (24+callbackArgs_result)(SP) // result
118+ LEAQ 24(SP), AX // take the address of callbackArgs
119+
120+ // Call cgocallback, which will call callbackWrap(frame).
121+ MOVQ $0, 16(SP) // context
122+ MOVQ AX, 8(SP) // frame (address of callbackArgs)
123+ MOVQ $callbackWrapInternal<>(SB), BX
124+ MOVQ BX, 0(SP) // PC of function value to call (callbackWrap)
125+ CALL runtime·cgocallback(SB) // runtime.cgocallback(fn, frame, ctxt uintptr)
126+
127+ // Get callback result.
128+ MOVQ (24+callbackArgs_result)(SP), AX
129+ ADDQ $(24+callbackArgs__size), SP // remove callbackArgs struct
130+
131+ POP_REGS_HOST_TO_ABI0()
132+
133+ ADJSP $-6*8, SP // remove arguments
134+
135+ RET
M sys_darwin_arm64.s +73 -1
@@ -1,8 +1,10 @@
11 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
44 #include "textflag.h"
55 #include "go_asm.h"
6+#include "funcdata.h"
7+#include "internal/abi/abi_arm64.h"
68
79 // syscall9X calls a function in libc on behalf of the syscall package.
810 // syscall9X takes a pointer to a struct like:
@@ -60,3 +62,73 @@ TEXT syscall9X(SB), NOSPLIT, $0
6062 MOVD R0, syscall9Args_r1(R2) // save r1
6163 MOVD R1, syscall9Args_r2(R2) // save r2
6264 RET
65+
66+// runtime·cgocallback expects a call to the ABIInternal function
67+// However, the tag <ABIInternal> is only available in the runtime :(
68+// This is a small wrapper function that moves the parameter from R0 to the stack
69+// where the Go function can find it. It then branches without link.
70+TEXT callbackWrapInternal<>(SB), NOSPLIT, $0-0
71+ MOVD R0, 8(RSP)
72+ B ·callbackWrap(SB)
73+ RET
74+
75+TEXT callbackasm1(SB), NOSPLIT, $208-0
76+ NO_LOCAL_POINTERS
77+
78+ // On entry, the trampoline in zcallback_windows_arm64.s left
79+ // the callback index in R12 (which is volatile in the C ABI).
80+
81+ // Save callback register arguments R0-R7.
82+ // We do this at the top of the frame so they're contiguous with stack arguments.
83+ // The 7*8 setting up R14 looks like a bug but is not: the eighth word
84+ // is the space the assembler reserved for our caller's frame pointer,
85+ // but we are not called from Go so that space is ours to use,
86+ // and we must to be contiguous with the stack arguments.
87+ MOVD $arg0-(7*8)(SP), R14
88+ STP (R0, R1), (0*8)(R14)
89+ STP (R2, R3), (2*8)(R14)
90+ STP (R4, R5), (4*8)(R14)
91+ STP (R6, R7), (6*8)(R14)
92+
93+ // Create a struct callbackArgs on our stack.
94+ MOVD $cbargs-(18*8+callbackArgs__size)(SP), 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+ MOVD $callbackWrapInternal<>(SB), R0 // fn unsafe.Pointer
103+ MOVD R13, R1 // frame (&callbackArgs{...})
104+ MOVD $0, R3 // ctxt uintptr
105+
106+ // We still need to save all callee save register as before, and then
107+ // push 3 args for fn (R0, R1, R3), skipping R2.
108+ // Also note that at procedure entry in gc world, 8(RSP) will be the
109+ // first arg.
110+ SUB $(8*24), RSP
111+ STP (R0, R1), (8*1)(RSP)
112+ MOVD R3, (8*3)(RSP)
113+
114+ // Push C callee-save registers R19-R28.
115+ // LR, FP already saved.
116+ SAVE_R19_TO_R28(8*4)
117+ SAVE_F8_TO_F15(8*14)
118+ STP (R29, R30), (8*22)(RSP)
119+
120+ // Initialize Go ABI environment
121+ BL runtime·load_g(SB)
122+ BL runtime·cgocallback(SB)
123+
124+ RESTORE_R19_TO_R28(8*4)
125+ RESTORE_F8_TO_F15(8*14)
126+ LDP (8*22)(RSP), (R29, R30)
127+
128+ ADD $(8*24), RSP
129+
130+ // Get callback result.
131+ MOVD $cbargs-(18*8+callbackArgs__size)(SP), R13
132+ MOVD callbackArgs_result(R13), R0
133+
134+ RET
@@ -1,8 +1,10 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4 #include "textflag.h"4 #include "textflag.h"
5 #include "go_asm.h"5 #include "go_asm.h"
6+#include "funcdata.h"
7+#include "internal/abi/abi_arm64.h"
6 8
7 // syscall9X calls a function in libc on behalf of the syscall package.9 // syscall9X calls a function in libc on behalf of the syscall package.
8 // syscall9X takes a pointer to a struct like:10 // syscall9X takes a pointer to a struct like:
@@ -60,3 +62,73 @@ TEXT syscall9X(SB), NOSPLIT, $0
60 MOVD R0, syscall9Args_r1(R2) // save r162 MOVD R0, syscall9Args_r1(R2) // save r1
61 MOVD R1, syscall9Args_r2(R2) // save r263 MOVD R1, syscall9Args_r2(R2) // save r2
62 RET64 RET
65+
66+// runtime·cgocallback expects a call to the ABIInternal function
67+// However, the tag <ABIInternal> is only available in the runtime :(
68+// This is a small wrapper function that moves the parameter from R0 to the stack
69+// where the Go function can find it. It then branches without link.
70+TEXT callbackWrapInternal<>(SB), NOSPLIT, $0-0
71+ MOVD R0, 8(RSP)
72+ B ·callbackWrap(SB)
73+ RET
74+
75+TEXT callbackasm1(SB), NOSPLIT, $208-0
76+ NO_LOCAL_POINTERS
77+
78+ // On entry, the trampoline in zcallback_windows_arm64.s left
79+ // the callback index in R12 (which is volatile in the C ABI).
80+
81+ // Save callback register arguments R0-R7.
82+ // We do this at the top of the frame so they're contiguous with stack arguments.
83+ // The 7*8 setting up R14 looks like a bug but is not: the eighth word
84+ // is the space the assembler reserved for our caller's frame pointer,
85+ // but we are not called from Go so that space is ours to use,
86+ // and we must to be contiguous with the stack arguments.
87+ MOVD $arg0-(7*8)(SP), R14
88+ STP (R0, R1), (0*8)(R14)
89+ STP (R2, R3), (2*8)(R14)
90+ STP (R4, R5), (4*8)(R14)
91+ STP (R6, R7), (6*8)(R14)
92+
93+ // Create a struct callbackArgs on our stack.
94+ MOVD $cbargs-(18*8+callbackArgs__size)(SP), 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+ MOVD $callbackWrapInternal<>(SB), R0 // fn unsafe.Pointer
103+ MOVD R13, R1 // frame (&callbackArgs{...})
104+ MOVD $0, R3 // ctxt uintptr
105+
106+ // We still need to save all callee save register as before, and then
107+ // push 3 args for fn (R0, R1, R3), skipping R2.
108+ // Also note that at procedure entry in gc world, 8(RSP) will be the
109+ // first arg.
110+ SUB $(8*24), RSP
111+ STP (R0, R1), (8*1)(RSP)
112+ MOVD R3, (8*3)(RSP)
113+
114+ // Push C callee-save registers R19-R28.
115+ // LR, FP already saved.
116+ SAVE_R19_TO_R28(8*4)
117+ SAVE_F8_TO_F15(8*14)
118+ STP (R29, R30), (8*22)(RSP)
119+
120+ // Initialize Go ABI environment
121+ BL runtime·load_g(SB)
122+ BL runtime·cgocallback(SB)
123+
124+ RESTORE_R19_TO_R28(8*4)
125+ RESTORE_F8_TO_F15(8*14)
126+ LDP (8*22)(RSP), (R29, R30)
127+
128+ ADD $(8*24), RSP
129+
130+ // Get callback result.
131+ MOVD $cbargs-(18*8+callbackArgs__size)(SP), R13
132+ MOVD callbackArgs_result(R13), R0
133+
134+ RET
M syscall.go +1 -1
@@ -1,5 +1,5 @@
11 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
44 //go:build darwin || windows
55 // +build darwin windows
@@ -1,5 +1,5 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2-// SPDX-FileCopyrightText: 2022 The Ebiten Authors2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4 //go:build darwin || windows4 //go:build darwin || windows
5 // +build darwin windows5 // +build darwin windows
M syscall_darwin.go +126 -1
@@ -1,6 +1,14 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
14 package purego
25
3-import "unsafe"
6+import (
7+ "reflect"
8+ "runtime"
9+ "sync"
10+ "unsafe"
11+)
412
513 func callc(fn uintptr, args unsafe.Pointer) {
614 runtime_entersyscall()
@@ -18,3 +26,120 @@ func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2,
1826 callc(syscall9XABI0, unsafe.Pointer(&args))
1927 return args.r1, args.r2, args.err
2028 }
29+
30+// NewCallback converts a Go function to a function pointer conforming to the C calling convention
31+// This is useful when interoperating with C code requiring callbacks. The argument is expected to be a
32+// function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size
33+// of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated
34+// for these callbacks is never released. At least 2000 callbacks can always be created. Although this function
35+// provides similar functionality to windows.NewCallback it is distinct.
36+func NewCallback(fn interface{}) uintptr {
37+ return compileCallback(fn)
38+}
39+
40+// maxCb is the maximum number of callbacks
41+// only increase this if you have added more to the callbackasm function
42+const maxCB = 2000
43+
44+var cbs struct {
45+ lock sync.Mutex
46+ numFn int // the number of functions currently in cbs.funcs
47+ funcs [maxCB]reflect.Value // the saved callbacks
48+}
49+
50+type callbackArgs struct {
51+ index uintptr
52+ // args points to the argument block.
53+ //
54+ // For cdecl and stdcall, all arguments are on the stack.
55+ //
56+ // For fastcall, the trampoline spills register arguments to
57+ // the reserved spill slots below the stack arguments,
58+ // resulting in a layout equivalent to stdcall.
59+ //
60+ // For arm, the trampoline stores the register arguments just
61+ // below the stack arguments, so again we can treat it as one
62+ // big stack arguments frame.
63+ args unsafe.Pointer
64+ // Below are out-args from callbackWrap
65+ result uintptr
66+}
67+
68+func compileCallback(fn interface{}) uintptr {
69+ val := reflect.ValueOf(fn)
70+ if val.Kind() != reflect.Func {
71+ panic("purego: type is not a function")
72+ }
73+ ty := val.Type()
74+ for i := 0; i < ty.NumIn(); i++ {
75+ in := ty.In(i)
76+ switch in.Kind() {
77+ case reflect.Struct, reflect.Float32, reflect.Float64,
78+ reflect.Interface, reflect.Func, reflect.Slice,
79+ reflect.Chan, reflect.Complex64, reflect.Complex128,
80+ reflect.String, reflect.Map, reflect.Invalid:
81+ panic("purego: unsupported argument type: " + in.Kind().String())
82+ }
83+ }
84+ if ty.NumOut() > 1 || ty.NumOut() == 1 && ty.Out(0).Size() != ptrSize {
85+ panic("purego: callbacks can only have one pointer-sized return")
86+ }
87+ cbs.lock.Lock()
88+ defer cbs.lock.Unlock()
89+ if cbs.numFn >= maxCB {
90+ panic("purego: the maximum number of callbacks has been reached")
91+ }
92+ cbs.funcs[cbs.numFn] = val
93+ cbs.numFn++
94+ return callbackasmAddr(cbs.numFn - 1)
95+}
96+
97+const ptrSize = unsafe.Sizeof((*int)(nil))
98+
99+const callbackMaxFrame = 64 * ptrSize
100+
101+// callbackasmABI0 is implemented in zcallback_GOOS_GOARCH.s
102+var callbackasmABI0 uintptr
103+
104+// callbackWrap is called by assembly code which determines which Go function to call.
105+// This function takes the arguments and passes them to the Go function and returns the result.
106+func callbackWrap(a *callbackArgs) {
107+ cbs.lock.Lock()
108+ fn := cbs.funcs[a.index]
109+ cbs.lock.Unlock()
110+ fnType := fn.Type()
111+ args := make([]reflect.Value, fnType.NumIn())
112+ frame := (*[callbackMaxFrame]uintptr)(a.args)
113+ for i := range args {
114+ //TODO: support float32 and float64
115+ args[i] = reflect.NewAt(fnType.In(i), unsafe.Pointer(&frame[i])).Elem()
116+ }
117+ ret := fn.Call(args)
118+ if len(ret) > 0 {
119+ a.result = uintptr(ret[0].Uint())
120+ }
121+}
122+
123+// callbackasmAddr returns address of runtime.callbackasm
124+// function adjusted by i.
125+// On x86 and amd64, runtime.callbackasm is a series of CALL instructions,
126+// and we want callback to arrive at
127+// correspondent call instruction instead of start of
128+// runtime.callbackasm.
129+// On ARM, runtime.callbackasm is a series of mov and branch instructions.
130+// R12 is loaded with the callback index. Each entry is two instructions,
131+// hence 8 bytes.
132+func callbackasmAddr(i int) uintptr {
133+ var entrySize int
134+ switch runtime.GOARCH {
135+ default:
136+ panic("purego: unsupported architecture")
137+ case "386", "amd64":
138+ entrySize = 5
139+ case "arm", "arm64":
140+ // On ARM and ARM64, each entry is a MOV instruction
141+ // followed by a branch instruction
142+ entrySize = 8
143+ }
144+ return callbackasmABI0 + uintptr(i*entrySize)
145+}
@@ -1,6 +1,14 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
1 package purego4 package purego
2 5
3-import "unsafe"6+import (
7+ "reflect"
8+ "runtime"
9+ "sync"
10+ "unsafe"
11+)
4 12
5 func callc(fn uintptr, args unsafe.Pointer) {13 func callc(fn uintptr, args unsafe.Pointer) {
6 runtime_entersyscall()14 runtime_entersyscall()
@@ -18,3 +26,120 @@ func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2,
18 callc(syscall9XABI0, unsafe.Pointer(&args))26 callc(syscall9XABI0, unsafe.Pointer(&args))
19 return args.r1, args.r2, args.err27 return args.r1, args.r2, args.err
20 }28 }
29+
30+// NewCallback converts a Go function to a function pointer conforming to the C calling convention
31+// This is useful when interoperating with C code requiring callbacks. The argument is expected to be a
32+// function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size
33+// of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated
34+// for these callbacks is never released. At least 2000 callbacks can always be created. Although this function
35+// provides similar functionality to windows.NewCallback it is distinct.
36+func NewCallback(fn interface{}) uintptr {
37+ return compileCallback(fn)
38+}
39+
40+// maxCb is the maximum number of callbacks
41+// only increase this if you have added more to the callbackasm function
42+const maxCB = 2000
43+
44+var cbs struct {
45+ lock sync.Mutex
46+ numFn int // the number of functions currently in cbs.funcs
47+ funcs [maxCB]reflect.Value // the saved callbacks
48+}
49+
50+type callbackArgs struct {
51+ index uintptr
52+ // args points to the argument block.
53+ //
54+ // For cdecl and stdcall, all arguments are on the stack.
55+ //
56+ // For fastcall, the trampoline spills register arguments to
57+ // the reserved spill slots below the stack arguments,
58+ // resulting in a layout equivalent to stdcall.
59+ //
60+ // For arm, the trampoline stores the register arguments just
61+ // below the stack arguments, so again we can treat it as one
62+ // big stack arguments frame.
63+ args unsafe.Pointer
64+ // Below are out-args from callbackWrap
65+ result uintptr
66+}
67+
68+func compileCallback(fn interface{}) uintptr {
69+ val := reflect.ValueOf(fn)
70+ if val.Kind() != reflect.Func {
71+ panic("purego: type is not a function")
72+ }
73+ ty := val.Type()
74+ for i := 0; i < ty.NumIn(); i++ {
75+ in := ty.In(i)
76+ switch in.Kind() {
77+ case reflect.Struct, reflect.Float32, reflect.Float64,
78+ reflect.Interface, reflect.Func, reflect.Slice,
79+ reflect.Chan, reflect.Complex64, reflect.Complex128,
80+ reflect.String, reflect.Map, reflect.Invalid:
81+ panic("purego: unsupported argument type: " + in.Kind().String())
82+ }
83+ }
84+ if ty.NumOut() > 1 || ty.NumOut() == 1 && ty.Out(0).Size() != ptrSize {
85+ panic("purego: callbacks can only have one pointer-sized return")
86+ }
87+ cbs.lock.Lock()
88+ defer cbs.lock.Unlock()
89+ if cbs.numFn >= maxCB {
90+ panic("purego: the maximum number of callbacks has been reached")
91+ }
92+ cbs.funcs[cbs.numFn] = val
93+ cbs.numFn++
94+ return callbackasmAddr(cbs.numFn - 1)
95+}
96+
97+const ptrSize = unsafe.Sizeof((*int)(nil))
98+
99+const callbackMaxFrame = 64 * ptrSize
100+
101+// callbackasmABI0 is implemented in zcallback_GOOS_GOARCH.s
102+var callbackasmABI0 uintptr
103+
104+// callbackWrap is called by assembly code which determines which Go function to call.
105+// This function takes the arguments and passes them to the Go function and returns the result.
106+func callbackWrap(a *callbackArgs) {
107+ cbs.lock.Lock()
108+ fn := cbs.funcs[a.index]
109+ cbs.lock.Unlock()
110+ fnType := fn.Type()
111+ args := make([]reflect.Value, fnType.NumIn())
112+ frame := (*[callbackMaxFrame]uintptr)(a.args)
113+ for i := range args {
114+ //TODO: support float32 and float64
115+ args[i] = reflect.NewAt(fnType.In(i), unsafe.Pointer(&frame[i])).Elem()
116+ }
117+ ret := fn.Call(args)
118+ if len(ret) > 0 {
119+ a.result = uintptr(ret[0].Uint())
120+ }
121+}
122+
123+// callbackasmAddr returns address of runtime.callbackasm
124+// function adjusted by i.
125+// On x86 and amd64, runtime.callbackasm is a series of CALL instructions,
126+// and we want callback to arrive at
127+// correspondent call instruction instead of start of
128+// runtime.callbackasm.
129+// On ARM, runtime.callbackasm is a series of mov and branch instructions.
130+// R12 is loaded with the callback index. Each entry is two instructions,
131+// hence 8 bytes.
132+func callbackasmAddr(i int) uintptr {
133+ var entrySize int
134+ switch runtime.GOARCH {
135+ default:
136+ panic("purego: unsupported architecture")
137+ case "386", "amd64":
138+ entrySize = 5
139+ case "arm", "arm64":
140+ // On ARM and ARM64, each entry is a MOV instruction
141+ // followed by a branch instruction
142+ entrySize = 8
143+ }
144+ return callbackasmABI0 + uintptr(i*entrySize)
145+}
M syscall_windows.go +14 -0
@@ -1,3 +1,6 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
14 package purego
25
36 import (
@@ -9,3 +12,14 @@ func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2,
912 r1, r2, errno := syscall.Syscall9(fn, 9, a1, a2, a3, a4, a5, a6, a7, a8, a9)
1013 return r1, r2, uintptr(errno)
1114 }
15+
16+// NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention.
17+// This is useful when interoperating with Windows code requiring callbacks. The argument is expected to be a
18+// function with one uintptr-sized result. The function must not have arguments with size larger than the
19+// size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory
20+// allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024
21+// callbacks can always be created. Although this function is similiar to the darwin version it may act
22+// differently.
23+func NewCallback(fn interface{}) uintptr {
24+ return syscall.NewCallback(fn)
25+}
@@ -1,3 +1,6 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
1 package purego4 package purego
2 5
3 import (6 import (
@@ -9,3 +12,14 @@ func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2,
9 r1, r2, errno := syscall.Syscall9(fn, 9, a1, a2, a3, a4, a5, a6, a7, a8, a9)12 r1, r2, errno := syscall.Syscall9(fn, 9, a1, a2, a3, a4, a5, a6, a7, a8, a9)
10 return r1, r2, uintptr(errno)13 return r1, r2, uintptr(errno)
11 }14 }
15+
16+// NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention.
17+// This is useful when interoperating with Windows code requiring callbacks. The argument is expected to be a
18+// function with one uintptr-sized result. The function must not have arguments with size larger than the
19+// size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory
20+// allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024
21+// callbacks can always be created. Although this function is similiar to the darwin version it may act
22+// differently.
23+func NewCallback(fn interface{}) uintptr {
24+ return syscall.NewCallback(fn)
25+}
A zcallback_darwin_amd64.s +2014 -0 large diff (2014 lines changed): collapsed
A zcallback_darwin_arm64.s +4014 -0 large diff (4014 lines changed): collapsed

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