Files
garble/scripts/gen_go_std_tables.go
Daniel Martí ad47efbe72 actually collect intrinsics from Go 1.24
The diffstat for go_std_tables.go shows that we were missing
more than two dozen new intrinsic functions from Go 1.24,
which could lead to the intrinsification done by the toolchain
to no longer work and leave programs with slower generic functions.
2025-03-29 02:09:34 +01:00

248 lines
6.5 KiB
Go

// Copyright (c) 2024, The Garble Authors.
// See LICENSE for licensing information.
//go:build ignore
// This is a program used with `go generate`, so it handles errors via panic.
package main
import (
"bytes"
"cmp"
"fmt"
"go/format"
"go/version"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"
"text/template"
)
var goVersions = []string{"go1.23.7", "go1.24.1"}
var tmplTables = template.Must(template.New("").Parse(`
// Code generated by scripts/gen_go_std_tables.go; DO NOT EDIT.
// Generated from Go versions {{ .GoVersions }}.
package main
var runtimeAndDeps = map[string]bool{
{{- range $path := .RuntimeAndDeps }}
"{{ $path.String }}": true, // {{ $path.GoVersionLang }}
{{- end }}
}
var runtimeLinknamed = []string{
{{- range $path := .RuntimeLinknamed }}
"{{ $path.String }}", // {{ $path.GoVersionLang }}
{{- end }}
// The net package linknames to the runtime, not the other way around.
// TODO: support this automatically via our script.
"net",
}
var compilerIntrinsics = map[string]map[string]bool{
{{- range $intr := .CompilerIntrinsics }}
"{{ $intr.Path }}": {
{{- range $name := $intr.Names }}
"{{ $name.String }}": true, // {{ $name.GoVersionLang }}
{{- end }}
},
{{- end }}
}
var reflectSkipPkg = map[string]bool{
"fmt": true,
}
`[1:]))
type tmplData struct {
GoVersions []string
RuntimeAndDeps []versionedString
RuntimeLinknamed []versionedString
CompilerIntrinsics []tmplIntrinsic
}
type tmplIntrinsic struct {
Path string
Names []versionedString
}
func (t tmplIntrinsic) Compare(t2 tmplIntrinsic) int {
return cmp.Compare(t.Path, t2.Path)
}
func (t tmplIntrinsic) Equal(t2 tmplIntrinsic) bool {
return t.Compare(t2) == 0
}
type versionedString struct {
String string
GoVersionLang string
}
func (v versionedString) Compare(v2 versionedString) int {
if c := cmp.Compare(v.String, v2.String); c != 0 {
return c
}
// Negated so that newer Go versions go first.
return -cmp.Compare(v.GoVersionLang, v2.GoVersionLang)
}
func (v versionedString) Equal(v2 versionedString) bool {
// Note that we do equality based on String alone,
// because we only need one String entry with the latest version.
return v.String == v2.String
}
func cmdGo(goVersion string, args ...string) versionedString {
cmd := exec.Command("go", args...)
cmd.Env = append(cmd.Environ(), "GOTOOLCHAIN="+goVersion)
out, err := cmd.Output()
if err != nil {
panic(err)
}
return versionedString{
String: string(bytes.TrimSpace(out)), // no trailing newline
GoVersionLang: version.Lang(goVersion),
}
}
func readFile(path string) string {
data, err := os.ReadFile(path)
if err != nil {
panic(err)
}
return string(data)
}
func lines(vs versionedString) []versionedString {
split := strings.Split(vs.String, "\n")
var versioned []versionedString
for _, s := range split {
versioned = append(versioned, versionedString{
String: s,
GoVersionLang: vs.GoVersionLang,
})
}
return versioned
}
var rxLinkname = regexp.MustCompile(`^//go:linkname .* ([^.]*)\.[^.]*$`)
var rxIntrinsic = regexp.MustCompile(`\b(addF|alias)\("([^"]*)", "([^"]*)",`)
func main() {
var runtimeAndDeps []versionedString
for _, goVersion := range goVersions {
runtimeAndDeps = append(runtimeAndDeps, lines(cmdGo(goVersion, "list", "-deps", "runtime"))...)
}
slices.SortFunc(runtimeAndDeps, versionedString.Compare)
runtimeAndDeps = slices.CompactFunc(runtimeAndDeps, versionedString.Equal)
var goroots []versionedString
for _, goVersion := range goVersions {
goroots = append(goroots, cmdGo(goVersion, "env", "GOROOT"))
}
// All packages that the runtime linknames to, except runtime and its dependencies.
// This resulting list is what we need to "go list" when obfuscating the runtime,
// as they are the packages that we may be missing.
var runtimeLinknamed []versionedString
for _, goroot := range goroots {
runtimeGoFiles, err := filepath.Glob(filepath.Join(goroot.String, "src", "runtime", "*.go"))
if err != nil {
panic(err)
}
for _, goFile := range runtimeGoFiles {
for _, line := range strings.Split(readFile(goFile), "\n") {
m := rxLinkname.FindStringSubmatch(line)
if m == nil {
continue
}
path := m[1]
switch path {
case "main", "runtime/metrics_test":
continue
}
runtimeLinknamed = append(runtimeLinknamed, versionedString{
String: path,
GoVersionLang: goroot.GoVersionLang,
})
}
}
}
slices.SortFunc(runtimeLinknamed, versionedString.Compare)
runtimeLinknamed = slices.CompactFunc(runtimeLinknamed, versionedString.Equal)
runtimeLinknamed = slices.DeleteFunc(runtimeLinknamed, func(path versionedString) bool {
for _, prev := range runtimeAndDeps {
if prev.String == path.String {
return true
}
}
return false
})
compilerIntrinsicsIndexByPath := make(map[string]int)
var compilerIntrinsics []tmplIntrinsic
for _, goroot := range goroots {
// Go 1.24 moved the "alias" intrinsic calls from ssa.go to intrinsics.go.
name := "ssa.go"
if goroot.GoVersionLang == "go1.24" {
name = "intrinsics.go"
}
for _, line := range strings.Split(readFile(filepath.Join(
goroot.String, "src", "cmd", "compile", "internal", "ssagen", name,
)), "\n") {
m := rxIntrinsic.FindStringSubmatch(line)
if m == nil {
continue
}
path, name := m[2], m[3]
vs := versionedString{
String: name,
GoVersionLang: goroot.GoVersionLang,
}
if i := compilerIntrinsicsIndexByPath[path]; i == 0 {
compilerIntrinsicsIndexByPath[path] = len(compilerIntrinsics)
compilerIntrinsics = append(compilerIntrinsics, tmplIntrinsic{
Path: path,
Names: []versionedString{vs},
})
} else {
compilerIntrinsics[i].Names = append(compilerIntrinsics[i].Names, vs)
}
}
}
slices.SortFunc(compilerIntrinsics, tmplIntrinsic.Compare)
compilerIntrinsics = slices.CompactFunc(compilerIntrinsics, tmplIntrinsic.Equal)
for path := range compilerIntrinsics {
intr := &compilerIntrinsics[path]
slices.SortFunc(intr.Names, versionedString.Compare)
intr.Names = slices.CompactFunc(intr.Names, versionedString.Equal)
}
var buf bytes.Buffer
if err := tmplTables.Execute(&buf, tmplData{
GoVersions: goVersions,
RuntimeAndDeps: runtimeAndDeps,
RuntimeLinknamed: runtimeLinknamed,
CompilerIntrinsics: compilerIntrinsics,
}); err != nil {
panic(err)
}
out := buf.Bytes()
formatted, err := format.Source(out)
if err != nil {
fmt.Println(string(out))
panic(err)
}
if err := os.WriteFile("go_std_tables.go", formatted, 0o666); err != nil {
panic(err)
}
}