golang.org/x/arch@v0.17.0/internal/simdgen/main.go (about)

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // simdgen is an experiment in generating Go <-> asm SIMD mappings.
     6  //
     7  // Usage: simdgen [-xedPath=path] [-q=query] input.yaml...
     8  //
     9  // If -xedPath is provided, one of the inputs is a sum of op-code definitions
    10  // generated from the Intel XED data at path.
    11  //
    12  // If input YAML files are provided, each file is read as an input value. See
    13  // [unify.Closure.UnmarshalYAML] or "go doc unify.Closure.UnmarshalYAML" for the
    14  // format of these files.
    15  //
    16  // TODO: Example definitions and values.
    17  //
    18  // The command unifies across all of the inputs and prints all possible results
    19  // of this unification.
    20  //
    21  // If the -q flag is provided, its string value is parsed as a value and treated
    22  // as another input to unification. This is intended as a way to "query" the
    23  // result, typically by narrowing it down to a small subset of results.
    24  //
    25  // Typical usage:
    26  //
    27  //	go run . -xedPath $XEDPATH *.yaml
    28  //
    29  // To see just the definitions generated from XED, run:
    30  //
    31  //	go run . -xedPath $XEDPATH
    32  //
    33  // (This works because if there's only one input, there's nothing to unify it
    34  // with, so the result is simply itself.)
    35  //
    36  // To see just the definitions for VPADDQ:
    37  //
    38  //	go run . -xedPath $XEDPATH -q '{asm: VPADDQ}'
    39  package main
    40  
    41  // Big TODOs:
    42  //
    43  // - This can produce duplicates, which can also lead to less efficient
    44  // environment merging. Add hashing and use it for deduplication. Be careful
    45  // about how this shows up in debug traces, since it could make things
    46  // confusing if we don't show it happening.
    47  //
    48  // - Do I need Closure, Value, and Domain? It feels like I should only need two
    49  // types.
    50  
    51  import (
    52  	"cmp"
    53  	"flag"
    54  	"fmt"
    55  	"log"
    56  	"maps"
    57  	"os"
    58  	"slices"
    59  	"strings"
    60  
    61  	"golang.org/x/arch/internal/unify"
    62  	"gopkg.in/yaml.v3"
    63  )
    64  
    65  var (
    66  	xedPath = flag.String("xedPath", "", "load XED datafiles from `path`")
    67  	flagQ   = flag.String("q", "", "query: read `def` as another input (skips final validation)")
    68  	flagO   = flag.String("o", "yaml", "output type: yaml, godefs")
    69  
    70  	flagDebugXED   = flag.Bool("debug-xed", false, "show XED instructions")
    71  	flagDebugUnify = flag.Bool("debug-unify", false, "print unification trace")
    72  	flagDebugHTML  = flag.String("debug-html", "", "write unification trace to `file.html`")
    73  )
    74  
    75  var yamlSubs = strings.NewReplacer(
    76  	"$xi", "[BWDQ]", // x86 integer suffixes
    77  	"$xf", "[SD]", // x86 float suffixes
    78  )
    79  
    80  func main() {
    81  	flag.Parse()
    82  
    83  	var inputs []unify.Closure
    84  
    85  	// Load XED into a defs set.
    86  	if *xedPath != "" {
    87  		xedDefs := loadXED(*xedPath)
    88  		inputs = append(inputs, unify.NewSum(xedDefs...))
    89  	}
    90  
    91  	// Load query.
    92  	if *flagQ != "" {
    93  		r := strings.NewReader(*flagQ)
    94  		var def unify.Closure
    95  		if err := def.Unmarshal(r, unify.UnmarshalOpts{Path: "<query>", StringReplacer: yamlSubs.Replace}); err != nil {
    96  			log.Fatalf("parsing -q: %s", err)
    97  		}
    98  		inputs = append(inputs, def)
    99  	}
   100  
   101  	// Load defs files.
   102  	must := make(map[*unify.Value]struct{})
   103  	for _, path := range flag.Args() {
   104  		defs, err := loadValue(path)
   105  		if err != nil {
   106  			log.Fatal(err)
   107  		}
   108  		inputs = append(inputs, defs)
   109  
   110  		if path == "go.yaml" {
   111  			// These must all be used in the final result
   112  			for def := range defs.Summands() {
   113  				must[def] = struct{}{}
   114  			}
   115  		}
   116  	}
   117  
   118  	// Prepare for unification
   119  	if *flagDebugUnify {
   120  		unify.Debug.UnifyLog = os.Stderr
   121  	}
   122  	if *flagDebugHTML != "" {
   123  		f, err := os.Create(*flagDebugHTML)
   124  		if err != nil {
   125  			log.Fatal(err)
   126  		}
   127  		unify.Debug.HTML = f
   128  		defer f.Close()
   129  	}
   130  
   131  	// Unify!
   132  	unified, err := unify.Unify(inputs...)
   133  	if err != nil {
   134  		log.Fatal(err)
   135  	}
   136  
   137  	// Print results.
   138  	switch *flagO {
   139  	case "yaml":
   140  		// Produce a result that looks like encoding a slice, but stream it.
   141  		var val1 [1]*unify.Value
   142  		for val := range unified.All() {
   143  			val1[0] = val
   144  			// We have to make a new encoder each time or it'll print a document
   145  			// separator between each object.
   146  			enc := yaml.NewEncoder(os.Stdout)
   147  			if err := enc.Encode(val1); err != nil {
   148  				log.Fatal(err)
   149  			}
   150  			enc.Close()
   151  		}
   152  	case "godefs":
   153  		writeGoDefs(os.Stdout, unified)
   154  	}
   155  
   156  	// Validate results.
   157  	//
   158  	// Don't validate if this is a command-line query because that tends to
   159  	// eliminate lots of required defs and is used in cases where maybe defs
   160  	// aren't enumerable anyway.
   161  	if *flagQ == "" && len(must) > 0 {
   162  		validate(unified, must)
   163  	}
   164  }
   165  
   166  func loadValue(path string) (unify.Closure, error) {
   167  	f, err := os.Open(path)
   168  	if err != nil {
   169  		return unify.Closure{}, err
   170  	}
   171  	defer f.Close()
   172  
   173  	var c unify.Closure
   174  	if err := c.Unmarshal(f, unify.UnmarshalOpts{StringReplacer: yamlSubs.Replace}); err != nil {
   175  		return unify.Closure{}, fmt.Errorf("%s: %v", path, err)
   176  	}
   177  	return c, nil
   178  }
   179  
   180  func validate(cl unify.Closure, required map[*unify.Value]struct{}) {
   181  	// Validate that:
   182  	// 1. All final defs are exact
   183  	// 2. All required defs are used
   184  	for def := range cl.All() {
   185  		if _, ok := def.Domain.(unify.Def); !ok {
   186  			fmt.Fprintf(os.Stderr, "%s: expected Def, got %T\n", def.PosString(), def.Domain)
   187  			continue
   188  		}
   189  
   190  		if !def.Exact() {
   191  			fmt.Fprintf(os.Stderr, "%s: def not reduced to an exact value:\n", def.PosString())
   192  			fmt.Fprintf(os.Stderr, "\t%s\n", strings.ReplaceAll(def.String(), "\n", "\n\t"))
   193  		}
   194  
   195  		for root := range def.Provenance() {
   196  			delete(required, root)
   197  		}
   198  	}
   199  	// Report unused defs
   200  	unused := slices.SortedFunc(maps.Keys(required),
   201  		func(a, b *unify.Value) int {
   202  			return cmp.Or(
   203  				cmp.Compare(a.Pos().Path, b.Pos().Path),
   204  				cmp.Compare(a.Pos().Line, b.Pos().Line),
   205  			)
   206  		})
   207  	for _, def := range unused {
   208  		// TODO: Can we say anything more actionable? This is always a problem
   209  		// with unification: if it fails, it's very hard to point a finger at
   210  		// any particular reason. We could go back and try unifying this again
   211  		// with each subset of the inputs (starting with individual inputs) to
   212  		// at least say "it doesn't unify with anything in x.yaml". That's a lot
   213  		// of work, but if we have trouble debugging unification failure it may
   214  		// be worth it.
   215  		fmt.Fprintf(os.Stderr, "%s: def required, but did not unify\n", def.PosString())
   216  	}
   217  }