├── go.sum ├── .gitignore ├── go.mod ├── gen.go ├── internal ├── cgo │ ├── empty.go │ ├── dlfcn_cgo_unix.go │ └── syscall_cgo_unix.go ├── xreflect │ ├── reflect_go125.go │ └── reflect_go124.go ├── buildtest │ └── main.go ├── fakecgo │ ├── go_setenv.go │ ├── libcgo_linux.go │ ├── libcgo_freebsd.go │ ├── fakecgo.go │ ├── libcgo_darwin.go │ ├── netbsd.go │ ├── setenv.go │ ├── libcgo_netbsd.go │ ├── iscgo.go │ ├── freebsd.go │ ├── asm_amd64.s │ ├── libcgo.go │ ├── asm_arm64.s │ ├── asm_loong64.s │ ├── doc.go │ ├── go_util.go │ ├── abi_arm64.h │ ├── zsymbols_netbsd.go │ ├── zsymbols_freebsd.go │ ├── zsymbols_linux.go │ ├── trampolines_loong64.s │ ├── zsymbols_darwin.go │ ├── trampolines_arm64.s │ ├── abi_loong64.h │ ├── ztrampolines_stubs.s │ ├── go_libinit.go │ ├── go_darwin.go │ ├── trampolines_amd64.s │ ├── abi_amd64.h │ ├── go_linux.go │ ├── go_freebsd.go │ ├── go_netbsd.go │ ├── callbacks.go │ ├── gen.go │ └── zsymbols.go ├── load │ ├── load_windows.go │ └── load_unix.go └── strings │ └── strings.go ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 01-feature.yml │ └── 00-bug.yml ├── PULL_REQUEST_TEMPLATE.md ├── scripts │ └── bsd_tests.sh └── workflows │ └── test.yml ├── dlfcn_nocgo_netbsd.go ├── examples ├── libc │ ├── main_unix.go │ ├── main_windows.go │ └── main.go ├── objc │ └── main_darwin.go ├── window │ ├── main_darwin.go │ └── main_windows.go └── protocol-dumper │ └── main_darwin.go ├── go_runtime.go ├── testdata ├── libdlnested │ └── nested_test.cpp ├── libcbtest │ └── callback_test.c ├── abitest │ └── abi_test.c └── structtest │ └── structreturn_test.c ├── dlfcn_nocgo_freebsd.go ├── is_ios.go ├── dlerror.go ├── syscall_test.go ├── dlfcn_playground.go ├── syscall_cgo_linux.go ├── dlfcn_stubs.s ├── struct_other.go ├── dlfcn_linux.go ├── dlfcn_nocgo_linux.go ├── dlfcn_netbsd.go ├── dlfcn_freebsd.go ├── cgo.go ├── dlfcn_android.go ├── dlfcn_darwin.go ├── nocgo.go ├── dlfcn_test.go ├── abi_arm64.h ├── syscall_windows.go ├── sys_unix_loong64.s ├── abi_loong64.h ├── sys_unix_arm64.s ├── syscall.go ├── sys_loong64.s ├── sys_arm64.s ├── abi_amd64.h ├── wincallback.go ├── dlfcn.go ├── objc ├── objc_block_darwin_test.go └── objc_runtime_darwin_test.go ├── README.md ├── struct_loong64.go ├── sys_amd64.s ├── callback_test.go ├── func_test.go ├── syscall_sysv.go └── struct_amd64.go /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ebitengine/purego 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /gen.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | package purego 5 | 6 | //go:generate go run wincallback.go 7 | -------------------------------------------------------------------------------- /internal/cgo/empty.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | package cgo 5 | 6 | // Empty so that importing this package doesn't cause issue for certain platforms. 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions 4 | about: Please use one of the forums for questions or general discussions 5 | url: https://github.com/ebitengine/purego/discussions 6 | -------------------------------------------------------------------------------- /internal/xreflect/reflect_go125.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | //go:build go1.25 5 | 6 | package xreflect 7 | 8 | import "reflect" 9 | 10 | func TypeAssert[T any](v reflect.Value) (T, bool) { 11 | return reflect.TypeAssert[T](v) 12 | } 13 | -------------------------------------------------------------------------------- /dlfcn_nocgo_netbsd.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | package purego 5 | 6 | //go:cgo_import_dynamic purego_dlopen dlopen "libc.so" 7 | //go:cgo_import_dynamic purego_dlsym dlsym "libc.so" 8 | //go:cgo_import_dynamic purego_dlerror dlerror "libc.so" 9 | //go:cgo_import_dynamic purego_dlclose dlclose "libc.so" 10 | -------------------------------------------------------------------------------- /examples/libc/main_unix.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || linux || netbsd 5 | 6 | package main 7 | 8 | import "github.com/ebitengine/purego" 9 | 10 | func openLibrary(name string) (uintptr, error) { 11 | return purego.Dlopen(name, purego.RTLD_NOW|purego.RTLD_GLOBAL) 12 | } 13 | -------------------------------------------------------------------------------- /internal/buildtest/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | //go:build !windows 5 | 6 | package main 7 | 8 | import ( 9 | _ "github.com/ebitengine/purego" 10 | ) 11 | 12 | import "C" 13 | 14 | // This file tests that build Cgo and purego at the same time succeeds to build (#189). 15 | func main() { 16 | } 17 | -------------------------------------------------------------------------------- /go_runtime.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || linux || netbsd || windows 5 | 6 | package purego 7 | 8 | import ( 9 | "unsafe" 10 | ) 11 | 12 | //go:linkname runtime_cgocall runtime.cgocall 13 | func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go 14 | -------------------------------------------------------------------------------- /testdata/libdlnested/nested_test.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | #include 5 | 6 | struct nested_libdl 7 | { 8 | nested_libdl() { 9 | // Fails for sure because a symbol cannot be named like this 10 | dlsym(RTLD_DEFAULT, "@/*<>"); 11 | } 12 | }; 13 | 14 | static nested_libdl test; 15 | -------------------------------------------------------------------------------- /dlfcn_nocgo_freebsd.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo 5 | 6 | package purego 7 | 8 | //go:cgo_import_dynamic purego_dlopen dlopen "libc.so.7" 9 | //go:cgo_import_dynamic purego_dlsym dlsym "libc.so.7" 10 | //go:cgo_import_dynamic purego_dlerror dlerror "libc.so.7" 11 | //go:cgo_import_dynamic purego_dlclose dlclose "libc.so.7" 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # What issue is this addressing? 8 | 9 | 10 | ## What _type_ of issue is this addressing? 11 | 12 | 13 | ## What this PR does | solves 14 | 15 | -------------------------------------------------------------------------------- /internal/xreflect/reflect_go124.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | //go:build !go1.25 5 | 6 | package xreflect 7 | 8 | import "reflect" 9 | 10 | // TODO: remove this and use Go 1.25's reflect.TypeAssert when minimum go.mod version is 1.25 11 | 12 | func TypeAssert[T any](v reflect.Value) (T, bool) { 13 | v2, ok := v.Interface().(T) 14 | return v2, ok 15 | } 16 | -------------------------------------------------------------------------------- /is_ios.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo 5 | 6 | package purego 7 | 8 | // if you are getting this error it means that you have 9 | // CGO_ENABLED=0 while trying to build for ios. 10 | // purego does not support this mode yet. 11 | // the fix is to set CGO_ENABLED=1 which will require 12 | // a C compiler. 13 | var _ = _PUREGO_REQUIRES_CGO_ON_IOS 14 | -------------------------------------------------------------------------------- /internal/fakecgo/go_setenv.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 5 | 6 | package fakecgo 7 | 8 | //go:nosplit 9 | //go:norace 10 | func x_cgo_setenv(arg *[2]*byte) { 11 | setenv(arg[0], arg[1], 1) 12 | } 13 | 14 | //go:nosplit 15 | //go:norace 16 | func x_cgo_unsetenv(arg *[1]*byte) { 17 | unsetenv(arg[0]) 18 | } 19 | -------------------------------------------------------------------------------- /internal/fakecgo/libcgo_linux.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo 5 | 6 | package fakecgo 7 | 8 | type ( 9 | pthread_cond_t [48]byte 10 | pthread_mutex_t [48]byte 11 | ) 12 | 13 | var ( 14 | PTHREAD_COND_INITIALIZER = pthread_cond_t{} 15 | PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{} 16 | ) 17 | 18 | type stack_t struct { 19 | /* not implemented */ 20 | } 21 | -------------------------------------------------------------------------------- /internal/fakecgo/libcgo_freebsd.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo 5 | 6 | package fakecgo 7 | 8 | type ( 9 | pthread_cond_t uintptr 10 | pthread_mutex_t uintptr 11 | ) 12 | 13 | var ( 14 | PTHREAD_COND_INITIALIZER = pthread_cond_t(0) 15 | PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t(0) 16 | ) 17 | 18 | type stack_t struct { 19 | /* not implemented */ 20 | } 21 | -------------------------------------------------------------------------------- /testdata/libcbtest/callback_test.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | #include 5 | 6 | typedef int (*callback)(const char *, int); 7 | 8 | int callCallback(const void *fp, const char *s) { 9 | // If the callback corrupts FP, this local variable on the stack will have incorrect value. 10 | int sentinel = 10101; 11 | ((callback)(fp))(s, strlen(s)); 12 | return sentinel; 13 | } 14 | -------------------------------------------------------------------------------- /dlerror.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || linux || netbsd 5 | 6 | package purego 7 | 8 | // Dlerror represents an error value returned from Dlopen, Dlsym, or Dlclose. 9 | // 10 | // This type is not available on Windows as there is no counterpart to it on Windows. 11 | type Dlerror struct { 12 | s string 13 | } 14 | 15 | func (e Dlerror) Error() string { 16 | return e.s 17 | } 18 | -------------------------------------------------------------------------------- /examples/libc/main_windows.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | package main 5 | 6 | import "syscall" 7 | 8 | func openLibrary(name string) (uintptr, error) { 9 | // Use [syscall.LoadLibrary] here to avoid external dependencies (#270). 10 | // For actual use cases, [golang.org/x/sys/windows.NewLazySystemDLL] is recommended. 11 | handle, err := syscall.LoadLibrary(name) 12 | return uintptr(handle), err 13 | } 14 | -------------------------------------------------------------------------------- /internal/fakecgo/fakecgo.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 5 | 6 | package fakecgo 7 | 8 | import _ "unsafe" 9 | 10 | // setg_trampoline calls setg with the G provided 11 | func setg_trampoline(setg uintptr, G uintptr) 12 | 13 | // call5 takes fn the C function and 5 arguments and calls the function with those arguments 14 | func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr 15 | -------------------------------------------------------------------------------- /internal/fakecgo/libcgo_darwin.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo 5 | 6 | package fakecgo 7 | 8 | type ( 9 | pthread_mutex_t struct { 10 | sig int64 11 | opaque [56]byte 12 | } 13 | pthread_cond_t struct { 14 | sig int64 15 | opaque [40]byte 16 | } 17 | ) 18 | 19 | var ( 20 | PTHREAD_COND_INITIALIZER = pthread_cond_t{sig: 0x3CB0B1BB} 21 | PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{sig: 0x32AAABA7} 22 | ) 23 | 24 | type stack_t struct { 25 | /* not implemented */ 26 | } 27 | -------------------------------------------------------------------------------- /internal/load/load_windows.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | package load 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | func OpenLibrary(name string) (uintptr, error) { 11 | handle, err := syscall.LoadLibrary(name) 12 | return uintptr(handle), err 13 | } 14 | 15 | func CloseLibrary(handle uintptr) error { 16 | return syscall.FreeLibrary(syscall.Handle(handle)) 17 | } 18 | 19 | func OpenSymbol(lib uintptr, name string) (uintptr, error) { 20 | return syscall.GetProcAddress(syscall.Handle(lib), name) 21 | } 22 | -------------------------------------------------------------------------------- /syscall_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | package purego_test 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | 10 | _ "github.com/ebitengine/purego" 11 | ) 12 | 13 | func TestOS(t *testing.T) { 14 | // set and unset an environment variable since this calls into fakecgo. 15 | err := os.Setenv("TESTING", "SOMETHING") 16 | if err != nil { 17 | t.Errorf("failed to Setenv: %s", err) 18 | } 19 | err = os.Unsetenv("TESTING") 20 | if err != nil { 21 | t.Errorf("failed to Unsetenv: %s", err) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/load/load_unix.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || linux || netbsd 5 | 6 | package load 7 | 8 | import "github.com/ebitengine/purego" 9 | 10 | func OpenLibrary(name string) (uintptr, error) { 11 | return purego.Dlopen(name, purego.RTLD_NOW|purego.RTLD_GLOBAL) 12 | } 13 | 14 | func CloseLibrary(handle uintptr) error { 15 | return purego.Dlclose(handle) 16 | } 17 | 18 | func OpenSymbol(lib uintptr, name string) (uintptr, error) { 19 | return purego.Dlsym(lib, name) 20 | } 21 | -------------------------------------------------------------------------------- /internal/fakecgo/netbsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 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 | //go:build netbsd 6 | 7 | package fakecgo 8 | 9 | import _ "unsafe" // for go:linkname 10 | 11 | // Supply environ and __progname, because we don't 12 | // link against the standard NetBSD crt0.o and the 13 | // libc dynamic library needs them. 14 | 15 | //go:linkname _environ environ 16 | //go:linkname _progname __progname 17 | //go:linkname ___ps_strings __ps_strings 18 | 19 | var ( 20 | _environ uintptr 21 | _progname uintptr 22 | ___ps_strings uintptr 23 | ) 24 | -------------------------------------------------------------------------------- /dlfcn_playground.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | //go:build faketime 5 | 6 | package purego 7 | 8 | import "errors" 9 | 10 | func Dlopen(path string, mode int) (uintptr, error) { 11 | return 0, errors.New("Dlopen is not supported in the playground") 12 | } 13 | 14 | func Dlsym(handle uintptr, name string) (uintptr, error) { 15 | return 0, errors.New("Dlsym is not supported in the playground") 16 | } 17 | 18 | func Dlclose(handle uintptr) error { 19 | return errors.New("Dlclose is not supported in the playground") 20 | } 21 | 22 | func loadSymbol(handle uintptr, name string) (uintptr, error) { 23 | return Dlsym(handle, name) 24 | } 25 | -------------------------------------------------------------------------------- /syscall_cgo_linux.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build cgo && !(amd64 || arm64 || loong64) 5 | 6 | package purego 7 | 8 | import ( 9 | "github.com/ebitengine/purego/internal/cgo" 10 | ) 11 | 12 | var syscall15XABI0 = uintptr(cgo.Syscall15XABI0) 13 | 14 | //go:nosplit 15 | func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { 16 | return cgo.Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) 17 | } 18 | 19 | func NewCallback(_ any) uintptr { 20 | panic("purego: NewCallback on Linux is only supported on amd64/arm64/loong64") 21 | } 22 | -------------------------------------------------------------------------------- /dlfcn_stubs.s: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build darwin || !cgo && (freebsd || linux || netbsd) && !faketime 5 | 6 | #include "textflag.h" 7 | 8 | // func dlopen(path *byte, mode int) (ret uintptr) 9 | TEXT dlopen(SB), NOSPLIT|NOFRAME, $0-0 10 | JMP purego_dlopen(SB) 11 | 12 | // func dlsym(handle uintptr, symbol *byte) (ret uintptr) 13 | TEXT dlsym(SB), NOSPLIT|NOFRAME, $0-0 14 | JMP purego_dlsym(SB) 15 | 16 | // func dlerror() (ret *byte) 17 | TEXT dlerror(SB), NOSPLIT|NOFRAME, $0-0 18 | JMP purego_dlerror(SB) 19 | 20 | // func dlclose(handle uintptr) (ret int) 21 | TEXT dlclose(SB), NOSPLIT|NOFRAME, $0-0 22 | JMP purego_dlclose(SB) 23 | -------------------------------------------------------------------------------- /struct_other.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | //go:build !amd64 && !arm64 && !loong64 5 | 6 | package purego 7 | 8 | import "reflect" 9 | 10 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { 11 | panic("purego: struct arguments are not supported") 12 | } 13 | 14 | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { 15 | panic("purego: struct returns are not supported") 16 | } 17 | 18 | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { 19 | panic("purego: not needed on other platforms") 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01-feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Provide details about a feature you'd like to see. 3 | labels: 4 | - feature 5 | - request 6 | body: 7 | - type: checkboxes 8 | id: os 9 | attributes: 10 | label: Operating System 11 | options: 12 | - label: Windows 13 | - label: macOS 14 | - label: Linux 15 | - label: FreeBSD 16 | - label: Android 17 | - label: iOS 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: what 22 | attributes: 23 | label: What feature would you like to be added? 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: why 28 | attributes: 29 | label: Why is this needed? 30 | -------------------------------------------------------------------------------- /dlfcn_linux.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !android 5 | 6 | package purego 7 | 8 | // Source for constants: https://codebrowser.dev/glibc/glibc/bits/dlfcn.h.html 9 | 10 | const ( 11 | RTLD_DEFAULT = 0x00000 // Pseudo-handle for dlsym so search for any loaded symbol 12 | RTLD_LAZY = 0x00001 // Relocations are performed at an implementation-dependent time. 13 | RTLD_NOW = 0x00002 // Relocations are performed when the object is loaded. 14 | RTLD_LOCAL = 0x00000 // All symbols are not made available for relocation processing by other modules. 15 | RTLD_GLOBAL = 0x00100 // All symbols are available for relocation processing of other modules. 16 | ) 17 | -------------------------------------------------------------------------------- /internal/fakecgo/setenv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 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 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 6 | 7 | package fakecgo 8 | 9 | import _ "unsafe" // for go:linkname 10 | 11 | //go:linkname x_cgo_setenv_trampoline x_cgo_setenv_trampoline 12 | //go:linkname _cgo_setenv runtime._cgo_setenv 13 | var x_cgo_setenv_trampoline byte 14 | var _cgo_setenv = &x_cgo_setenv_trampoline 15 | 16 | //go:linkname x_cgo_unsetenv_trampoline x_cgo_unsetenv_trampoline 17 | //go:linkname _cgo_unsetenv runtime._cgo_unsetenv 18 | var x_cgo_unsetenv_trampoline byte 19 | var _cgo_unsetenv = &x_cgo_unsetenv_trampoline 20 | -------------------------------------------------------------------------------- /internal/fakecgo/libcgo_netbsd.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | //go:build !cgo 5 | 6 | package fakecgo 7 | 8 | type ( 9 | pthread_cond_t uintptr 10 | pthread_mutex_t uintptr 11 | ) 12 | 13 | var ( 14 | PTHREAD_COND_INITIALIZER = pthread_cond_t(0) 15 | PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t(0) 16 | ) 17 | 18 | // Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/compat/linux/arch/m68k/linux_signal.h#L133 19 | type stack_t struct { 20 | ss_sp uintptr 21 | ss_flags int32 22 | ss_size uintptr 23 | } 24 | 25 | // Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/sys/signal.h#L261 26 | const SS_DISABLE = 0x004 27 | -------------------------------------------------------------------------------- /internal/fakecgo/iscgo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 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 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 6 | 7 | // The runtime package contains an uninitialized definition 8 | // for runtime·iscgo. Override it to tell the runtime we're here. 9 | // There are various function pointers that should be set too, 10 | // but those depend on dynamic linker magic to get initialized 11 | // correctly, and sometimes they break. This variable is a 12 | // backup: it depends only on old C style static linking rules. 13 | 14 | package fakecgo 15 | 16 | import _ "unsafe" // for go:linkname 17 | 18 | //go:linkname _iscgo runtime.iscgo 19 | var _iscgo bool = true 20 | -------------------------------------------------------------------------------- /dlfcn_nocgo_linux.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo && !faketime 5 | 6 | package purego 7 | 8 | // if there is no Cgo we must link to each of the functions from dlfcn.h 9 | // then the functions are called inside dlfcn_stubs.s 10 | 11 | //go:cgo_import_dynamic purego_dlopen dlopen "libdl.so.2" 12 | //go:cgo_import_dynamic purego_dlsym dlsym "libdl.so.2" 13 | //go:cgo_import_dynamic purego_dlerror dlerror "libdl.so.2" 14 | //go:cgo_import_dynamic purego_dlclose dlclose "libdl.so.2" 15 | 16 | // on amd64 we don't need the following line - on 386 we do... 17 | // anyway - with those lines the output is better (but doesn't matter) - without it on amd64 we get multiple DT_NEEDED with "libc.so.6" etc 18 | 19 | //go:cgo_import_dynamic _ _ "libdl.so.2" 20 | -------------------------------------------------------------------------------- /dlfcn_netbsd.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | package purego 5 | 6 | // Source for constants: https://github.com/NetBSD/src/blob/trunk/include/dlfcn.h 7 | 8 | const ( 9 | intSize = 32 << (^uint(0) >> 63) // 32 or 64 10 | RTLD_DEFAULT = 1<> 63) // 32 or 64 9 | RTLD_DEFAULT = 1<> 63) / 2 12 | is32bit = 1 - is64bit 13 | RTLD_DEFAULT = is32bit * 0xffffffff 14 | RTLD_LAZY = 0x00000001 15 | RTLD_NOW = is64bit * 0x00000002 16 | RTLD_LOCAL = 0x00000000 17 | RTLD_GLOBAL = is64bit*0x00100 | is32bit*0x00000002 18 | ) 19 | 20 | func Dlopen(path string, mode int) (uintptr, error) { 21 | return cgo.Dlopen(path, mode) 22 | } 23 | 24 | func Dlsym(handle uintptr, name string) (uintptr, error) { 25 | return cgo.Dlsym(handle, name) 26 | } 27 | 28 | func Dlclose(handle uintptr) error { 29 | return cgo.Dlclose(handle) 30 | } 31 | 32 | func loadSymbol(handle uintptr, name string) (uintptr, error) { 33 | return Dlsym(handle, name) 34 | } 35 | -------------------------------------------------------------------------------- /dlfcn_darwin.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | package purego 5 | 6 | // Source for constants: https://opensource.apple.com/source/dyld/dyld-360.14/include/dlfcn.h.auto.html 7 | 8 | const ( 9 | RTLD_DEFAULT = 1<<64 - 2 // Pseudo-handle for dlsym so search for any loaded symbol 10 | RTLD_LAZY = 0x1 // Relocations are performed at an implementation-dependent time. 11 | RTLD_NOW = 0x2 // Relocations are performed when the object is loaded. 12 | RTLD_LOCAL = 0x4 // All symbols are not made available for relocation processing by other modules. 13 | RTLD_GLOBAL = 0x8 // All symbols are available for relocation processing of other modules. 14 | ) 15 | 16 | //go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib" 17 | //go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib" 18 | //go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib" 19 | //go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib" 20 | -------------------------------------------------------------------------------- /internal/fakecgo/asm_amd64.s: -------------------------------------------------------------------------------- 1 | // Copyright 2009 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 | #include "textflag.h" 6 | #include "abi_amd64.h" 7 | 8 | // Called by C code generated by cmd/cgo. 9 | // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) 10 | // Saves C callee-saved registers and calls cgocallback with three arguments. 11 | // fn is the PC of a func(a unsafe.Pointer) function. 12 | // This signature is known to SWIG, so we can't change it. 13 | TEXT crosscall2(SB), NOSPLIT, $0-0 14 | PUSH_REGS_HOST_TO_ABI0() 15 | 16 | // Make room for arguments to cgocallback. 17 | ADJSP $0x18 18 | 19 | #ifndef GOOS_windows 20 | MOVQ DI, 0x0(SP) // fn 21 | MOVQ SI, 0x8(SP) // arg 22 | 23 | // Skip n in DX. 24 | MOVQ CX, 0x10(SP) // ctxt 25 | 26 | #else 27 | MOVQ CX, 0x0(SP) // fn 28 | MOVQ DX, 0x8(SP) // arg 29 | 30 | // Skip n in R8. 31 | MOVQ R9, 0x10(SP) // ctxt 32 | 33 | #endif 34 | 35 | CALL runtime·cgocallback(SB) 36 | 37 | ADJSP $-0x18 38 | POP_REGS_HOST_TO_ABI0() 39 | RET 40 | -------------------------------------------------------------------------------- /internal/fakecgo/libcgo.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 5 | 6 | package fakecgo 7 | 8 | type ( 9 | size_t uintptr 10 | // Sources: 11 | // Darwin (32 bytes) - https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/_types.h#L74 12 | // FreeBSD (32 bytes) - https://github.com/DoctorWkt/xv6-freebsd/blob/d2a294c2a984baed27676068b15ed9a29b06ab6f/include/signal.h#L98C9-L98C21 13 | // Linux (128 bytes) - https://github.com/torvalds/linux/blob/ab75170520d4964f3acf8bb1f91d34cbc650688e/arch/x86/include/asm/signal.h#L25 14 | sigset_t [128]byte 15 | pthread_attr_t [64]byte 16 | pthread_t int 17 | pthread_key_t uint64 18 | ) 19 | 20 | // for pthread_sigmask: 21 | 22 | type sighow int32 23 | 24 | const ( 25 | SIG_BLOCK sighow = 0 26 | SIG_UNBLOCK sighow = 1 27 | SIG_SETMASK sighow = 2 28 | ) 29 | 30 | type G struct { 31 | stacklo uintptr 32 | stackhi uintptr 33 | } 34 | 35 | type ThreadStart struct { 36 | g *G 37 | tls *uintptr 38 | fn uintptr 39 | } 40 | -------------------------------------------------------------------------------- /internal/strings/strings.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | package strings 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | // hasSuffix tests whether the string s ends with suffix. 11 | func hasSuffix(s, suffix string) bool { 12 | return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix 13 | } 14 | 15 | // CString converts a go string to *byte that can be passed to C code. 16 | func CString(name string) *byte { 17 | if hasSuffix(name, "\x00") { 18 | return &(*(*[]byte)(unsafe.Pointer(&name)))[0] 19 | } 20 | b := make([]byte, len(name)+1) 21 | copy(b, name) 22 | return &b[0] 23 | } 24 | 25 | // GoString copies a null-terminated char* to a Go string. 26 | func GoString(c uintptr) string { 27 | // We take the address and then dereference it to trick go vet from creating a possible misuse of unsafe.Pointer 28 | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&c)) 29 | if ptr == nil { 30 | return "" 31 | } 32 | var length int 33 | for { 34 | if *(*byte)(unsafe.Add(ptr, uintptr(length))) == '\x00' { 35 | break 36 | } 37 | length++ 38 | } 39 | return string(unsafe.Slice((*byte)(ptr), length)) 40 | } 41 | -------------------------------------------------------------------------------- /internal/fakecgo/asm_arm64.s: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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 | #include "textflag.h" 6 | #include "abi_arm64.h" 7 | 8 | // Called by C code generated by cmd/cgo. 9 | // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) 10 | // Saves C callee-saved registers and calls cgocallback with three arguments. 11 | // fn is the PC of a func(a unsafe.Pointer) function. 12 | TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 13 | /* 14 | * We still need to save all callee save register as before, and then 15 | * push 3 args for fn (R0, R1, R3), skipping R2. 16 | * Also note that at procedure entry in gc world, 8(RSP) will be the 17 | * first arg. 18 | */ 19 | SUB $(8*24), RSP 20 | STP (R0, R1), (8*1)(RSP) 21 | MOVD R3, (8*3)(RSP) 22 | 23 | SAVE_R19_TO_R28(8*4) 24 | SAVE_F8_TO_F15(8*14) 25 | STP (R29, R30), (8*22)(RSP) 26 | 27 | // Initialize Go ABI environment 28 | BL runtime·load_g(SB) 29 | BL runtime·cgocallback(SB) 30 | 31 | RESTORE_R19_TO_R28(8*4) 32 | RESTORE_F8_TO_F15(8*14) 33 | LDP (8*22)(RSP), (R29, R30) 34 | 35 | ADD $(8*24), RSP 36 | RET 37 | -------------------------------------------------------------------------------- /internal/fakecgo/asm_loong64.s: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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 | #include "textflag.h" 6 | #include "abi_loong64.h" 7 | 8 | // Called by C code generated by cmd/cgo. 9 | // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) 10 | // Saves C callee-saved registers and calls cgocallback with three arguments. 11 | // fn is the PC of a func(a unsafe.Pointer) function. 12 | TEXT crosscall2(SB),NOSPLIT|NOFRAME,$0 13 | /* 14 | * We still need to save all callee save register as before, and then 15 | * push 3 args for fn (R4, R5, R7), skipping R6. 16 | * Also note that at procedure entry in gc world, 8(R29) will be the 17 | * first arg. 18 | */ 19 | 20 | ADDV $(-23*8), R3 21 | MOVV R4, (1*8)(R3) // fn unsafe.Pointer 22 | MOVV R5, (2*8)(R3) // a unsafe.Pointer 23 | MOVV R7, (3*8)(R3) // ctxt uintptr 24 | 25 | SAVE_R22_TO_R31((4*8)) 26 | SAVE_F24_TO_F31((14*8)) 27 | MOVV R1, (22*8)(R3) 28 | 29 | // Initialize Go ABI environment 30 | JAL runtime·load_g(SB) 31 | 32 | JAL runtime·cgocallback(SB) 33 | 34 | RESTORE_R22_TO_R31((4*8)) 35 | RESTORE_F24_TO_F31((14*8)) 36 | MOVV (22*8)(R3), R1 37 | 38 | ADDV $(23*8), R3 39 | 40 | RET 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/00-bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug you've found. 3 | labels: bug 4 | body: 5 | - type: input 6 | id: version_of_purego 7 | attributes: 8 | label: PureGo Version 9 | validations: 10 | required: true 11 | - type: checkboxes 12 | id: os 13 | attributes: 14 | label: Operating System 15 | options: 16 | - label: Windows 17 | - label: macOS 18 | - label: Linux 19 | - label: FreeBSD 20 | - label: NetBSD 21 | - label: Android 22 | - label: iOS 23 | validations: 24 | required: true 25 | - type: input 26 | id: version_of_go 27 | attributes: 28 | label: Go Version (`go version`) 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: repro_steps 33 | attributes: 34 | label: What steps will reproduce the problem? 35 | validations: 36 | required: true 37 | - type: textarea 38 | id: expected_result 39 | attributes: 40 | label: What is the expected result? 41 | validations: 42 | required: true 43 | - type: textarea 44 | id: actual_result 45 | attributes: 46 | label: What happens instead? 47 | validations: 48 | required: true 49 | - type: textarea 50 | id: additional 51 | attributes: 52 | label: Anything else you feel useful to add? 53 | -------------------------------------------------------------------------------- /testdata/abitest/abi_test.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | uint32_t stack_uint8_t(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f, uint32_t g, uint32_t h, uint8_t i, uint8_t j, uint32_t k ) { 10 | assert(i == 1); 11 | assert(j == 2); 12 | assert(k == 1024); 13 | return a | b | c | d | e | f | g | h | (uint32_t) i | (uint32_t) j | k; 14 | } 15 | 16 | uint32_t reg_uint8_t(uint8_t a, uint8_t b, uint32_t c) { 17 | assert(a == 1); 18 | assert(b == 2); 19 | assert(c == 1024); 20 | return a | b | c; 21 | } 22 | 23 | uint32_t stack_string(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f, uint32_t g, uint32_t h, const char * i) { 24 | assert(i != 0); 25 | assert(strcmp(i, "test") == 0); 26 | return a | b | c | d | e | f | g | h; 27 | } 28 | 29 | void stack_8i32_3strings(char* result, size_t size, int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8, const char* s1, const char* s2, const char* s3) { 30 | snprintf(result, size, "%d:%d:%d:%d:%d:%d:%d:%d:%s:%s:%s", a1, a2, a3, a4, a5, a6, a7, a8, s1, s2, s3); 31 | } 32 | -------------------------------------------------------------------------------- /nocgo.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 5 | 6 | package purego 7 | 8 | // if CGO_ENABLED=0 import fakecgo to setup the Cgo runtime correctly. 9 | // This is required since some frameworks need TLS setup the C way which Go doesn't do. 10 | // We currently don't support ios in fakecgo mode so force Cgo or fail 11 | // 12 | // The way that the Cgo runtime (runtime/cgo) works is by setting some variables found 13 | // in runtime with non-null GCC compiled functions. The variables that are replaced are 14 | // var ( 15 | // iscgo bool // in runtime/cgo.go 16 | // _cgo_init unsafe.Pointer // in runtime/cgo.go 17 | // _cgo_thread_start unsafe.Pointer // in runtime/cgo.go 18 | // _cgo_notify_runtime_init_done unsafe.Pointer // in runtime/cgo.go 19 | // _cgo_setenv unsafe.Pointer // in runtime/env_posix.go 20 | // _cgo_unsetenv unsafe.Pointer // in runtime/env_posix.go 21 | // ) 22 | // importing fakecgo will set these (using //go:linkname) with functions written 23 | // entirely in Go (except for some assembly trampolines to change GCC ABI to Go ABI). 24 | // Doing so makes it possible to build applications that call into C without CGO_ENABLED=1. 25 | import _ "github.com/ebitengine/purego/internal/fakecgo" 26 | -------------------------------------------------------------------------------- /examples/objc/main_darwin.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "reflect" 9 | 10 | "github.com/ebitengine/purego/objc" 11 | ) 12 | 13 | var ( 14 | sel_new = objc.RegisterName("new") 15 | sel_init = objc.RegisterName("init") 16 | sel_setBar = objc.RegisterName("setBar:") 17 | sel_bar = objc.RegisterName("bar") 18 | ) 19 | 20 | func BarInit(id objc.ID, cmd objc.SEL) objc.ID { 21 | return id.SendSuper(cmd) 22 | } 23 | 24 | func main() { 25 | // This struct is equivalent to the following Objective-C definition. 26 | // 27 | // @interface BarObject : NSObject 28 | // @property (readwrite) bar int 29 | // @end 30 | class, err := objc.RegisterClass( 31 | "BarObject", 32 | objc.GetClass("NSObject"), 33 | []*objc.Protocol{ 34 | objc.GetProtocol("NSDelegateWindow"), 35 | }, 36 | []objc.FieldDef{ 37 | { 38 | Name: "bar", 39 | Type: reflect.TypeOf(int(0)), 40 | Attribute: objc.ReadWrite, 41 | }, 42 | }, 43 | []objc.MethodDef{ 44 | { 45 | Cmd: sel_init, 46 | Fn: BarInit, 47 | }, 48 | }, 49 | ) 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | object := objc.ID(class).Send(sel_new) 55 | object.Send(sel_setBar, 123) 56 | bar := int(object.Send(sel_bar)) 57 | fmt.Println(bar) 58 | // Output: 123 59 | } 60 | -------------------------------------------------------------------------------- /internal/fakecgo/doc.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 5 | 6 | // Package fakecgo implements the Cgo runtime (runtime/cgo) entirely in Go. 7 | // This allows code that calls into C to function properly when CGO_ENABLED=0. 8 | // 9 | // # Goals 10 | // 11 | // fakecgo attempts to replicate the same naming structure as in the runtime. 12 | // For example, functions that have the prefix "gcc_*" are named "go_*". 13 | // This makes it easier to port other GOOSs and GOARCHs as well as to keep 14 | // it in sync with runtime/cgo. 15 | // 16 | // # Support 17 | // 18 | // Currently, fakecgo only supports macOS on amd64 & arm64. It also cannot 19 | // be used with -buildmode=c-archive because that requires special initialization 20 | // that fakecgo does not implement at the moment. 21 | // 22 | // # Usage 23 | // 24 | // Using fakecgo is easy just import _ "github.com/ebitengine/purego" and then 25 | // set the environment variable CGO_ENABLED=0. 26 | // The recommended usage for fakecgo is to prefer using runtime/cgo if possible 27 | // but if cross-compiling or fast build times are important fakecgo is available. 28 | // Purego will pick which ever Cgo runtime is available and prefer the one that 29 | // comes with Go (runtime/cgo). 30 | package fakecgo 31 | 32 | //go:generate go run gen.go 33 | -------------------------------------------------------------------------------- /internal/fakecgo/go_util.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 5 | 6 | package fakecgo 7 | 8 | import "unsafe" 9 | 10 | // _cgo_thread_start is split into three parts in cgo since only one part is system dependent (keep it here for easier handling) 11 | 12 | // _cgo_thread_start(ThreadStart *arg) (runtime/cgo/gcc_util.c) 13 | // This get's called instead of the go code for creating new threads 14 | // -> pthread_* stuff is used, so threads are setup correctly for C 15 | // If this is missing, TLS is only setup correctly on thread 1! 16 | // This function should be go:systemstack instead of go:nosplit (but that requires runtime) 17 | // 18 | //go:nosplit 19 | //go:norace 20 | func x_cgo_thread_start(arg *ThreadStart) { 21 | var ts *ThreadStart 22 | // Make our own copy that can persist after we return. 23 | // _cgo_tsan_acquire(); 24 | ts = (*ThreadStart)(malloc(unsafe.Sizeof(*ts))) 25 | // _cgo_tsan_release(); 26 | if ts == nil { 27 | println("fakecgo: out of memory in thread_start") 28 | abort() 29 | } 30 | // *ts = *arg would cause a writebarrier so copy using slices 31 | s1 := unsafe.Slice((*uintptr)(unsafe.Pointer(ts)), unsafe.Sizeof(*ts)/8) 32 | s2 := unsafe.Slice((*uintptr)(unsafe.Pointer(arg)), unsafe.Sizeof(*arg)/8) 33 | for i := range s2 { 34 | s1[i] = s2[i] 35 | } 36 | _cgo_sys_thread_start(ts) // OS-dependent half 37 | } 38 | -------------------------------------------------------------------------------- /internal/cgo/dlfcn_cgo_unix.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | //go:build freebsd || linux || netbsd 5 | 6 | package cgo 7 | 8 | /* 9 | #cgo !netbsd LDFLAGS: -ldl 10 | 11 | #include 12 | #include 13 | */ 14 | import "C" 15 | 16 | import ( 17 | "errors" 18 | "unsafe" 19 | ) 20 | 21 | func Dlopen(filename string, flag int) (uintptr, error) { 22 | cfilename := C.CString(filename) 23 | defer C.free(unsafe.Pointer(cfilename)) 24 | handle := C.dlopen(cfilename, C.int(flag)) 25 | if handle == nil { 26 | return 0, errors.New(C.GoString(C.dlerror())) 27 | } 28 | return uintptr(handle), nil 29 | } 30 | 31 | func Dlsym(handle uintptr, symbol string) (uintptr, error) { 32 | csymbol := C.CString(symbol) 33 | defer C.free(unsafe.Pointer(csymbol)) 34 | symbolAddr := C.dlsym(*(*unsafe.Pointer)(unsafe.Pointer(&handle)), csymbol) 35 | if symbolAddr == nil { 36 | return 0, errors.New(C.GoString(C.dlerror())) 37 | } 38 | return uintptr(symbolAddr), nil 39 | } 40 | 41 | func Dlclose(handle uintptr) error { 42 | result := C.dlclose(*(*unsafe.Pointer)(unsafe.Pointer(&handle))) 43 | if result != 0 { 44 | return errors.New(C.GoString(C.dlerror())) 45 | } 46 | return nil 47 | } 48 | 49 | // all that is needed is to assign each dl function because then its 50 | // symbol will then be made available to the linker and linked to inside dlfcn.go 51 | var ( 52 | _ = C.dlopen 53 | _ = C.dlsym 54 | _ = C.dlerror 55 | _ = C.dlclose 56 | ) 57 | -------------------------------------------------------------------------------- /dlfcn_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || linux || netbsd 5 | 6 | package purego_test 7 | 8 | import ( 9 | "os" 10 | "path/filepath" 11 | "testing" 12 | "unsafe" 13 | 14 | "github.com/ebitengine/purego" 15 | ) 16 | 17 | func TestSimpleDlsym(t *testing.T) { 18 | if _, err := purego.Dlsym(purego.RTLD_DEFAULT, "dlsym"); err != nil { 19 | t.Errorf("Dlsym with RTLD_DEFAULT failed: %v", err) 20 | } 21 | } 22 | 23 | func TestNestedDlopenCall(t *testing.T) { 24 | libFileName := filepath.Join(t.TempDir(), "libdlnested.so") 25 | t.Logf("Build %v", libFileName) 26 | 27 | if err := buildSharedLib("CXX", libFileName, filepath.Join("testdata", "libdlnested", "nested_test.cpp")); err != nil { 28 | t.Fatal(err) 29 | } 30 | defer os.Remove(libFileName) 31 | 32 | lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL) 33 | if err != nil { 34 | t.Fatalf("Dlopen(%q) failed: %v", libFileName, err) 35 | } 36 | 37 | purego.Dlclose(lib) 38 | } 39 | 40 | func TestSyscallN(t *testing.T) { 41 | var dlsym uintptr 42 | var err error 43 | if dlsym, err = purego.Dlsym(purego.RTLD_DEFAULT, "dlsym"); err != nil { 44 | t.Errorf("Dlsym with RTLD_DEFAULT failed: %v", err) 45 | } 46 | r1, _, err2 := purego.SyscallN(dlsym, purego.RTLD_DEFAULT, uintptr(unsafe.Pointer(&[]byte("dlsym\x00")[0]))) 47 | if dlsym != r1 { 48 | t.Fatalf("SyscallN didn't return the same result as purego.Dlsym: %d", err2) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /abi_arm64.h: -------------------------------------------------------------------------------- 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) 40 | -------------------------------------------------------------------------------- /internal/fakecgo/abi_arm64.h: -------------------------------------------------------------------------------- 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) 40 | -------------------------------------------------------------------------------- /syscall_windows.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | package purego 5 | 6 | import ( 7 | "reflect" 8 | "syscall" 9 | ) 10 | 11 | var syscall15XABI0 uintptr 12 | 13 | func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { 14 | r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) 15 | return r1, r2, uintptr(errno) 16 | } 17 | 18 | // NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention. 19 | // This is useful when interoperating with Windows code requiring callbacks. The argument is expected to be a 20 | // function with one uintptr-sized result. The function must not have arguments with size larger than the 21 | // size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory 22 | // allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024 23 | // callbacks can always be created. Although this function is similiar to the darwin version it may act 24 | // differently. 25 | func NewCallback(fn any) uintptr { 26 | isCDecl := false 27 | ty := reflect.TypeOf(fn) 28 | for i := 0; i < ty.NumIn(); i++ { 29 | in := ty.In(i) 30 | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { 31 | continue 32 | } 33 | if i != 0 { 34 | panic("purego: CDecl must be the first argument") 35 | } 36 | isCDecl = true 37 | } 38 | if isCDecl { 39 | return syscall.NewCallbackCDecl(fn) 40 | } 41 | return syscall.NewCallback(fn) 42 | } 43 | 44 | func loadSymbol(handle uintptr, name string) (uintptr, error) { 45 | return syscall.GetProcAddress(syscall.Handle(handle), name) 46 | } 47 | -------------------------------------------------------------------------------- /examples/window/main_darwin.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | package main 5 | 6 | import ( 7 | "runtime" 8 | 9 | "github.com/ebitengine/purego" 10 | "github.com/ebitengine/purego/objc" 11 | ) 12 | 13 | const ( 14 | NSApplicationActivationPolicyRegular = 0 15 | NSWindowStyleMaskTitled = 1 << 0 16 | NSBackingStoreBuffered = 2 17 | ) 18 | 19 | type NSPoint struct { 20 | X, Y float64 21 | } 22 | 23 | type NSSize struct { 24 | Width, Height float64 25 | } 26 | 27 | type NSRect struct { 28 | Origin NSPoint 29 | Size NSSize 30 | } 31 | 32 | func init() { 33 | runtime.LockOSThread() 34 | } 35 | 36 | func main() { 37 | if _, err := purego.Dlopen("/System/Library/Frameworks/Cocoa.framework/Cocoa", purego.RTLD_GLOBAL|purego.RTLD_LAZY); err != nil { 38 | panic(err) 39 | } 40 | nsApp := objc.ID(objc.GetClass("NSApplication")).Send(objc.RegisterName("sharedApplication")) 41 | nsApp.Send(objc.RegisterName("setActivationPolicy:"), NSApplicationActivationPolicyRegular) 42 | wnd := objc.ID(objc.GetClass("NSWindow")).Send(objc.RegisterName("alloc")) 43 | wnd = wnd.Send(objc.RegisterName("initWithContentRect:styleMask:backing:defer:"), 44 | NSMakeRect(0, 0, 320, 240), 45 | NSWindowStyleMaskTitled, 46 | NSBackingStoreBuffered, 47 | false, 48 | ) 49 | 50 | title := objc.ID(objc.GetClass("NSString")).Send(objc.RegisterName("stringWithUTF8String:"), "My Title") 51 | wnd.Send(objc.RegisterName("setTitle:"), title) 52 | wnd.Send(objc.RegisterName("makeKeyAndOrderFront:"), objc.ID(0)) 53 | wnd.Send(objc.RegisterName("center")) 54 | nsApp.Send(objc.RegisterName("activateIgnoringOtherApps:"), true) 55 | nsApp.Send(objc.RegisterName("run")) 56 | } 57 | 58 | func NSMakeRect(x, y, width, height float64) NSRect { 59 | return NSRect{Origin: NSPoint{x, y}, Size: NSSize{width, height}} 60 | } 61 | -------------------------------------------------------------------------------- /internal/fakecgo/zsymbols_netbsd.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate' with gen.go. DO NOT EDIT. 2 | 3 | // SPDX-License-Identifier: Apache-2.0 4 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 5 | 6 | //go:build !cgo 7 | 8 | package fakecgo 9 | 10 | //go:cgo_import_dynamic purego_malloc malloc "libc.so" 11 | //go:cgo_import_dynamic purego_free free "libc.so" 12 | //go:cgo_import_dynamic purego_setenv setenv "libc.so" 13 | //go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so" 14 | //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so" 15 | //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so" 16 | //go:cgo_import_dynamic purego_abort abort "libc.so" 17 | //go:cgo_import_dynamic purego_sigaltstack sigaltstack "libc.so" 18 | //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so" 19 | //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so" 20 | //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so" 21 | //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so" 22 | //go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so" 23 | //go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so" 24 | //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so" 25 | //go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so" 26 | //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so" 27 | //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so" 28 | //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so" 29 | //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so" 30 | //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so" 31 | -------------------------------------------------------------------------------- /internal/fakecgo/zsymbols_freebsd.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate' with gen.go. DO NOT EDIT. 2 | 3 | // SPDX-License-Identifier: Apache-2.0 4 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 5 | 6 | //go:build !cgo 7 | 8 | package fakecgo 9 | 10 | //go:cgo_import_dynamic purego_malloc malloc "libc.so.7" 11 | //go:cgo_import_dynamic purego_free free "libc.so.7" 12 | //go:cgo_import_dynamic purego_setenv setenv "libc.so.7" 13 | //go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so.7" 14 | //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7" 15 | //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7" 16 | //go:cgo_import_dynamic purego_abort abort "libc.so.7" 17 | //go:cgo_import_dynamic purego_sigaltstack sigaltstack "libc.so.7" 18 | //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so" 19 | //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so" 20 | //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so" 21 | //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so" 22 | //go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so" 23 | //go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so" 24 | //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so" 25 | //go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so" 26 | //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so" 27 | //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so" 28 | //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so" 29 | //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so" 30 | //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so" 31 | -------------------------------------------------------------------------------- /internal/fakecgo/zsymbols_linux.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate' with gen.go. DO NOT EDIT. 2 | 3 | // SPDX-License-Identifier: Apache-2.0 4 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 5 | 6 | //go:build !cgo 7 | 8 | package fakecgo 9 | 10 | //go:cgo_import_dynamic purego_malloc malloc "libc.so.6" 11 | //go:cgo_import_dynamic purego_free free "libc.so.6" 12 | //go:cgo_import_dynamic purego_setenv setenv "libc.so.6" 13 | //go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so.6" 14 | //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.6" 15 | //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.6" 16 | //go:cgo_import_dynamic purego_abort abort "libc.so.6" 17 | //go:cgo_import_dynamic purego_sigaltstack sigaltstack "libc.so.6" 18 | //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so.0" 19 | //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0" 20 | //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so.0" 21 | //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so.0" 22 | //go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so.0" 23 | //go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so.0" 24 | //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so.0" 25 | //go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so.0" 26 | //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so.0" 27 | //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so.0" 28 | //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so.0" 29 | //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so.0" 30 | //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so.0" 31 | -------------------------------------------------------------------------------- /internal/fakecgo/trampolines_loong64.s: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | //go:build !cgo && linux 5 | 6 | #include "textflag.h" 7 | #include "go_asm.h" 8 | #include "abi_loong64.h" 9 | 10 | // these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. 11 | 12 | TEXT x_cgo_init_trampoline(SB), NOSPLIT, $16 13 | MOVV R4, 8(R3) 14 | MOVV R5, 16(R3) 15 | MOVV ·x_cgo_init_call(SB), R6 16 | MOVV (R6), R7 17 | CALL (R7) 18 | RET 19 | 20 | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 21 | MOVV R4, 8(R3) 22 | MOVV ·x_cgo_thread_start_call(SB), R5 23 | MOVV (R5), R6 24 | CALL (R6) 25 | RET 26 | 27 | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 28 | MOVV R4, 8(R3) 29 | MOVV ·x_cgo_setenv_call(SB), R5 30 | MOVV (R5), R6 31 | CALL (R6) 32 | RET 33 | 34 | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 35 | MOVV R4, 8(R3) 36 | MOVV ·x_cgo_unsetenv_call(SB), R5 37 | MOVV (R5), R6 38 | CALL (R6) 39 | RET 40 | 41 | TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0 42 | CALL ·x_cgo_notify_runtime_init_done(SB) 43 | RET 44 | 45 | TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 46 | CALL ·x_cgo_bindm(SB) 47 | RET 48 | 49 | // func setg_trampoline(setg uintptr, g uintptr) 50 | TEXT ·setg_trampoline(SB), NOSPLIT, $0 51 | MOVV G+8(FP), R4 52 | MOVV setg+0(FP), R5 53 | CALL (R5) 54 | RET 55 | 56 | TEXT threadentry_trampoline(SB), NOSPLIT, $0 57 | // See crosscall2. 58 | ADDV $(-23*8), R3 59 | MOVV R4, (1*8)(R3) // fn unsafe.Pointer 60 | MOVV R5, (2*8)(R3) // a unsafe.Pointer 61 | MOVV R7, (3*8)(R3) // ctxt uintptr 62 | 63 | SAVE_R22_TO_R31((4*8)) 64 | SAVE_F24_TO_F31((14*8)) 65 | MOVV R1, (22*8)(R3) 66 | 67 | MOVV ·threadentry_call(SB), R5 68 | MOVV (R5), R6 69 | CALL (R6) 70 | 71 | RESTORE_R22_TO_R31((4*8)) 72 | RESTORE_F24_TO_F31((14*8)) 73 | MOVV (22*8)(R3), R1 74 | 75 | ADDV $(23*8), R3 76 | RET 77 | 78 | TEXT ·call5(SB), NOSPLIT, $0-0 79 | MOVV fn+0(FP), R9 80 | MOVV a1+8(FP), R4 81 | MOVV a2+16(FP), R5 82 | MOVV a3+24(FP), R6 83 | MOVV a4+32(FP), R7 84 | MOVV a5+40(FP), R8 85 | CALL (R9) 86 | MOVV R4, ret+48(FP) 87 | RET 88 | -------------------------------------------------------------------------------- /internal/cgo/syscall_cgo_unix.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build freebsd || (linux && !(arm64 || amd64 || loong64)) || netbsd 5 | 6 | package cgo 7 | 8 | // this file is placed inside internal/cgo and not package purego 9 | // because Cgo and assembly files can't be in the same package. 10 | 11 | /* 12 | #cgo !netbsd LDFLAGS: -ldl 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | typedef struct syscall15Args { 20 | uintptr_t fn; 21 | uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15; 22 | uintptr_t f1, f2, f3, f4, f5, f6, f7, f8; 23 | uintptr_t err; 24 | } syscall15Args; 25 | 26 | void syscall15(struct syscall15Args *args) { 27 | assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0); 28 | uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, 29 | uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, 30 | uintptr_t a13, uintptr_t a14, uintptr_t a15); 31 | *(void**)(&func_name) = (void*)(args->fn); 32 | uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9, 33 | args->a10,args->a11,args->a12,args->a13,args->a14,args->a15); 34 | args->a1 = r1; 35 | args->err = errno; 36 | } 37 | 38 | */ 39 | import "C" 40 | import "unsafe" 41 | 42 | // assign purego.syscall15XABI0 to the C version of this function. 43 | var Syscall15XABI0 = unsafe.Pointer(C.syscall15) 44 | 45 | //go:nosplit 46 | func Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { 47 | args := C.syscall15Args{ 48 | C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3), 49 | C.uintptr_t(a4), C.uintptr_t(a5), C.uintptr_t(a6), 50 | C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), C.uintptr_t(a10), C.uintptr_t(a11), C.uintptr_t(a12), 51 | C.uintptr_t(a13), C.uintptr_t(a14), C.uintptr_t(a15), 0, 0, 0, 0, 0, 0, 0, 0, 0, 52 | } 53 | C.syscall15(&args) 54 | return uintptr(args.a1), 0, uintptr(args.err) 55 | } 56 | -------------------------------------------------------------------------------- /sys_unix_loong64.s: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | //go:build linux 5 | 6 | #include "textflag.h" 7 | #include "go_asm.h" 8 | #include "funcdata.h" 9 | #include "abi_loong64.h" 10 | 11 | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 12 | NO_LOCAL_POINTERS 13 | 14 | SUBV $(16*8), R3, R14 15 | MOVD F0, 0(R14) 16 | MOVD F1, 8(R14) 17 | MOVD F2, 16(R14) 18 | MOVD F3, 24(R14) 19 | MOVD F4, 32(R14) 20 | MOVD F5, 40(R14) 21 | MOVD F6, 48(R14) 22 | MOVD F7, 56(R14) 23 | MOVV R4, 64(R14) 24 | MOVV R5, 72(R14) 25 | MOVV R6, 80(R14) 26 | MOVV R7, 88(R14) 27 | MOVV R8, 96(R14) 28 | MOVV R9, 104(R14) 29 | MOVV R10, 112(R14) 30 | MOVV R11, 120(R14) 31 | 32 | // Adjust SP by frame size. 33 | SUBV $(22*8), R3 34 | 35 | // It is important to save R30 because the go assembler 36 | // uses it for move instructions for a variable. 37 | // This line: 38 | // MOVV ·callbackWrap_call(SB), R4 39 | // Creates the instructions: 40 | // PCALAU12I off1(PC), R30 41 | // MOVV off2(R30), R4 42 | // R30 is a callee saved register so we are responsible 43 | // for ensuring its value doesn't change. So save it and 44 | // restore it at the end of this function. 45 | // R1 is the link register. crosscall2 doesn't save it 46 | // so it's saved here. 47 | MOVV R1, 0(R3) 48 | MOVV R30, 8(R3) 49 | 50 | // Create a struct callbackArgs on our stack. 51 | MOVV $(callbackArgs__size)(R3), R13 52 | MOVV R12, callbackArgs_index(R13) // callback index 53 | MOVV R14, callbackArgs_args(R13) // address of args vector 54 | MOVV $0, callbackArgs_result(R13) // result 55 | 56 | // Move parameters into registers 57 | // Get the ABIInternal function pointer 58 | // without by using a closure. 59 | MOVV ·callbackWrap_call(SB), R4 60 | MOVV (R4), R4 // fn unsafe.Pointer 61 | MOVV R13, R5 // frame (&callbackArgs{...}) 62 | MOVV $0, R7 // ctxt uintptr 63 | 64 | JAL crosscall2(SB) 65 | 66 | // Get callback result. 67 | MOVV $(callbackArgs__size)(R3), R13 68 | MOVV callbackArgs_result(R13), R4 69 | 70 | // Restore LR and R30 71 | MOVV 0(R3), R1 72 | MOVV 8(R3), R30 73 | ADDV $(22*8), R3 74 | 75 | RET 76 | -------------------------------------------------------------------------------- /internal/fakecgo/zsymbols_darwin.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate' with gen.go. DO NOT EDIT. 2 | 3 | // SPDX-License-Identifier: Apache-2.0 4 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 5 | 6 | //go:build !cgo 7 | 8 | package fakecgo 9 | 10 | //go:cgo_import_dynamic purego_malloc malloc "/usr/lib/libSystem.B.dylib" 11 | //go:cgo_import_dynamic purego_free free "/usr/lib/libSystem.B.dylib" 12 | //go:cgo_import_dynamic purego_setenv setenv "/usr/lib/libSystem.B.dylib" 13 | //go:cgo_import_dynamic purego_unsetenv unsetenv "/usr/lib/libSystem.B.dylib" 14 | //go:cgo_import_dynamic purego_sigfillset sigfillset "/usr/lib/libSystem.B.dylib" 15 | //go:cgo_import_dynamic purego_nanosleep nanosleep "/usr/lib/libSystem.B.dylib" 16 | //go:cgo_import_dynamic purego_abort abort "/usr/lib/libSystem.B.dylib" 17 | //go:cgo_import_dynamic purego_sigaltstack sigaltstack "/usr/lib/libSystem.B.dylib" 18 | //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "/usr/lib/libSystem.B.dylib" 19 | //go:cgo_import_dynamic purego_pthread_create pthread_create "/usr/lib/libSystem.B.dylib" 20 | //go:cgo_import_dynamic purego_pthread_detach pthread_detach "/usr/lib/libSystem.B.dylib" 21 | //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib" 22 | //go:cgo_import_dynamic purego_pthread_self pthread_self "/usr/lib/libSystem.B.dylib" 23 | //go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "/usr/lib/libSystem.B.dylib" 24 | //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "/usr/lib/libSystem.B.dylib" 25 | //go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "/usr/lib/libSystem.B.dylib" 26 | //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "/usr/lib/libSystem.B.dylib" 27 | //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "/usr/lib/libSystem.B.dylib" 28 | //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "/usr/lib/libSystem.B.dylib" 29 | //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "/usr/lib/libSystem.B.dylib" 30 | //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "/usr/lib/libSystem.B.dylib" 31 | -------------------------------------------------------------------------------- /abi_loong64.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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_R22_TO_R31(offset) saves R22 ~ R31 to the stack space 11 | // of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3). 12 | // 13 | // SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space 14 | // of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3). 15 | // 16 | // Note: g is R22 17 | 18 | #define SAVE_R22_TO_R31(offset) \ 19 | MOVV g, ((offset)+(0*8))(R3) \ 20 | MOVV R23, ((offset)+(1*8))(R3) \ 21 | MOVV R24, ((offset)+(2*8))(R3) \ 22 | MOVV R25, ((offset)+(3*8))(R3) \ 23 | MOVV R26, ((offset)+(4*8))(R3) \ 24 | MOVV R27, ((offset)+(5*8))(R3) \ 25 | MOVV R28, ((offset)+(6*8))(R3) \ 26 | MOVV R29, ((offset)+(7*8))(R3) \ 27 | MOVV R30, ((offset)+(8*8))(R3) \ 28 | MOVV R31, ((offset)+(9*8))(R3) 29 | 30 | #define SAVE_F24_TO_F31(offset) \ 31 | MOVD F24, ((offset)+(0*8))(R3) \ 32 | MOVD F25, ((offset)+(1*8))(R3) \ 33 | MOVD F26, ((offset)+(2*8))(R3) \ 34 | MOVD F27, ((offset)+(3*8))(R3) \ 35 | MOVD F28, ((offset)+(4*8))(R3) \ 36 | MOVD F29, ((offset)+(5*8))(R3) \ 37 | MOVD F30, ((offset)+(6*8))(R3) \ 38 | MOVD F31, ((offset)+(7*8))(R3) 39 | 40 | #define RESTORE_R22_TO_R31(offset) \ 41 | MOVV ((offset)+(0*8))(R3), g \ 42 | MOVV ((offset)+(1*8))(R3), R23 \ 43 | MOVV ((offset)+(2*8))(R3), R24 \ 44 | MOVV ((offset)+(3*8))(R3), R25 \ 45 | MOVV ((offset)+(4*8))(R3), R26 \ 46 | MOVV ((offset)+(5*8))(R3), R27 \ 47 | MOVV ((offset)+(6*8))(R3), R28 \ 48 | MOVV ((offset)+(7*8))(R3), R29 \ 49 | MOVV ((offset)+(8*8))(R3), R30 \ 50 | MOVV ((offset)+(9*8))(R3), R31 51 | 52 | #define RESTORE_F24_TO_F31(offset) \ 53 | MOVD ((offset)+(0*8))(R3), F24 \ 54 | MOVD ((offset)+(1*8))(R3), F25 \ 55 | MOVD ((offset)+(2*8))(R3), F26 \ 56 | MOVD ((offset)+(3*8))(R3), F27 \ 57 | MOVD ((offset)+(4*8))(R3), F28 \ 58 | MOVD ((offset)+(5*8))(R3), F29 \ 59 | MOVD ((offset)+(6*8))(R3), F30 \ 60 | MOVD ((offset)+(7*8))(R3), F31 61 | -------------------------------------------------------------------------------- /internal/fakecgo/trampolines_arm64.s: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo && (darwin || freebsd || linux) 5 | 6 | #include "textflag.h" 7 | #include "go_asm.h" 8 | #include "abi_arm64.h" 9 | 10 | // These trampolines map the gcc ABI to Go ABIInternal and then calls into the Go equivalent functions. 11 | // Note that C arguments are passed in R0-R7, which matches Go ABIInternal for the first eight arguments. 12 | 13 | TEXT x_cgo_init_trampoline(SB), NOSPLIT, $0-0 14 | MOVD ·x_cgo_init_call(SB), R26 15 | MOVD (R26), R2 16 | CALL (R2) 17 | RET 18 | 19 | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $0-0 20 | MOVD ·x_cgo_thread_start_call(SB), R26 21 | MOVD (R26), R2 22 | CALL (R2) 23 | RET 24 | 25 | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $0-0 26 | MOVD ·x_cgo_setenv_call(SB), R26 27 | MOVD (R26), R2 28 | CALL (R2) 29 | RET 30 | 31 | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $0-0 32 | MOVD ·x_cgo_unsetenv_call(SB), R26 33 | MOVD (R26), R2 34 | CALL (R2) 35 | RET 36 | 37 | TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0-0 38 | CALL ·x_cgo_notify_runtime_init_done(SB) 39 | RET 40 | 41 | TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 42 | CALL ·x_cgo_bindm(SB) 43 | RET 44 | 45 | // func setg_trampoline(setg uintptr, g uintptr) 46 | TEXT ·setg_trampoline(SB), NOSPLIT, $0-16 47 | MOVD G+8(FP), R0 48 | MOVD setg+0(FP), R1 49 | CALL R1 50 | RET 51 | 52 | TEXT threadentry_trampoline(SB), NOSPLIT, $0-0 53 | // See crosscall2. 54 | SUB $(8*24), RSP 55 | STP (R0, R1), (8*1)(RSP) 56 | MOVD R3, (8*3)(RSP) 57 | 58 | SAVE_R19_TO_R28(8*4) 59 | SAVE_F8_TO_F15(8*14) 60 | STP (R29, R30), (8*22)(RSP) 61 | 62 | MOVD ·threadentry_call(SB), R26 63 | MOVD (R26), R2 64 | CALL (R2) 65 | MOVD $0, R0 // TODO: get the return value from threadentry 66 | 67 | RESTORE_R19_TO_R28(8*4) 68 | RESTORE_F8_TO_F15(8*14) 69 | LDP (8*22)(RSP), (R29, R30) 70 | 71 | ADD $(8*24), RSP 72 | RET 73 | 74 | TEXT ·call5(SB), NOSPLIT, $0-0 75 | MOVD fn+0(FP), R6 76 | MOVD a1+8(FP), R0 77 | MOVD a2+16(FP), R1 78 | MOVD a3+24(FP), R2 79 | MOVD a4+32(FP), R3 80 | MOVD a5+40(FP), R4 81 | CALL R6 82 | MOVD R0, ret+48(FP) 83 | RET 84 | -------------------------------------------------------------------------------- /internal/fakecgo/abi_loong64.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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_R22_TO_R31(offset) saves R22 ~ R31 to the stack space 11 | // of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3). 12 | // 13 | // SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space 14 | // of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3). 15 | // 16 | // Note: g is R22 17 | 18 | #define SAVE_R22_TO_R31(offset) \ 19 | MOVV g, ((offset)+(0*8))(R3) \ 20 | MOVV R23, ((offset)+(1*8))(R3) \ 21 | MOVV R24, ((offset)+(2*8))(R3) \ 22 | MOVV R25, ((offset)+(3*8))(R3) \ 23 | MOVV R26, ((offset)+(4*8))(R3) \ 24 | MOVV R27, ((offset)+(5*8))(R3) \ 25 | MOVV R28, ((offset)+(6*8))(R3) \ 26 | MOVV R29, ((offset)+(7*8))(R3) \ 27 | MOVV R30, ((offset)+(8*8))(R3) \ 28 | MOVV R31, ((offset)+(9*8))(R3) 29 | 30 | #define SAVE_F24_TO_F31(offset) \ 31 | MOVD F24, ((offset)+(0*8))(R3) \ 32 | MOVD F25, ((offset)+(1*8))(R3) \ 33 | MOVD F26, ((offset)+(2*8))(R3) \ 34 | MOVD F27, ((offset)+(3*8))(R3) \ 35 | MOVD F28, ((offset)+(4*8))(R3) \ 36 | MOVD F29, ((offset)+(5*8))(R3) \ 37 | MOVD F30, ((offset)+(6*8))(R3) \ 38 | MOVD F31, ((offset)+(7*8))(R3) 39 | 40 | #define RESTORE_R22_TO_R31(offset) \ 41 | MOVV ((offset)+(0*8))(R3), g \ 42 | MOVV ((offset)+(1*8))(R3), R23 \ 43 | MOVV ((offset)+(2*8))(R3), R24 \ 44 | MOVV ((offset)+(3*8))(R3), R25 \ 45 | MOVV ((offset)+(4*8))(R3), R26 \ 46 | MOVV ((offset)+(5*8))(R3), R27 \ 47 | MOVV ((offset)+(6*8))(R3), R28 \ 48 | MOVV ((offset)+(7*8))(R3), R29 \ 49 | MOVV ((offset)+(8*8))(R3), R30 \ 50 | MOVV ((offset)+(9*8))(R3), R31 51 | 52 | #define RESTORE_F24_TO_F31(offset) \ 53 | MOVD ((offset)+(0*8))(R3), F24 \ 54 | MOVD ((offset)+(1*8))(R3), F25 \ 55 | MOVD ((offset)+(2*8))(R3), F26 \ 56 | MOVD ((offset)+(3*8))(R3), F27 \ 57 | MOVD ((offset)+(4*8))(R3), F28 \ 58 | MOVD ((offset)+(5*8))(R3), F29 \ 59 | MOVD ((offset)+(6*8))(R3), F30 \ 60 | MOVD ((offset)+(7*8))(R3), F31 61 | -------------------------------------------------------------------------------- /internal/fakecgo/ztrampolines_stubs.s: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate' with gen.go. DO NOT EDIT. 2 | 3 | // SPDX-License-Identifier: Apache-2.0 4 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 5 | 6 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 7 | 8 | #include "textflag.h" 9 | 10 | // these stubs are here because it is not possible to go:linkname directly the C functions on darwin arm64 11 | 12 | TEXT _malloc(SB), NOSPLIT|NOFRAME, $0-0 13 | JMP purego_malloc(SB) 14 | 15 | TEXT _free(SB), NOSPLIT|NOFRAME, $0-0 16 | JMP purego_free(SB) 17 | 18 | TEXT _setenv(SB), NOSPLIT|NOFRAME, $0-0 19 | JMP purego_setenv(SB) 20 | 21 | TEXT _unsetenv(SB), NOSPLIT|NOFRAME, $0-0 22 | JMP purego_unsetenv(SB) 23 | 24 | TEXT _sigfillset(SB), NOSPLIT|NOFRAME, $0-0 25 | JMP purego_sigfillset(SB) 26 | 27 | TEXT _nanosleep(SB), NOSPLIT|NOFRAME, $0-0 28 | JMP purego_nanosleep(SB) 29 | 30 | TEXT _abort(SB), NOSPLIT|NOFRAME, $0-0 31 | JMP purego_abort(SB) 32 | 33 | TEXT _sigaltstack(SB), NOSPLIT|NOFRAME, $0-0 34 | JMP purego_sigaltstack(SB) 35 | 36 | TEXT _pthread_attr_init(SB), NOSPLIT|NOFRAME, $0-0 37 | JMP purego_pthread_attr_init(SB) 38 | 39 | TEXT _pthread_create(SB), NOSPLIT|NOFRAME, $0-0 40 | JMP purego_pthread_create(SB) 41 | 42 | TEXT _pthread_detach(SB), NOSPLIT|NOFRAME, $0-0 43 | JMP purego_pthread_detach(SB) 44 | 45 | TEXT _pthread_sigmask(SB), NOSPLIT|NOFRAME, $0-0 46 | JMP purego_pthread_sigmask(SB) 47 | 48 | TEXT _pthread_self(SB), NOSPLIT|NOFRAME, $0-0 49 | JMP purego_pthread_self(SB) 50 | 51 | TEXT _pthread_get_stacksize_np(SB), NOSPLIT|NOFRAME, $0-0 52 | JMP purego_pthread_get_stacksize_np(SB) 53 | 54 | TEXT _pthread_attr_getstacksize(SB), NOSPLIT|NOFRAME, $0-0 55 | JMP purego_pthread_attr_getstacksize(SB) 56 | 57 | TEXT _pthread_attr_setstacksize(SB), NOSPLIT|NOFRAME, $0-0 58 | JMP purego_pthread_attr_setstacksize(SB) 59 | 60 | TEXT _pthread_attr_destroy(SB), NOSPLIT|NOFRAME, $0-0 61 | JMP purego_pthread_attr_destroy(SB) 62 | 63 | TEXT _pthread_mutex_lock(SB), NOSPLIT|NOFRAME, $0-0 64 | JMP purego_pthread_mutex_lock(SB) 65 | 66 | TEXT _pthread_mutex_unlock(SB), NOSPLIT|NOFRAME, $0-0 67 | JMP purego_pthread_mutex_unlock(SB) 68 | 69 | TEXT _pthread_cond_broadcast(SB), NOSPLIT|NOFRAME, $0-0 70 | JMP purego_pthread_cond_broadcast(SB) 71 | 72 | TEXT _pthread_setspecific(SB), NOSPLIT|NOFRAME, $0-0 73 | JMP purego_pthread_setspecific(SB) 74 | -------------------------------------------------------------------------------- /sys_unix_arm64.s: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || linux || netbsd 5 | 6 | #include "textflag.h" 7 | #include "go_asm.h" 8 | #include "funcdata.h" 9 | #include "abi_arm64.h" 10 | 11 | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 12 | NO_LOCAL_POINTERS 13 | 14 | // On entry, the trampoline in zcallback_darwin_arm64.s left 15 | // the callback index in R12 (which is volatile in the C ABI). 16 | 17 | // Save callback register arguments R0-R7 and F0-F7. 18 | // We do this at the top of the frame so they're contiguous with stack arguments. 19 | SUB $(16*8), RSP, R14 20 | FSTPD (F0, F1), (0*8)(R14) 21 | FSTPD (F2, F3), (2*8)(R14) 22 | FSTPD (F4, F5), (4*8)(R14) 23 | FSTPD (F6, F7), (6*8)(R14) 24 | STP (R0, R1), (8*8)(R14) 25 | STP (R2, R3), (10*8)(R14) 26 | STP (R4, R5), (12*8)(R14) 27 | STP (R6, R7), (14*8)(R14) 28 | 29 | // Adjust SP by frame size. 30 | SUB $(26*8), RSP 31 | 32 | // It is important to save R27 because the go assembler 33 | // uses it for move instructions for a variable. 34 | // This line: 35 | // MOVD ·callbackWrap_call(SB), R0 36 | // Creates the instructions: 37 | // ADRP 14335(PC), R27 38 | // MOVD 388(27), R0 39 | // R27 is a callee saved register so we are responsible 40 | // for ensuring its value doesn't change. So save it and 41 | // restore it at the end of this function. 42 | // R30 is the link register. crosscall2 doesn't save it 43 | // so it's saved here. 44 | STP (R27, R30), 0(RSP) 45 | 46 | // Create a struct callbackArgs on our stack. 47 | MOVD $(callbackArgs__size)(RSP), R13 48 | MOVD R12, callbackArgs_index(R13) // callback index 49 | MOVD R14, callbackArgs_args(R13) // address of args vector 50 | MOVD ZR, callbackArgs_result(R13) // result 51 | 52 | // Move parameters into registers 53 | // Get the ABIInternal function pointer 54 | // without by using a closure. 55 | MOVD ·callbackWrap_call(SB), R0 56 | MOVD (R0), R0 // fn unsafe.Pointer 57 | MOVD R13, R1 // frame (&callbackArgs{...}) 58 | MOVD $0, R3 // ctxt uintptr 59 | 60 | BL crosscall2(SB) 61 | 62 | // Get callback result. 63 | MOVD $(callbackArgs__size)(RSP), R13 64 | MOVD callbackArgs_result(R13), R0 65 | 66 | // Restore LR and R27 67 | LDP 0(RSP), (R27, R30) 68 | ADD $(26*8), RSP 69 | 70 | RET 71 | -------------------------------------------------------------------------------- /syscall.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || linux || netbsd || windows 5 | 6 | package purego 7 | 8 | // CDecl marks a function as being called using the __cdecl calling convention as defined in 9 | // the [MSDocs] when passed to NewCallback. It must be the first argument to the function. 10 | // This is only useful on 386 Windows, but it is safe to use on other platforms. 11 | // 12 | // [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 13 | type CDecl struct{} 14 | 15 | const ( 16 | maxArgs = 15 17 | numOfFloatRegisters = 8 // arm64 and amd64 both have 8 float registers 18 | ) 19 | 20 | type syscall15Args struct { 21 | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr 22 | f1, f2, f3, f4, f5, f6, f7, f8 uintptr 23 | arm64_r8 uintptr 24 | } 25 | 26 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. 27 | // There is an internal maximum number of arguments that SyscallN can take. It panics 28 | // when the maximum is exceeded. It returns the result and the libc error code if there is one. 29 | // 30 | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] 31 | // especially point 4. 32 | // 33 | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. 34 | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 35 | // for an explanation of why that is. 36 | // 37 | // On amd64, if there are more than 8 floats the 9th and so on will be placed incorrectly on the 38 | // stack. 39 | // 40 | // The pragma go:nosplit is not needed at this function declaration because it uses go:uintptrescapes 41 | // which forces all the objects that the uintptrs point to onto the heap where a stack split won't affect 42 | // their memory location. 43 | // 44 | //go:uintptrescapes 45 | func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { 46 | if fn == 0 { 47 | panic("purego: fn is nil") 48 | } 49 | if len(args) > maxArgs { 50 | panic("purego: too many arguments to SyscallN") 51 | } 52 | // add padding so there is no out-of-bounds slicing 53 | var tmp [maxArgs]uintptr 54 | copy(tmp[:], args) 55 | return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) 56 | } 57 | -------------------------------------------------------------------------------- /internal/fakecgo/go_libinit.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 5 | 6 | package fakecgo 7 | 8 | import ( 9 | "syscall" 10 | "unsafe" 11 | ) 12 | 13 | var ( 14 | pthread_g pthread_key_t 15 | 16 | runtime_init_cond = PTHREAD_COND_INITIALIZER 17 | runtime_init_mu = PTHREAD_MUTEX_INITIALIZER 18 | runtime_init_done int 19 | ) 20 | 21 | //go:nosplit 22 | //go:norace 23 | func x_cgo_notify_runtime_init_done() { 24 | pthread_mutex_lock(&runtime_init_mu) 25 | runtime_init_done = 1 26 | pthread_cond_broadcast(&runtime_init_cond) 27 | pthread_mutex_unlock(&runtime_init_mu) 28 | } 29 | 30 | // Store the g into a thread-specific value associated with the pthread key pthread_g. 31 | // And pthread_key_destructor will dropm when the thread is exiting. 32 | // 33 | //go:norace 34 | func x_cgo_bindm(g unsafe.Pointer) { 35 | // We assume this will always succeed, otherwise, there might be extra M leaking, 36 | // when a C thread exits after a cgo call. 37 | // We only invoke this function once per thread in runtime.needAndBindM, 38 | // and the next calls just reuse the bound m. 39 | pthread_setspecific(pthread_g, g) 40 | } 41 | 42 | // _cgo_try_pthread_create retries pthread_create if it fails with 43 | // EAGAIN. 44 | // 45 | //go:nosplit 46 | //go:norace 47 | func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe.Pointer, arg *ThreadStart) int { 48 | var ts syscall.Timespec 49 | // tries needs to be the same type as syscall.Timespec.Nsec 50 | // but the fields are int32 on 32bit and int64 on 64bit. 51 | // tries is assigned to syscall.Timespec.Nsec in order to match its type. 52 | tries := ts.Nsec 53 | var err int 54 | 55 | for tries = 0; tries < 20; tries++ { 56 | // inlined this call because it ran out of stack when inlining was disabled 57 | err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0)) 58 | if err == 0 { 59 | // inlined this call because it ran out of stack when inlining was disabled 60 | call5(pthread_detachABI0, uintptr(*thread), 0, 0, 0, 0) 61 | return 0 62 | } 63 | if err != int(syscall.EAGAIN) { 64 | return err 65 | } 66 | ts.Sec = 0 67 | ts.Nsec = (tries + 1) * 1000 * 1000 // Milliseconds. 68 | // inlined this call because it ran out of stack when inlining was disabled 69 | call5(nanosleepABI0, uintptr(unsafe.Pointer(&ts)), 0, 0, 0, 0) 70 | } 71 | return int(syscall.EAGAIN) 72 | } 73 | -------------------------------------------------------------------------------- /internal/fakecgo/go_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 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 | //go:build !cgo 6 | 7 | package fakecgo 8 | 9 | import "unsafe" 10 | 11 | //go:nosplit 12 | //go:norace 13 | func _cgo_sys_thread_start(ts *ThreadStart) { 14 | var attr pthread_attr_t 15 | var ign, oset sigset_t 16 | var p pthread_t 17 | var size size_t 18 | var err int 19 | 20 | sigfillset(&ign) 21 | pthread_sigmask(SIG_SETMASK, &ign, &oset) 22 | 23 | size = pthread_get_stacksize_np(pthread_self()) 24 | pthread_attr_init(&attr) 25 | pthread_attr_setstacksize(&attr, size) 26 | // Leave stacklo=0 and set stackhi=size; mstart will do the rest. 27 | ts.g.stackhi = uintptr(size) 28 | 29 | err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) 30 | 31 | pthread_sigmask(SIG_SETMASK, &oset, nil) 32 | 33 | if err != 0 { 34 | print("fakecgo: pthread_create failed: ") 35 | println(err) 36 | abort() 37 | } 38 | } 39 | 40 | // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function 41 | // 42 | //go:linkname x_threadentry_trampoline threadentry_trampoline 43 | var x_threadentry_trampoline byte 44 | var threadentry_trampolineABI0 = &x_threadentry_trampoline 45 | 46 | //go:nosplit 47 | //go:norace 48 | func threadentry(v unsafe.Pointer) unsafe.Pointer { 49 | ts := *(*ThreadStart)(v) 50 | free(v) 51 | 52 | // TODO: support ios 53 | //#if TARGET_OS_IPHONE 54 | // darwin_arm_init_thread_exception_port(); 55 | //#endif 56 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) 57 | 58 | // faking funcs in go is a bit a... involved - but the following works :) 59 | fn := uintptr(unsafe.Pointer(&ts.fn)) 60 | (*(*func())(unsafe.Pointer(&fn)))() 61 | 62 | return nil 63 | } 64 | 65 | // here we will store a pointer to the provided setg func 66 | var setg_func uintptr 67 | 68 | // x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) 69 | // This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us 70 | // Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup 71 | // This function can't be go:systemstack since go is not in a state where the systemcheck would work. 72 | // 73 | //go:nosplit 74 | //go:norace 75 | func x_cgo_init(g *G, setg uintptr) { 76 | var size size_t 77 | 78 | setg_func = setg 79 | size = pthread_get_stacksize_np(pthread_self()) 80 | g.stacklo = uintptr(unsafe.Add(unsafe.Pointer(&size), -size+4096)) 81 | 82 | //TODO: support ios 83 | //#if TARGET_OS_IPHONE 84 | // darwin_arm_init_mach_exception_handler(); 85 | // darwin_arm_init_thread_exception_port(); 86 | // init_working_dir(); 87 | //#endif 88 | } 89 | -------------------------------------------------------------------------------- /sys_loong64.s: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | //go:build linux 5 | 6 | #include "textflag.h" 7 | #include "go_asm.h" 8 | #include "funcdata.h" 9 | 10 | #define STACK_SIZE 64 11 | #define PTR_ADDRESS (STACK_SIZE - 8) 12 | 13 | // syscall15X calls a function in libc on behalf of the syscall package. 14 | // syscall15X takes a pointer to a struct like: 15 | // struct { 16 | // fn uintptr 17 | // a1 uintptr 18 | // a2 uintptr 19 | // a3 uintptr 20 | // a4 uintptr 21 | // a5 uintptr 22 | // a6 uintptr 23 | // a7 uintptr 24 | // a8 uintptr 25 | // a9 uintptr 26 | // a10 uintptr 27 | // a11 uintptr 28 | // a12 uintptr 29 | // a13 uintptr 30 | // a14 uintptr 31 | // a15 uintptr 32 | // r1 uintptr 33 | // r2 uintptr 34 | // err uintptr 35 | // } 36 | // syscall15X must be called on the g0 stack with the 37 | // C calling convention (use libcCall). 38 | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 39 | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) 40 | TEXT syscall15X(SB), NOSPLIT, $0 41 | // push structure pointer 42 | SUBV $STACK_SIZE, R3 43 | MOVV R4, PTR_ADDRESS(R3) 44 | MOVV R4, R13 45 | 46 | MOVD syscall15Args_f1(R13), F0 // f1 47 | MOVD syscall15Args_f2(R13), F1 // f2 48 | MOVD syscall15Args_f3(R13), F2 // f3 49 | MOVD syscall15Args_f4(R13), F3 // f4 50 | MOVD syscall15Args_f5(R13), F4 // f5 51 | MOVD syscall15Args_f6(R13), F5 // f6 52 | MOVD syscall15Args_f7(R13), F6 // f7 53 | MOVD syscall15Args_f8(R13), F7 // f8 54 | 55 | MOVV syscall15Args_a1(R13), R4 // a1 56 | MOVV syscall15Args_a2(R13), R5 // a2 57 | MOVV syscall15Args_a3(R13), R6 // a3 58 | MOVV syscall15Args_a4(R13), R7 // a4 59 | MOVV syscall15Args_a5(R13), R8 // a5 60 | MOVV syscall15Args_a6(R13), R9 // a6 61 | MOVV syscall15Args_a7(R13), R10 // a7 62 | MOVV syscall15Args_a8(R13), R11 // a8 63 | 64 | // push a9-a15 onto stack 65 | MOVV syscall15Args_a9(R13), R12 66 | MOVV R12, 0(R3) 67 | MOVV syscall15Args_a10(R13), R12 68 | MOVV R12, 8(R3) 69 | MOVV syscall15Args_a11(R13), R12 70 | MOVV R12, 16(R3) 71 | MOVV syscall15Args_a12(R13), R12 72 | MOVV R12, 24(R3) 73 | MOVV syscall15Args_a13(R13), R12 74 | MOVV R12, 32(R3) 75 | MOVV syscall15Args_a14(R13), R12 76 | MOVV R12, 40(R3) 77 | MOVV syscall15Args_a15(R13), R12 78 | MOVV R12, 48(R3) 79 | 80 | MOVV syscall15Args_fn(R13), R12 81 | JAL (R12) 82 | 83 | // pop structure pointer 84 | MOVV PTR_ADDRESS(R3), R13 85 | ADDV $STACK_SIZE, R3 86 | 87 | // save R4, R5 88 | MOVV R4, syscall15Args_a1(R13) 89 | MOVV R5, syscall15Args_a2(R13) 90 | 91 | // save f0-f3 92 | MOVD F0, syscall15Args_f1(R13) 93 | MOVD F1, syscall15Args_f2(R13) 94 | MOVD F2, syscall15Args_f3(R13) 95 | MOVD F3, syscall15Args_f4(R13) 96 | RET 97 | -------------------------------------------------------------------------------- /.github/scripts/bsd_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # SPDX-License-Identifier: Apache-2.0 4 | # SPDX-FileCopyrightText: 2025 The Ebitengine Authors 5 | 6 | # BSD tests run within QEMU on Ubuntu. 7 | # vmactions/*-vm only supports a single "step" where it 8 | # brings down the VM at the end of the step, so all 9 | # the commands to run need to be put into this single block. 10 | 11 | echo "Running tests on $(uname -a) at $PWD" 12 | 13 | PATH=$PATH:/usr/local/go/bin/ 14 | 15 | # verify Go is available 16 | go version 17 | 18 | echo "=> go build" 19 | go build -v ./... 20 | # Compile without optimization to check potential stack overflow. 21 | # The option '-gcflags=all=-N -l' is often used at Visual Studio Code. 22 | # See also https://go.googlesource.com/vscode-go/+/HEAD/docs/debugging.md#launch and the issue hajimehoshi/ebiten#2120. 23 | go build "-gcflags=all=-N -l" -v ./... 24 | 25 | # Check cross-compiling Windows binaries. 26 | env GOOS=windows GOARCH=386 go build -v ./... 27 | env GOOS=windows GOARCH=amd64 go build -v ./... 28 | env GOOS=windows GOARCH=arm64 go build -v ./... 29 | 30 | # Check cross-compiling macOS binaries. 31 | env GOOS=darwin GOARCH=amd64 go build -v ./... 32 | env GOOS=darwin GOARCH=arm64 go build -v ./... 33 | 34 | # Check cross-compiling Linux binaries. 35 | env GOOS=linux GOARCH=amd64 go build -v ./... 36 | env GOOS=linux GOARCH=arm64 go build -v ./... 37 | 38 | # Check cross-compiling FreeBSD binaries. 39 | env GOOS=freebsd GOARCH=amd64 go build -gcflags="github.com/ebitengine/purego/internal/fakecgo=-std" -v ./... 40 | env GOOS=freebsd GOARCH=arm64 go build -gcflags="github.com/ebitengine/purego/internal/fakecgo=-std" -v ./... 41 | 42 | # Check cross-compiling NetBSD binaries. 43 | env GOOS=netbsd GOARCH=amd64 go build -v ./... 44 | env GOOS=netbsd GOARCH=arm64 go build -v ./... 45 | 46 | echo "=> go build (plugin)" 47 | # Make sure that plugin buildmode works since we save the R15 register (#254) 48 | go build -buildmode=plugin ./examples/libc 49 | 50 | echo "=> go mod vendor" 51 | mkdir /tmp/vendoring 52 | cd /tmp/vendoring 53 | go mod init foo 54 | echo 'package main' > main.go 55 | echo 'import (' >> main.go 56 | echo ' _ "github.com/ebitengine/purego"' >> main.go 57 | echo ')' >> main.go 58 | echo 'func main() {}' >> main.go 59 | go mod edit -replace github.com/ebitengine/purego=$GITHUB_WORKSPACE 60 | go mod tidy 61 | go mod vendor 62 | go build -v . 63 | 64 | cd $GITHUB_WORKSPACE 65 | echo "=> go test CGO_ENABLED=0" 66 | env CGO_ENABLED=0 go test -gcflags="github.com/ebitengine/purego/internal/fakecgo=-std" -shuffle=on -v -count=10 ./... 67 | 68 | echo "=> go test CGO_ENABLED=1" 69 | env CGO_ENABLED=1 go test -shuffle=on -v -count=10 ./... 70 | 71 | echo "=> go test CGO_ENABLED=0 w/o optimization" 72 | env CGO_ENABLED=0 go test "-gcflags=all=-N -l -std" -v ./... 73 | 74 | echo "=> go test CGO_ENABLED=1 w/o optimization" 75 | env CGO_ENABLED=1 go test "-gcflags=all=-N -l" -v ./... 76 | 77 | if [ -z "$(go version | grep '^1.1')" ]; then 78 | echo "=> go test race" 79 | go test -race -shuffle=on -v -count=10 ./... 80 | fi 81 | -------------------------------------------------------------------------------- /sys_arm64.s: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || linux || netbsd || windows 5 | 6 | #include "textflag.h" 7 | #include "go_asm.h" 8 | #include "funcdata.h" 9 | 10 | #define STACK_SIZE 64 11 | #define PTR_ADDRESS (STACK_SIZE - 8) 12 | 13 | // syscall15X calls a function in libc on behalf of the syscall package. 14 | // syscall15X takes a pointer to a struct like: 15 | // struct { 16 | // fn uintptr 17 | // a1 uintptr 18 | // a2 uintptr 19 | // a3 uintptr 20 | // a4 uintptr 21 | // a5 uintptr 22 | // a6 uintptr 23 | // a7 uintptr 24 | // a8 uintptr 25 | // a9 uintptr 26 | // a10 uintptr 27 | // a11 uintptr 28 | // a12 uintptr 29 | // a13 uintptr 30 | // a14 uintptr 31 | // a15 uintptr 32 | // r1 uintptr 33 | // r2 uintptr 34 | // err uintptr 35 | // } 36 | // syscall15X must be called on the g0 stack with the 37 | // C calling convention (use libcCall). 38 | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 39 | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) 40 | TEXT syscall15X(SB), NOSPLIT, $0 41 | SUB $STACK_SIZE, RSP // push structure pointer 42 | MOVD R0, PTR_ADDRESS(RSP) 43 | MOVD R0, R9 44 | 45 | FMOVD syscall15Args_f1(R9), F0 // f1 46 | FMOVD syscall15Args_f2(R9), F1 // f2 47 | FMOVD syscall15Args_f3(R9), F2 // f3 48 | FMOVD syscall15Args_f4(R9), F3 // f4 49 | FMOVD syscall15Args_f5(R9), F4 // f5 50 | FMOVD syscall15Args_f6(R9), F5 // f6 51 | FMOVD syscall15Args_f7(R9), F6 // f7 52 | FMOVD syscall15Args_f8(R9), F7 // f8 53 | 54 | MOVD syscall15Args_a1(R9), R0 // a1 55 | MOVD syscall15Args_a2(R9), R1 // a2 56 | MOVD syscall15Args_a3(R9), R2 // a3 57 | MOVD syscall15Args_a4(R9), R3 // a4 58 | MOVD syscall15Args_a5(R9), R4 // a5 59 | MOVD syscall15Args_a6(R9), R5 // a6 60 | MOVD syscall15Args_a7(R9), R6 // a7 61 | MOVD syscall15Args_a8(R9), R7 // a8 62 | MOVD syscall15Args_arm64_r8(R9), R8 // r8 63 | 64 | MOVD syscall15Args_a9(R9), R10 65 | MOVD R10, 0(RSP) // push a9 onto stack 66 | MOVD syscall15Args_a10(R9), R10 67 | MOVD R10, 8(RSP) // push a10 onto stack 68 | MOVD syscall15Args_a11(R9), R10 69 | MOVD R10, 16(RSP) // push a11 onto stack 70 | MOVD syscall15Args_a12(R9), R10 71 | MOVD R10, 24(RSP) // push a12 onto stack 72 | MOVD syscall15Args_a13(R9), R10 73 | MOVD R10, 32(RSP) // push a13 onto stack 74 | MOVD syscall15Args_a14(R9), R10 75 | MOVD R10, 40(RSP) // push a14 onto stack 76 | MOVD syscall15Args_a15(R9), R10 77 | MOVD R10, 48(RSP) // push a15 onto stack 78 | 79 | MOVD syscall15Args_fn(R9), R10 // fn 80 | BL (R10) 81 | 82 | MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer 83 | ADD $STACK_SIZE, RSP 84 | 85 | MOVD R0, syscall15Args_a1(R2) // save r1 86 | MOVD R1, syscall15Args_a2(R2) // save r3 87 | FMOVD F0, syscall15Args_f1(R2) // save f0 88 | FMOVD F1, syscall15Args_f2(R2) // save f1 89 | FMOVD F2, syscall15Args_f3(R2) // save f2 90 | FMOVD F3, syscall15Args_f4(R2) // save f3 91 | 92 | RET 93 | -------------------------------------------------------------------------------- /internal/fakecgo/trampolines_amd64.s: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build !cgo && (darwin || linux || freebsd) 5 | 6 | /* 7 | trampoline for emulating required C functions for cgo in go (see cgo.go) 8 | (we convert cdecl calling convention to go and vice-versa) 9 | 10 | Since we're called from go and call into C we can cheat a bit with the calling conventions: 11 | - in go all the registers are caller saved 12 | - in C we have a couple of callee saved registers 13 | 14 | => we can use BX, R12, R13, R14, R15 instead of the stack 15 | 16 | C Calling convention cdecl used here (we only need integer args): 17 | 1. arg: DI 18 | 2. arg: SI 19 | 3. arg: DX 20 | 4. arg: CX 21 | 5. arg: R8 22 | 6. arg: R9 23 | We don't need floats with these functions -> AX=0 24 | return value will be in AX 25 | */ 26 | #include "textflag.h" 27 | #include "go_asm.h" 28 | #include "abi_amd64.h" 29 | 30 | // these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. 31 | 32 | TEXT x_cgo_init_trampoline(SB), NOSPLIT, $16 33 | MOVQ DI, AX 34 | MOVQ SI, BX 35 | MOVQ ·x_cgo_init_call(SB), DX 36 | MOVQ (DX), CX 37 | CALL CX 38 | RET 39 | 40 | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 41 | MOVQ DI, AX 42 | MOVQ ·x_cgo_thread_start_call(SB), DX 43 | MOVQ (DX), CX 44 | CALL CX 45 | RET 46 | 47 | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 48 | MOVQ DI, AX 49 | MOVQ ·x_cgo_setenv_call(SB), DX 50 | MOVQ (DX), CX 51 | CALL CX 52 | RET 53 | 54 | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 55 | MOVQ DI, AX 56 | MOVQ ·x_cgo_unsetenv_call(SB), DX 57 | MOVQ (DX), CX 58 | CALL CX 59 | RET 60 | 61 | TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0 62 | CALL ·x_cgo_notify_runtime_init_done(SB) 63 | RET 64 | 65 | TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 66 | CALL ·x_cgo_bindm(SB) 67 | RET 68 | 69 | // func setg_trampoline(setg uintptr, g uintptr) 70 | TEXT ·setg_trampoline(SB), NOSPLIT, $0-16 71 | MOVQ G+8(FP), DI 72 | MOVQ setg+0(FP), BX 73 | XORL AX, AX 74 | CALL BX 75 | RET 76 | 77 | TEXT threadentry_trampoline(SB), NOSPLIT, $0 78 | // See crosscall2. 79 | PUSH_REGS_HOST_TO_ABI0() 80 | 81 | // X15 is designated by Go as a fixed zero register. 82 | // Calling directly into ABIInternal, ensure it is zero. 83 | PXOR X15, X15 84 | 85 | MOVQ DI, AX 86 | MOVQ ·threadentry_call(SB), DX 87 | MOVQ (DX), CX 88 | CALL CX 89 | 90 | POP_REGS_HOST_TO_ABI0() 91 | RET 92 | 93 | TEXT ·call5(SB), NOSPLIT, $0-56 94 | MOVQ fn+0(FP), BX 95 | MOVQ a1+8(FP), DI 96 | MOVQ a2+16(FP), SI 97 | MOVQ a3+24(FP), DX 98 | MOVQ a4+32(FP), CX 99 | MOVQ a5+40(FP), R8 100 | 101 | XORL AX, AX // no floats 102 | 103 | PUSHQ BP // save BP 104 | MOVQ SP, BP // save SP inside BP bc BP is callee-saved 105 | SUBQ $16, SP // allocate space for alignment 106 | ANDQ $-16, SP // align on 16 bytes for SSE 107 | 108 | CALL BX 109 | 110 | MOVQ BP, SP // get SP back 111 | POPQ BP // restore BP 112 | 113 | MOVQ AX, ret+48(FP) 114 | RET 115 | -------------------------------------------------------------------------------- /abi_amd64.h: -------------------------------------------------------------------------------- 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 100 | -------------------------------------------------------------------------------- /internal/fakecgo/abi_amd64.h: -------------------------------------------------------------------------------- 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 100 | -------------------------------------------------------------------------------- /internal/fakecgo/go_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 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 | //go:build !cgo 6 | 7 | package fakecgo 8 | 9 | import "unsafe" 10 | 11 | //go:nosplit 12 | func _cgo_sys_thread_start(ts *ThreadStart) { 13 | var attr pthread_attr_t 14 | var ign, oset sigset_t 15 | var p pthread_t 16 | var size size_t 17 | var err int 18 | 19 | //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug 20 | sigfillset(&ign) 21 | pthread_sigmask(SIG_SETMASK, &ign, &oset) 22 | 23 | pthread_attr_init(&attr) 24 | pthread_attr_getstacksize(&attr, &size) 25 | // Leave stacklo=0 and set stackhi=size; mstart will do the rest. 26 | ts.g.stackhi = uintptr(size) 27 | 28 | err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) 29 | 30 | pthread_sigmask(SIG_SETMASK, &oset, nil) 31 | 32 | if err != 0 { 33 | print("fakecgo: pthread_create failed: ") 34 | println(err) 35 | abort() 36 | } 37 | } 38 | 39 | // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function 40 | // 41 | //go:linkname x_threadentry_trampoline threadentry_trampoline 42 | var x_threadentry_trampoline byte 43 | var threadentry_trampolineABI0 = &x_threadentry_trampoline 44 | 45 | //go:nosplit 46 | func threadentry(v unsafe.Pointer) unsafe.Pointer { 47 | ts := *(*ThreadStart)(v) 48 | free(v) 49 | 50 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) 51 | 52 | // faking funcs in go is a bit a... involved - but the following works :) 53 | fn := uintptr(unsafe.Pointer(&ts.fn)) 54 | (*(*func())(unsafe.Pointer(&fn)))() 55 | 56 | return nil 57 | } 58 | 59 | // here we will store a pointer to the provided setg func 60 | var setg_func uintptr 61 | 62 | // x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) 63 | // This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us 64 | // Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup 65 | // This function can't be go:systemstack since go is not in a state where the systemcheck would work. 66 | // 67 | //go:nosplit 68 | func x_cgo_init(g *G, setg uintptr) { 69 | var size size_t 70 | var attr *pthread_attr_t 71 | 72 | /* The memory sanitizer distributed with versions of clang 73 | before 3.8 has a bug: if you call mmap before malloc, mmap 74 | may return an address that is later overwritten by the msan 75 | library. Avoid this problem by forcing a call to malloc 76 | here, before we ever call malloc. 77 | 78 | This is only required for the memory sanitizer, so it's 79 | unfortunate that we always run it. It should be possible 80 | to remove this when we no longer care about versions of 81 | clang before 3.8. The test for this is 82 | misc/cgo/testsanitizers. 83 | 84 | GCC works hard to eliminate a seemingly unnecessary call to 85 | malloc, so we actually use the memory we allocate. */ 86 | 87 | setg_func = setg 88 | attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) 89 | if attr == nil { 90 | println("fakecgo: malloc failed") 91 | abort() 92 | } 93 | pthread_attr_init(attr) 94 | pthread_attr_getstacksize(attr, &size) 95 | // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` 96 | // but this should be OK since we are taking the address of the first variable in this function. 97 | g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 98 | pthread_attr_destroy(attr) 99 | free(unsafe.Pointer(attr)) 100 | } 101 | -------------------------------------------------------------------------------- /internal/fakecgo/go_freebsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 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 | //go:build !cgo 6 | 7 | package fakecgo 8 | 9 | import "unsafe" 10 | 11 | //go:nosplit 12 | func _cgo_sys_thread_start(ts *ThreadStart) { 13 | var attr pthread_attr_t 14 | var ign, oset sigset_t 15 | var p pthread_t 16 | var size size_t 17 | var err int 18 | 19 | // fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug 20 | sigfillset(&ign) 21 | pthread_sigmask(SIG_SETMASK, &ign, &oset) 22 | 23 | pthread_attr_init(&attr) 24 | pthread_attr_getstacksize(&attr, &size) 25 | // Leave stacklo=0 and set stackhi=size; mstart will do the rest. 26 | ts.g.stackhi = uintptr(size) 27 | 28 | err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) 29 | 30 | pthread_sigmask(SIG_SETMASK, &oset, nil) 31 | 32 | if err != 0 { 33 | print("fakecgo: pthread_create failed: ") 34 | println(err) 35 | abort() 36 | } 37 | } 38 | 39 | // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function 40 | // 41 | //go:linkname x_threadentry_trampoline threadentry_trampoline 42 | var x_threadentry_trampoline byte 43 | var threadentry_trampolineABI0 = &x_threadentry_trampoline 44 | 45 | //go:nosplit 46 | func threadentry(v unsafe.Pointer) unsafe.Pointer { 47 | ts := *(*ThreadStart)(v) 48 | free(v) 49 | 50 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) 51 | 52 | // faking funcs in go is a bit a... involved - but the following works :) 53 | fn := uintptr(unsafe.Pointer(&ts.fn)) 54 | (*(*func())(unsafe.Pointer(&fn)))() 55 | 56 | return nil 57 | } 58 | 59 | // here we will store a pointer to the provided setg func 60 | var setg_func uintptr 61 | 62 | // x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) 63 | // This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us 64 | // Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup 65 | // This function can't be go:systemstack since go is not in a state where the systemcheck would work. 66 | // 67 | //go:nosplit 68 | func x_cgo_init(g *G, setg uintptr) { 69 | var size size_t 70 | var attr *pthread_attr_t 71 | 72 | /* The memory sanitizer distributed with versions of clang 73 | before 3.8 has a bug: if you call mmap before malloc, mmap 74 | may return an address that is later overwritten by the msan 75 | library. Avoid this problem by forcing a call to malloc 76 | here, before we ever call malloc. 77 | 78 | This is only required for the memory sanitizer, so it's 79 | unfortunate that we always run it. It should be possible 80 | to remove this when we no longer care about versions of 81 | clang before 3.8. The test for this is 82 | misc/cgo/testsanitizers. 83 | 84 | GCC works hard to eliminate a seemingly unnecessary call to 85 | malloc, so we actually use the memory we allocate. */ 86 | 87 | setg_func = setg 88 | attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) 89 | if attr == nil { 90 | println("fakecgo: malloc failed") 91 | abort() 92 | } 93 | pthread_attr_init(attr) 94 | pthread_attr_getstacksize(attr, &size) 95 | // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` 96 | // but this should be OK since we are taking the address of the first variable in this function. 97 | g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 98 | pthread_attr_destroy(attr) 99 | free(unsafe.Pointer(attr)) 100 | } 101 | -------------------------------------------------------------------------------- /internal/fakecgo/go_netbsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 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 | //go:build !cgo && (amd64 || arm64) 6 | 7 | package fakecgo 8 | 9 | import "unsafe" 10 | 11 | //go:nosplit 12 | func _cgo_sys_thread_start(ts *ThreadStart) { 13 | var attr pthread_attr_t 14 | var ign, oset sigset_t 15 | var p pthread_t 16 | var size size_t 17 | var err int 18 | 19 | // fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug 20 | sigfillset(&ign) 21 | pthread_sigmask(SIG_SETMASK, &ign, &oset) 22 | 23 | pthread_attr_init(&attr) 24 | pthread_attr_getstacksize(&attr, &size) 25 | // Leave stacklo=0 and set stackhi=size; mstart will do the rest. 26 | ts.g.stackhi = uintptr(size) 27 | 28 | err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) 29 | 30 | pthread_sigmask(SIG_SETMASK, &oset, nil) 31 | 32 | if err != 0 { 33 | print("fakecgo: pthread_create failed: ") 34 | println(err) 35 | abort() 36 | } 37 | } 38 | 39 | // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function 40 | // 41 | //go:linkname x_threadentry_trampoline threadentry_trampoline 42 | var x_threadentry_trampoline byte 43 | var threadentry_trampolineABI0 = &x_threadentry_trampoline 44 | 45 | //go:nosplit 46 | func threadentry(v unsafe.Pointer) unsafe.Pointer { 47 | var ss stack_t 48 | ts := *(*ThreadStart)(v) 49 | free(v) 50 | 51 | // On NetBSD, a new thread inherits the signal stack of the 52 | // creating thread. That confuses minit, so we remove that 53 | // signal stack here before calling the regular mstart. It's 54 | // a bit baroque to remove a signal stack here only to add one 55 | // in minit, but it's a simple change that keeps NetBSD 56 | // working like other OS's. At this point all signals are 57 | // blocked, so there is no race. 58 | ss.ss_flags = SS_DISABLE 59 | sigaltstack(&ss, nil) 60 | 61 | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) 62 | 63 | // faking funcs in go is a bit a... involved - but the following works :) 64 | fn := uintptr(unsafe.Pointer(&ts.fn)) 65 | (*(*func())(unsafe.Pointer(&fn)))() 66 | 67 | return nil 68 | } 69 | 70 | // here we will store a pointer to the provided setg func 71 | var setg_func uintptr 72 | 73 | //go:nosplit 74 | func x_cgo_init(g *G, setg uintptr) { 75 | var size size_t 76 | var attr *pthread_attr_t 77 | 78 | /* The memory sanitizer distributed with versions of clang 79 | before 3.8 has a bug: if you call mmap before malloc, mmap 80 | may return an address that is later overwritten by the msan 81 | library. Avoid this problem by forcing a call to malloc 82 | here, before we ever call malloc. 83 | 84 | This is only required for the memory sanitizer, so it's 85 | unfortunate that we always run it. It should be possible 86 | to remove this when we no longer care about versions of 87 | clang before 3.8. The test for this is 88 | misc/cgo/testsanitizers. 89 | 90 | GCC works hard to eliminate a seemingly unnecessary call to 91 | malloc, so we actually use the memory we allocate. */ 92 | 93 | setg_func = setg 94 | attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) 95 | if attr == nil { 96 | println("fakecgo: malloc failed") 97 | abort() 98 | } 99 | pthread_attr_init(attr) 100 | pthread_attr_getstacksize(attr, &size) 101 | // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` 102 | // but this should be OK since we are taking the address of the first variable in this function. 103 | g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 104 | pthread_attr_destroy(attr) 105 | free(unsafe.Pointer(attr)) 106 | } 107 | -------------------------------------------------------------------------------- /wincallback.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 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 | //go:build ignore 6 | 7 | // Generate Windows callback assembly file. 8 | // This was copied from the Go repository and modified to generate non-Windows assembly files. 9 | 10 | package main 11 | 12 | import ( 13 | "bytes" 14 | "fmt" 15 | "os" 16 | ) 17 | 18 | const maxCallback = 2000 19 | 20 | func genasmAmd64() { 21 | var buf bytes.Buffer 22 | 23 | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. 24 | 25 | //go:build darwin || freebsd || linux || netbsd 26 | 27 | // runtime·callbackasm is called by external code to 28 | // execute Go implemented callback function. It is not 29 | // called from the start, instead runtime·compilecallback 30 | // always returns address into runtime·callbackasm offset 31 | // appropriately so different callbacks start with different 32 | // CALL instruction in runtime·callbackasm. This determines 33 | // which Go callback function is executed later on. 34 | #include "textflag.h" 35 | 36 | TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 37 | `) 38 | for i := 0; i < maxCallback; i++ { 39 | buf.WriteString("\tCALL\tcallbackasm1(SB)\n") 40 | } 41 | if err := os.WriteFile("zcallback_amd64.s", buf.Bytes(), 0644); err != nil { 42 | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) 43 | os.Exit(2) 44 | } 45 | } 46 | 47 | func genasmArm64() { 48 | var buf bytes.Buffer 49 | 50 | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. 51 | 52 | //go:build darwin || freebsd || linux || netbsd 53 | 54 | // External code calls into callbackasm at an offset corresponding 55 | // to the callback index. Callbackasm is a table of MOV and B instructions. 56 | // The MOV instruction loads R12 with the callback index, and the 57 | // B instruction branches to callbackasm1. 58 | // callbackasm1 takes the callback index from R12 and 59 | // indexes into an array that stores information about each callback. 60 | // It then calls the Go implementation for that callback. 61 | #include "textflag.h" 62 | 63 | TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 64 | `) 65 | for i := 0; i < maxCallback; i++ { 66 | fmt.Fprintf(&buf, "\tMOVD\t$%d, R12\n", i) 67 | buf.WriteString("\tB\tcallbackasm1(SB)\n") 68 | } 69 | if err := os.WriteFile("zcallback_arm64.s", buf.Bytes(), 0644); err != nil { 70 | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) 71 | os.Exit(2) 72 | } 73 | } 74 | 75 | func genasmLoong64() { 76 | var buf bytes.Buffer 77 | 78 | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. 79 | 80 | //go:build darwin || freebsd || linux || netbsd 81 | 82 | // External code calls into callbackasm at an offset corresponding 83 | // to the callback index. Callbackasm is a table of MOVV and JMP instructions. 84 | // The MOVV instruction loads R12 with the callback index, and the 85 | // JMP instruction branches to callbackasm1. 86 | // callbackasm1 takes the callback index from R12 and 87 | // indexes into an array that stores information about each callback. 88 | // It then calls the Go implementation for that callback. 89 | #include "textflag.h" 90 | 91 | TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 92 | `) 93 | for i := 0; i < maxCallback; i++ { 94 | fmt.Fprintf(&buf, "\tMOVV\t$%d, R13\n", i) 95 | buf.WriteString("\tJMP\tcallbackasm1(SB)\n") 96 | } 97 | if err := os.WriteFile("zcallback_loong64.s", buf.Bytes(), 0644); err != nil { 98 | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) 99 | os.Exit(2) 100 | } 101 | } 102 | 103 | func main() { 104 | genasmAmd64() 105 | genasmArm64() 106 | genasmLoong64() 107 | } 108 | -------------------------------------------------------------------------------- /dlfcn.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build (darwin || freebsd || linux || netbsd) && !android && !faketime 5 | 6 | package purego 7 | 8 | import ( 9 | "unsafe" 10 | ) 11 | 12 | // Unix Specification for dlfcn.h: https://pubs.opengroup.org/onlinepubs/7908799/xsh/dlfcn.h.html 13 | 14 | var ( 15 | fnDlopen func(path string, mode int) uintptr 16 | fnDlsym func(handle uintptr, name string) uintptr 17 | fnDlerror func() string 18 | fnDlclose func(handle uintptr) bool 19 | ) 20 | 21 | func init() { 22 | RegisterFunc(&fnDlopen, dlopenABI0) 23 | RegisterFunc(&fnDlsym, dlsymABI0) 24 | RegisterFunc(&fnDlerror, dlerrorABI0) 25 | RegisterFunc(&fnDlclose, dlcloseABI0) 26 | } 27 | 28 | // Dlopen examines the dynamic library or bundle file specified by path. If the file is compatible 29 | // with the current process and has not already been loaded into the 30 | // current process, it is loaded and linked. After being linked, if it contains 31 | // any initializer functions, they are called, before Dlopen 32 | // returns. It returns a handle that can be used with Dlsym and Dlclose. 33 | // A second call to Dlopen with the same path will return the same handle, but the internal 34 | // reference count for the handle will be incremented. Therefore, all 35 | // Dlopen calls should be balanced with a Dlclose call. 36 | // 37 | // This function is not available on Windows. 38 | // Use [golang.org/x/sys/windows.LoadLibrary], [golang.org/x/sys/windows.LoadLibraryEx], 39 | // [golang.org/x/sys/windows.NewLazyDLL], or [golang.org/x/sys/windows.NewLazySystemDLL] for Windows instead. 40 | func Dlopen(path string, mode int) (uintptr, error) { 41 | u := fnDlopen(path, mode) 42 | if u == 0 { 43 | return 0, Dlerror{fnDlerror()} 44 | } 45 | return u, nil 46 | } 47 | 48 | // Dlsym takes a "handle" of a dynamic library returned by Dlopen and the symbol name. 49 | // It returns the address where that symbol is loaded into memory. If the symbol is not found, 50 | // in the specified library or any of the libraries that were automatically loaded by Dlopen 51 | // when that library was loaded, Dlsym returns zero. 52 | // 53 | // This function is not available on Windows. 54 | // Use [golang.org/x/sys/windows.GetProcAddress] for Windows instead. 55 | func Dlsym(handle uintptr, name string) (uintptr, error) { 56 | u := fnDlsym(handle, name) 57 | if u == 0 { 58 | return 0, Dlerror{fnDlerror()} 59 | } 60 | return u, nil 61 | } 62 | 63 | // Dlclose decrements the reference count on the dynamic library handle. 64 | // If the reference count drops to zero and no other loaded libraries 65 | // use symbols in it, then the dynamic library is unloaded. 66 | // 67 | // This function is not available on Windows. 68 | // Use [golang.org/x/sys/windows.FreeLibrary] for Windows instead. 69 | func Dlclose(handle uintptr) error { 70 | if fnDlclose(handle) { 71 | return Dlerror{fnDlerror()} 72 | } 73 | return nil 74 | } 75 | 76 | func loadSymbol(handle uintptr, name string) (uintptr, error) { 77 | return Dlsym(handle, name) 78 | } 79 | 80 | // these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go 81 | // the indirection is necessary because a function is actually a pointer to the pointer to the code. 82 | // sadly, I do not know of anyway to remove the assembly stubs entirely because //go:linkname doesn't 83 | // appear to work if you link directly to the C function on darwin arm64. 84 | 85 | //go:linkname dlopen dlopen 86 | var dlopen uint8 87 | var dlopenABI0 = uintptr(unsafe.Pointer(&dlopen)) 88 | 89 | //go:linkname dlsym dlsym 90 | var dlsym uint8 91 | var dlsymABI0 = uintptr(unsafe.Pointer(&dlsym)) 92 | 93 | //go:linkname dlclose dlclose 94 | var dlclose uint8 95 | var dlcloseABI0 = uintptr(unsafe.Pointer(&dlclose)) 96 | 97 | //go:linkname dlerror dlerror 98 | var dlerror uint8 99 | var dlerrorABI0 = uintptr(unsafe.Pointer(&dlerror)) 100 | -------------------------------------------------------------------------------- /internal/fakecgo/callbacks.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 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 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 6 | 7 | package fakecgo 8 | 9 | import ( 10 | _ "unsafe" 11 | ) 12 | 13 | // TODO: decide if we need _runtime_cgo_panic_internal 14 | 15 | //go:linkname x_cgo_init_trampoline x_cgo_init_trampoline 16 | //go:linkname _cgo_init _cgo_init 17 | var x_cgo_init_trampoline byte 18 | var _cgo_init = &x_cgo_init_trampoline 19 | 20 | // Creates a new system thread without updating any Go state. 21 | // 22 | // This method is invoked during shared library loading to create a new OS 23 | // thread to perform the runtime initialization. This method is similar to 24 | // _cgo_sys_thread_start except that it doesn't update any Go state. 25 | 26 | //go:linkname x_cgo_thread_start_trampoline x_cgo_thread_start_trampoline 27 | //go:linkname _cgo_thread_start _cgo_thread_start 28 | var x_cgo_thread_start_trampoline byte 29 | var _cgo_thread_start = &x_cgo_thread_start_trampoline 30 | 31 | // Notifies that the runtime has been initialized. 32 | // 33 | // We currently block at every CGO entry point (via _cgo_wait_runtime_init_done) 34 | // to ensure that the runtime has been initialized before the CGO call is 35 | // executed. This is necessary for shared libraries where we kickoff runtime 36 | // initialization in a separate thread and return without waiting for this 37 | // thread to complete the init. 38 | 39 | //go:linkname x_cgo_notify_runtime_init_done_trampoline x_cgo_notify_runtime_init_done_trampoline 40 | //go:linkname _cgo_notify_runtime_init_done _cgo_notify_runtime_init_done 41 | var x_cgo_notify_runtime_init_done_trampoline byte 42 | var _cgo_notify_runtime_init_done = &x_cgo_notify_runtime_init_done_trampoline 43 | 44 | // Indicates whether a dummy thread key has been created or not. 45 | // 46 | // When calling go exported function from C, we register a destructor 47 | // callback, for a dummy thread key, by using pthread_key_create. 48 | 49 | //go:linkname _cgo_pthread_key_created _cgo_pthread_key_created 50 | var x_cgo_pthread_key_created uintptr 51 | var _cgo_pthread_key_created = &x_cgo_pthread_key_created 52 | 53 | // Set the x_crosscall2_ptr C function pointer variable point to crosscall2. 54 | // It's for the runtime package to call at init time. 55 | func set_crosscall2() { 56 | // nothing needs to be done here for fakecgo 57 | // because it's possible to just call cgocallback directly 58 | } 59 | 60 | //go:linkname _set_crosscall2 runtime.set_crosscall2 61 | var _set_crosscall2 = set_crosscall2 62 | 63 | // Store the g into the thread-specific value. 64 | // So that pthread_key_destructor will dropm when the thread is exiting. 65 | 66 | //go:linkname x_cgo_bindm_trampoline x_cgo_bindm_trampoline 67 | //go:linkname _cgo_bindm _cgo_bindm 68 | var x_cgo_bindm_trampoline byte 69 | var _cgo_bindm = &x_cgo_bindm_trampoline 70 | 71 | // TODO: decide if we need x_cgo_set_context_function 72 | // TODO: decide if we need _cgo_yield 73 | 74 | var ( 75 | // In Go 1.20 the race detector was rewritten to pure Go 76 | // on darwin. This means that when CGO_ENABLED=0 is set 77 | // fakecgo is built with race detector code. This is not 78 | // good since this code is pretending to be C. The go:norace 79 | // pragma is not enough, since it only applies to the native 80 | // ABIInternal function. The ABIO wrapper (which is necessary, 81 | // since all references to text symbols from assembly will use it) 82 | // does not inherit the go:norace pragma, so it will still be 83 | // instrumented by the race detector. 84 | // 85 | // To circumvent this issue, using closure calls in the 86 | // assembly, which forces the compiler to use the ABIInternal 87 | // native implementation (which has go:norace) instead. 88 | threadentry_call = threadentry 89 | x_cgo_init_call = x_cgo_init 90 | x_cgo_setenv_call = x_cgo_setenv 91 | x_cgo_unsetenv_call = x_cgo_unsetenv 92 | x_cgo_thread_start_call = x_cgo_thread_start 93 | ) 94 | -------------------------------------------------------------------------------- /objc/objc_block_darwin_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | package objc_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/ebitengine/purego" 11 | "github.com/ebitengine/purego/objc" 12 | ) 13 | 14 | func ExampleNewBlock() { 15 | _, err := purego.Dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", purego.RTLD_GLOBAL|purego.RTLD_NOW) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | var count = 0 21 | block := objc.NewBlock( 22 | func(block objc.Block, line objc.ID, stop *bool) { 23 | count++ 24 | fmt.Printf("LINE %d: %s\n", count, objc.Send[string](line, objc.RegisterName("UTF8String"))) 25 | *stop = count == 3 26 | }, 27 | ) 28 | defer block.Release() 29 | 30 | lines := objc.ID(objc.GetClass("NSString")).Send(objc.RegisterName("stringWithUTF8String:"), "Alpha\nBeta\nGamma\nDelta\nEpsilon") 31 | defer lines.Send(objc.RegisterName("release")) 32 | 33 | lines.Send(objc.RegisterName("enumerateLinesUsingBlock:"), block) 34 | // Output: 35 | // LINE 1: Alpha 36 | // LINE 2: Beta 37 | // LINE 3: Gamma 38 | } 39 | 40 | func ExampleInvokeBlock() { 41 | type vector struct { 42 | X, Y, Z float64 43 | } 44 | 45 | block := objc.NewBlock( 46 | func(block objc.Block, v1, v2 *vector) *vector { 47 | return &vector{ 48 | X: v1.Y*v2.Z - v1.Z*v2.Y, 49 | Y: v1.Z*v2.X - v1.X*v2.Z, 50 | Z: v1.X*v2.Y - v1.Y*v2.X, 51 | } 52 | }, 53 | ) 54 | defer block.Release() 55 | 56 | result, err := objc.InvokeBlock[*vector]( 57 | block, 58 | &vector{X: 0.1, Y: 2.3, Z: 4.5}, 59 | &vector{X: 6.7, Y: 8.9, Z: 0.1}, 60 | ) 61 | 62 | fmt.Println(*result, err) 63 | // Output: {-39.82 30.14 -14.52} 64 | } 65 | 66 | func TestInvoke(t *testing.T) { 67 | t.Run("return an error when passing an invalid number of arguments", func(t *testing.T) { 68 | block := objc.NewBlock(func(_ objc.Block, a int32, b int32) int32 { 69 | return a + b 70 | }) 71 | defer block.Release() 72 | 73 | if _, err := objc.InvokeBlock[int32](block, int32(8)); err == nil { 74 | t.Fatal(err) 75 | } 76 | }) 77 | 78 | t.Run("return an error when passing an invalid return type", func(t *testing.T) { 79 | block := objc.NewBlock(func(_ objc.Block, a int32, b int32) int32 { 80 | return a + b 81 | }) 82 | defer block.Release() 83 | 84 | if _, err := objc.InvokeBlock[string](block, int32(8), int32(2)); err == nil { 85 | t.Fatal(err) 86 | } 87 | }) 88 | 89 | t.Run("add two int32's and returns the result", func(t *testing.T) { 90 | block := objc.NewBlock(func(_ objc.Block, a int32, b int32) int32 { 91 | return a + b 92 | }) 93 | defer block.Release() 94 | 95 | result, err := objc.InvokeBlock[int32](block, int32(8), int32(2)) 96 | if err != nil { 97 | t.Fatal(err) 98 | } 99 | if result != 10 { 100 | t.Fatalf("expected 10, got %d", result) 101 | } 102 | }) 103 | 104 | t.Run("add two int32's and store the result in a variable", func(t *testing.T) { 105 | var result int32 106 | block := objc.NewBlock(func(_ objc.Block, a int32, b int32) { 107 | result = a + b 108 | }) 109 | defer block.Release() 110 | 111 | block.Invoke(int32(8), int32(2)) 112 | if result != 10 { 113 | t.Fatalf("expected 10, got %d", result) 114 | } 115 | }) 116 | } 117 | 118 | func TestBlockCopyAndBlockRelease(t *testing.T) { 119 | t.Parallel() 120 | 121 | var refCount int 122 | block := objc.NewBlock( 123 | func(objc.Block) { 124 | refCount++ 125 | }, 126 | ) 127 | defer block.Release() 128 | refCount++ 129 | 130 | copies := make([]objc.Block, 17) 131 | copies[0] = block 132 | for index := 1; index < len(copies); index++ { 133 | if refCount != index { 134 | t.Fatalf("refCount: %d != %d", refCount, index) 135 | } 136 | 137 | copies[index] = copies[index-1].Copy() 138 | if copies[index] != block { 139 | t.Fatalf("Block.Copy(): %v != %v", copies[index], block) 140 | } 141 | copies[index].Invoke() 142 | } 143 | 144 | for _, copy := range copies[1:] { 145 | copy.Release() 146 | refCount-- 147 | } 148 | refCount-- 149 | 150 | block.Invoke() 151 | if refCount != 1 { 152 | t.Fatalf("refCount: %d != 1", refCount) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /examples/window/main_windows.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | package main 5 | 6 | import ( 7 | "runtime" 8 | "syscall" 9 | "unsafe" 10 | 11 | "github.com/ebitengine/purego" 12 | ) 13 | 14 | const ( 15 | WS_OVERLAPPEDWINDOW = 0x00000000 | 0x00C00000 | 0x00080000 | 0x00040000 | 0x00020000 | 0x00010000 16 | CW_USEDEFAULT = ^0x7fffffff 17 | SW_SHOW = 5 18 | WM_DESTROY = 2 19 | ) 20 | 21 | type ( 22 | ATOM uint16 23 | HANDLE uintptr 24 | HINSTANCE HANDLE 25 | HICON HANDLE 26 | HCURSOR HANDLE 27 | HBRUSH HANDLE 28 | HWND HANDLE 29 | HMENU HANDLE 30 | ) 31 | 32 | type WNDCLASSEX struct { 33 | Size uint32 34 | Style uint32 35 | WndProc uintptr 36 | ClsExtra int32 37 | WndExtra int32 38 | Instance HINSTANCE 39 | Icon HICON 40 | Cursor HCURSOR 41 | Background HBRUSH 42 | MenuName *uint16 43 | ClassName *uint16 44 | IconSm HICON 45 | } 46 | 47 | type RECT struct { 48 | Left, Top, Right, Bottom int32 49 | } 50 | 51 | type POINT struct { 52 | X, Y int32 53 | } 54 | 55 | type MSG struct { 56 | Hwnd HWND 57 | Message uint32 58 | WParam uintptr 59 | LParam uintptr 60 | Time uint32 61 | Pt POINT 62 | } 63 | 64 | var ( 65 | GetModuleHandle func(modulename *uint16) HINSTANCE 66 | RegisterClassEx func(w *WNDCLASSEX) ATOM 67 | CreateWindowEx func(exStyle uint, className, windowName *uint16, 68 | style uint, x, y, width, height int, parent HWND, menu HMENU, 69 | instance HINSTANCE, param unsafe.Pointer) HWND 70 | AdjustWindowRect func(rect *RECT, style uint, menu bool) bool 71 | ShowWindow func(hwnd HWND, cmdshow int) bool 72 | GetMessage func(msg *MSG, hwnd HWND, msgFilterMin, msgFilterMax uint32) int 73 | TranslateMessage func(msg *MSG) bool 74 | DispatchMessage func(msg *MSG) uintptr 75 | DefWindowProc func(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr 76 | PostQuitMessage func(exitCode int) 77 | ) 78 | 79 | func init() { 80 | // Use [syscall.NewLazyDLL] here to avoid external dependencies (#270). 81 | // For actual use cases, [golang.org/x/sys/windows.NewLazySystemDLL] is recommended. 82 | kernel32 := syscall.NewLazyDLL("kernel32.dll").Handle() 83 | purego.RegisterLibFunc(&GetModuleHandle, kernel32, "GetModuleHandleW") 84 | 85 | // Use [syscall.NewLazyDLL] here to avoid external dependencies (#270). 86 | // For actual use cases, [golang.org/x/sys/windows.NewLazySystemDLL] is recommended. 87 | user32 := syscall.NewLazyDLL("user32.dll").Handle() 88 | purego.RegisterLibFunc(&RegisterClassEx, user32, "RegisterClassExW") 89 | purego.RegisterLibFunc(&CreateWindowEx, user32, "CreateWindowExW") 90 | purego.RegisterLibFunc(&AdjustWindowRect, user32, "AdjustWindowRect") 91 | purego.RegisterLibFunc(&ShowWindow, user32, "ShowWindow") 92 | purego.RegisterLibFunc(&GetMessage, user32, "GetMessageW") 93 | purego.RegisterLibFunc(&TranslateMessage, user32, "TranslateMessage") 94 | purego.RegisterLibFunc(&DispatchMessage, user32, "DispatchMessageW") 95 | purego.RegisterLibFunc(&DefWindowProc, user32, "DefWindowProcW") 96 | purego.RegisterLibFunc(&PostQuitMessage, user32, "PostQuitMessage") 97 | 98 | runtime.LockOSThread() 99 | } 100 | 101 | func main() { 102 | className, err := syscall.UTF16PtrFromString("Sample Window Class") 103 | if err != nil { 104 | panic(err) 105 | } 106 | inst := GetModuleHandle(className) 107 | 108 | wc := WNDCLASSEX{ 109 | Size: uint32(unsafe.Sizeof(WNDCLASSEX{})), 110 | WndProc: syscall.NewCallback(wndProc), 111 | Instance: inst, 112 | ClassName: className, 113 | } 114 | 115 | RegisterClassEx(&wc) 116 | 117 | wr := RECT{ 118 | Left: 0, 119 | Top: 0, 120 | Right: 320, 121 | Bottom: 240, 122 | } 123 | title, err := syscall.UTF16PtrFromString("My Title") 124 | if err != nil { 125 | panic(err) 126 | } 127 | AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, false) 128 | hwnd := CreateWindowEx( 129 | 0, className, 130 | title, 131 | WS_OVERLAPPEDWINDOW, 132 | CW_USEDEFAULT, CW_USEDEFAULT, int(wr.Right-wr.Left), int(wr.Bottom-wr.Top), 133 | 0, 0, inst, nil, 134 | ) 135 | if hwnd == 0 { 136 | panic(syscall.GetLastError()) 137 | } 138 | 139 | ShowWindow(hwnd, SW_SHOW) 140 | 141 | var msg MSG 142 | for GetMessage(&msg, 0, 0, 0) != 0 { 143 | TranslateMessage(&msg) 144 | DispatchMessage(&msg) 145 | } 146 | } 147 | 148 | func wndProc(hwnd HWND, msg uint32, wparam, lparam uintptr) uintptr { 149 | switch msg { 150 | case WM_DESTROY: 151 | PostQuitMessage(0) 152 | } 153 | return DefWindowProc(hwnd, msg, wparam, lparam) 154 | } 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purego 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 | 4 | A library for calling C functions from Go without Cgo. 5 | 6 | > This is beta software so expect bugs and potentially API breaking changes 7 | > but each release will be tagged to avoid breaking people's code. 8 | > Bug reports are encouraged. 9 | 10 | ## Motivation 11 | 12 | The [Ebitengine](https://github.com/hajimehoshi/ebiten) game engine was ported to use only Go on Windows. This enabled 13 | cross-compiling to Windows from any other operating system simply by setting `GOOS=windows`. The purego project was 14 | born to bring that same vision to the other platforms supported by Ebitengine. 15 | 16 | ## Benefits 17 | 18 | - **Simple Cross-Compilation**: No C means you can build for other platforms easily without a C compiler. 19 | - **Faster Compilation**: Efficiently cache your entirely Go builds. 20 | - **Smaller Binaries**: Using Cgo generates a C wrapper function for each C function called. Purego doesn't! 21 | - **Dynamic Linking**: Load symbols at runtime and use it as a plugin system. 22 | - **Foreign Function Interface**: Call into other languages that are compiled into shared objects. 23 | - **Cgo Fallback**: Works even with CGO_ENABLED=1 so incremental porting is possible. 24 | This also means unsupported GOARCHs (freebsd/riscv64, linux/mips, etc.) will still work 25 | except for float arguments and return values. 26 | 27 | ## Supported Platforms 28 | 29 | ### Tier 1 30 | 31 | Tier 1 platforms are the primary targets officially supported by PureGo. When a new version of PureGo is released, any critical bugs found on Tier 1 platforms are treated as release blockers. The release will be postponed until such issues are resolved. 32 | 33 | - **Android**: amd64, arm64 34 | - **iOS**: amd64, arm64 35 | - **Linux**: amd64, arm64 36 | - **macOS**: amd64, arm64 37 | - **Windows**: amd64, arm64 38 | 39 | ### Tier 2 40 | 41 | Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs on Tier 2 platforms do not block new PureGo releases. However, fixes contributed by external contributors are very welcome and encouraged. 42 | 43 | - **Android**: 386, arm 44 | - **FreeBSD**: amd64, arm64 45 | - **Linux**: 386, arm, loong64 46 | - **Windows**: 386*, arm* 47 | 48 | `*` These architectures only support `SyscallN` and `NewCallback` 49 | 50 | ## Example 51 | 52 | The example below only showcases purego use for macOS and Linux. The other platforms require special handling which can 53 | be seen in the complete example at [examples/libc](https://github.com/ebitengine/purego/tree/main/examples/libc) which supports FreeBSD and Windows. 54 | 55 | ```go 56 | package main 57 | 58 | import ( 59 | "fmt" 60 | "runtime" 61 | 62 | "github.com/ebitengine/purego" 63 | ) 64 | 65 | func getSystemLibrary() string { 66 | switch runtime.GOOS { 67 | case "darwin": 68 | return "/usr/lib/libSystem.B.dylib" 69 | case "linux": 70 | return "libc.so.6" 71 | default: 72 | panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)) 73 | } 74 | } 75 | 76 | func main() { 77 | libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) 78 | if err != nil { 79 | panic(err) 80 | } 81 | var puts func(string) 82 | purego.RegisterLibFunc(&puts, libc, "puts") 83 | puts("Calling C from Go without Cgo!") 84 | } 85 | ``` 86 | 87 | Then to run: `CGO_ENABLED=0 go run main.go` 88 | 89 | ## Questions 90 | 91 | If you have questions about how to incorporate purego in your project or want to discuss 92 | how it works join the [Discord](https://discord.gg/HzGZVD6BkY)! 93 | 94 | ### External Code 95 | 96 | Purego uses code that originates from the Go runtime. These files are under the BSD-3 97 | License that can be found [in the Go Source](https://github.com/golang/go/blob/master/LICENSE). 98 | This is a list of the copied files: 99 | 100 | * `abi_*.h` from package `runtime/cgo` 101 | * `wincallback.go` from package `runtime` 102 | * `zcallback_darwin_*.s` from package `runtime` 103 | * `internal/fakecgo/abi_*.h` from package `runtime/cgo` 104 | * `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo` 105 | * `internal/fakecgo/callbacks.go` from package `runtime/cgo` 106 | * `internal/fakecgo/iscgo.go` from package `runtime/cgo` 107 | * `internal/fakecgo/setenv.go` from package `runtime/cgo` 108 | * `internal/fakecgo/freebsd.go` from package `runtime/cgo` 109 | * `internal/fakecgo/netbsd.go` from package `runtime/cgo` 110 | 111 | The `internal/fakecgo/go_GOOS.go` files were modified from `runtime/cgo/gcc_GOOS_GOARCH.go`. 112 | 113 | The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of 114 | `#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636)) 115 | -------------------------------------------------------------------------------- /testdata/structtest/structreturn_test.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | #include 5 | 6 | struct Empty{}; 7 | 8 | struct Empty ReturnEmpty() { 9 | struct Empty e = {}; 10 | return e; 11 | } 12 | 13 | struct StructInStruct{ 14 | struct{ int16_t a; } a; 15 | struct{ int16_t b; } b; 16 | struct{ int16_t c; } c; 17 | }; 18 | 19 | struct StructInStruct ReturnStructInStruct(int16_t a, int16_t b, int16_t c) { 20 | struct StructInStruct e = {{a}, {b}, {c}}; 21 | return e; 22 | } 23 | 24 | struct ThreeShorts{ 25 | int16_t a, b, c; 26 | }; 27 | 28 | struct ThreeShorts ReturnThreeShorts(int16_t a, int16_t b, int16_t c) { 29 | struct ThreeShorts e = {a, b, c}; 30 | return e; 31 | } 32 | 33 | struct FourShorts{ 34 | int16_t a, b, c, d; 35 | }; 36 | 37 | struct FourShorts ReturnFourShorts(int16_t a, int16_t b, int16_t c, int16_t d) { 38 | struct FourShorts e = {a, b, c, d}; 39 | return e; 40 | } 41 | 42 | struct OneLong{ 43 | int64_t a; 44 | }; 45 | 46 | struct OneLong ReturnOneLong(int64_t a) { 47 | struct OneLong e = {a}; 48 | return e; 49 | } 50 | 51 | struct TwoLongs{ 52 | int64_t a, b; 53 | }; 54 | 55 | struct TwoLongs ReturnTwoLongs(int64_t a, int64_t b) { 56 | struct TwoLongs e = {a, b}; 57 | return e; 58 | } 59 | 60 | struct ThreeLongs{ 61 | int64_t a, b, c; 62 | }; 63 | 64 | struct ThreeLongs ReturnThreeLongs(int64_t a, int64_t b, int64_t c) { 65 | struct ThreeLongs e = {a, b, c}; 66 | return e; 67 | } 68 | 69 | struct OneFloat{ 70 | float a; 71 | }; 72 | 73 | struct TwoFloats{ 74 | float a, b; 75 | }; 76 | 77 | struct TwoFloats ReturnTwoFloats(float a, float b) { 78 | struct TwoFloats e = {a-b, a*b}; 79 | return e; 80 | } 81 | 82 | struct ThreeFloats{ 83 | float a, b, c; 84 | }; 85 | 86 | struct ThreeFloats ReturnThreeFloats(float a, float b, float c) { 87 | struct ThreeFloats e = {a, b, c}; 88 | return e; 89 | } 90 | 91 | struct OneFloat ReturnOneFloat(float a) { 92 | struct OneFloat e = {a}; 93 | return e; 94 | } 95 | 96 | struct OneDouble{ 97 | double a; 98 | }; 99 | 100 | struct OneDouble ReturnOneDouble(double a) { 101 | struct OneDouble e = {a}; 102 | return e; 103 | } 104 | 105 | struct TwoDoubles{ 106 | double a, b; 107 | }; 108 | 109 | struct TwoDoubles ReturnTwoDoubles(double a, double b) { 110 | struct TwoDoubles e = {a, b}; 111 | return e; 112 | } 113 | 114 | struct ThreeDoubles{ 115 | double a, b, c; 116 | }; 117 | 118 | struct ThreeDoubles ReturnThreeDoubles(double a, double b, double c) { 119 | struct ThreeDoubles e = {a, b, c}; 120 | return e; 121 | } 122 | 123 | struct FourDoubles{ 124 | double a, b, c, d; 125 | }; 126 | 127 | struct FourDoubles ReturnFourDoubles(double a, double b, double c, double d) { 128 | struct FourDoubles e = {a, b, c, d}; 129 | return e; 130 | } 131 | 132 | struct FourDoublesInternal{ 133 | struct { 134 | double a, b; 135 | } f; 136 | struct { 137 | double c, d; 138 | } g; 139 | }; 140 | 141 | struct FourDoublesInternal ReturnFourDoublesInternal(double a, double b, double c, double d) { 142 | struct FourDoublesInternal e = { {a, b}, {c, d} }; 143 | return e; 144 | } 145 | 146 | struct FiveDoubles{ 147 | double a, b, c, d, e; 148 | }; 149 | 150 | struct FiveDoubles ReturnFiveDoubles(double a, double b, double c, double d, double e) { 151 | struct FiveDoubles s = {a, b, c, d, e}; 152 | return s; 153 | } 154 | 155 | struct OneFloatOneDouble{ 156 | float a; 157 | double b; 158 | }; 159 | 160 | struct OneFloatOneDouble ReturnOneFloatOneDouble(float a, double b) { 161 | struct OneFloatOneDouble e = {a, b}; 162 | return e; 163 | } 164 | 165 | struct OneDoubleOneFloat{ 166 | double a; 167 | float b; 168 | }; 169 | 170 | struct OneDoubleOneFloat ReturnOneDoubleOneFloat(double a, float b) { 171 | struct OneDoubleOneFloat e = {a, b}; 172 | return e; 173 | } 174 | 175 | struct Unaligned1{ 176 | int8_t a; 177 | int16_t b; 178 | int64_t c; 179 | }; 180 | 181 | struct Unaligned1 ReturnUnaligned1(int8_t a, int16_t b, int64_t c) { 182 | struct Unaligned1 e = {a, b, c}; 183 | return e; 184 | } 185 | 186 | struct Mixed1{ 187 | float a; 188 | int32_t b; 189 | }; 190 | 191 | struct Mixed1 ReturnMixed1(float a, int32_t b) { 192 | struct Mixed1 e = {a, b}; 193 | return e; 194 | } 195 | 196 | struct Mixed2{ 197 | float a; 198 | int32_t b; 199 | float c; 200 | int32_t d; 201 | }; 202 | 203 | struct Mixed2 ReturnMixed2(float a, int32_t b, float c, int32_t d) { 204 | struct Mixed2 e = {a, b, c, d}; 205 | return e; 206 | } 207 | 208 | struct Mixed3{ 209 | float a; 210 | uint32_t b; 211 | double c; 212 | }; 213 | 214 | struct Mixed3 ReturnMixed3(float a, uint32_t b, double c) { 215 | struct Mixed3 s = {a, b, c}; 216 | return s; 217 | } 218 | 219 | struct Mixed4{ 220 | double a; 221 | uint32_t b; 222 | float c; 223 | }; 224 | 225 | struct Mixed4 ReturnMixed4(double a, uint32_t b, float c) { 226 | struct Mixed4 s = {a, b, c}; 227 | return s; 228 | } 229 | 230 | struct Ptr1{ 231 | int64_t *a; 232 | void *b; 233 | }; 234 | 235 | struct Ptr1 ReturnPtr1(int64_t *a, void *b) { 236 | struct Ptr1 s = {a, b}; 237 | return s; 238 | } 239 | -------------------------------------------------------------------------------- /struct_loong64.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | package purego 5 | 6 | import ( 7 | "math" 8 | "reflect" 9 | "unsafe" 10 | ) 11 | 12 | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { 13 | outSize := outType.Size() 14 | switch { 15 | case outSize == 0: 16 | return reflect.New(outType).Elem() 17 | case outSize <= 8: 18 | r1 := syscall.a1 19 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { 20 | r1 = syscall.f1 21 | if numFields == 2 { 22 | r1 = syscall.f2<<32 | syscall.f1 23 | } 24 | } 25 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() 26 | case outSize <= 16: 27 | r1, r2 := syscall.a1, syscall.a2 28 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { 29 | switch numFields { 30 | case 4: 31 | r1 = syscall.f2<<32 | syscall.f1 32 | r2 = syscall.f4<<32 | syscall.f3 33 | case 3: 34 | r1 = syscall.f2<<32 | syscall.f1 35 | r2 = syscall.f3 36 | case 2: 37 | r1 = syscall.f1 38 | r2 = syscall.f2 39 | default: 40 | panic("unreachable") 41 | } 42 | } 43 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() 44 | default: 45 | // create struct from the Go pointer created above 46 | // weird pointer dereference to circumvent go vet 47 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() 48 | } 49 | } 50 | 51 | const ( 52 | _NO_CLASS = 0b00 53 | _FLOAT = 0b01 54 | _INT = 0b11 55 | ) 56 | 57 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { 58 | if v.Type().Size() == 0 { 59 | return keepAlive 60 | } 61 | 62 | if size := v.Type().Size(); size <= 16 { 63 | placeRegisters(v, addFloat, addInt) 64 | } else { 65 | keepAlive = placeStack(v, keepAlive, addInt) 66 | } 67 | return keepAlive // the struct was allocated so don't panic 68 | } 69 | 70 | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { 71 | var val uint64 72 | var shift byte 73 | var flushed bool 74 | class := _NO_CLASS 75 | var place func(v reflect.Value) 76 | place = func(v reflect.Value) { 77 | var numFields int 78 | if v.Kind() == reflect.Struct { 79 | numFields = v.Type().NumField() 80 | } else { 81 | numFields = v.Type().Len() 82 | } 83 | for k := 0; k < numFields; k++ { 84 | flushed = false 85 | var f reflect.Value 86 | if v.Kind() == reflect.Struct { 87 | f = v.Field(k) 88 | } else { 89 | f = v.Index(k) 90 | } 91 | align := byte(f.Type().Align()*8 - 1) 92 | shift = (shift + align) &^ align 93 | if shift >= 64 { 94 | shift = 0 95 | flushed = true 96 | if class == _FLOAT { 97 | addFloat(uintptr(val)) 98 | } else { 99 | addInt(uintptr(val)) 100 | } 101 | } 102 | switch f.Type().Kind() { 103 | case reflect.Struct: 104 | place(f) 105 | case reflect.Bool: 106 | if f.Bool() { 107 | val |= 1 << shift 108 | } 109 | shift += 8 110 | class |= _INT 111 | case reflect.Uint8: 112 | val |= f.Uint() << shift 113 | shift += 8 114 | class |= _INT 115 | case reflect.Uint16: 116 | val |= f.Uint() << shift 117 | shift += 16 118 | class |= _INT 119 | case reflect.Uint32: 120 | val |= f.Uint() << shift 121 | shift += 32 122 | class |= _INT 123 | case reflect.Uint64, reflect.Uint, reflect.Uintptr: 124 | addInt(uintptr(f.Uint())) 125 | shift = 0 126 | flushed = true 127 | class = _NO_CLASS 128 | case reflect.Int8: 129 | val |= uint64(f.Int()&0xFF) << shift 130 | shift += 8 131 | class |= _INT 132 | case reflect.Int16: 133 | val |= uint64(f.Int()&0xFFFF) << shift 134 | shift += 16 135 | class |= _INT 136 | case reflect.Int32: 137 | val |= uint64(f.Int()&0xFFFF_FFFF) << shift 138 | shift += 32 139 | class |= _INT 140 | case reflect.Int64, reflect.Int: 141 | addInt(uintptr(f.Int())) 142 | shift = 0 143 | flushed = true 144 | class = _NO_CLASS 145 | case reflect.Float32: 146 | if class == _FLOAT { 147 | addFloat(uintptr(val)) 148 | val = 0 149 | shift = 0 150 | } 151 | val |= uint64(math.Float32bits(float32(f.Float()))) << shift 152 | shift += 32 153 | class |= _FLOAT 154 | case reflect.Float64: 155 | addFloat(uintptr(math.Float64bits(float64(f.Float())))) 156 | shift = 0 157 | flushed = true 158 | class = _NO_CLASS 159 | case reflect.Ptr: 160 | addInt(f.Pointer()) 161 | shift = 0 162 | flushed = true 163 | class = _NO_CLASS 164 | case reflect.Array: 165 | place(f) 166 | default: 167 | panic("purego: unsupported kind " + f.Kind().String()) 168 | } 169 | } 170 | } 171 | place(v) 172 | if !flushed { 173 | if class == _FLOAT { 174 | addFloat(uintptr(val)) 175 | } else { 176 | addInt(uintptr(val)) 177 | } 178 | } 179 | } 180 | 181 | func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { 182 | // Struct is too big to be placed in registers. 183 | // Copy to heap and place the pointer in register 184 | ptrStruct := reflect.New(v.Type()) 185 | ptrStruct.Elem().Set(v) 186 | ptr := ptrStruct.Elem().Addr().UnsafePointer() 187 | keepAlive = append(keepAlive, ptr) 188 | addInt(uintptr(ptr)) 189 | return keepAlive 190 | } 191 | -------------------------------------------------------------------------------- /examples/protocol-dumper/main_darwin.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors 3 | 4 | package main 5 | 6 | // #cgo CFLAGS: -x objective-c 7 | // #cgo LDFLAGS: -framework AppKit 8 | // 9 | // #import 10 | // 11 | // __attribute__((used)) 12 | // static void __force_protocol_load() { 13 | // id o = NULL; 14 | // o = @protocol(NSAccessibility); 15 | // o = @protocol(NSApplicationDelegate); 16 | // } 17 | import "C" 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "os" 23 | "strconv" 24 | "strings" 25 | "text/template" 26 | 27 | "github.com/ebitengine/purego/objc" 28 | ) 29 | 30 | var protocols = []string{ 31 | "NSAccessibility", 32 | "NSApplicationDelegate", 33 | } 34 | 35 | type ProtocolImpl struct { 36 | Name string 37 | 38 | RequiredInstanceMethods []objc.MethodDescription 39 | RequiredClassMethods []objc.MethodDescription 40 | OptionalInstanceMethods []objc.MethodDescription 41 | OptionalClassMethods []objc.MethodDescription 42 | 43 | AdoptedProtocols []*objc.Protocol 44 | 45 | RequiredInstanceProperties []objc.Property 46 | RequiredClassProperties []objc.Property 47 | OptionalInstanceProperties []objc.Property 48 | OptionalClassProperties []objc.Property 49 | } 50 | 51 | func readProtocols(names []string) (imps []ProtocolImpl, err error) { 52 | for _, name := range names { 53 | p := objc.GetProtocol(name) 54 | if p == nil { 55 | return nil, fmt.Errorf("protocol '%s' does not exist", name) 56 | } 57 | imp := ProtocolImpl{} 58 | imp.Name = name 59 | imp.RequiredInstanceMethods = p.CopyMethodDescriptionList(true, true) 60 | imp.RequiredClassMethods = p.CopyMethodDescriptionList(true, false) 61 | imp.OptionalInstanceMethods = p.CopyMethodDescriptionList(false, true) 62 | imp.OptionalClassMethods = p.CopyMethodDescriptionList(false, false) 63 | 64 | imp.AdoptedProtocols = p.CopyProtocolList() 65 | 66 | imp.RequiredInstanceProperties = p.CopyPropertyList(true, true) 67 | imp.RequiredClassProperties = p.CopyPropertyList(true, false) 68 | imp.OptionalInstanceProperties = p.CopyPropertyList(false, true) 69 | imp.OptionalClassProperties = p.CopyPropertyList(false, false) 70 | imps = append(imps, imp) 71 | } 72 | return imps, nil 73 | } 74 | 75 | const templ = `// Code generated by protocol-dumper; DO NOT EDIT. 76 | 77 | package main 78 | 79 | import ( 80 | "log" 81 | 82 | "github.com/ebitengine/purego/objc" 83 | ) 84 | 85 | func init() { 86 | var p *objc.Protocol 87 | {{- range . }} 88 | {{- $protocolName := .Name }} 89 | if p = objc.AllocateProtocol("{{$protocolName}}"); p != nil { 90 | {{- range .RequiredInstanceMethods }} 91 | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", true, true) 92 | {{- end }} 93 | 94 | {{- range .RequiredClassMethods }} 95 | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", true, false) 96 | {{- end }} 97 | 98 | {{- range .OptionalInstanceMethods }} 99 | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", false, true) 100 | {{- end }} 101 | 102 | {{- range .OptionalClassMethods }} 103 | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", false, false) 104 | {{- end }} 105 | var adoptedProtocol *objc.Protocol 106 | {{- range .AdoptedProtocols }} 107 | adoptedProtocol = objc.GetProtocol("{{ .Name }}") 108 | if adoptedProtocol == nil { 109 | log.Fatalln("protocol '{{ .Name }}' does not exist") 110 | } 111 | p.AddProtocol(adoptedProtocol) 112 | {{- end }} 113 | 114 | {{- range .RequiredInstanceProperties }} 115 | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, true, true) 116 | {{- end }} 117 | 118 | 119 | {{- range .RequiredClassProperties }} 120 | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, true, false) 121 | {{- end }} 122 | 123 | {{- range .OptionalInstanceProperties }} 124 | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, false, true) 125 | {{- end }} 126 | 127 | {{- range .OptionalClassProperties }} 128 | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, false, false) 129 | {{- end }} 130 | p.Register() 131 | } // Finished protocol: {{$protocolName}} 132 | {{- end }} 133 | } 134 | ` 135 | 136 | func printProtocols(impls []ProtocolImpl, w io.Writer) error { 137 | tmpl, err := template.New("protocol.tmpl").Funcs(template.FuncMap{ 138 | "attributeToStructString": attributeToStructString, 139 | }).Parse(templ) 140 | if err != nil { 141 | return err 142 | } 143 | err = tmpl.Execute(w, impls) 144 | if err != nil { 145 | return err 146 | } 147 | return nil 148 | } 149 | 150 | func attributeToStructString(p objc.Property) string { 151 | attribs := strings.Split(p.Attributes(), ",") 152 | var b strings.Builder 153 | b.WriteString("[]objc.PropertyAttribute{") 154 | for i, attrib := range attribs { 155 | b.WriteString(fmt.Sprintf(`{Name: &[]byte("%s\x00")[0], Value: &[]byte(%s)[0]}`, string(attrib[0]), strconv.Quote(attrib[1:]+"\x00"))) 156 | if i != len(attribs)-1 { 157 | b.WriteString(", ") 158 | } 159 | } 160 | b.WriteString("}") 161 | return b.String() 162 | } 163 | 164 | // This program prints out the Go code to generate the OBJC protocols list. 165 | // To use it add the @protocol(name) to the C import section and then add it to the protocols list. 166 | // You'll want to run it as amd64 and arm64 since the types are slightly different for each architecture. 167 | func main() { 168 | var imps []ProtocolImpl 169 | var err error 170 | if imps, err = readProtocols(protocols); err != nil { 171 | panic(err) 172 | } 173 | if err = printProtocols(imps, os.Stdout); err != nil { 174 | panic(err) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /sys_amd64.s: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || linux || netbsd 5 | 6 | #include "textflag.h" 7 | #include "abi_amd64.h" 8 | #include "go_asm.h" 9 | #include "funcdata.h" 10 | 11 | #define STACK_SIZE 80 12 | #define PTR_ADDRESS (STACK_SIZE - 8) 13 | 14 | // syscall15X calls a function in libc on behalf of the syscall package. 15 | // syscall15X takes a pointer to a struct like: 16 | // struct { 17 | // fn uintptr 18 | // a1 uintptr 19 | // a2 uintptr 20 | // a3 uintptr 21 | // a4 uintptr 22 | // a5 uintptr 23 | // a6 uintptr 24 | // a7 uintptr 25 | // a8 uintptr 26 | // a9 uintptr 27 | // a10 uintptr 28 | // a11 uintptr 29 | // a12 uintptr 30 | // a13 uintptr 31 | // a14 uintptr 32 | // a15 uintptr 33 | // r1 uintptr 34 | // r2 uintptr 35 | // err uintptr 36 | // } 37 | // syscall15X must be called on the g0 stack with the 38 | // C calling convention (use libcCall). 39 | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 40 | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) 41 | TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0 42 | PUSHQ BP 43 | MOVQ SP, BP 44 | SUBQ $STACK_SIZE, SP 45 | MOVQ DI, PTR_ADDRESS(BP) // save the pointer 46 | MOVQ DI, R11 47 | 48 | MOVQ syscall15Args_f1(R11), X0 // f1 49 | MOVQ syscall15Args_f2(R11), X1 // f2 50 | MOVQ syscall15Args_f3(R11), X2 // f3 51 | MOVQ syscall15Args_f4(R11), X3 // f4 52 | MOVQ syscall15Args_f5(R11), X4 // f5 53 | MOVQ syscall15Args_f6(R11), X5 // f6 54 | MOVQ syscall15Args_f7(R11), X6 // f7 55 | MOVQ syscall15Args_f8(R11), X7 // f8 56 | 57 | MOVQ syscall15Args_a1(R11), DI // a1 58 | MOVQ syscall15Args_a2(R11), SI // a2 59 | MOVQ syscall15Args_a3(R11), DX // a3 60 | MOVQ syscall15Args_a4(R11), CX // a4 61 | MOVQ syscall15Args_a5(R11), R8 // a5 62 | MOVQ syscall15Args_a6(R11), R9 // a6 63 | 64 | // push the remaining paramters onto the stack 65 | MOVQ syscall15Args_a7(R11), R12 66 | MOVQ R12, 0(SP) // push a7 67 | MOVQ syscall15Args_a8(R11), R12 68 | MOVQ R12, 8(SP) // push a8 69 | MOVQ syscall15Args_a9(R11), R12 70 | MOVQ R12, 16(SP) // push a9 71 | MOVQ syscall15Args_a10(R11), R12 72 | MOVQ R12, 24(SP) // push a10 73 | MOVQ syscall15Args_a11(R11), R12 74 | MOVQ R12, 32(SP) // push a11 75 | MOVQ syscall15Args_a12(R11), R12 76 | MOVQ R12, 40(SP) // push a12 77 | MOVQ syscall15Args_a13(R11), R12 78 | MOVQ R12, 48(SP) // push a13 79 | MOVQ syscall15Args_a14(R11), R12 80 | MOVQ R12, 56(SP) // push a14 81 | MOVQ syscall15Args_a15(R11), R12 82 | MOVQ R12, 64(SP) // push a15 83 | XORL AX, AX // vararg: say "no float args" 84 | 85 | MOVQ syscall15Args_fn(R11), R10 // fn 86 | CALL R10 87 | 88 | MOVQ PTR_ADDRESS(BP), DI // get the pointer back 89 | MOVQ AX, syscall15Args_a1(DI) // r1 90 | MOVQ DX, syscall15Args_a2(DI) // r3 91 | MOVQ X0, syscall15Args_f1(DI) // f1 92 | MOVQ X1, syscall15Args_f2(DI) // f2 93 | 94 | XORL AX, AX // no error (it's ignored anyway) 95 | ADDQ $STACK_SIZE, SP 96 | MOVQ BP, SP 97 | POPQ BP 98 | RET 99 | 100 | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 101 | MOVQ 0(SP), AX // save the return address to calculate the cb index 102 | MOVQ 8(SP), R10 // get the return SP so that we can align register args with stack args 103 | ADDQ $8, SP // remove return address from stack, we are not returning to callbackasm, but to its caller. 104 | 105 | // make space for first six int and 8 float arguments below the frame 106 | ADJSP $14*8, SP 107 | MOVSD X0, (1*8)(SP) 108 | MOVSD X1, (2*8)(SP) 109 | MOVSD X2, (3*8)(SP) 110 | MOVSD X3, (4*8)(SP) 111 | MOVSD X4, (5*8)(SP) 112 | MOVSD X5, (6*8)(SP) 113 | MOVSD X6, (7*8)(SP) 114 | MOVSD X7, (8*8)(SP) 115 | MOVQ DI, (9*8)(SP) 116 | MOVQ SI, (10*8)(SP) 117 | MOVQ DX, (11*8)(SP) 118 | MOVQ CX, (12*8)(SP) 119 | MOVQ R8, (13*8)(SP) 120 | MOVQ R9, (14*8)(SP) 121 | LEAQ 8(SP), R8 // R8 = address of args vector 122 | 123 | PUSHQ R10 // push the stack pointer below registers 124 | 125 | // Switch from the host ABI to the Go ABI. 126 | PUSH_REGS_HOST_TO_ABI0() 127 | 128 | // determine index into runtime·cbs table 129 | MOVQ $callbackasm(SB), DX 130 | SUBQ DX, AX 131 | MOVQ $0, DX 132 | MOVQ $5, CX // divide by 5 because each call instruction in ·callbacks is 5 bytes long 133 | DIVL CX 134 | SUBQ $1, AX // subtract 1 because return PC is to the next slot 135 | 136 | // Create a struct callbackArgs on our stack to be passed as 137 | // the "frame" to cgocallback and on to callbackWrap. 138 | // $24 to make enough room for the arguments to runtime.cgocallback 139 | SUBQ $(24+callbackArgs__size), SP 140 | MOVQ AX, (24+callbackArgs_index)(SP) // callback index 141 | MOVQ R8, (24+callbackArgs_args)(SP) // address of args vector 142 | MOVQ $0, (24+callbackArgs_result)(SP) // result 143 | LEAQ 24(SP), AX // take the address of callbackArgs 144 | 145 | // Call cgocallback, which will call callbackWrap(frame). 146 | MOVQ ·callbackWrap_call(SB), DI // Get the ABIInternal function pointer 147 | MOVQ (DI), DI // without by using a closure. 148 | MOVQ AX, SI // frame (address of callbackArgs) 149 | MOVQ $0, CX // context 150 | 151 | CALL crosscall2(SB) // runtime.cgocallback(fn, frame, ctxt uintptr) 152 | 153 | // Get callback result. 154 | MOVQ (24+callbackArgs_result)(SP), AX 155 | ADDQ $(24+callbackArgs__size), SP // remove callbackArgs struct 156 | 157 | POP_REGS_HOST_TO_ABI0() 158 | 159 | POPQ R10 // get the SP back 160 | ADJSP $-14*8, SP // remove arguments 161 | 162 | MOVQ R10, 0(SP) 163 | 164 | RET 165 | -------------------------------------------------------------------------------- /callback_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | //go:build darwin || (linux && (amd64 || arm64 || loong64)) 5 | 6 | package purego_test 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | "unsafe" 14 | 15 | "github.com/ebitengine/purego" 16 | ) 17 | 18 | // TestCallGoFromSharedLib is a test that checks for stack corruption on arm64 19 | // when C calls Go code from a non-Go thread in a dynamically loaded share library. 20 | func TestCallGoFromSharedLib(t *testing.T) { 21 | libFileName := filepath.Join(t.TempDir(), "libcbtest.so") 22 | t.Logf("Build %v", libFileName) 23 | 24 | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil { 25 | t.Fatal(err) 26 | } 27 | defer os.Remove(libFileName) 28 | 29 | lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL) 30 | if err != nil { 31 | t.Fatalf("Dlopen(%q) failed: %v", libFileName, err) 32 | } 33 | 34 | var callCallback func(p uintptr, s string) int 35 | purego.RegisterLibFunc(&callCallback, lib, "callCallback") 36 | 37 | goFunc := func(cstr *byte, n int) int { 38 | s := string(unsafe.Slice(cstr, n)) 39 | t.Logf("FROM Go: %s\n", s) 40 | return 1 41 | } 42 | 43 | const want = 10101 44 | cb := purego.NewCallback(goFunc) 45 | for i := 0; i < 10; i++ { 46 | got := callCallback(cb, "a test string") 47 | if got != want { 48 | t.Fatalf("%d: callCallback() got %v want %v", i, got, want) 49 | } 50 | } 51 | } 52 | 53 | func TestNewCallbackFloat64(t *testing.T) { 54 | // This tests the maximum number of arguments a function to NewCallback can take 55 | const ( 56 | expectCbTotal = -3 57 | expectedCbTotalF = float64(36) 58 | ) 59 | var cbTotal int 60 | var cbTotalF float64 61 | imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int, 62 | f1, f2, f3, f4, f5, f6, f7, f8 float64, 63 | ) { 64 | cbTotal = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 65 | cbTotalF = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 66 | }) 67 | var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int, 68 | f1, f2, f3, f4, f5, f6, f7, f8 float64) 69 | purego.RegisterFunc(&fn, imp) 70 | fn(1, 2, -3, 4, -5, 6, -7, 8, -9, 71 | 1, 2, 3, 4, 5, 6, 7, 8) 72 | 73 | if cbTotal != expectCbTotal { 74 | t.Errorf("cbTotal not correct got %d but wanted %d", cbTotal, expectCbTotal) 75 | } 76 | if cbTotalF != expectedCbTotalF { 77 | t.Errorf("cbTotalF not correct got %f but wanted %f", cbTotalF, expectedCbTotalF) 78 | } 79 | } 80 | 81 | func TestNewCallbackFloat64AndIntMix(t *testing.T) { 82 | // This tests interleaving float and integer arguments to NewCallback 83 | const ( 84 | expectCbTotal = 54.75 85 | ) 86 | var cbTotal float64 87 | imp := purego.NewCallback(func(a1, a2 float64, a3, a4, a5 int, a6, a7, a8 float64, a9 int) { 88 | cbTotal = a1 + a2 + float64(a3) + float64(a4) + float64(a5) + a6 + a7 + a8 + float64(a9) 89 | }) 90 | var fn func(a1, a2 float64, a3, a4, a5 int, a6, a7, a8 float64, a9 int) 91 | purego.RegisterFunc(&fn, imp) 92 | fn(1.25, 3.25, 4, 5, 6, 7.5, 8.25, 9.5, 10) 93 | 94 | if cbTotal != expectCbTotal { 95 | t.Errorf("cbTotal not correct got %f but wanted %f", cbTotal, expectCbTotal) 96 | } 97 | } 98 | 99 | func TestNewCallbackFloat32(t *testing.T) { 100 | // This tests the maximum number of float32 arguments a function to NewCallback can take 101 | const ( 102 | expectCbTotal = 6 103 | expectedCbTotalF = float32(45) 104 | ) 105 | var cbTotal int 106 | var cbTotalF float32 107 | imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8 int, 108 | f1, f2, f3, f4, f5, f6, f7, f8, f9 float32, 109 | ) { 110 | cbTotal = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 111 | cbTotalF = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 112 | }) 113 | var fn func(a1, a2, a3, a4, a5, a6, a7, a8 int, 114 | f1, f2, f3, f4, f5, f6, f7, f8, f9 float32) 115 | purego.RegisterFunc(&fn, imp) 116 | fn(1, 2, -3, 4, -5, 6, -7, 8, 117 | 1, 2, 3, 4, 5, 6, 7, 8, 9) 118 | 119 | if cbTotal != expectCbTotal { 120 | t.Errorf("cbTotal not correct got %d but wanted %d", cbTotal, expectCbTotal) 121 | } 122 | if cbTotalF != expectedCbTotalF { 123 | t.Errorf("cbTotalF not correct got %f but wanted %f", cbTotalF, expectedCbTotalF) 124 | } 125 | } 126 | 127 | func TestNewCallbackFloat32AndFloat64(t *testing.T) { 128 | // This tests that calling a function with a mix of float32 and float64 arguments works 129 | const ( 130 | expectedCbTotalF32 = float32(72) 131 | expectedCbTotalF64 = float64(48) 132 | ) 133 | var cbTotalF32 float32 134 | var cbTotalF64 float64 135 | imp := purego.NewCallback(func(f1, f2, f3 float32, f4, f5, f6 float64, f7, f8, f9 float32, f10, f11, f12 float64, f13, f14, f15 float32) { 136 | cbTotalF32 = f1 + f2 + f3 + f7 + f8 + f9 + f13 + f14 + f15 137 | cbTotalF64 = f4 + f5 + f6 + f10 + f11 + f12 138 | }) 139 | var fn func(f1, f2, f3 float32, f4, f5, f6 float64, f7, f8, f9 float32, f10, f11, f12 float64, f13, f14, f15 float32) 140 | purego.RegisterFunc(&fn, imp) 141 | fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) 142 | 143 | if cbTotalF32 != expectedCbTotalF32 { 144 | t.Errorf("cbTotalF32 not correct got %f but wanted %f", cbTotalF32, expectedCbTotalF32) 145 | } 146 | if cbTotalF64 != expectedCbTotalF64 { 147 | t.Errorf("cbTotalF64 not correct got %f but wanted %f", cbTotalF64, expectedCbTotalF64) 148 | } 149 | } 150 | 151 | func ExampleNewCallback() { 152 | cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int) int { 153 | fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) 154 | return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15 155 | }) 156 | 157 | var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int) int 158 | purego.RegisterFunc(&fn, cb) 159 | 160 | ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) 161 | fmt.Println(ret) 162 | 163 | // Output: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 164 | // 120 165 | } 166 | 167 | func ExampleNewCallback_cdecl() { 168 | fn := func(_ purego.CDecl, a int) { 169 | fmt.Println(a) 170 | } 171 | cb := purego.NewCallback(fn) 172 | purego.SyscallN(cb, 83) 173 | 174 | // Output: 83 175 | } 176 | -------------------------------------------------------------------------------- /objc/objc_runtime_darwin_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | package objc_test 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/ebitengine/purego" 13 | "github.com/ebitengine/purego/objc" 14 | ) 15 | 16 | func ExampleRegisterClass_helloworld() { 17 | class, err := objc.RegisterClass( 18 | "FooObject", 19 | objc.GetClass("NSObject"), 20 | nil, 21 | nil, 22 | []objc.MethodDef{ 23 | { 24 | Cmd: objc.RegisterName("run"), 25 | Fn: func(self objc.ID, _cmd objc.SEL) { 26 | fmt.Println("Hello World!") 27 | }, 28 | }, 29 | }, 30 | ) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | object := objc.ID(class).Send(objc.RegisterName("new")) 36 | object.Send(objc.RegisterName("run")) 37 | // Output: Hello World! 38 | } 39 | 40 | func ExampleRegisterClass() { 41 | var ( 42 | sel_new = objc.RegisterName("new") 43 | sel_init = objc.RegisterName("init") 44 | sel_setBar = objc.RegisterName("setBar:") 45 | sel_bar = objc.RegisterName("bar") 46 | 47 | BarInit = func(id objc.ID, cmd objc.SEL) objc.ID { 48 | return id.SendSuper(cmd) 49 | } 50 | ) 51 | 52 | class, err := objc.RegisterClass( 53 | "BarObject", 54 | objc.GetClass("NSObject"), 55 | []*objc.Protocol{ 56 | objc.GetProtocol("NSDelegateWindow"), 57 | }, 58 | []objc.FieldDef{ 59 | { 60 | Name: "bar", 61 | Type: reflect.TypeOf(int(0)), 62 | Attribute: objc.ReadWrite, 63 | }, 64 | { 65 | Name: "foo", 66 | Type: reflect.TypeOf(false), 67 | Attribute: objc.ReadWrite, 68 | }, 69 | }, 70 | []objc.MethodDef{ 71 | { 72 | Cmd: sel_init, 73 | Fn: BarInit, 74 | }, 75 | }, 76 | ) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | object := objc.ID(class).Send(sel_new) 82 | object.Send(sel_setBar, 123) 83 | bar := int(object.Send(sel_bar)) 84 | fmt.Println(bar) 85 | // Output: 123 86 | } 87 | 88 | func ExampleIMP() { 89 | imp := objc.NewIMP(func(self objc.ID, _cmd objc.SEL, a3, a4, a5, a6, a7, a8, a9 int) { 90 | fmt.Println("IMP:", self, _cmd, a3, a4, a5, a6, a7, a8, a9) 91 | }) 92 | 93 | purego.SyscallN(uintptr(imp), 105, 567, 9, 2, 3, ^uintptr(4), 4, 8, 9) 94 | // Output: IMP: 105 567 9 2 3 -5 4 8 9 95 | } 96 | 97 | func ExampleID_SendSuper() { 98 | super, err := objc.RegisterClass( 99 | "SuperObject", 100 | objc.GetClass("NSObject"), 101 | nil, 102 | nil, 103 | []objc.MethodDef{ 104 | { 105 | Cmd: objc.RegisterName("doSomething"), 106 | Fn: func(self objc.ID, _cmd objc.SEL) { 107 | fmt.Println("In Super!") 108 | }, 109 | }, 110 | }, 111 | ) 112 | if err != nil { 113 | panic(err) 114 | } 115 | 116 | child, err := objc.RegisterClass( 117 | "ChildObject", 118 | super, 119 | nil, 120 | nil, 121 | []objc.MethodDef{ 122 | { 123 | Cmd: objc.RegisterName("doSomething"), 124 | Fn: func(self objc.ID, _cmd objc.SEL) { 125 | fmt.Println("In Child") 126 | self.SendSuper(_cmd) 127 | }, 128 | }, 129 | }, 130 | ) 131 | if err != nil { 132 | panic(err) 133 | } 134 | 135 | objc.ID(child).Send(objc.RegisterName("new")).Send(objc.RegisterName("doSomething")) 136 | // Output: In Child 137 | // In Super! 138 | } 139 | 140 | func TestSend(t *testing.T) { 141 | // NSNumber comes from Foundation so make sure we have linked to that framework. 142 | _, err := purego.Dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", purego.RTLD_GLOBAL|purego.RTLD_NOW) 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | const double = float64(2.34) 147 | // Initialize a NSNumber 148 | NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double) 149 | // Then get that number back using the generic Send function. 150 | number := objc.Send[float64](NSNumber, objc.RegisterName("doubleValue")) 151 | if double != number { 152 | t.Failed() 153 | } 154 | } 155 | 156 | func ExampleSend() { 157 | type NSRange struct { 158 | Location, Range uint 159 | } 160 | class_NSString := objc.GetClass("NSString") 161 | sel_stringWithUTF8String := objc.RegisterName("stringWithUTF8String:") 162 | 163 | fullString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "Hello, World!\x00") 164 | subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00") 165 | 166 | r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString) 167 | fmt.Println(r) 168 | // Output: {3 7} 169 | } 170 | 171 | func ExampleSendSuper() { 172 | super, err := objc.RegisterClass( 173 | "SuperObject2", 174 | objc.GetClass("NSObject"), 175 | nil, 176 | nil, 177 | []objc.MethodDef{ 178 | { 179 | Cmd: objc.RegisterName("doSomething"), 180 | Fn: func(self objc.ID, _cmd objc.SEL) int { 181 | return 16 182 | }, 183 | }, 184 | }, 185 | ) 186 | if err != nil { 187 | panic(err) 188 | } 189 | 190 | child, err := objc.RegisterClass( 191 | "ChildObject2", 192 | super, 193 | nil, 194 | nil, 195 | []objc.MethodDef{ 196 | { 197 | Cmd: objc.RegisterName("doSomething"), 198 | Fn: func(self objc.ID, _cmd objc.SEL) int { 199 | return 24 200 | }, 201 | }, 202 | }, 203 | ) 204 | if err != nil { 205 | panic(err) 206 | } 207 | 208 | res := objc.SendSuper[int](objc.ID(child).Send(objc.RegisterName("new")), objc.RegisterName("doSomething")) 209 | fmt.Println(res) 210 | // Output: 16 211 | } 212 | 213 | func ExampleAllocateProtocol() { 214 | var p *objc.Protocol 215 | if p = objc.AllocateProtocol("MyCustomProtocol"); p != nil { 216 | p.AddMethodDescription(objc.RegisterName("isFoo"), "B16@0:8", true, true) 217 | var adoptedProtocol *objc.Protocol 218 | adoptedProtocol = objc.GetProtocol("NSObject") 219 | if adoptedProtocol == nil { 220 | log.Fatalln("protocol 'NSObject' does not exist") 221 | } 222 | p.AddProtocol(adoptedProtocol) 223 | p.AddProperty("accessibilityElement", []objc.PropertyAttribute{ 224 | {Name: &[]byte("T\x00")[0], Value: &[]byte("B\x00")[0]}, 225 | {Name: &[]byte("G\x00")[0], Value: &[]byte("isBar\x00")[0]}, 226 | }, true, true) 227 | p.Register() 228 | } 229 | 230 | p = objc.GetProtocol("MyCustomProtocol") 231 | 232 | for _, protocol := range p.CopyProtocolList() { 233 | fmt.Println(protocol.Name()) 234 | } 235 | for _, property := range p.CopyPropertyList(true, true) { 236 | fmt.Println(property.Name(), property.Attributes()) 237 | } 238 | for _, method := range p.CopyMethodDescriptionList(true, true) { 239 | fmt.Println(method.Name(), method.Types()) 240 | } 241 | 242 | // Output: 243 | // NSObject 244 | // accessibilityElement TB,GisBar 245 | // isFoo B16@0:8 246 | } 247 | -------------------------------------------------------------------------------- /internal/fakecgo/gen.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build ignore 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "go/format" 12 | "log" 13 | "os" 14 | "strings" 15 | "text/template" 16 | ) 17 | 18 | const templateSymbols = `// Code generated by 'go generate' with gen.go. DO NOT EDIT. 19 | 20 | // SPDX-License-Identifier: Apache-2.0 21 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 22 | 23 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 24 | 25 | package fakecgo 26 | 27 | import ( 28 | "syscall" 29 | "unsafe" 30 | ) 31 | 32 | {{ range . -}} 33 | //go:nosplit 34 | //go:norace 35 | func {{.Name}}( 36 | {{- range .Args -}} 37 | {{- if .Name -}} 38 | {{.Name}} {{.Type}}, 39 | {{- end -}} 40 | {{- end -}}) {{.Return}} { 41 | {{- if .Return -}} 42 | {{- if eq .Return "unsafe.Pointer" -}} 43 | ret := 44 | {{- else -}} 45 | return {{.Return}}( 46 | {{- end -}} 47 | {{- end -}} 48 | call5({{.Name}}ABI0, 49 | {{- range .Args}} 50 | {{- if .Name -}} 51 | {{- if hasPrefix .Type "*" -}} 52 | uintptr(unsafe.Pointer({{.Name}})), 53 | {{- else -}} 54 | uintptr({{.Name}}), 55 | {{- end -}} 56 | {{- else -}} 57 | 0, 58 | {{- end -}} 59 | {{- end -}} 60 | ) {{/* end of call5 */}} 61 | {{- if .Return -}} 62 | {{- if eq .Return "unsafe.Pointer"}} 63 | // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer 64 | return *(*unsafe.Pointer)(unsafe.Pointer(&ret)) 65 | {{- else -}} 66 | ) {{/* end of cast */}} 67 | {{- end -}} 68 | {{- end}} 69 | } 70 | 71 | {{end}} 72 | {{- range . }} 73 | //go:linkname _{{.Name}} _{{.Name}} 74 | var _{{.Name}} uint8 75 | var {{.Name}}ABI0 = uintptr(unsafe.Pointer(&_{{.Name}})) 76 | {{ end }} 77 | ` 78 | 79 | const templateTrampolinesStubs = `// Code generated by 'go generate' with gen.go. DO NOT EDIT. 80 | 81 | // SPDX-License-Identifier: Apache-2.0 82 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 83 | 84 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 85 | 86 | #include "textflag.h" 87 | 88 | // these stubs are here because it is not possible to go:linkname directly the C functions on darwin arm64 89 | {{ range . }} 90 | TEXT _{{.Name}}(SB), NOSPLIT|NOFRAME, $0-0 91 | JMP purego_{{.Name}}(SB) 92 | {{ end -}} 93 | ` 94 | 95 | const templateSymbolsGoos = `// Code generated by 'go generate' with gen.go. DO NOT EDIT. 96 | 97 | // SPDX-License-Identifier: Apache-2.0 98 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 99 | 100 | //go:build !cgo 101 | 102 | package fakecgo 103 | 104 | {{- range $location := . }} 105 | {{- range .Symbols }} 106 | //go:cgo_import_dynamic purego_{{ .Name }} {{ .Name }} "{{ $location.SharedObject }}" 107 | {{- end }} 108 | {{- end }} 109 | ` 110 | 111 | type Arg struct { 112 | Name string 113 | Type string 114 | } 115 | 116 | type Symbol struct { 117 | Name string 118 | Args [5]Arg 119 | Return string 120 | } 121 | 122 | type LocatedSymbols struct { 123 | SharedObject string 124 | Symbols []Symbol 125 | } 126 | 127 | var ( 128 | libcSymbols = []Symbol{ 129 | {"malloc", [5]Arg{{"size", "uintptr"}}, "unsafe.Pointer"}, 130 | {"free", [5]Arg{{"ptr", "unsafe.Pointer"}}, ""}, 131 | {"setenv", [5]Arg{{"name", "*byte"}, {"value", "*byte"}, {"overwrite", "int32"}}, "int32"}, 132 | {"unsetenv", [5]Arg{{"name", "*byte"}}, "int32"}, 133 | {"sigfillset", [5]Arg{{"set", "*sigset_t"}}, "int32"}, 134 | {"nanosleep", [5]Arg{{"ts", "*syscall.Timespec"}, {"rem", "*syscall.Timespec"}}, "int32"}, 135 | {"abort", [5]Arg{}, ""}, 136 | {"sigaltstack", [5]Arg{{"ss", "*stack_t"}, {"old_ss", "*stack_t"}}, "int32"}, 137 | } 138 | pthreadSymbols = []Symbol{ 139 | {"pthread_attr_init", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32"}, 140 | {"pthread_create", [5]Arg{{"thread", "*pthread_t"}, {"attr", "*pthread_attr_t"}, {"start", "unsafe.Pointer"}, {"arg", "unsafe.Pointer"}}, "int32"}, 141 | {"pthread_detach", [5]Arg{{"thread", "pthread_t"}}, "int32"}, 142 | {"pthread_sigmask", [5]Arg{{"how", "sighow"}, {"ign", "*sigset_t"}, {"oset", "*sigset_t"}}, "int32"}, 143 | {"pthread_self", [5]Arg{}, "pthread_t"}, 144 | {"pthread_get_stacksize_np", [5]Arg{{"thread", "pthread_t"}}, "size_t"}, 145 | {"pthread_attr_getstacksize", [5]Arg{{"attr", "*pthread_attr_t"}, {"stacksize", "*size_t"}}, "int32"}, 146 | {"pthread_attr_setstacksize", [5]Arg{{"attr", "*pthread_attr_t"}, {"size", "size_t"}}, "int32"}, 147 | {"pthread_attr_destroy", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32"}, 148 | {"pthread_mutex_lock", [5]Arg{{"mutex", "*pthread_mutex_t"}}, "int32"}, 149 | {"pthread_mutex_unlock", [5]Arg{{"mutex", "*pthread_mutex_t"}}, "int32"}, 150 | {"pthread_cond_broadcast", [5]Arg{{"cond", "*pthread_cond_t"}}, "int32"}, 151 | {"pthread_setspecific", [5]Arg{{"key", "pthread_key_t"}, {"value", "unsafe.Pointer"}}, "int32"}, 152 | } 153 | ) 154 | 155 | var funcs = map[string]any{ 156 | "hasPrefix": strings.HasPrefix, 157 | } 158 | 159 | func run() error { 160 | t, err := template.New("zsymbol.go").Funcs(funcs).Parse(templateSymbols) 161 | if err != nil { 162 | return err 163 | } 164 | f, err := os.Create("zsymbols.go") 165 | defer f.Close() 166 | if err != nil { 167 | return err 168 | } 169 | allSymbols := append(append([]Symbol{}, libcSymbols...), pthreadSymbols...) 170 | buf := new(bytes.Buffer) 171 | if err := t.Execute(buf, allSymbols); err != nil { 172 | return err 173 | } 174 | source, err := format.Source(buf.Bytes()) 175 | if err != nil { 176 | return err 177 | } 178 | if _, err = f.Write(source); err != nil { 179 | return err 180 | } 181 | t, err = template.New("ztrampolines_stubs.s").Funcs(funcs).Parse(templateTrampolinesStubs) 182 | if err != nil { 183 | return err 184 | } 185 | f, err = os.Create("ztrampolines_stubs.s") 186 | defer f.Close() 187 | if err != nil { 188 | return err 189 | } 190 | if err := t.Execute(f, allSymbols); err != nil { 191 | return err 192 | } 193 | t, err = template.New("zsymbols_goos.go").Parse(templateSymbolsGoos) 194 | if err != nil { 195 | return err 196 | } 197 | for _, goos := range []string{"darwin", "freebsd", "linux", "netbsd"} { 198 | f, err = os.Create(fmt.Sprintf("zsymbols_%s.go", goos)) 199 | defer f.Close() 200 | if err != nil { 201 | return err 202 | } 203 | b := &bytes.Buffer{} 204 | var libcSO, pthreadSO string 205 | switch goos { 206 | case "darwin": 207 | libcSO = "/usr/lib/libSystem.B.dylib" 208 | pthreadSO = "/usr/lib/libSystem.B.dylib" 209 | case "freebsd": 210 | libcSO = "libc.so.7" 211 | pthreadSO = "libpthread.so" 212 | case "linux": 213 | libcSO = "libc.so.6" 214 | pthreadSO = "libpthread.so.0" 215 | case "netbsd": 216 | libcSO = "libc.so" 217 | pthreadSO = "libpthread.so" 218 | default: 219 | return fmt.Errorf("unsupported OS: %s", goos) 220 | } 221 | located := []LocatedSymbols{ 222 | {SharedObject: libcSO, Symbols: libcSymbols}, 223 | {SharedObject: pthreadSO, Symbols: pthreadSymbols}, 224 | } 225 | if err = t.Execute(b, located); err != nil { 226 | return err 227 | } 228 | var src []byte 229 | src, err = format.Source(b.Bytes()) 230 | if err != nil { 231 | return err 232 | } 233 | if _, err = f.Write(src); err != nil { 234 | return err 235 | } 236 | } 237 | return nil 238 | } 239 | 240 | func main() { 241 | if err := run(); err != nil { 242 | log.Fatal(err) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /func_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors 3 | 4 | package purego_test 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "os/exec" 10 | "path/filepath" 11 | "runtime" 12 | "strings" 13 | "testing" 14 | "unsafe" 15 | 16 | "github.com/ebitengine/purego" 17 | "github.com/ebitengine/purego/internal/load" 18 | ) 19 | 20 | func getSystemLibrary() (string, error) { 21 | switch runtime.GOOS { 22 | case "darwin": 23 | return "/usr/lib/libSystem.B.dylib", nil 24 | case "freebsd": 25 | return "libc.so.7", nil 26 | case "linux": 27 | return "libc.so.6", nil 28 | case "netbsd": 29 | return "libc.so", nil 30 | case "windows": 31 | return "ucrtbase.dll", nil 32 | default: 33 | return "", fmt.Errorf("GOOS=%s is not supported", runtime.GOOS) 34 | } 35 | } 36 | 37 | func TestRegisterFunc(t *testing.T) { 38 | library, err := getSystemLibrary() 39 | if err != nil { 40 | t.Fatalf("couldn't get system library: %s", err) 41 | } 42 | libc, err := load.OpenLibrary(library) 43 | if err != nil { 44 | t.Fatalf("failed to dlopen: %s", err) 45 | } 46 | var puts func(string) 47 | purego.RegisterLibFunc(&puts, libc, "puts") 48 | puts("Calling C from from Go without Cgo!") 49 | } 50 | 51 | func Test_qsort(t *testing.T) { 52 | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { 53 | t.Skip("Platform doesn't support Floats") 54 | return 55 | } 56 | library, err := getSystemLibrary() 57 | if err != nil { 58 | t.Fatalf("couldn't get system library: %s", err) 59 | } 60 | libc, err := load.OpenLibrary(library) 61 | if err != nil { 62 | t.Fatalf("failed to dlopen: %s", err) 63 | } 64 | 65 | data := []int{88, 56, 100, 2, 25} 66 | sorted := []int{2, 25, 56, 88, 100} 67 | compare := func(_ purego.CDecl, a, b *int) int { 68 | return *a - *b 69 | } 70 | var qsort func(data []int, nitms uintptr, size uintptr, compar func(_ purego.CDecl, a, b *int) int) 71 | purego.RegisterLibFunc(&qsort, libc, "qsort") 72 | qsort(data, uintptr(len(data)), unsafe.Sizeof(int(0)), compare) 73 | for i := range data { 74 | if data[i] != sorted[i] { 75 | t.Errorf("got %d wanted %d at %d", data[i], sorted[i], i) 76 | } 77 | } 78 | } 79 | 80 | func TestRegisterFunc_Floats(t *testing.T) { 81 | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { 82 | t.Skip("Platform doesn't support Floats") 83 | return 84 | } 85 | library, err := getSystemLibrary() 86 | if err != nil { 87 | t.Fatalf("couldn't get system library: %s", err) 88 | } 89 | libc, err := load.OpenLibrary(library) 90 | if err != nil { 91 | t.Fatalf("failed to dlopen: %s", err) 92 | } 93 | { 94 | var strtof func(arg string) float32 95 | purego.RegisterLibFunc(&strtof, libc, "strtof") 96 | const ( 97 | arg = "2" 98 | ) 99 | got := strtof(arg) 100 | expected := float32(2) 101 | if got != expected { 102 | t.Errorf("strtof failed. got %f but wanted %f", got, expected) 103 | } 104 | } 105 | { 106 | var strtod func(arg string, ptr **byte) float64 107 | purego.RegisterLibFunc(&strtod, libc, "strtod") 108 | const ( 109 | arg = "1" 110 | ) 111 | got := strtod(arg, nil) 112 | expected := float64(1) 113 | if got != expected { 114 | t.Errorf("strtod failed. got %f but wanted %f", got, expected) 115 | } 116 | } 117 | } 118 | 119 | func TestRegisterLibFunc_Bool(t *testing.T) { 120 | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { 121 | t.Skip("Platform doesn't support callbacks") 122 | return 123 | } 124 | // this callback recreates the state where the return register 125 | // contains other information but the least significant byte is false 126 | cbFalse := purego.NewCallback(func() uintptr { 127 | x := uint64(0x7F5948AE9A00) 128 | return uintptr(x) 129 | }) 130 | var runFalse func() bool 131 | purego.RegisterFunc(&runFalse, cbFalse) 132 | expected := false 133 | if got := runFalse(); got != expected { 134 | t.Errorf("runFalse failed. got %t but wanted %t", got, expected) 135 | } 136 | } 137 | 138 | func TestABI(t *testing.T) { 139 | if runtime.GOOS == "windows" && runtime.GOARCH == "386" { 140 | t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test 141 | } 142 | libFileName := filepath.Join(t.TempDir(), "abitest.so") 143 | t.Logf("Build %v", libFileName) 144 | 145 | if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "abitest", "abi_test.c")); err != nil { 146 | t.Fatal(err) 147 | } 148 | 149 | lib, err := load.OpenLibrary(libFileName) 150 | if err != nil { 151 | t.Fatalf("Dlopen(%q) failed: %v", libFileName, err) 152 | } 153 | defer func() { 154 | if err := load.CloseLibrary(lib); err != nil { 155 | t.Fatalf("failed to close library: %s", err) 156 | } 157 | }() 158 | { 159 | const cName = "stack_uint8_t" 160 | const expect = 2047 161 | var fn func(a, b, c, d, e, f, g, h uint32, i, j uint8, k uint32) uint32 162 | purego.RegisterLibFunc(&fn, lib, cName) 163 | res := fn(256, 512, 4, 8, 16, 32, 64, 128, 1, 2, 1024) 164 | if res != expect { 165 | t.Fatalf("%s: got %d, want %d", cName, res, expect) 166 | } 167 | } 168 | { 169 | const cName = "reg_uint8_t" 170 | const expect = 1027 171 | var fn func(a, b uint8, c uint32) uint32 172 | purego.RegisterLibFunc(&fn, lib, cName) 173 | res := fn(1, 2, 1024) 174 | if res != expect { 175 | t.Fatalf("%s: got %d, want %d", cName, res, expect) 176 | } 177 | } 178 | { 179 | const cName = "stack_string" 180 | const expect = 255 181 | var fn func(a, b, c, d, e, f, g, h uint32, i string) uint32 182 | purego.RegisterLibFunc(&fn, lib, cName) 183 | res := fn(1, 2, 4, 8, 16, 32, 64, 128, "test") 184 | if res != expect { 185 | t.Fatalf("%s: got %d, want %d", cName, res, expect) 186 | } 187 | } 188 | { 189 | const cName = "stack_8i32_3strings" 190 | var fn func(*byte, uintptr, int32, int32, int32, int32, int32, int32, int32, int32, string, string, string) 191 | purego.RegisterLibFunc(&fn, lib, cName) 192 | buf := make([]byte, 256) 193 | fn(&buf[0], uintptr(len(buf)), 1, 2, 3, 4, 5, 6, 7, 8, "foo", "bar", "baz") 194 | res := string(buf[:strings.IndexByte(string(buf), 0)]) 195 | const want = "1:2:3:4:5:6:7:8:foo:bar:baz" 196 | if res != want { 197 | t.Fatalf("%s: got %q, want %q", cName, res, want) 198 | } 199 | } 200 | } 201 | 202 | func buildSharedLib(compilerEnv, libFile string, sources ...string) error { 203 | out, err := exec.Command("go", "env", compilerEnv).Output() 204 | if err != nil { 205 | return fmt.Errorf("go env %s error: %w", compilerEnv, err) 206 | } 207 | 208 | compiler := strings.TrimSpace(string(out)) 209 | if compiler == "" { 210 | return errors.New("compiler not found") 211 | } 212 | 213 | args := []string{"-shared", "-Wall", "-Werror", "-fPIC", "-o", libFile} 214 | if runtime.GOARCH == "386" { 215 | args = append(args, "-m32") 216 | } 217 | // macOS arm64 can run amd64 tests through Rossetta. 218 | // Build the shared library based on the GOARCH and not 219 | // the default behavior of the compiler. 220 | if runtime.GOOS == "darwin" { 221 | var arch string 222 | switch runtime.GOARCH { 223 | case "arm64": 224 | arch = "arm64" 225 | case "amd64": 226 | arch = "x86_64" 227 | default: 228 | return fmt.Errorf("unknown macOS architecture %s", runtime.GOARCH) 229 | } 230 | args = append(args, "-arch", arch) 231 | } 232 | cmd := exec.Command(compiler, append(args, sources...)...) 233 | if out, err := cmd.CombinedOutput(); err != nil { 234 | return fmt.Errorf("compile lib: %w\n%q\n%s", err, cmd, string(out)) 235 | } 236 | 237 | return nil 238 | } 239 | -------------------------------------------------------------------------------- /syscall_sysv.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 | 4 | //go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd 5 | 6 | package purego 7 | 8 | import ( 9 | "reflect" 10 | "runtime" 11 | "sync" 12 | "unsafe" 13 | ) 14 | 15 | var syscall15XABI0 uintptr 16 | 17 | func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { 18 | args := thePool.Get().(*syscall15Args) 19 | defer thePool.Put(args) 20 | 21 | *args = syscall15Args{ 22 | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, 23 | a1, a2, a3, a4, a5, a6, a7, a8, 24 | 0, 25 | } 26 | 27 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(args)) 28 | return args.a1, args.a2, 0 29 | } 30 | 31 | // NewCallback converts a Go function to a function pointer conforming to the C calling convention. 32 | // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a 33 | // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size 34 | // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated 35 | // for these callbacks is never released. At least 2000 callbacks can always be created. Although this function 36 | // provides similar functionality to windows.NewCallback it is distinct. 37 | func NewCallback(fn any) uintptr { 38 | ty := reflect.TypeOf(fn) 39 | for i := 0; i < ty.NumIn(); i++ { 40 | in := ty.In(i) 41 | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { 42 | continue 43 | } 44 | if i != 0 { 45 | panic("purego: CDecl must be the first argument") 46 | } 47 | } 48 | return compileCallback(fn) 49 | } 50 | 51 | // maxCb is the maximum number of callbacks 52 | // only increase this if you have added more to the callbackasm function 53 | const maxCB = 2000 54 | 55 | var cbs struct { 56 | lock sync.Mutex 57 | numFn int // the number of functions currently in cbs.funcs 58 | funcs [maxCB]reflect.Value // the saved callbacks 59 | } 60 | 61 | type callbackArgs struct { 62 | index uintptr 63 | // args points to the argument block. 64 | // 65 | // The structure of the arguments goes 66 | // float registers followed by the 67 | // integer registers followed by the stack. 68 | // 69 | // This variable is treated as a continuous 70 | // block of memory containing all of the arguments 71 | // for this callback. 72 | args unsafe.Pointer 73 | // Below are out-args from callbackWrap 74 | result uintptr 75 | } 76 | 77 | func compileCallback(fn any) uintptr { 78 | val := reflect.ValueOf(fn) 79 | if val.Kind() != reflect.Func { 80 | panic("purego: the type must be a function but was not") 81 | } 82 | if val.IsNil() { 83 | panic("purego: function must not be nil") 84 | } 85 | ty := val.Type() 86 | for i := 0; i < ty.NumIn(); i++ { 87 | in := ty.In(i) 88 | switch in.Kind() { 89 | case reflect.Struct: 90 | if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) { 91 | continue 92 | } 93 | fallthrough 94 | case reflect.Interface, reflect.Func, reflect.Slice, 95 | reflect.Chan, reflect.Complex64, reflect.Complex128, 96 | reflect.String, reflect.Map, reflect.Invalid: 97 | panic("purego: unsupported argument type: " + in.Kind().String()) 98 | } 99 | } 100 | output: 101 | switch { 102 | case ty.NumOut() == 1: 103 | switch ty.Out(0).Kind() { 104 | case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 105 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 106 | reflect.Bool, reflect.UnsafePointer: 107 | break output 108 | } 109 | panic("purego: unsupported return type: " + ty.String()) 110 | case ty.NumOut() > 1: 111 | panic("purego: callbacks can only have one return") 112 | } 113 | cbs.lock.Lock() 114 | defer cbs.lock.Unlock() 115 | if cbs.numFn >= maxCB { 116 | panic("purego: the maximum number of callbacks has been reached") 117 | } 118 | cbs.funcs[cbs.numFn] = val 119 | cbs.numFn++ 120 | return callbackasmAddr(cbs.numFn - 1) 121 | } 122 | 123 | const ptrSize = unsafe.Sizeof((*int)(nil)) 124 | 125 | const callbackMaxFrame = 64 * ptrSize 126 | 127 | // callbackasm is implemented in zcallback_GOOS_GOARCH.s 128 | // 129 | //go:linkname __callbackasm callbackasm 130 | var __callbackasm byte 131 | var callbackasmABI0 = uintptr(unsafe.Pointer(&__callbackasm)) 132 | 133 | // callbackWrap_call allows the calling of the ABIInternal wrapper 134 | // which is required for runtime.cgocallback without the 135 | // tag which is only allowed in the runtime. 136 | // This closure is used inside sys_darwin_GOARCH.s 137 | var callbackWrap_call = callbackWrap 138 | 139 | // callbackWrap is called by assembly code which determines which Go function to call. 140 | // This function takes the arguments and passes them to the Go function and returns the result. 141 | func callbackWrap(a *callbackArgs) { 142 | cbs.lock.Lock() 143 | fn := cbs.funcs[a.index] 144 | cbs.lock.Unlock() 145 | fnType := fn.Type() 146 | args := make([]reflect.Value, fnType.NumIn()) 147 | frame := (*[callbackMaxFrame]uintptr)(a.args) 148 | var floatsN int // floatsN represents the number of float arguments processed 149 | var intsN int // intsN represents the number of integer arguments processed 150 | // stack points to the index into frame of the current stack element. 151 | // The stack begins after the float and integer registers. 152 | stack := numOfIntegerRegisters() + numOfFloatRegisters 153 | for i := range args { 154 | var pos int 155 | switch fnType.In(i).Kind() { 156 | case reflect.Float32, reflect.Float64: 157 | if floatsN >= numOfFloatRegisters { 158 | pos = stack 159 | stack++ 160 | } else { 161 | pos = floatsN 162 | } 163 | floatsN++ 164 | case reflect.Struct: 165 | // This is the CDecl field 166 | args[i] = reflect.Zero(fnType.In(i)) 167 | continue 168 | default: 169 | 170 | if intsN >= numOfIntegerRegisters() { 171 | pos = stack 172 | stack++ 173 | } else { 174 | // the integers begin after the floats in frame 175 | pos = intsN + numOfFloatRegisters 176 | } 177 | intsN++ 178 | } 179 | args[i] = reflect.NewAt(fnType.In(i), unsafe.Pointer(&frame[pos])).Elem() 180 | } 181 | ret := fn.Call(args) 182 | if len(ret) > 0 { 183 | switch k := ret[0].Kind(); k { 184 | case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr: 185 | a.result = uintptr(ret[0].Uint()) 186 | case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: 187 | a.result = uintptr(ret[0].Int()) 188 | case reflect.Bool: 189 | if ret[0].Bool() { 190 | a.result = 1 191 | } else { 192 | a.result = 0 193 | } 194 | case reflect.Pointer: 195 | a.result = ret[0].Pointer() 196 | case reflect.UnsafePointer: 197 | a.result = ret[0].Pointer() 198 | default: 199 | panic("purego: unsupported kind: " + k.String()) 200 | } 201 | } 202 | } 203 | 204 | // callbackasmAddr returns address of runtime.callbackasm 205 | // function adjusted by i. 206 | // On x86 and amd64, runtime.callbackasm is a series of CALL instructions, 207 | // and we want callback to arrive at 208 | // correspondent call instruction instead of start of 209 | // runtime.callbackasm. 210 | // On ARM, runtime.callbackasm is a series of mov and branch instructions. 211 | // R12 is loaded with the callback index. Each entry is two instructions, 212 | // hence 8 bytes. 213 | func callbackasmAddr(i int) uintptr { 214 | var entrySize int 215 | switch runtime.GOARCH { 216 | default: 217 | panic("purego: unsupported architecture") 218 | case "386", "amd64": 219 | entrySize = 5 220 | case "arm", "arm64", "loong64": 221 | // On ARM and ARM64, each entry is a MOV instruction 222 | // followed by a branch instruction 223 | entrySize = 8 224 | } 225 | return callbackasmABI0 + uintptr(i*entrySize) 226 | } 227 | -------------------------------------------------------------------------------- /internal/fakecgo/zsymbols.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate' with gen.go. DO NOT EDIT. 2 | 3 | // SPDX-License-Identifier: Apache-2.0 4 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 5 | 6 | //go:build !cgo && (darwin || freebsd || linux || netbsd) 7 | 8 | package fakecgo 9 | 10 | import ( 11 | "syscall" 12 | "unsafe" 13 | ) 14 | 15 | //go:nosplit 16 | //go:norace 17 | func malloc(size uintptr) unsafe.Pointer { 18 | ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0) 19 | // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer 20 | return *(*unsafe.Pointer)(unsafe.Pointer(&ret)) 21 | } 22 | 23 | //go:nosplit 24 | //go:norace 25 | func free(ptr unsafe.Pointer) { 26 | call5(freeABI0, uintptr(ptr), 0, 0, 0, 0) 27 | } 28 | 29 | //go:nosplit 30 | //go:norace 31 | func setenv(name *byte, value *byte, overwrite int32) int32 { 32 | return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0)) 33 | } 34 | 35 | //go:nosplit 36 | //go:norace 37 | func unsetenv(name *byte) int32 { 38 | return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0)) 39 | } 40 | 41 | //go:nosplit 42 | //go:norace 43 | func sigfillset(set *sigset_t) int32 { 44 | return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0)) 45 | } 46 | 47 | //go:nosplit 48 | //go:norace 49 | func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 { 50 | return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0)) 51 | } 52 | 53 | //go:nosplit 54 | //go:norace 55 | func abort() { 56 | call5(abortABI0, 0, 0, 0, 0, 0) 57 | } 58 | 59 | //go:nosplit 60 | //go:norace 61 | func sigaltstack(ss *stack_t, old_ss *stack_t) int32 { 62 | return int32(call5(sigaltstackABI0, uintptr(unsafe.Pointer(ss)), uintptr(unsafe.Pointer(old_ss)), 0, 0, 0)) 63 | } 64 | 65 | //go:nosplit 66 | //go:norace 67 | func pthread_attr_init(attr *pthread_attr_t) int32 { 68 | return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) 69 | } 70 | 71 | //go:nosplit 72 | //go:norace 73 | func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 { 74 | return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0)) 75 | } 76 | 77 | //go:nosplit 78 | //go:norace 79 | func pthread_detach(thread pthread_t) int32 { 80 | return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0)) 81 | } 82 | 83 | //go:nosplit 84 | //go:norace 85 | func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 { 86 | return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0)) 87 | } 88 | 89 | //go:nosplit 90 | //go:norace 91 | func pthread_self() pthread_t { 92 | return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0)) 93 | } 94 | 95 | //go:nosplit 96 | //go:norace 97 | func pthread_get_stacksize_np(thread pthread_t) size_t { 98 | return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0)) 99 | } 100 | 101 | //go:nosplit 102 | //go:norace 103 | func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 { 104 | return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0)) 105 | } 106 | 107 | //go:nosplit 108 | //go:norace 109 | func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 { 110 | return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0)) 111 | } 112 | 113 | //go:nosplit 114 | //go:norace 115 | func pthread_attr_destroy(attr *pthread_attr_t) int32 { 116 | return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) 117 | } 118 | 119 | //go:nosplit 120 | //go:norace 121 | func pthread_mutex_lock(mutex *pthread_mutex_t) int32 { 122 | return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) 123 | } 124 | 125 | //go:nosplit 126 | //go:norace 127 | func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 { 128 | return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) 129 | } 130 | 131 | //go:nosplit 132 | //go:norace 133 | func pthread_cond_broadcast(cond *pthread_cond_t) int32 { 134 | return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0)) 135 | } 136 | 137 | //go:nosplit 138 | //go:norace 139 | func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 { 140 | return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0)) 141 | } 142 | 143 | //go:linkname _malloc _malloc 144 | var _malloc uint8 145 | var mallocABI0 = uintptr(unsafe.Pointer(&_malloc)) 146 | 147 | //go:linkname _free _free 148 | var _free uint8 149 | var freeABI0 = uintptr(unsafe.Pointer(&_free)) 150 | 151 | //go:linkname _setenv _setenv 152 | var _setenv uint8 153 | var setenvABI0 = uintptr(unsafe.Pointer(&_setenv)) 154 | 155 | //go:linkname _unsetenv _unsetenv 156 | var _unsetenv uint8 157 | var unsetenvABI0 = uintptr(unsafe.Pointer(&_unsetenv)) 158 | 159 | //go:linkname _sigfillset _sigfillset 160 | var _sigfillset uint8 161 | var sigfillsetABI0 = uintptr(unsafe.Pointer(&_sigfillset)) 162 | 163 | //go:linkname _nanosleep _nanosleep 164 | var _nanosleep uint8 165 | var nanosleepABI0 = uintptr(unsafe.Pointer(&_nanosleep)) 166 | 167 | //go:linkname _abort _abort 168 | var _abort uint8 169 | var abortABI0 = uintptr(unsafe.Pointer(&_abort)) 170 | 171 | //go:linkname _sigaltstack _sigaltstack 172 | var _sigaltstack uint8 173 | var sigaltstackABI0 = uintptr(unsafe.Pointer(&_sigaltstack)) 174 | 175 | //go:linkname _pthread_attr_init _pthread_attr_init 176 | var _pthread_attr_init uint8 177 | var pthread_attr_initABI0 = uintptr(unsafe.Pointer(&_pthread_attr_init)) 178 | 179 | //go:linkname _pthread_create _pthread_create 180 | var _pthread_create uint8 181 | var pthread_createABI0 = uintptr(unsafe.Pointer(&_pthread_create)) 182 | 183 | //go:linkname _pthread_detach _pthread_detach 184 | var _pthread_detach uint8 185 | var pthread_detachABI0 = uintptr(unsafe.Pointer(&_pthread_detach)) 186 | 187 | //go:linkname _pthread_sigmask _pthread_sigmask 188 | var _pthread_sigmask uint8 189 | var pthread_sigmaskABI0 = uintptr(unsafe.Pointer(&_pthread_sigmask)) 190 | 191 | //go:linkname _pthread_self _pthread_self 192 | var _pthread_self uint8 193 | var pthread_selfABI0 = uintptr(unsafe.Pointer(&_pthread_self)) 194 | 195 | //go:linkname _pthread_get_stacksize_np _pthread_get_stacksize_np 196 | var _pthread_get_stacksize_np uint8 197 | var pthread_get_stacksize_npABI0 = uintptr(unsafe.Pointer(&_pthread_get_stacksize_np)) 198 | 199 | //go:linkname _pthread_attr_getstacksize _pthread_attr_getstacksize 200 | var _pthread_attr_getstacksize uint8 201 | var pthread_attr_getstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_getstacksize)) 202 | 203 | //go:linkname _pthread_attr_setstacksize _pthread_attr_setstacksize 204 | var _pthread_attr_setstacksize uint8 205 | var pthread_attr_setstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_setstacksize)) 206 | 207 | //go:linkname _pthread_attr_destroy _pthread_attr_destroy 208 | var _pthread_attr_destroy uint8 209 | var pthread_attr_destroyABI0 = uintptr(unsafe.Pointer(&_pthread_attr_destroy)) 210 | 211 | //go:linkname _pthread_mutex_lock _pthread_mutex_lock 212 | var _pthread_mutex_lock uint8 213 | var pthread_mutex_lockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_lock)) 214 | 215 | //go:linkname _pthread_mutex_unlock _pthread_mutex_unlock 216 | var _pthread_mutex_unlock uint8 217 | var pthread_mutex_unlockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_unlock)) 218 | 219 | //go:linkname _pthread_cond_broadcast _pthread_cond_broadcast 220 | var _pthread_cond_broadcast uint8 221 | var pthread_cond_broadcastABI0 = uintptr(unsafe.Pointer(&_pthread_cond_broadcast)) 222 | 223 | //go:linkname _pthread_setspecific _pthread_setspecific 224 | var _pthread_setspecific uint8 225 | var pthread_setspecificABI0 = uintptr(unsafe.Pointer(&_pthread_setspecific)) 226 | -------------------------------------------------------------------------------- /struct_amd64.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors 3 | 4 | package purego 5 | 6 | import ( 7 | "math" 8 | "reflect" 9 | "unsafe" 10 | ) 11 | 12 | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { 13 | outSize := outType.Size() 14 | switch { 15 | case outSize == 0: 16 | return reflect.New(outType).Elem() 17 | case outSize <= 8: 18 | if isAllFloats(outType) { 19 | // 2 float32s or 1 float64s are return in the float register 20 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem() 21 | } 22 | // up to 8 bytes is returned in RAX 23 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() 24 | case outSize <= 16: 25 | r1, r2 := syscall.a1, syscall.a2 26 | if isAllFloats(outType) { 27 | r1 = syscall.f1 28 | r2 = syscall.f2 29 | } else { 30 | // check first 8 bytes if it's floats 31 | hasFirstFloat := false 32 | f1 := outType.Field(0).Type 33 | if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 { 34 | r1 = syscall.f1 35 | hasFirstFloat = true 36 | } 37 | 38 | // find index of the field that starts the second 8 bytes 39 | var i int 40 | for i = 0; i < outType.NumField(); i++ { 41 | if outType.Field(i).Offset == 8 { 42 | break 43 | } 44 | } 45 | 46 | // check last 8 bytes if they are floats 47 | f1 = outType.Field(i).Type 48 | if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() { 49 | r2 = syscall.f1 50 | } else if hasFirstFloat { 51 | // if the first field was a float then that means the second integer field 52 | // comes from the first integer register 53 | r2 = syscall.a1 54 | } 55 | } 56 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() 57 | default: 58 | // create struct from the Go pointer created above 59 | // weird pointer dereference to circumvent go vet 60 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() 61 | } 62 | } 63 | 64 | func isAllFloats(ty reflect.Type) bool { 65 | for i := 0; i < ty.NumField(); i++ { 66 | f := ty.Field(i) 67 | switch f.Type.Kind() { 68 | case reflect.Float64, reflect.Float32: 69 | default: 70 | return false 71 | } 72 | } 73 | return true 74 | } 75 | 76 | // https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf 77 | // https://gitlab.com/x86-psABIs/x86-64-ABI 78 | // Class determines where the 8 byte value goes. 79 | // Higher value classes win over lower value classes 80 | const ( 81 | _NO_CLASS = 0b0000 82 | _SSE = 0b0001 83 | _X87 = 0b0011 // long double not used in Go 84 | _INTEGER = 0b0111 85 | _MEMORY = 0b1111 86 | ) 87 | 88 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { 89 | if v.Type().Size() == 0 { 90 | return keepAlive 91 | } 92 | 93 | // if greater than 64 bytes place on stack 94 | if v.Type().Size() > 8*8 { 95 | placeStack(v, addStack) 96 | return keepAlive 97 | } 98 | var ( 99 | savedNumFloats = *numFloats 100 | savedNumInts = *numInts 101 | savedNumStack = *numStack 102 | ) 103 | placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt) 104 | if placeOnStack { 105 | // reset any values placed in registers 106 | *numFloats = savedNumFloats 107 | *numInts = savedNumInts 108 | *numStack = savedNumStack 109 | placeStack(v, addStack) 110 | } 111 | return keepAlive 112 | } 113 | 114 | func postMerger(t reflect.Type) (passInMemory bool) { 115 | // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other 116 | // eightbyte isn’t SSEUP, the whole argument is passed in memory. 117 | if t.Kind() != reflect.Struct { 118 | return false 119 | } 120 | if t.Size() <= 2*8 { 121 | return false 122 | } 123 | return true // Go does not have an SSE/SSEUP type so this is always true 124 | } 125 | 126 | func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) { 127 | ok = true 128 | var val uint64 129 | var shift byte // # of bits to shift 130 | var flushed bool 131 | class := _NO_CLASS 132 | flushIfNeeded := func() { 133 | if flushed { 134 | return 135 | } 136 | flushed = true 137 | if class == _SSE { 138 | addFloat(uintptr(val)) 139 | } else { 140 | addInt(uintptr(val)) 141 | } 142 | val = 0 143 | shift = 0 144 | class = _NO_CLASS 145 | } 146 | var place func(v reflect.Value) 147 | place = func(v reflect.Value) { 148 | var numFields int 149 | if v.Kind() == reflect.Struct { 150 | numFields = v.Type().NumField() 151 | } else { 152 | numFields = v.Type().Len() 153 | } 154 | 155 | for i := 0; i < numFields; i++ { 156 | flushed = false 157 | var f reflect.Value 158 | if v.Kind() == reflect.Struct { 159 | f = v.Field(i) 160 | } else { 161 | f = v.Index(i) 162 | } 163 | switch f.Kind() { 164 | case reflect.Struct: 165 | place(f) 166 | case reflect.Bool: 167 | if f.Bool() { 168 | val |= 1 << shift 169 | } 170 | shift += 8 171 | class |= _INTEGER 172 | case reflect.Pointer: 173 | ok = false 174 | return 175 | case reflect.Int8: 176 | val |= uint64(f.Int()&0xFF) << shift 177 | shift += 8 178 | class |= _INTEGER 179 | case reflect.Int16: 180 | val |= uint64(f.Int()&0xFFFF) << shift 181 | shift += 16 182 | class |= _INTEGER 183 | case reflect.Int32: 184 | val |= uint64(f.Int()&0xFFFF_FFFF) << shift 185 | shift += 32 186 | class |= _INTEGER 187 | case reflect.Int64, reflect.Int: 188 | val = uint64(f.Int()) 189 | shift = 64 190 | class = _INTEGER 191 | case reflect.Uint8: 192 | val |= f.Uint() << shift 193 | shift += 8 194 | class |= _INTEGER 195 | case reflect.Uint16: 196 | val |= f.Uint() << shift 197 | shift += 16 198 | class |= _INTEGER 199 | case reflect.Uint32: 200 | val |= f.Uint() << shift 201 | shift += 32 202 | class |= _INTEGER 203 | case reflect.Uint64, reflect.Uint, reflect.Uintptr: 204 | val = f.Uint() 205 | shift = 64 206 | class = _INTEGER 207 | case reflect.Float32: 208 | val |= uint64(math.Float32bits(float32(f.Float()))) << shift 209 | shift += 32 210 | class |= _SSE 211 | case reflect.Float64: 212 | if v.Type().Size() > 16 { 213 | ok = false 214 | return 215 | } 216 | val = uint64(math.Float64bits(f.Float())) 217 | shift = 64 218 | class = _SSE 219 | case reflect.Array: 220 | place(f) 221 | default: 222 | panic("purego: unsupported kind " + f.Kind().String()) 223 | } 224 | 225 | if shift == 64 { 226 | flushIfNeeded() 227 | } else if shift > 64 { 228 | // Should never happen, but may if we forget to reset shift after flush (or forget to flush), 229 | // better fall apart here, than corrupt arguments. 230 | panic("purego: tryPlaceRegisters shift > 64") 231 | } 232 | } 233 | } 234 | 235 | place(v) 236 | flushIfNeeded() 237 | return ok 238 | } 239 | 240 | func placeStack(v reflect.Value, addStack func(uintptr)) { 241 | for i := 0; i < v.Type().NumField(); i++ { 242 | f := v.Field(i) 243 | switch f.Kind() { 244 | case reflect.Pointer: 245 | addStack(f.Pointer()) 246 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 247 | addStack(uintptr(f.Int())) 248 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 249 | addStack(uintptr(f.Uint())) 250 | case reflect.Float32: 251 | addStack(uintptr(math.Float32bits(float32(f.Float())))) 252 | case reflect.Float64: 253 | addStack(uintptr(math.Float64bits(f.Float()))) 254 | case reflect.Struct: 255 | placeStack(f, addStack) 256 | default: 257 | panic("purego: unsupported kind " + f.Kind().String()) 258 | } 259 | } 260 | } 261 | 262 | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { 263 | panic("purego: not needed on amd64") 264 | } 265 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest] 10 | go: ['1.18.x', '1.19.x', '1.20.x', '1.21.x', '1.22.x', '1.23.x', '1.24.x', '1.25.x', '1.26.0-rc.1'] 11 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | defaults: 14 | run: 15 | shell: bash 16 | steps: 17 | - name: Git 18 | run: | 19 | # See actions/checkout#135 20 | git config --global core.autocrlf false 21 | git config --global core.eol lf 22 | 23 | - name: Checkout 24 | uses: actions/checkout@v5 25 | 26 | - name: Setup Go 27 | uses: actions/setup-go@v6 28 | with: 29 | go-version: ${{ matrix.go }} 30 | 31 | - name: Set up the prerequisites 32 | if: runner.os == 'Windows' 33 | uses: msys2/setup-msys2@v2 34 | 35 | - name: go vet 36 | run: | 37 | env CGO_ENABLED=0 go vet -v ./... 38 | 39 | - name: go build 40 | run: | 41 | go build -v ./... 42 | # Compile without optimization to check potential stack overflow. 43 | # The option '-gcflags=all=-N -l' is often used at Visual Studio Code. 44 | # See also https://go.googlesource.com/vscode-go/+/HEAD/docs/debugging.md#launch and the issue hajimehoshi/ebiten#2120. 45 | go build "-gcflags=all=-N -l" -v ./... 46 | 47 | # Check cross-compiling Windows binaries. 48 | env GOOS=windows GOARCH=386 go build -v ./... 49 | env GOOS=windows GOARCH=amd64 go build -v ./... 50 | env GOOS=windows GOARCH=arm64 go build -v ./... 51 | 52 | # Check cross-compiling macOS binaries. 53 | env GOOS=darwin GOARCH=amd64 go build -v ./... 54 | env GOOS=darwin GOARCH=arm64 go build -v ./... 55 | 56 | # Check cross-compiling Linux binaries. 57 | env GOOS=linux GOARCH=amd64 go build -v ./... 58 | env GOOS=linux GOARCH=arm64 go build -v ./... 59 | 60 | # Check cross-compiling FreeBSD binaries. 61 | # gcflags -std is necessary to make fakecgo the Cgo for 62 | # FreeBSD to add the symbols that libc.so depends on. 63 | env GOOS=freebsd GOARCH=amd64 go build -gcflags="github.com/ebitengine/purego/internal/fakecgo=-std" -v ./... 64 | env GOOS=freebsd GOARCH=arm64 go build -gcflags="github.com/ebitengine/purego/internal/fakecgo=-std" -v ./... 65 | 66 | # Check cross-compiling NetBSD binaries. 67 | env GOOS=netbsd GOARCH=amd64 go build -v ./... 68 | env GOOS=netbsd GOARCH=arm64 go build -v ./... 69 | 70 | - name: go build (Linux loong64) 71 | if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') && !startsWith(matrix.go, '1.22.') && !startsWith(matrix.go, '1.23.') && !startsWith(matrix.go, '1.24.') }} 72 | run: | 73 | # Check cross-compiling Linux binaries for loong64. 74 | # Loong64 support internal linking from go1.25. 75 | env GOOS=linux GOARCH=loong64 go build -v ./... 76 | 77 | - name: go build (plugin) 78 | if: runner.os == 'Linux' || runner.os == 'macOS' 79 | run: 80 | # Make sure that plugin buildmode works since we save the R15 register (#254) 81 | go build -buildmode=plugin ./examples/libc 82 | 83 | - name: go mod vendor 84 | if: runner.os != 'Linux' 85 | run: | 86 | mkdir /tmp/vendoring 87 | cd /tmp/vendoring 88 | go mod init foo 89 | echo 'package main' > main.go 90 | echo 'import (' >> main.go 91 | echo ' _ "github.com/ebitengine/purego"' >> main.go 92 | echo ')' >> main.go 93 | echo 'func main() {}' >> main.go 94 | go mod edit -replace github.com/ebitengine/purego=$GITHUB_WORKSPACE 95 | go mod tidy 96 | go mod vendor 97 | go build -v . 98 | 99 | - name: go test 100 | run: | 101 | env CGO_ENABLED=0 go test -shuffle=on -v -count=10 ./... 102 | env CGO_ENABLED=1 go test -shuffle=on -v -count=10 ./... 103 | # Compile without optimization to check potential stack overflow. 104 | # The option '-gcflags=all=-N -l' is often used at Visual Studio Code. 105 | # See also https://go.googlesource.com/vscode-go/+/HEAD/docs/debugging.md#launch. 106 | env CGO_ENABLED=0 go test "-gcflags=all=-N -l" -v ./... 107 | env CGO_ENABLED=1 go test "-gcflags=all=-N -l" -v ./... 108 | 109 | - name: go test (Windows 386) 110 | if: runner.os == 'Windows' 111 | run: | 112 | env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./... 113 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... 114 | 115 | - name: go test (Linux 386) 116 | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' }} 117 | run: | 118 | sudo apt-get update 119 | sudo apt-get install gcc-multilib 120 | sudo apt-get install g++-multilib 121 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... 122 | 123 | - name: go test race (no Cgo) 124 | if: ${{ runner.os == 'macOS' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} 125 | run: | 126 | # -race usually requires Cgo, but macOS is an exception. See https://go.dev/doc/articles/race_detector#Requirements 127 | env CGO_ENABLED=0 go test -race -shuffle=on -v -count=10 ./... 128 | 129 | - name: go test race (Cgo) 130 | if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} 131 | run: | 132 | env CGO_ENABLED=1 go test -race -shuffle=on -v -count=10 ./... 133 | 134 | loong: 135 | strategy: 136 | matrix: 137 | # Loong64 support internal linking from go1.25. 138 | go: ['1.25.x'] 139 | name: Test with Go ${{ matrix.go }} on Linux loong64 140 | runs-on: ubuntu-latest 141 | defaults: 142 | run: 143 | shell: bash 144 | steps: 145 | - uses: actions/checkout@v5 146 | - name: Setup Go 147 | uses: actions/setup-go@v6 148 | with: 149 | go-version: ${{ matrix.go }} 150 | - name: Set up the prerequisites 151 | run: | 152 | sudo apt-get update 153 | sudo apt-get install -y gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu qemu-user 154 | - name: go test (Linux loong64) 155 | run: | 156 | go env -w CC=loongarch64-linux-gnu-gcc-14 157 | go env -w CXX=loongarch64-linux-gnu-g++-14 158 | env GOOS=linux GOARCH=loong64 CGO_ENABLED=0 go test -c -o=purego-test-nocgo . 159 | env QEMU_LD_PREFIX=/usr/loongarch64-linux-gnu qemu-loongarch64 ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 160 | env GOOS=linux GOARCH=loong64 CGO_ENABLED=1 go test -c -o=purego-test-cgo . 161 | env QEMU_LD_PREFIX=/usr/loongarch64-linux-gnu qemu-loongarch64 ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 162 | go env -u CC 163 | go env -u CXX 164 | 165 | bsd: 166 | strategy: 167 | matrix: 168 | os: ['FreeBSD'] # TODO: Add 'NetBSD' again (#304) 169 | go: ['1.18.10', '1.19.13', '1.20.14', '1.21.13', '1.22.12', '1.23.12', '1.24.8', '1.25.2', '1.26rc1'] 170 | exclude: 171 | # there are no prebuilt download links for these versions of Go for NetBSD 172 | - os: NetBSD 173 | go: '1.18.10' 174 | - os: NetBSD 175 | go: '1.19.13' 176 | - os: NetBSD 177 | go: '1.20.14' 178 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} 179 | runs-on: ubuntu-22.04 180 | defaults: 181 | run: 182 | shell: bash 183 | steps: 184 | - uses: actions/checkout@v5 185 | - name: Run in FreeBSD 186 | if: matrix.os == 'FreeBSD' 187 | uses: vmactions/freebsd-vm@v1 188 | with: 189 | usesh: true 190 | prepare: | 191 | fetch https://go.dev/dl/go${{matrix.go}}.freebsd-amd64.tar.gz 192 | rm -fr /usr/local/go && tar -C /usr/local -xf go${{matrix.go}}.freebsd-amd64.tar.gz 193 | chmod +x $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh 194 | run: $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh 195 | - name: Run in NetBSD 196 | if: matrix.os == 'NetBSD' 197 | uses: vmactions/netbsd-vm@v1 198 | with: 199 | usesh: true 200 | prepare: | 201 | ftp https://go.dev/dl/go${{matrix.go}}.netbsd-amd64.tar.gz 202 | mkdir /usr/local 203 | rm -fr /usr/local/go && tar -C /usr/local -xf go${{matrix.go}}.netbsd-amd64.tar.gz 204 | chmod +x $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh 205 | run: $GITHUB_WORKSPACE/.github/scripts/bsd_tests.sh 206 | --------------------------------------------------------------------------------