1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4//go:build ignore
5
6package main
7
8import (
9 "bytes"
10 "fmt"
11 "go/format"
12 "log"
13 "os"
14 "path/filepath"
15 "sort"
16 "strings"
17 "text/template"
18)
19
20var templateSymbols = template.Must(template.New("symbols").Funcs(funcs).Parse(
21 `// Code generated by 'go generate' with gen.go. DO NOT EDIT.
22
23// SPDX-License-Identifier: Apache-2.0
24// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
25
26//go:build {{.Tag}}
27
28package fakecgo
29
30{{ imports .Symbols }}
31
32{{- range $di := .DynamicImports }}
33{{- range $sym := $di.Symbols }}
34//go:cgo_import_dynamic purego_{{ $sym }} {{ $sym }} "{{ $di.Name }}"
35{{- end }}
36{{- end }}
37
38{{ range .Symbols -}}
39//go:nosplit
40//go:norace
41func {{.Name}}(
42{{- range .Args -}}
43 {{- if .Name -}}
44 {{.Name}} {{.Type}},
45 {{- end -}}
46{{- end -}}) {{.Return}} {
47 {{- if .Return -}}
48 {{- if eq .Return "unsafe.Pointer" -}}
49 ret :=
50 {{- else -}}
51 return {{.Return}}(
52 {{- end -}}
53 {{- end -}}
54call5({{.Name}}ABI0,
55{{- range .Args}}
56 {{- if .Name -}}
57 {{- if hasPrefix .Type "*" -}}
58 uintptr(unsafe.Pointer({{.Name}})),
59 {{- else -}}
60 uintptr({{.Name}}),
61 {{- end -}}
62 {{- else -}}
63 0,
64 {{- end -}}
65{{- end -}}
66 ) {{/* end of call5 */}}
67{{- if .Return -}}
68 {{- if eq .Return "unsafe.Pointer"}}
69 // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer
70 return *(*unsafe.Pointer)(unsafe.Pointer(&ret))
71 {{- else -}}
72 ) {{/* end of cast */}}
73 {{- end -}}
74{{- end}}
75}
76
77{{end}}
78{{- range .Symbols }}
79//go:linkname _{{.Name}} _{{.Name}}
80var _{{.Name}} uint8
81var {{.Name}}ABI0 = uintptr(unsafe.Pointer(&_{{.Name}}))
82{{ end }}
83`))
84
85var templateTrampolinesStubs = template.Must(template.New("trampolines").Parse(
86 `// Code generated by 'go generate' with gen.go. DO NOT EDIT.
87
88// SPDX-License-Identifier: Apache-2.0
89// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
90
91//go:build {{.Tag}}
92
93#include "textflag.h"
94
95// these stubs are here because it is not possible to go:linkname directly the C functions
96{{ range .Symbols }}
97TEXT _{{.Name}}(SB), NOSPLIT|NOFRAME, $0-0
98 JMP purego_{{.Name}}(SB)
99{{ end -}}
100`))
101
102type Arg struct {
103 Name string
104 Type string
105}
106
107type Symbol struct {
108 Name string
109 Args [5]Arg
110 Return string
111 GOOSes []string // empty means all GOOSes
112}
113
114var (
115 libcSymbols = []Symbol{
116 {"malloc", [5]Arg{{"size", "uintptr"}}, "unsafe.Pointer", nil},
117 {"free", [5]Arg{{"ptr", "unsafe.Pointer"}}, "", nil},
118 {"setenv", [5]Arg{{"name", "*byte"}, {"value", "*byte"}, {"overwrite", "int32"}}, "int32", nil},
119 {"unsetenv", [5]Arg{{"name", "*byte"}}, "int32", nil},
120 {"sigfillset", [5]Arg{{"set", "*sigset_t"}}, "int32", nil},
121 {"nanosleep", [5]Arg{{"ts", "*syscall.Timespec"}, {"rem", "*syscall.Timespec"}}, "int32", nil},
122 {"abort", [5]Arg{}, "", nil},
123 {"sigaltstack", [5]Arg{{"ss", "*stack_t"}, {"old_ss", "*stack_t"}}, "int32", []string{"netbsd"}},
124 {"__errno_location", [5]Arg{}, "uintptr", []string{"linux"}},
125 {"setegid", [5]Arg{{"egid", "uint32"}}, "int32", []string{"linux"}},
126 {"seteuid", [5]Arg{{"euid", "uint32"}}, "int32", []string{"linux"}},
127 {"setgid", [5]Arg{{"gid", "uint32"}}, "int32", []string{"linux"}},
128 {"setregid", [5]Arg{{"rgid", "uint32"}, {"egid", "uint32"}}, "int32", []string{"linux"}},
129 {"setresgid", [5]Arg{{"rgid", "uint32"}, {"egid", "uint32"}, {"sgid", "uint32"}}, "int32", []string{"linux"}},
130 {"setresuid", [5]Arg{{"ruid", "uint32"}, {"euid", "uint32"}, {"suid", "uint32"}}, "int32", []string{"linux"}},
131 {"setreuid", [5]Arg{{"ruid", "uint32"}, {"euid", "uint32"}}, "int32", []string{"linux"}},
132 {"setuid", [5]Arg{{"uid", "uint32"}}, "int32", []string{"linux"}},
133 {"setgroups", [5]Arg{{"ngid", "uint32"}, {"gidset", "*uint32"}}, "int32", []string{"linux"}},
134 }
135 pthreadSymbols = []Symbol{
136 {"pthread_attr_init", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32", nil},
137 {"pthread_create", [5]Arg{{"thread", "*pthread_t"}, {"attr", "*pthread_attr_t"}, {"start", "unsafe.Pointer"}, {"arg", "unsafe.Pointer"}}, "int32", nil},
138 {"pthread_detach", [5]Arg{{"thread", "pthread_t"}}, "int32", nil},
139 {"pthread_sigmask", [5]Arg{{"how", "sighow"}, {"ign", "*sigset_t"}, {"oset", "*sigset_t"}}, "int32", nil},
140 {"pthread_self", [5]Arg{}, "pthread_t", []string{"darwin"}},
141 {"pthread_get_stacksize_np", [5]Arg{{"thread", "pthread_t"}}, "size_t", []string{"darwin"}},
142 {"pthread_attr_getstacksize", [5]Arg{{"attr", "*pthread_attr_t"}, {"stacksize", "*size_t"}}, "int32", []string{"linux", "freebsd", "netbsd"}},
143 {"pthread_attr_setstacksize", [5]Arg{{"attr", "*pthread_attr_t"}, {"size", "size_t"}}, "int32", []string{"darwin"}},
144 {"pthread_attr_destroy", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32", []string{"linux", "freebsd", "netbsd"}},
145 {"pthread_mutex_lock", [5]Arg{{"mutex", "*pthread_mutex_t"}}, "int32", nil},
146 {"pthread_mutex_unlock", [5]Arg{{"mutex", "*pthread_mutex_t"}}, "int32", nil},
147 {"pthread_cond_broadcast", [5]Arg{{"cond", "*pthread_cond_t"}}, "int32", nil},
148 {"pthread_setspecific", [5]Arg{{"key", "pthread_key_t"}, {"value", "unsafe.Pointer"}}, "int32", nil},
149 }
150)
151
152var funcs = map[string]any{
153 "hasPrefix": strings.HasPrefix,
154 "imports": imports,
155}
156
157var GOOSes = []string{"darwin", "freebsd", "linux", "netbsd"}
158
159func imports(symbols []Symbol) string {
160 if len(symbols) == 0 {
161 return ""
162 }
163 imports := make(map[string]struct{})
164 imports["unsafe"] = struct{}{}
165 for _, s := range symbols {
166 for _, arg := range s.Args {
167 if pkg, _, found := strings.Cut(arg.Type, "."); found {
168 pkg = strings.TrimPrefix(pkg, "*")
169 imports[pkg] = struct{}{}
170 }
171 }
172 }
173 if len(imports) == 1 {
174 return `import "unsafe"`
175 }
176 result := make([]string, 0, len(imports))
177 for imp := range imports {
178 result = append(result, imp)
179 }
180 sort.Strings(result)
181 var sb strings.Builder
182 sb.WriteString("import (")
183 for _, imp := range result {
184 sb.WriteString("\n\t\"")
185 sb.WriteString(imp)
186 sb.WriteString("\"")
187 }
188 sb.WriteString("\n)")
189 return sb.String()
190}
191
192// filterSymbols filters the symbols by GOOS.
193// If goos is empty, it returns symbols that are not specific to any GOOS.
194func filterSymbols(symbols []Symbol, goos string) []Symbol {
195 filtered := make([]Symbol, 0, len(symbols))
196 for _, s := range symbols {
197 if (s.GOOSes == nil && goos == "") || contains(s.GOOSes, goos) {
198 filtered = append(filtered, s)
199 }
200 }
201 return filtered
202}
203
204func contains(slice []string, item string) bool {
205 for _, s := range slice {
206 if s == item {
207 return true
208 }
209 }
210 return false
211}
212
213func execute(t *template.Template, path string, data any) error {
214 var buf bytes.Buffer
215 if err := t.Execute(&buf, data); err != nil {
216 return err
217 }
218 if filepath.Ext(path) == ".go" {
219 source, err := format.Source(buf.Bytes())
220 if err != nil {
221 return err
222 }
223 buf.Reset()
224 buf.Write(source)
225 }
226 return os.WriteFile(path, buf.Bytes(), 0644)
227}
228
229func symbolsNames(symbols []Symbol) []string {
230 names := make([]string, len(symbols))
231 for i, s := range symbols {
232 names[i] = s.Name
233 }
234 return names
235}
236
237type dynamicImport struct {
238 Name string
239 Symbols []string
240}
241
242type fileContent struct {
243 Tag string
244 Symbols []Symbol
245 DynamicImports []dynamicImport
246}
247
248func run() error {
249 allSymbols := append(append([]Symbol{}, libcSymbols...), pthreadSymbols...)
250 goosAgnosticContent := fileContent{
251 Symbols: filterSymbols(allSymbols, ""),
252 Tag: "!cgo && (" + strings.Join(GOOSes, " || ") + ")",
253 }
254 if err := execute(templateSymbols, "zsymbols.go", goosAgnosticContent); err != nil {
255 return err
256 }
257 if err := execute(templateTrampolinesStubs, "ztrampolines_stubs.s", goosAgnosticContent); err != nil {
258 return err
259 }
260 goosAgnosticLibCSymbols := filterSymbols(libcSymbols, "")
261 goosAgnosticPthreadSymbols := filterSymbols(pthreadSymbols, "")
262 for _, goos := range GOOSes {
263 goosSymbols := filterSymbols(allSymbols, goos)
264 var libcSO, pthreadSO string
265 switch goos {
266 case "darwin":
267 libcSO = "/usr/lib/libSystem.B.dylib"
268 pthreadSO = "/usr/lib/libSystem.B.dylib"
269 case "freebsd":
270 libcSO = "libc.so.7"
271 pthreadSO = "libthr.so.3"
272 case "linux":
273 libcSO = "libc.so.6"
274 pthreadSO = "libpthread.so.0"
275 case "netbsd":
276 libcSO = "libc.so"
277 pthreadSO = "libpthread.so"
278 default:
279 return fmt.Errorf("unsupported OS: %s", goos)
280 }
281 located := fileContent{
282 DynamicImports: []dynamicImport{
283 {libcSO, symbolsNames(append(goosAgnosticLibCSymbols, filterSymbols(libcSymbols, goos)...))},
284 {pthreadSO, symbolsNames(append(goosAgnosticPthreadSymbols, filterSymbols(pthreadSymbols, goos)...))},
285 },
286 Symbols: goosSymbols,
287 Tag: "!cgo",
288 }
289 if err := execute(templateSymbols, fmt.Sprintf("zsymbols_%s.go", goos), located); err != nil {
290 return err
291 }
292 if len(goosSymbols) != 0 {
293 if err := execute(templateTrampolinesStubs, fmt.Sprintf("ztrampolines_%s.s", goos), located); err != nil {
294 return err
295 }
296 }
297 }
298 return nil
299}
300
301func main() {
302 if err := run(); err != nil {
303 log.Fatal(err)
304 }
305}