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