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 }