| // Copyright 2026 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. | |
| package main | |
| import ( | |
| "simd/archsimd/_gen/specgen" | |
| "slices" | |
| ) | |
| // A result is one figure: how many of how many. | |
| type result struct{ n, d int } | |
| func (r result) miss() int { return r.d - r.n } | |
| // A tally counts distinct keys at one grain, and how many of them are done. A | |
| // key counts as done if any row says so, which is well defined because every | |
| // predicate here is a function of the key rather than of the row. | |
| type tally[K comparable] struct { | |
| all map[K]bool | |
| hit map[K]bool | |
| } | |
| func (t *tally[K]) add(k K, done bool) { | |
| if t.all == nil { | |
| t.all, t.hit = map[K]bool{}, map[K]bool{} | |
| } | |
| t.all[k] = true | |
| if done { | |
| t.hit[k] = true | |
| } | |
| } | |
| func (t *tally[K]) result() result { return result{len(t.hit), len(t.all)} } | |
| // has reports whether the key was counted at all; done, whether it is done. | |
| func (t *tally[K]) has(k K) bool { return t.all[k] } | |
| func (t *tally[K]) done(k K) bool { return t.hit[k] } | |
| // A pivot is a tally split by the value of some dimension: the report's | |
| // recurring shape is "for each X, what fraction of Y is done". | |
| type pivot[K comparable] struct { | |
| by map[string]*tally[K] | |
| } | |
| func (p *pivot[K]) add(dim string, k K, done bool) { | |
| if p.by == nil { | |
| p.by = map[string]*tally[K]{} | |
| } | |
| t := p.by[dim] | |
| if t == nil { | |
| t = new(tally[K]) | |
| p.by[dim] = t | |
| } | |
| t.add(k, done) | |
| } | |
| // fold merges the tally for one dimension value into another, for reporting a | |
| // roll-up (simdgen's per-target runs) alongside its parts. | |
| func (p *pivot[K]) fold(from, to string) { | |
| src := p.by[from] | |
| for k := range src.all { | |
| p.add(to, k, src.done(k)) | |
| } | |
| } | |
| func (p *pivot[K]) dims() []string { return sortedKeys(p.by) } | |
| func (p *pivot[K]) result(dim string) result { | |
| if t := p.by[dim]; t != nil { | |
| return t.result() | |
| } | |
| return result{} | |
| } | |
| // tallyBy is the common case: one pass over the fact table, keyed at a grain, | |
| // scored by a predicate. | |
| func tallyBy[K comparable](facts []fact, key func(fact) K, done func(fact) bool) *tally[K] { | |
| t := new(tally[K]) | |
| for _, f := range facts { | |
| t.add(key(f), done(f)) | |
| } | |
| return t | |
| } | |
| // pivotBy is tallyBy, split by a dimension. | |
| func pivotBy[K comparable](facts []fact, dim func(fact) string, key func(fact) K, done func(fact) bool) *pivot[K] { | |
| p := new(pivot[K]) | |
| for _, f := range facts { | |
| p.add(dim(f), key(f), done(f)) | |
| } | |
| return p | |
| } | |
| // countBy counts distinct keys among the rows, for figures that are a plain | |
| // count rather than a fraction. | |
| func countBy[K comparable](facts []fact, key func(fact) K) int { | |
| seen := map[K]bool{} | |
| for _, f := range facts { | |
| seen[key(f)] = true | |
| } | |
| return len(seen) | |
| } | |
| func groupBy[T any, K comparable](s []T, key func(T) K) map[K][]T { | |
| m := map[K][]T{} | |
| for _, v := range s { | |
| m[key(v)] = append(m[key(v)], v) | |
| } | |
| return m | |
| } | |
| func sortedKeys[V any](m map[string]V) []string { | |
| ks := make([]string, 0, len(m)) | |
| for k := range m { | |
| ks = append(ks, k) | |
| } | |
| slices.Sort(ks) | |
| return ks | |
| } | |
| // equalBy reports whether two parameter lists agree under f. Types and names | |
| // are compared with the same code because the only difference is the field. | |
| func equalBy(a, b []param, f func(param) string) bool { | |
| if len(a) != len(b) { | |
| return false | |
| } | |
| for i := range a { | |
| if f(a[i]) != f(b[i]) { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| func paramType(p param) string { return p.typ } | |
| // specParams converts a spec signature's arguments to the same representation | |
| // as the API's, so one comparison serves both directions. | |
| func specParams(as []specgen.Arg) []param { | |
| ps := make([]param, len(as)) | |
| for i, a := range as { | |
| ps[i] = param{a.Name, a.Type.String()} | |
| } | |
| return ps | |
| } | |
| // named reports whether p has a name a doc or body could refer to. As in | |
| // specdoc.Fill, the blank identifier does not count. | |
| func named(p param) bool { return p.name != "" && p.name != "_" } | |
| // namesClash reports whether any argument named in both api and spec has | |
| // different names on the two sides. specdoc.Fill reports these as errors. Lists | |
| // of different lengths are a type mismatch, not a name one. | |
| func namesClash(api, spec []param) bool { | |
| if len(api) != len(spec) { | |
| return false | |
| } | |
| for i := range api { | |
| if named(api[i]) && named(spec[i]) && api[i].name != spec[i].name { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // namesMissing reports whether any argument spec names is unnamed in api. | |
| // specdoc.Fill fills these in from spec. | |
| func namesMissing(api, spec []param) bool { | |
| if len(api) != len(spec) { | |
| return false | |
| } | |
| for i := range api { | |
| if named(spec[i]) && !named(api[i]) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // namesConsistent reports whether argument lists agree on names, where an | |
| // unnamed argument agrees with any name. Lists of different lengths disagree. | |
| func namesConsistent(lists [][]param) bool { | |
| for _, l := range lists[1:] { | |
| if len(l) != len(lists[0]) { | |
| return false | |
| } | |
| } | |
| for i := range lists[0] { | |
| var name string | |
| for _, l := range lists { | |
| if !named(l[i]) { | |
| continue | |
| } | |
| if name == "" { | |
| name = l[i].name | |
| } else if l[i].name != name { | |
| return false | |
| } | |
| } | |
| } | |
| return true | |
| } |