// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build ignore package main import ( "bytes" "fmt" "go/format" "log" "os" "path/filepath" "sort" "strings" "text/template" ) var templateSymbols = template.Must(template.New("symbols").Funcs(funcs).Parse( `// Code generated by 'go generate' with gen.go. DO NOT EDIT. // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build {{.Tag}} package fakecgo {{ imports .Symbols }} {{- range $di := .DynamicImports }} {{- range $sym := $di.Symbols }} //go:cgo_import_dynamic purego_{{ $sym }} {{ $sym }} "{{ $di.Name }}" {{- end }} {{- end }} {{ range .Symbols -}} //go:nosplit //go:norace func {{.Name}}( {{- range .Args -}} {{- if .Name -}} {{.Name}} {{.Type}}, {{- end -}} {{- end -}}) {{.Return}} { {{- if .Return -}} {{- if eq .Return "unsafe.Pointer" -}} ret := {{- else -}} return {{.Return}}( {{- end -}} {{- end -}} call5({{.Name}}ABI0, {{- range .Args}} {{- if .Name -}} {{- if hasPrefix .Type "*" -}} uintptr(unsafe.Pointer({{.Name}})), {{- else -}} uintptr({{.Name}}), {{- end -}} {{- else -}} 0, {{- end -}} {{- end -}} ) {{/* end of call5 */}} {{- if .Return -}} {{- if eq .Return "unsafe.Pointer"}} // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer return *(*unsafe.Pointer)(unsafe.Pointer(&ret)) {{- else -}} ) {{/* end of cast */}} {{- end -}} {{- end}} } {{end}} {{- range .Symbols }} //go:linkname _{{.Name}} _{{.Name}} var _{{.Name}} uint8 var {{.Name}}ABI0 = uintptr(unsafe.Pointer(&_{{.Name}})) {{ end }} `)) var templateTrampolinesStubs = template.Must(template.New("trampolines").Parse( `// Code generated by 'go generate' with gen.go. DO NOT EDIT. // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build {{.Tag}} #include "textflag.h" // these stubs are here because it is not possible to go:linkname directly the C functions {{ range .Symbols }} TEXT _{{.Name}}(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_{{.Name}}(SB) {{ end -}} `)) type Arg struct { Name string Type string } type Symbol struct { Name string Args [5]Arg Return string GOOSes []string // empty means all GOOSes } var ( libcSymbols = []Symbol{ {"malloc", [5]Arg{{"size", "uintptr"}}, "unsafe.Pointer", nil}, {"free", [5]Arg{{"ptr", "unsafe.Pointer"}}, "", nil}, {"setenv", [5]Arg{{"name", "*byte"}, {"value", "*byte"}, {"overwrite", "int32"}}, "int32", nil}, {"unsetenv", [5]Arg{{"name", "*byte"}}, "int32", nil}, {"sigfillset", [5]Arg{{"set", "*sigset_t"}}, "int32", nil}, {"nanosleep", [5]Arg{{"ts", "*syscall.Timespec"}, {"rem", "*syscall.Timespec"}}, "int32", nil}, {"abort", [5]Arg{}, "", nil}, {"sigaltstack", [5]Arg{{"ss", "*stack_t"}, {"old_ss", "*stack_t"}}, "int32", []string{"netbsd"}}, } pthreadSymbols = []Symbol{ {"pthread_attr_init", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32", nil}, {"pthread_create", [5]Arg{{"thread", "*pthread_t"}, {"attr", "*pthread_attr_t"}, {"start", "unsafe.Pointer"}, {"arg", "unsafe.Pointer"}}, "int32", nil}, {"pthread_detach", [5]Arg{{"thread", "pthread_t"}}, "int32", nil}, {"pthread_sigmask", [5]Arg{{"how", "sighow"}, {"ign", "*sigset_t"}, {"oset", "*sigset_t"}}, "int32", nil}, {"pthread_self", [5]Arg{}, "pthread_t", []string{"darwin"}}, {"pthread_get_stacksize_np", [5]Arg{{"thread", "pthread_t"}}, "size_t", []string{"darwin"}}, {"pthread_attr_getstacksize", [5]Arg{{"attr", "*pthread_attr_t"}, {"stacksize", "*size_t"}}, "int32", []string{"linux", "freebsd", "netbsd"}}, {"pthread_attr_setstacksize", [5]Arg{{"attr", "*pthread_attr_t"}, {"size", "size_t"}}, "int32", []string{"darwin"}}, {"pthread_attr_destroy", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32", []string{"linux", "freebsd", "netbsd"}}, {"pthread_mutex_lock", [5]Arg{{"mutex", "*pthread_mutex_t"}}, "int32", nil}, {"pthread_mutex_unlock", [5]Arg{{"mutex", "*pthread_mutex_t"}}, "int32", nil}, {"pthread_cond_broadcast", [5]Arg{{"cond", "*pthread_cond_t"}}, "int32", nil}, {"pthread_setspecific", [5]Arg{{"key", "pthread_key_t"}, {"value", "unsafe.Pointer"}}, "int32", nil}, } ) var funcs = map[string]any{ "hasPrefix": strings.HasPrefix, "imports": imports, } var GOOSes = []string{"darwin", "freebsd", "linux", "netbsd"} func imports(symbols []Symbol) string { if len(symbols) == 0 { return "" } imports := make(map[string]struct{}) imports["unsafe"] = struct{}{} for _, s := range symbols { for _, arg := range s.Args { if pkg, _, found := strings.Cut(arg.Type, "."); found { pkg = strings.TrimPrefix(pkg, "*") imports[pkg] = struct{}{} } } } if len(imports) == 1 { return `import "unsafe"` } result := make([]string, 0, len(imports)) for imp := range imports { result = append(result, imp) } sort.Strings(result) var sb strings.Builder sb.WriteString("import (") for _, imp := range result { sb.WriteString("\n\t\"") sb.WriteString(imp) sb.WriteString("\"") } sb.WriteString("\n)") return sb.String() } // filterSymbols filters the symbols by GOOS. // If goos is empty, it returns symbols that are not specific to any GOOS. func filterSymbols(symbols []Symbol, goos string) []Symbol { filtered := make([]Symbol, 0, len(symbols)) for _, s := range symbols { if (s.GOOSes == nil && goos == "") || contains(s.GOOSes, goos) { filtered = append(filtered, s) } } return filtered } func contains(slice []string, item string) bool { for _, s := range slice { if s == item { return true } } return false } func execute(t *template.Template, path string, data any) error { var buf bytes.Buffer if err := t.Execute(&buf, data); err != nil { return err } if filepath.Ext(path) == ".go" { source, err := format.Source(buf.Bytes()) if err != nil { return err } buf.Reset() buf.Write(source) } return os.WriteFile(path, buf.Bytes(), 0644) } func symbolsNames(symbols []Symbol) []string { names := make([]string, len(symbols)) for i, s := range symbols { names[i] = s.Name } return names } type dynamicImport struct { Name string Symbols []string } type fileContent struct { Tag string Symbols []Symbol DynamicImports []dynamicImport } func run() error { allSymbols := append(append([]Symbol{}, libcSymbols...), pthreadSymbols...) goosAgnosticContent := fileContent{ Symbols: filterSymbols(allSymbols, ""), Tag: "!cgo && (" + strings.Join(GOOSes, " || ") + ")", } if err := execute(templateSymbols, "zsymbols.go", goosAgnosticContent); err != nil { return err } if err := execute(templateTrampolinesStubs, "ztrampolines_stubs.s", goosAgnosticContent); err != nil { return err } goosAgnosticLibCSymbols := filterSymbols(libcSymbols, "") goosAgnosticPthreadSymbols := filterSymbols(pthreadSymbols, "") for _, goos := range GOOSes { goosSymbols := filterSymbols(allSymbols, goos) var libcSO, pthreadSO string switch goos { case "darwin": libcSO = "/usr/lib/libSystem.B.dylib" pthreadSO = "/usr/lib/libSystem.B.dylib" case "freebsd": libcSO = "libc.so.7" pthreadSO = "libpthread.so" case "linux": libcSO = "libc.so.6" pthreadSO = "libpthread.so.0" case "netbsd": libcSO = "libc.so" pthreadSO = "libpthread.so" default: return fmt.Errorf("unsupported OS: %s", goos) } located := fileContent{ DynamicImports: []dynamicImport{ {libcSO, symbolsNames(append(goosAgnosticLibCSymbols, filterSymbols(libcSymbols, goos)...))}, {pthreadSO, symbolsNames(append(goosAgnosticPthreadSymbols, filterSymbols(pthreadSymbols, goos)...))}, }, Symbols: goosSymbols, Tag: "!cgo", } if err := execute(templateSymbols, fmt.Sprintf("zsymbols_%s.go", goos), located); err != nil { return err } if len(goosSymbols) != 0 { if err := execute(templateTrampolinesStubs, fmt.Sprintf("ztrampolines_%s.s", goos), located); err != nil { return err } } } return nil } func main() { if err := run(); err != nil { log.Fatal(err) } }