blob: 7e3433e43b6e4c1e0429d31403511d95de07b2ff [file]
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Run all SIMD-related code generators.
package main
import (
"flag"
"fmt"
"maps"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"simd/archsimd/_gen/gentools"
"simd/archsimd/_gen/sgutil"
)
var (
flagTools = flagVar("tools", ToolSet{"tmplgen": true, "simdgen": true, "wasmgen": true, "midway": true, "refgen": true}, "comma-separated list of tools (or +/-tools) to run")
flagN = flag.Bool("n", false, "dry run")
flagXedPath = sgutil.FlagXEDPath(".")
flagArm64Path = sgutil.FlagARM64Path(".")
genFlags = gentools.RegisterFlags(nil)
)
// ToolSet is a [flag.Value] that accepts a comma-separated list of tool names.
// It rejects any tool names that aren't in the map. A list like "a,c" sets only
// "a" and "c" to true and all other tools to false. Alternatively, the list
// items may each start with + or -, which enables or disables (respectively)
// only the named tools.
type ToolSet map[string]bool
func (s ToolSet) String() string {
var have []string
for k, v := range s {
if v {
have = append(have, k)
}
}
slices.Sort(have)
return strings.Join(have, ",")
}
func (s ToolSet) Set(list string) error {
list = strings.TrimSpace(list)
isDelta := len(list) > 0 && (list[0] == '+' || list[0] == '-')
if !isDelta {
for k := range s {
s[k] = false
}
}
for item := range strings.SplitSeq(list, ",") {
item = strings.TrimSpace(item)
if item == "" {
continue
}
var itemDelta bool
val := true
switch item[0] {
case '+':
val, itemDelta, item = true, true, item[1:]
case '-':
val, itemDelta, item = false, true, item[1:]
}
if isDelta != itemDelta {
return fmt.Errorf("tool list %q mixes +/- and regular items", list)
}
if _, ok := s[item]; !ok {
all := slices.Sorted(maps.Keys(s))
return fmt.Errorf("unknown tool %s; valid tools are: %s", item, strings.Join(all, ", "))
}
s[item] = val
}
return nil
}
func flagVar[T flag.Value](name string, value T, usage string) T {
flag.Var(value, name, usage)
return value
}
func main() {
flag.Parse()
if flag.NArg() > 0 {
flag.Usage()
os.Exit(1)
}
if genFlags.GOROOT == "" {
fmt.Fprintln(os.Stderr, "failed to find Go dev tree root from current directory")
os.Exit(1)
}
// If we need data paths, resolve them before we start running any tools so
// we can report errors immediately.
var xedPath, armPath string
if flagTools["simdgen"] {
var err error
resolveError := false
xedPath, err = sgutil.ResolveXEDPath(flagXedPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
resolveError = true
}
armPath, err = sgutil.ResolveARM64Path(flagArm64Path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
resolveError = true
}
if resolveError {
os.Exit(1)
}
}
if flagTools["simdgen"] {
fmt.Fprintln(os.Stderr, "# This may take a few minutes...")
}
var files gentools.Files
defer files.FlushOrExit()
if flagTools["tmplgen"] {
doGen("tmplgen", &files)
}
if flagTools["wasmgen"] || flagTools["simdgen"] {
ssaGenPath := prettyPath(".", genFlags.OutputPath("cmd/compile/internal/ssa/_gen"))
// If there is garbage in ssa/_gen/simdgenericOps.go, it can affect the merge in simdgen/wasmgen.
if genFlags.Write {
removeSimdGenericOps(ssaGenPath)
}
if flagTools["wasmgen"] {
doGen("wasmgen", &files)
}
if flagTools["simdgen"] {
doSimdgen(xedPath, armPath, &files)
}
// ssaGen doesn't use gentools, so we can only run if it we're writing
// to the Go source tree, and we have to flush any file changes before
// we do.
if genFlags.WritingToInput() {
files.FlushOrExit()
ssaGen(ssaGenPath)
} else {
fmt.Fprintf(os.Stderr, "# skipping %s gen because we're not writing to -goroot\n", ssaGenPath)
}
}
if flagTools["refgen"] {
doGen("cmd/refgen", &files)
}
if flagTools["midway"] {
doGen("midway", &files)
}
}
func removeSimdGenericOps(ssaGenPath string) {
if *flagN {
// Dry run: keep the file. Without this, `go generate -n` (or any run that
// stops before regenerating — e.g. an unset XEDPATH failing the amd64
// step even when the arm64 path is valid) still deletes simdgenericOps.go
// here, and a later single -arch run recreates it with only that arch's
// ops, silently dropping the others.
return
}
ssaSimdGenericOps := filepath.Join(ssaGenPath, "simdgenericOps.go")
if _, err := os.Stat(ssaSimdGenericOps); err == nil {
if err = os.Remove(ssaSimdGenericOps); err != nil {
fmt.Fprintf(os.Stderr, "Failed to delete %s before regenerating it, %v\n", ssaSimdGenericOps, err)
os.Exit(1)
}
}
}
func ssaGen(ssaGenPath string) {
// simdgen produces SSA rule files, so update the SSA files
goRun("-C", ssaGenPath, ".")
fmt.Fprintln(os.Stderr, "# Compiler changed. Consider running \"go install cmd/compile\"")
}
func doGen(tool string, files *gentools.Files) {
flags := append([]string{"-C", tool, "."}, files.ExecFlags()...)
goRun(flags...)
}
func doSimdgen(xedPath, armPath string, files *gentools.Files) {
armArgs := append([]string{"-C", "simdgen", ".", "-o", "godefs", "-arch", "arm64", "-arm64Path", prettyPath("./simdgen", armPath)}, files.ExecFlags()...)
armArgs = append(armArgs, "go_arm64.yaml", "types.yaml", "categories.yaml")
goRun(armArgs...)
// Regenerate the ARM64 SVE files. SVE reads the same A64 ISA data as NEON,
// but is a separate target with its own generated files.
sveArgs := append([]string{"-C", "simdgen", ".", "-o", "godefs", "-arch", "sve", "-arm64Path", prettyPath("./simdgen", armPath)}, files.ExecFlags()...)
sveArgs = append(sveArgs, "go_sve.yaml", "types.yaml", "categories.yaml")
goRun(sveArgs...)
// Regenerate the XED-derived SIMD files
amdArgs := append([]string{"-C", "simdgen", ".", "-o", "godefs", "-arch", "amd64", "-xedPath", prettyPath("./simdgen", xedPath)}, files.ExecFlags()...)
amdArgs = append(amdArgs, "go_amd64.yaml", "types.yaml", "categories.yaml")
goRun(amdArgs...)
}
func goRun(args ...string) {
exe := "go" // Use go on the path, not GOROOT. GOROOT could be broken
cmd := exec.Command(exe, append([]string{"run"}, args...)...)
run(cmd)
}
func run(cmd *exec.Cmd) {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Fprintf(os.Stderr, "%s\n", cmdString(cmd))
if *flagN {
return
}
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "%s failed: %s\n", cmd, err)
os.Exit(1)
}
}
func prettyPath(base, path string) string {
base, err := filepath.Abs(base)
if err != nil {
return path
}
p, err := filepath.Rel(base, path)
if err != nil {
return path
}
return p
}
func cmdString(cmd *exec.Cmd) string {
// TODO: Shell quoting?
// TODO: Environment.
var buf strings.Builder
cmdPath, err := exec.LookPath(filepath.Base(cmd.Path))
if err == nil && cmdPath == cmd.Path {
cmdPath = filepath.Base(cmdPath)
} else {
cmdPath = prettyPath(".", cmd.Path)
}
buf.WriteString(cmdPath)
for _, arg := range cmd.Args[1:] {
buf.WriteByte(' ')
buf.WriteString(arg)
}
return buf.String()
}