blob: a497a01312d8c120b177e9215b01149510127f78 [file]
// 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
}