1
2
3
4
5 package main
6
7 import (
8 "cmp"
9 "errors"
10 "go/ast"
11 "go/token"
12 "go/types"
13 "io/fs"
14 "log"
15 "os"
16 "path/filepath"
17 "slices"
18 "strings"
19
20 "golang.org/x/tools/go/packages"
21 "gopkg.in/yaml.v3"
22
23 "simd/archsimd/_gen/gentools"
24 )
25
26
27
28
29
30
31
32
33
34
35 var buildConfigs = []struct{ name, goos, goarch string }{
36 {"amd64", "linux", "amd64"},
37 {"arm64", "linux", "arm64"},
38 {"wasm", "js", "wasm"},
39
40
41 {"portable", "linux", "386"},
42 }
43
44
45 type decl struct {
46 pkg string
47 file string
48 gen string
49 targets []string
50 recv string
51 name string
52 in []param
53 out []param
54
55 doc string
56 hasSep bool
57 }
58
59 type param struct{ name, typ string }
60
61
62
63
64 type (
65
66 methodKey struct{ recv, name string }
67
68
69
70
71
72 archMethodKey struct {
73 target string
74 pkg string
75 methodKey
76 }
77
78
79
80 declKey struct {
81 file string
82 methodKey
83 }
84 )
85
86
87
88 type fact struct {
89 *decl
90 target string
91 }
92
93 func (k methodKey) String() string { return k.recv + "." + k.name }
94 func (k declKey) String() string { return k.file + ": " + k.methodKey.String() }
95
96 func (d *decl) methodKey() methodKey { return methodKey{d.recv, d.name} }
97 func (f fact) declKey() declKey { return declKey{f.file, f.methodKey()} }
98 func (f fact) archMethodKey() archMethodKey { return archMethodKey{f.target, f.pkg, f.methodKey()} }
99
100
101
102 func (f fact) tool() string { t, _, _ := strings.Cut(f.gen, "/"); return t }
103
104
105
106
107
108 func nonSpecOp(name string) bool {
109 return name == "ToArch" || strings.HasSuffix(name, "FromArch")
110 }
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125 func nonSpecDecl(d *decl) bool {
126 switch {
127 case nonSpecOp(d.name):
128 return true
129 case d.recv == "X86Features" || d.recv == "ARM64Features":
130 return true
131 case d.recv == "" && nonSpecFuncs[d.name]:
132 return true
133 case d.recv != "" && d.name == "Merge":
134 return true
135 }
136 return false
137 }
138
139 var nonSpecFuncs = map[string]bool{
140 "Emulated": true,
141 "VectorBitSize": true,
142 "HasHardwareCarrylessMultiply": true,
143 "ClearAVXUpperBits": true,
144 }
145
146
147
148 func loadOverlay(genOpts *gentools.Options) map[string][]byte {
149 if genOpts == nil {
150 return nil
151 }
152 dir := genOpts.OverlayDir()
153 if dir == "" {
154 return nil
155 }
156 root := filepath.Join(dir, "src")
157 overlay := make(map[string][]byte)
158 _ = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
159 if err != nil || d.IsDir() {
160 return nil
161 }
162 rel, err := filepath.Rel(root, path)
163 if err != nil {
164 return nil
165 }
166 slashRel := filepath.ToSlash(rel)
167 if !strings.HasPrefix(slashRel, "simd/") {
168 return nil
169 }
170 b, err := genOpts.ReadFile(rel)
171 if err != nil {
172 return nil
173 }
174 gorootPath := filepath.Join(genOpts.GOROOT, "src", filepath.FromSlash(rel))
175 overlay[gorootPath] = b
176 return nil
177 })
178 return overlay
179 }
180
181 func loadAPI(genOpts *gentools.Options, simdDir string) []*decl {
182 fset := token.NewFileSet()
183 byKey := map[declKey]*decl{}
184 overlay := loadOverlay(genOpts)
185
186 for _, bc := range buildConfigs {
187 cfg := &packages.Config{
188
189
190
191 Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedSyntax,
192 Dir: simdDir,
193 Fset: fset,
194 Env: append(os.Environ(), "GOOS="+bc.goos, "GOARCH="+bc.goarch, "GOEXPERIMENT=simd"),
195 Overlay: overlay,
196 }
197 pkgs, err := packages.Load(cfg, ".", "./archsimd")
198 if err != nil {
199 log.Fatalf("loading simd packages for %s/%s: %s", bc.goos, bc.goarch, err)
200 }
201 for _, p := range pkgs {
202 for _, e := range p.Errors {
203 log.Fatalf("loading %s for %s/%s: %s", p.PkgPath, bc.goos, bc.goarch, e)
204 }
205 for _, f := range p.Syntax {
206 file := fset.Position(f.Pos()).Filename
207 if rel, err := filepath.Rel(simdDir, file); err == nil {
208 file = filepath.ToSlash(rel)
209 }
210 gen := generatorOf(f)
211 for _, d := range f.Decls {
212 fd, ok := d.(*ast.FuncDecl)
213 if !ok || !fd.Name.IsExported() {
214 continue
215 }
216 dec := declOf(fd, p.Name, file, gen)
217 key := declKey{file, dec.methodKey()}
218 if prev, ok := byKey[key]; ok {
219 dec = prev
220 } else {
221 byKey[key] = dec
222 }
223 dec.targets = append(dec.targets, bc.name)
224 }
225 }
226 }
227 }
228
229 decls := make([]*decl, 0, len(byKey))
230 for _, d := range byKey {
231 slices.Sort(d.targets)
232 d.targets = slices.Compact(d.targets)
233 decls = append(decls, d)
234 }
235 slices.SortFunc(decls, func(a, b *decl) int {
236 return cmp.Or(cmp.Compare(a.file, b.file), cmp.Compare(a.recv, b.recv), cmp.Compare(a.name, b.name))
237 })
238 return decls
239 }
240
241 func declOf(fd *ast.FuncDecl, pkg, file, gen string) *decl {
242 d := &decl{pkg: pkg, file: file, gen: gen, name: fd.Name.Name}
243 if fd.Recv != nil && len(fd.Recv.List) == 1 {
244
245 if id, ok := fd.Recv.List[0].Type.(*ast.Ident); ok {
246 d.recv = id.Name
247 } else {
248 d.recv = types.ExprString(fd.Recv.List[0].Type)
249 }
250 }
251 d.in = fields(fd.Type.Params)
252 d.out = fields(fd.Type.Results)
253 if fd.Doc != nil {
254 var lines []string
255 for _, c := range fd.Doc.List {
256 lines = append(lines, c.Text)
257 if strings.TrimSpace(c.Text) == "//" {
258 d.hasSep = true
259 }
260 }
261 d.doc = strings.Join(lines, "\n")
262 }
263 return d
264 }
265
266
267
268 func fields(fl *ast.FieldList) []param {
269 if fl == nil {
270 return nil
271 }
272 var ps []param
273 for _, f := range fl.List {
274 typ := types.ExprString(f.Type)
275 if len(f.Names) == 0 {
276 ps = append(ps, param{"", typ})
277 continue
278 }
279 for _, n := range f.Names {
280 ps = append(ps, param{n.Name, typ})
281 }
282 }
283 return ps
284 }
285
286
287
288
289
290
291 func generatorOf(f *ast.File) string {
292 for _, cg := range f.Comments {
293 for _, c := range cg.List {
294 if !strings.Contains(c.Text, "Code generated by") {
295 continue
296 }
297 for _, tool := range []string{"simdgen", "tmplgen", "wasmgen", "midway", "refgen"} {
298 if !strings.Contains(c.Text, tool) {
299 continue
300 }
301 if args := strings.Fields(c.Text); tool == "simdgen" {
302 for i, a := range args {
303 if a == "-arch" && i+1 < len(args) {
304 return tool + "/" + args[i+1]
305 }
306 }
307 }
308 return tool
309 }
310 return "generated"
311 }
312 break
313 }
314 return "hand-written"
315 }
316
317
318
319
320
321
322
323
324
325 func readYAML(path string) *yaml.Node {
326 b, err := os.ReadFile(path)
327 if errors.Is(err, fs.ErrNotExist) {
328 return nil
329 } else if err != nil {
330 log.Fatal(err)
331 }
332 var doc yaml.Node
333 if err := yaml.Unmarshal(b, &doc); err != nil {
334 log.Fatalf("%s: %s", path, err)
335 }
336 if doc.Kind == yaml.DocumentNode && len(doc.Content) == 1 {
337 return doc.Content[0]
338 }
339 return &doc
340 }
341
342
343
344
345
346
347
348 func eachEntry(n *yaml.Node, fn func(key, val *yaml.Node)) {
349 if n == nil || n.Kind == yaml.AliasNode {
350 return
351 }
352 if n.Kind == yaml.MappingNode {
353 for i := 0; i+1 < len(n.Content); i += 2 {
354 fn(n.Content[i], n.Content[i+1])
355 }
356 }
357 for _, c := range n.Content {
358 eachEntry(c, fn)
359 }
360 }
361
362
363 func mapValue(n *yaml.Node, key string) *yaml.Node {
364 if n == nil || n.Kind != yaml.MappingNode {
365 return nil
366 }
367 for i := 0; i+1 < len(n.Content); i += 2 {
368 if n.Content[i].Value == key {
369 return n.Content[i+1]
370 }
371 }
372 return nil
373 }
374
375
376
377 func isString(n *yaml.Node) bool {
378 return n != nil && n.Kind == yaml.ScalarNode && n.ShortTag() == "!!str"
379 }
380
381
382 type categoriesInventory struct {
383 allFields map[string]int
384 exportedFields map[string]int
385 entries int
386 uniq int
387 exported int
388 compilerOnly int
389 }
390
391 func makeCategoriesInventory(genDir string) categoriesInventory {
392 r := categoriesInventory{
393 allFields: map[string]int{},
394 exportedFields: map[string]int{},
395 }
396 names := map[string]bool{}
397 paths, _ := filepath.Glob(filepath.Join(genDir, "simdgen", "ops", "*", "categories.yaml"))
398 slices.Sort(paths)
399 for _, p := range paths {
400 root := readYAML(p)
401 if root == nil || root.Kind != yaml.SequenceNode {
402 continue
403 }
404 for _, entry := range root.Content {
405 if entry.Kind != yaml.MappingNode {
406 continue
407 }
408 goName := mapValue(entry, "go")
409 if goName == nil {
410 continue
411 }
412 r.entries++
413 names[goName.Value] = true
414 isExported := goName.Value != "" && !(goName.Value[0] >= 'a' && goName.Value[0] <= 'z')
415 hasNoTypes := mapValue(entry, "noTypes") != nil
416 if !isExported || hasNoTypes {
417 r.compilerOnly++
418 }
419 eachEntry(entry, func(k, _ *yaml.Node) {
420 r.allFields[k.Value]++
421 if isExported && !hasNoTypes {
422 r.exportedFields[k.Value]++
423 }
424 })
425 }
426 }
427 for n := range names {
428 r.uniq++
429 if n != "" && !(n[0] >= 'a' && n[0] <= 'z') {
430 r.exported++
431 }
432 }
433 return r
434 }
435
436
437
438 func countMidwayComments(genDir string) (n int) {
439 eachEntry(readYAML(commentsPath(genDir)), func(_, v *yaml.Node) {
440 if isString(v) {
441 n++
442 }
443 })
444 return n
445 }
446
447
448
449
450 func commonMethods(api []*decl) []string {
451 seen := map[string]bool{}
452 var names []string
453 for _, d := range api {
454 if d.file == "simd_emulated.go" && d.recv != "" && !nonSpecDecl(d) {
455 if !seen[d.name] {
456 seen[d.name] = true
457 names = append(names, d.name)
458 }
459 }
460 }
461 slices.Sort(names)
462 return names
463 }
464
465 func commentsPath(genDir string) string {
466 return filepath.Join(genDir, "midway", "comments.yaml")
467 }
468
View as plain text