fix callback calling convention (#26)
runtime.cgocallback takes a function pointer to the ABIInternal entry. The only way to get the ABIInternal entry is using the ABI selector <ABIInternal>. However, this is only available when compiling the runtime. This commit circumvents this limitation by using build constraints to determine if the Go version uses stack or register calling convention. Then in callbackWrapPicker the proper argument is sent to the real callbackWrap function. Closes #25
- commit
- 6ed67cd5f570b9c0d5710dfced2b1e77ad92ca1b
- author
- TotallyGamerJet · · 2022-08-29 17:19 UTC
- committer
- GitHub · · 2022-08-29 17:19 UTC
- parent
- 58aa9be
- signature
- GPG Unverified
4AEE18F83AFDEB23The key is unknown
5 files · +50 -8
A registercc_darwin.go +12 -0
| @@ -0,0 +1,12 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build (arm64 && go1.18) || (amd64 && go1.17) | |
| 5 | + | // +build arm64,go1.18 amd64,go1.17 | |
| 6 | + | ||
| 7 | + | package purego | |
| 8 | + | ||
| 9 | + | // stackCallingConvention represents whether the stacks or the | |
| 10 | + | // registers are used to pass parameters to functions. This is used to circumvent | |
| 11 | + | // the need for ABIInternal tag which is only allowed in the runtime. | |
| 12 | + | const stackCallingConvention = false | |
| @@ -0,0 +1,12 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build (arm64 && go1.18) || (amd64 && go1.17) | |||
| 5 | + | // +build arm64,go1.18 amd64,go1.17 | |||
| 6 | + | ||||
| 7 | + | package purego | |||
| 8 | + | ||||
| 9 | + | // stackCallingConvention represents whether the stacks or the | |||
| 10 | + | // registers are used to pass parameters to functions. This is used to circumvent | |||
| 11 | + | // the need for ABIInternal tag which is only allowed in the runtime. | |||
| 12 | + | const stackCallingConvention = false | |||
A stackcc_darwin.go +12 -0
| @@ -0,0 +1,12 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build (arm64 && !go1.18) || (amd64 && !go1.17) | |
| 5 | + | // +build arm64,!go1.18 amd64,!go1.17 | |
| 6 | + | ||
| 7 | + | package purego | |
| 8 | + | ||
| 9 | + | // stackCallingConvention represents whether the stacks or the | |
| 10 | + | // registers are used to pass parameters to functions. This is used to circumvent | |
| 11 | + | // the need for ABIInternal tag which is only allowed in the runtime. | |
| 12 | + | const stackCallingConvention = true | |
| @@ -0,0 +1,12 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build (arm64 && !go1.18) || (amd64 && !go1.17) | |||
| 5 | + | // +build arm64,!go1.18 amd64,!go1.17 | |||
| 6 | + | ||||
| 7 | + | package purego | |||
| 8 | + | ||||
| 9 | + | // stackCallingConvention represents whether the stacks or the | |||
| 10 | + | // registers are used to pass parameters to functions. This is used to circumvent | |||
| 11 | + | // the need for ABIInternal tag which is only allowed in the runtime. | |||
| 12 | + | const stackCallingConvention = true | |||
M sys_darwin_amd64.s +6 -4
| @@ -74,11 +74,13 @@ TEXT syscall9X(SB), NOSPLIT, $0 | |||
| 74 | 74 | ||
| 75 | 75 | // runtime·cgocallback expects a call to the ABIInternal function | |
| 76 | 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 jumps to callbackWrap | |
| 77 | + | // This is a small wrapper function that copies both whatever is in the register | |
| 78 | + | // and is on the stack and places both on the stack. It then calls callbackWrapPicker | |
| 79 | + | // which will choose which parameter should be used depending on the version of Go. | |
| 80 | + | // It then calls the real version of callbackWrap | |
| 79 | 81 | TEXT callbackWrapInternal<>(SB), NOSPLIT, $0-0 | |
| 80 | - | MOVQ AX, arg+8(SP) | |
| 81 | - | JMP ·callbackWrap(SB) | |
| 82 | + | MOVQ AX, arg+16(SP) | |
| 83 | + | JMP ·callbackWrapPicker(SB) | |
| 82 | 84 | RET | |
| 83 | 85 | ||
| 84 | 86 | TEXT callbackasm1(SB), NOSPLIT, $0 | |
| @@ -74,11 +74,13 @@ TEXT syscall9X(SB), NOSPLIT, $0 | |||||
| 74 | 74 | ||||
| 75 | // runtime·cgocallback expects a call to the ABIInternal function | 75 | // runtime·cgocallback expects a call to the ABIInternal function | ||
| 76 | // However, the tag <ABIInternal> is only available in the runtime :( | 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 | 77 | + | // This is a small wrapper function that copies both whatever is in the register |
| 78 | - | // where the Go function can find it. It then jumps to callbackWrap | 78 | + | // and is on the stack and places both on the stack. It then calls callbackWrapPicker |
| 79 | + | // which will choose which parameter should be used depending on the version of Go. | |||
| 80 | + | // It then calls the real version of callbackWrap | |||
| 79 | TEXT callbackWrapInternal<>(SB), NOSPLIT, $0-0 | 81 | TEXT callbackWrapInternal<>(SB), NOSPLIT, $0-0 | ||
| 80 | - | MOVQ AX, arg+8(SP) | 82 | + | MOVQ AX, arg+16(SP) |
| 81 | - | JMP ·callbackWrap(SB) | 83 | + | JMP ·callbackWrapPicker(SB) |
| 82 | RET | 84 | RET | ||
| 83 | 85 | ||||
| 84 | TEXT callbackasm1(SB), NOSPLIT, $0 | 86 | TEXT callbackasm1(SB), NOSPLIT, $0 | ||
M sys_darwin_arm64.s +6 -4
| @@ -65,11 +65,13 @@ TEXT syscall9X(SB), NOSPLIT, $0 | |||
| 65 | 65 | ||
| 66 | 66 | // runtime·cgocallback expects a call to the ABIInternal function | |
| 67 | 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. | |
| 68 | + | // This is a small wrapper function that copies both whatever is in the register | |
| 69 | + | // and is on the stack and places both on the stack. It then calls callbackWrapPicker | |
| 70 | + | // which will choose which parameter should be used depending on the version of Go. | |
| 71 | + | // It then calls the real version of callbackWrap | |
| 70 | 72 | TEXT callbackWrapInternal<>(SB), NOSPLIT, $0-0 | |
| 71 | - | MOVD R0, 8(RSP) | |
| 72 | - | B ·callbackWrap(SB) | |
| 73 | + | MOVD R0, 16(RSP) | |
| 74 | + | B ·callbackWrapPicker(SB) | |
| 73 | 75 | RET | |
| 74 | 76 | ||
| 75 | 77 | TEXT callbackasm1(SB), NOSPLIT, $208-0 | |
| @@ -65,11 +65,13 @@ TEXT syscall9X(SB), NOSPLIT, $0 | |||||
| 65 | 65 | ||||
| 66 | // runtime·cgocallback expects a call to the ABIInternal function | 66 | // runtime·cgocallback expects a call to the ABIInternal function | ||
| 67 | // However, the tag <ABIInternal> is only available in the runtime :( | 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 | 68 | + | // This is a small wrapper function that copies both whatever is in the register |
| 69 | - | // where the Go function can find it. It then branches without link. | 69 | + | // and is on the stack and places both on the stack. It then calls callbackWrapPicker |
| 70 | + | // which will choose which parameter should be used depending on the version of Go. | |||
| 71 | + | // It then calls the real version of callbackWrap | |||
| 70 | TEXT callbackWrapInternal<>(SB), NOSPLIT, $0-0 | 72 | TEXT callbackWrapInternal<>(SB), NOSPLIT, $0-0 | ||
| 71 | - | MOVD R0, 8(RSP) | 73 | + | MOVD R0, 16(RSP) |
| 72 | - | B ·callbackWrap(SB) | 74 | + | B ·callbackWrapPicker(SB) |
| 73 | RET | 75 | RET | ||
| 74 | 76 | ||||
| 75 | TEXT callbackasm1(SB), NOSPLIT, $208-0 | 77 | TEXT callbackasm1(SB), NOSPLIT, $208-0 | ||
M syscall_darwin.go +14 -0
| @@ -95,6 +95,20 @@ const callbackMaxFrame = 64 * ptrSize | |||
| 95 | 95 | // callbackasmABI0 is implemented in zcallback_GOOS_GOARCH.s | |
| 96 | 96 | var callbackasmABI0 uintptr | |
| 97 | 97 | ||
| 98 | + | // callbackWrapPicker gets whatever is on the stack and in the first register. | |
| 99 | + | // Depending on which version of Go that uses stack or register-based | |
| 100 | + | // calling it passes the respective argument to the real calbackWrap function. | |
| 101 | + | // The other argument is therefore invalid and points to undefined memory so don't use it. | |
| 102 | + | // This function is necessary since we can't use the ABIInternal selector which is only | |
| 103 | + | // valid in the runtime. | |
| 104 | + | func callbackWrapPicker(stack, register *callbackArgs) { | |
| 105 | + | if stackCallingConvention { | |
| 106 | + | callbackWrap(stack) | |
| 107 | + | } else { | |
| 108 | + | callbackWrap(register) | |
| 109 | + | } | |
| 110 | + | } | |
| 111 | + | ||
| 98 | 112 | // callbackWrap is called by assembly code which determines which Go function to call. | |
| 99 | 113 | // This function takes the arguments and passes them to the Go function and returns the result. | |
| 100 | 114 | func callbackWrap(a *callbackArgs) { | |
| @@ -95,6 +95,20 @@ const callbackMaxFrame = 64 * ptrSize | |||||
| 95 | // callbackasmABI0 is implemented in zcallback_GOOS_GOARCH.s | 95 | // callbackasmABI0 is implemented in zcallback_GOOS_GOARCH.s | ||
| 96 | var callbackasmABI0 uintptr | 96 | var callbackasmABI0 uintptr | ||
| 97 | 97 | ||||
| 98 | + | // callbackWrapPicker gets whatever is on the stack and in the first register. | |||
| 99 | + | // Depending on which version of Go that uses stack or register-based | |||
| 100 | + | // calling it passes the respective argument to the real calbackWrap function. | |||
| 101 | + | // The other argument is therefore invalid and points to undefined memory so don't use it. | |||
| 102 | + | // This function is necessary since we can't use the ABIInternal selector which is only | |||
| 103 | + | // valid in the runtime. | |||
| 104 | + | func callbackWrapPicker(stack, register *callbackArgs) { | |||
| 105 | + | if stackCallingConvention { | |||
| 106 | + | callbackWrap(stack) | |||
| 107 | + | } else { | |||
| 108 | + | callbackWrap(register) | |||
| 109 | + | } | |||
| 110 | + | } | |||
| 111 | + | ||||
| 98 | // callbackWrap is called by assembly code which determines which Go function to call. | 112 | // callbackWrap is called by assembly code which determines which Go function to call. | ||
| 99 | // This function takes the arguments and passes them to the Go function and returns the result. | 113 | // This function takes the arguments and passes them to the Go function and returns the result. | ||
| 100 | func callbackWrap(a *callbackArgs) { | 114 | func callbackWrap(a *callbackArgs) { | ||