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 (d *decl) methodKey() methodKey { return methodKey{d.recv, d.name} }
94 func (f fact) declKey() declKey { return declKey{f.file, f.methodKey()} }
95 func (f fact) archMethodKey() archMethodKey { return archMethodKey{f.target, f.pkg, f.methodKey()} }
96
97
98
99 func (f fact) tool() string { t, _, _ := strings.Cut(f.gen, "/"); return t }
100
101
102
103
104
105 func nonSpecOp(name string) bool {
106 return name == "String" || name == "Len" || name == "ToArch" || strings.HasSuffix(name, "FromArch")
107 }
108
109
110
111 func loadOverlay(genOpts *gentools.Options) map[string][]byte {
112 if genOpts == nil {
113 return nil
114 }
115 dir := genOpts.OverlayDir()
116 if dir == "" {
117 return nil
118 }
119 root := filepath.Join(dir, "src")
120 overlay := make(map[string][]byte)
121 _ = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
122 if err != nil || d.IsDir() {
123 return nil
124 }
125 rel, err := filepath.Rel(root, path)
126 if err != nil {
127 return nil
128 }
129 slashRel := filepath.ToSlash(rel)
130 if !strings.HasPrefix(slashRel, "simd/") {
131 return nil
132 }
133 b, err := genOpts.ReadFile(rel)
134 if err != nil {
135 return nil
136 }
137 gorootPath := filepath.Join(genOpts.GOROOT, "src", filepath.FromSlash(rel))
138 overlay[gorootPath] = b
139 return nil
140 })
141 return overlay
142 }
143
144 func loadAPI(genOpts *gentools.Options, simdDir string) []*decl {
145 fset := token.NewFileSet()
146 byKey := map[declKey]*decl{}
147 overlay := loadOverlay(genOpts)
148
149 for _, bc := range buildConfigs {
150 cfg := &packages.Config{
151
152
153
154 Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedSyntax,
155 Dir: simdDir,
156 Fset: fset,
157 Env: append(os.Environ(), "GOOS="+bc.goos, "GOARCH="+bc.goarch, "GOEXPERIMENT=simd"),
158 Overlay: overlay,
159 }
160 pkgs, err := packages.Load(cfg, ".", "./archsimd")
161 if err != nil {
162 log.Fatalf("loading simd packages for %s/%s: %s", bc.goos, bc.goarch, err)
163 }
164 for _, p := range pkgs {
165 for _, e := range p.Errors {
166 log.Fatalf("loading %s for %s/%s: %s", p.PkgPath, bc.goos, bc.goarch, e)
167 }
168 for _, f := range p.Syntax {
169 file := fset.Position(f.Pos()).Filename
170 if rel, err := filepath.Rel(simdDir, file); err == nil {
171 file = filepath.ToSlash(rel)
172 }
173 gen := generatorOf(f)
174 for _, d := range f.Decls {
175 fd, ok := d.(*ast.FuncDecl)
176 if !ok || !fd.Name.IsExported() {
177 continue
178 }
179 dec := declOf(fd, p.Name, file, gen)
180 key := declKey{file, dec.methodKey()}
181 if prev, ok := byKey[key]; ok {
182 dec = prev
183 } else {
184 byKey[key] = dec
185 }
186 dec.targets = append(dec.targets, bc.name)
187 }
188 }
189 }
190 }
191
192 decls := make([]*decl, 0, len(byKey))
193 for _, d := range byKey {
194 slices.Sort(d.targets)
195 d.targets = slices.Compact(d.targets)
196 decls = append(decls, d)
197 }
198 slices.SortFunc(decls, func(a, b *decl) int {
199 return cmp.Or(cmp.Compare(a.file, b.file), cmp.Compare(a.recv, b.recv), cmp.Compare(a.name, b.name))
200 })
201 return decls
202 }
203
204 func declOf(fd *ast.FuncDecl, pkg, file, gen string) *decl {
205 d := &decl{pkg: pkg, file: file, gen: gen, name: fd.Name.Name}
206 if fd.Recv != nil && len(fd.Recv.List) == 1 {
207
208 if id, ok := fd.Recv.List[0].Type.(*ast.Ident); ok {
209 d.recv = id.Name
210 } else {
211 d.recv = types.ExprString(fd.Recv.List[0].Type)
212 }
213 }
214 d.in = fields(fd.Type.Params)
215 d.out = fields(fd.Type.Results)
216 if fd.Doc != nil {
217 var lines []string
218 for _, c := range fd.Doc.List {
219 lines = append(lines, c.Text)
220 if strings.TrimSpace(c.Text) == "//" {
221 d.hasSep = true
222 }
223 }
224 d.doc = strings.Join(lines, "\n")
225 }
226 return d
227 }
228
229
230
231 func fields(fl *ast.FieldList) []param {
232 if fl == nil {
233 return nil
234 }
235 var ps []param
236 for _, f := range fl.List {
237 typ := types.ExprString(f.Type)
238 if len(f.Names) == 0 {
239 ps = append(ps, param{"", typ})
240 continue
241 }
242 for _, n := range f.Names {
243 ps = append(ps, param{n.Name, typ})
244 }
245 }
246 return ps
247 }
248
249
250
251
252
253
254 func generatorOf(f *ast.File) string {
255 for _, cg := range f.Comments {
256 for _, c := range cg.List {
257 if !strings.Contains(c.Text, "Code generated by") {
258 continue
259 }
260 for _, tool := range []string{"simdgen", "tmplgen", "wasmgen", "midway", "refgen"} {
261 if !strings.Contains(c.Text, tool) {
262 continue
263 }
264 if args := strings.Fields(c.Text); tool == "simdgen" {
265 for i, a := range args {
266 if a == "-arch" && i+1 < len(args) {
267 return tool + "/" + args[i+1]
268 }
269 }
270 }
271 return tool
272 }
273 return "generated"
274 }
275 break
276 }
277 return "hand-written"
278 }
279
280
281
282
283
284
285
286
287
288 func readYAML(path string) *yaml.Node {
289 b, err := os.ReadFile(path)
290 if errors.Is(err, fs.ErrNotExist) {
291 return nil
292 } else if err != nil {
293 log.Fatal(err)
294 }
295 var doc yaml.Node
296 if err := yaml.Unmarshal(b, &doc); err != nil {
297 log.Fatalf("%s: %s", path, err)
298 }
299 if doc.Kind == yaml.DocumentNode && len(doc.Content) == 1 {
300 return doc.Content[0]
301 }
302 return &doc
303 }
304
305
306
307
308
309
310
311 func eachEntry(n *yaml.Node, fn func(key, val *yaml.Node)) {
312 if n == nil || n.Kind == yaml.AliasNode {
313 return
314 }
315 if n.Kind == yaml.MappingNode {
316 for i := 0; i+1 < len(n.Content); i += 2 {
317 fn(n.Content[i], n.Content[i+1])
318 }
319 }
320 for _, c := range n.Content {
321 eachEntry(c, fn)
322 }
323 }
324
325
326 func mapValue(n *yaml.Node, key string) *yaml.Node {
327 if n == nil || n.Kind != yaml.MappingNode {
328 return nil
329 }
330 for i := 0; i+1 < len(n.Content); i += 2 {
331 if n.Content[i].Value == key {
332 return n.Content[i+1]
333 }
334 }
335 return nil
336 }
337
338
339
340 func isString(n *yaml.Node) bool {
341 return n != nil && n.Kind == yaml.ScalarNode && n.ShortTag() == "!!str"
342 }
343
344
345 type categoriesInventory struct {
346 allFields map[string]int
347 exportedFields map[string]int
348 entries int
349 uniq int
350 exported int
351 compilerOnly int
352 }
353
354 func makeCategoriesInventory(genDir string) categoriesInventory {
355 r := categoriesInventory{
356 allFields: map[string]int{},
357 exportedFields: map[string]int{},
358 }
359 names := map[string]bool{}
360 paths, _ := filepath.Glob(filepath.Join(genDir, "simdgen", "ops", "*", "categories.yaml"))
361 slices.Sort(paths)
362 for _, p := range paths {
363 root := readYAML(p)
364 if root == nil || root.Kind != yaml.SequenceNode {
365 continue
366 }
367 for _, entry := range root.Content {
368 if entry.Kind != yaml.MappingNode {
369 continue
370 }
371 goName := mapValue(entry, "go")
372 if goName == nil {
373 continue
374 }
375 r.entries++
376 names[goName.Value] = true
377 isExported := goName.Value != "" && !(goName.Value[0] >= 'a' && goName.Value[0] <= 'z')
378 hasNoTypes := mapValue(entry, "noTypes") != nil
379 if !isExported || hasNoTypes {
380 r.compilerOnly++
381 }
382 eachEntry(entry, func(k, _ *yaml.Node) {
383 r.allFields[k.Value]++
384 if isExported && !hasNoTypes {
385 r.exportedFields[k.Value]++
386 }
387 })
388 }
389 }
390 for n := range names {
391 r.uniq++
392 if n != "" && !(n[0] >= 'a' && n[0] <= 'z') {
393 r.exported++
394 }
395 }
396 return r
397 }
398
399
400
401 func countMidwayComments(genDir string) (n int) {
402 eachEntry(readYAML(commentsPath(genDir)), func(_, v *yaml.Node) {
403 if isString(v) {
404 n++
405 }
406 })
407 return n
408 }
409
410
411
412
413 func commonMethods(api []*decl) []string {
414 seen := map[string]bool{}
415 var names []string
416 for _, d := range api {
417 if d.file == "simd_emulated.go" && d.recv != "" && !nonSpecOp(d.name) {
418 if !seen[d.name] {
419 seen[d.name] = true
420 names = append(names, d.name)
421 }
422 }
423 }
424 slices.Sort(names)
425 return names
426 }
427
428 func commentsPath(genDir string) string {
429 return filepath.Join(genDir, "midway", "comments.yaml")
430 }
431
View as plain text