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 Unverified
4AEE18F83AFDEB23The key is unknown
16 files · +6673 -24
M README.md +9 -0
| @@ -2,3 +2,12 @@ | |||
| 2 | 2 | [](https://pkg.go.dev/github.com/ebitengine/purego?GOOS=darwin) | |
| 3 | 3 | ||
| 4 | 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`⏎̸ | |
| @@ -2,3 +2,12 @@ | |||||
| 2 | [](https://pkg.go.dev/github.com/ebitengine/purego?GOOS=darwin) | 2 | [](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 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | 4 | //go:build darwin | |
| 5 | 5 | // +build darwin | |
| @@ -9,35 +9,23 @@ package purego | |||
| 9 | 9 | import ( | |
| 10 | 10 | "runtime" | |
| 11 | 11 | "unsafe" | |
| 12 | + | ||
| 13 | + | "github.com/ebitengine/purego/internal/strings" | |
| 12 | 14 | ) | |
| 13 | 15 | ||
| 14 | 16 | const RTLD_GLOBAL = 0x8 | |
| 15 | 17 | ||
| 16 | 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 | 20 | func Dlopen(name string, mode int) uintptr { | |
| 33 | - | bs := cString(name) | |
| 21 | + | bs := strings.CString(name) | |
| 34 | 22 | ret, _, _ := SyscallN(dlopenABI0, uintptr(unsafe.Pointer(bs)), uintptr(mode), 0) | |
| 35 | 23 | runtime.KeepAlive(bs) | |
| 36 | 24 | return ret | |
| 37 | 25 | } | |
| 38 | 26 | ||
| 39 | 27 | func Dlsym(handle uintptr, name string) uintptr { | |
| 40 | - | bs := cString(name) | |
| 28 | + | bs := strings.CString(name) | |
| 41 | 29 | ret, _, _ := SyscallN(dlsymABI0, handle, uintptr(unsafe.Pointer(bs)), 0) | |
| 42 | 30 | runtime.KeepAlive(bs) | |
| 43 | 31 | return ret | |
| @@ -1,5 +1,5 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors |
| 3 | 3 | ||||
| 4 | //go:build darwin | 4 | //go:build darwin | ||
| 5 | // +build darwin | 5 | // +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 = 0x8 | 16 | 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 ret | 24 | 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 ret | 31 | return ret | ||
M dlfcn_darwin.go +1 -1
| @@ -1,5 +1,5 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | 4 | package purego | |
| 5 | 5 | ||
| @@ -1,5 +1,5 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors |
| 3 | 3 | ||||
| 4 | package purego | 4 | package purego | ||
| 5 | 5 | ||||
M dlfcn_stubs.s +1 -1
| @@ -1,5 +1,5 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | 4 | //go:build darwin | |
| 5 | 5 | // +build darwin | |
| @@ -1,5 +1,5 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors |
| 3 | 3 | ||||
| 4 | //go:build darwin | 4 | //go:build darwin | ||
| 5 | // +build darwin | 5 | // +build darwin | ||
M go_runtime.go +1 -1
| @@ -1,5 +1,5 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | 4 | //go:build darwin | |
| 5 | 5 | // +build darwin | |
| @@ -1,5 +1,5 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors |
| 3 | 3 | ||||
| 4 | //go:build darwin | 4 | //go:build darwin | ||
| 5 | // +build darwin | 5 | // +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 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | 4 | #include "textflag.h" | |
| 5 | + | #include "internal/abi/abi_amd64.h" | |
| 5 | 6 | #include "go_asm.h" | |
| 7 | + | #include "funcdata.h" | |
| 6 | 8 | ||
| 7 | 9 | // syscall9X calls a function in libc on behalf of the syscall package. | |
| 8 | 10 | // syscall9X takes a pointer to a struct like: | |
| @@ -69,3 +71,65 @@ TEXT syscall9X(SB), NOSPLIT, $0 | |||
| 69 | 71 | MOVQ BP, SP | |
| 70 | 72 | POPQ BP | |
| 71 | 73 | 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.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | 2 | + | // 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, SP | 71 | MOVQ BP, SP | ||
| 70 | POPQ BP | 72 | POPQ BP | ||
| 71 | RET | 73 | 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 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | 4 | #include "textflag.h" | |
| 5 | 5 | #include "go_asm.h" | |
| 6 | + | #include "funcdata.h" | |
| 7 | + | #include "internal/abi/abi_arm64.h" | |
| 6 | 8 | ||
| 7 | 9 | // syscall9X calls a function in libc on behalf of the syscall package. | |
| 8 | 10 | // syscall9X takes a pointer to a struct like: | |
| @@ -60,3 +62,73 @@ TEXT syscall9X(SB), NOSPLIT, $0 | |||
| 60 | 62 | MOVD R0, syscall9Args_r1(R2) // save r1 | |
| 61 | 63 | MOVD R1, syscall9Args_r2(R2) // save r2 | |
| 62 | 64 | 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.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | 2 | + | // 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 r1 | 62 | MOVD R0, syscall9Args_r1(R2) // save r1 | ||
| 61 | MOVD R1, syscall9Args_r2(R2) // save r2 | 63 | MOVD R1, syscall9Args_r2(R2) // save r2 | ||
| 62 | RET | 64 | 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 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | 4 | //go:build darwin || windows | |
| 5 | 5 | // +build darwin windows | |
| @@ -1,5 +1,5 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | - | // SPDX-FileCopyrightText: 2022 The Ebiten Authors | 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors |
| 3 | 3 | ||||
| 4 | //go:build darwin || windows | 4 | //go:build darwin || windows | ||
| 5 | // +build darwin windows | 5 | // +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 | + | ||
| 1 | 4 | package purego | |
| 2 | 5 | ||
| 3 | - | import "unsafe" | |
| 6 | + | import ( | |
| 7 | + | "reflect" | |
| 8 | + | "runtime" | |
| 9 | + | "sync" | |
| 10 | + | "unsafe" | |
| 11 | + | ) | |
| 4 | 12 | ||
| 5 | 13 | func callc(fn uintptr, args unsafe.Pointer) { | |
| 6 | 14 | runtime_entersyscall() | |
| @@ -18,3 +26,120 @@ func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, | |||
| 18 | 26 | callc(syscall9XABI0, unsafe.Pointer(&args)) | |
| 19 | 27 | 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 | + | } | |
| @@ -1,6 +1,14 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |||
| 3 | + | ||||
| 1 | package purego | 4 | 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.err | 27 | 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 | + | ||
| 1 | 4 | package purego | |
| 2 | 5 | ||
| 3 | 6 | import ( | |
| @@ -9,3 +12,14 @@ func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, | |||
| 9 | 12 | r1, r2, errno := syscall.Syscall9(fn, 9, a1, a2, a3, a4, a5, a6, a7, a8, a9) | |
| 10 | 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 | + | } | |
| @@ -1,3 +1,6 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |||
| 3 | + | ||||
| 1 | package purego | 4 | 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 | + | } | |||