gen.go raw
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 "path/filepath"
15 "sort"
16 "strings"
17 "text/template"
18 )
19
20 var 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
28 package 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
41 func {{.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 -}}
54 call5({{.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}}
80 var _{{.Name}} uint8
81 var {{.Name}}ABI0 = uintptr(unsafe.Pointer(&_{{.Name}}))
82 {{ end }}
83 `))
84
85 var 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 }}
97 TEXT _{{.Name}}(SB), NOSPLIT|NOFRAME, $0-0
98 JMP purego_{{.Name}}(SB)
99 {{ end -}}
100 `))
101
102 type Arg struct {
103 Name string
104 Type string
105 }
106
107 type Symbol struct {
108 Name string
109 Args [5]Arg
110 Return string
111 GOOSes []string // empty means all GOOSes
112 }
113
114 var (
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 }
125 pthreadSymbols = []Symbol{
126 {"pthread_attr_init", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32", nil},
127 {"pthread_create", [5]Arg{{"thread", "*pthread_t"}, {"attr", "*pthread_attr_t"}, {"start", "unsafe.Pointer"}, {"arg", "unsafe.Pointer"}}, "int32", nil},
128 {"pthread_detach", [5]Arg{{"thread", "pthread_t"}}, "int32", nil},
129 {"pthread_sigmask", [5]Arg{{"how", "sighow"}, {"ign", "*sigset_t"}, {"oset", "*sigset_t"}}, "int32", nil},
130 {"pthread_self", [5]Arg{}, "pthread_t", []string{"darwin"}},
131 {"pthread_get_stacksize_np", [5]Arg{{"thread", "pthread_t"}}, "size_t", []string{"darwin"}},
132 {"pthread_attr_getstacksize", [5]Arg{{"attr", "*pthread_attr_t"}, {"stacksize", "*size_t"}}, "int32", []string{"linux", "freebsd", "netbsd"}},
133 {"pthread_attr_setstacksize", [5]Arg{{"attr", "*pthread_attr_t"}, {"size", "size_t"}}, "int32", []string{"darwin"}},
134 {"pthread_attr_destroy", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32", []string{"linux", "freebsd", "netbsd"}},
135 {"pthread_mutex_lock", [5]Arg{{"mutex", "*pthread_mutex_t"}}, "int32", nil},
136 {"pthread_mutex_unlock", [5]Arg{{"mutex", "*pthread_mutex_t"}}, "int32", nil},
137 {"pthread_cond_broadcast", [5]Arg{{"cond", "*pthread_cond_t"}}, "int32", nil},
138 {"pthread_setspecific", [5]Arg{{"key", "pthread_key_t"}, {"value", "unsafe.Pointer"}}, "int32", nil},
139 }
140 )
141
142 var funcs = map[string]any{
143 "hasPrefix": strings.HasPrefix,
144 "imports": imports,
145 }
146
147 var GOOSes = []string{"darwin", "freebsd", "linux", "netbsd"}
148
149 func imports(symbols []Symbol) string {
150 if len(symbols) == 0 {
151 return ""
152 }
153 imports := make(map[string]struct{})
154 imports["unsafe"] = struct{}{}
155 for _, s := range symbols {
156 for _, arg := range s.Args {
157 if pkg, _, found := strings.Cut(arg.Type, "."); found {
158 pkg = strings.TrimPrefix(pkg, "*")
159 imports[pkg] = struct{}{}
160 }
161 }
162 }
163 if len(imports) == 1 {
164 return `import "unsafe"`
165 }
166 result := make([]string, 0, len(imports))
167 for imp := range imports {
168 result = append(result, imp)
169 }
170 sort.Strings(result)
171 var sb strings.Builder
172 sb.WriteString("import (")
173 for _, imp := range result {
174 sb.WriteString("\n\t\"")
175 sb.WriteString(imp)
176 sb.WriteString("\"")
177 }
178 sb.WriteString("\n)")
179 return sb.String()
180 }
181
182 // filterSymbols filters the symbols by GOOS.
183 // If goos is empty, it returns symbols that are not specific to any GOOS.
184 func filterSymbols(symbols []Symbol, goos string) []Symbol {
185 filtered := make([]Symbol, 0, len(symbols))
186 for _, s := range symbols {
187 if (s.GOOSes == nil && goos == "") || contains(s.GOOSes, goos) {
188 filtered = append(filtered, s)
189 }
190 }
191 return filtered
192 }
193
194 func contains(slice []string, item string) bool {
195 for _, s := range slice {
196 if s == item {
197 return true
198 }
199 }
200 return false
201 }
202
203 func execute(t *template.Template, path string, data any) error {
204 var buf bytes.Buffer
205 if err := t.Execute(&buf, data); err != nil {
206 return err
207 }
208 if filepath.Ext(path) == ".go" {
209 source, err := format.Source(buf.Bytes())
210 if err != nil {
211 return err
212 }
213 buf.Reset()
214 buf.Write(source)
215 }
216 return os.WriteFile(path, buf.Bytes(), 0644)
217 }
218
219 func symbolsNames(symbols []Symbol) []string {
220 names := make([]string, len(symbols))
221 for i, s := range symbols {
222 names[i] = s.Name
223 }
224 return names
225 }
226
227 type dynamicImport struct {
228 Name string
229 Symbols []string
230 }
231
232 type fileContent struct {
233 Tag string
234 Symbols []Symbol
235 DynamicImports []dynamicImport
236 }
237
238 func run() error {
239 allSymbols := append(append([]Symbol{}, libcSymbols...), pthreadSymbols...)
240 goosAgnosticContent := fileContent{
241 Symbols: filterSymbols(allSymbols, ""),
242 Tag: "!cgo && (" + strings.Join(GOOSes, " || ") + ")",
243 }
244 if err := execute(templateSymbols, "zsymbols.go", goosAgnosticContent); err != nil {
245 return err
246 }
247 if err := execute(templateTrampolinesStubs, "ztrampolines_stubs.s", goosAgnosticContent); err != nil {
248 return err
249 }
250 goosAgnosticLibCSymbols := filterSymbols(libcSymbols, "")
251 goosAgnosticPthreadSymbols := filterSymbols(pthreadSymbols, "")
252 for _, goos := range GOOSes {
253 goosSymbols := filterSymbols(allSymbols, goos)
254 var libcSO, pthreadSO string
255 switch goos {
256 case "darwin":
257 libcSO = "/usr/lib/libSystem.B.dylib"
258 pthreadSO = "/usr/lib/libSystem.B.dylib"
259 case "freebsd":
260 libcSO = "libc.so.7"
261 pthreadSO = "libpthread.so"
262 case "linux":
263 libcSO = "libc.so.6"
264 pthreadSO = "libpthread.so.0"
265 case "netbsd":
266 libcSO = "libc.so"
267 pthreadSO = "libpthread.so"
268 default:
269 return fmt.Errorf("unsupported OS: %s", goos)
270 }
271 located := fileContent{
272 DynamicImports: []dynamicImport{
273 {libcSO, symbolsNames(append(goosAgnosticLibCSymbols, filterSymbols(libcSymbols, goos)...))},
274 {pthreadSO, symbolsNames(append(goosAgnosticPthreadSymbols, filterSymbols(pthreadSymbols, goos)...))},
275 },
276 Symbols: goosSymbols,
277 Tag: "!cgo",
278 }
279 if err := execute(templateSymbols, fmt.Sprintf("zsymbols_%s.go", goos), located); err != nil {
280 return err
281 }
282 if len(goosSymbols) != 0 {
283 if err := execute(templateTrampolinesStubs, fmt.Sprintf("ztrampolines_%s.s", goos), located); err != nil {
284 return err
285 }
286 }
287 }
288 return nil
289 }
290
291 func main() {
292 if err := run(); err != nil {
293 log.Fatal(err)
294 }
295 }
296