Austin Clements would like David Chase and Junyang Shao to review this change.
[dev.simd] simd/archsimd/_gen/specgen: spec to SIMD API generator
This package processes the SIMD spec package to produce the SIMD API
description.
The basic mechanism is that, for each exported function in the spec
function, this iterates through every combination of type parameter
values, keeps any combinations that satisfy additional constraint
expressions, and plugs in the concrete types that worked.
diff --git a/src/simd/archsimd/_gen/go.mod b/src/simd/archsimd/_gen/go.mod
index c8c570d..75b86e5 100644
--- a/src/simd/archsimd/_gen/go.mod
+++ b/src/simd/archsimd/_gen/go.mod
@@ -4,5 +4,11 @@
require (
golang.org/x/arch v0.26.0
+ golang.org/x/tools v0.48.0
gopkg.in/yaml.v3 v3.0.1
)
+
+require (
+ golang.org/x/mod v0.38.0 // indirect
+ golang.org/x/sync v0.22.0 // indirect
+)
diff --git a/src/simd/archsimd/_gen/go.sum b/src/simd/archsimd/_gen/go.sum
index 245d4a6..3f49a97 100644
--- a/src/simd/archsimd/_gen/go.sum
+++ b/src/simd/archsimd/_gen/go.sum
@@ -1,5 +1,13 @@
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/arch v0.26.0 h1:jZ6dpec5haP/fUv1kLCbuJy6dnRrfX6iVK08lZBFpk4=
golang.org/x/arch v0.26.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
+golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
+golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
+golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
+golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
+golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/src/simd/archsimd/_gen/specgen/api.go b/src/simd/archsimd/_gen/specgen/api.go
new file mode 100644
index 0000000..e67e928
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/api.go
@@ -0,0 +1,79 @@
+// 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 specgen
+
+import (
+ "_gen/specgen/specexpr"
+ "fmt"
+ "strings"
+)
+
+// Func represents a function or method in the SIMD API.
+type Func struct {
+ Name string
+
+ // Doc is the function documentation, without any leading comment markers
+ Doc string
+
+ // Recv, if non-zero, is the shape of the receiver. The name of the receiver
+ // is always "x".
+ Recv Arg
+
+ In []Arg
+ Out []Arg
+}
+
+type Arg struct {
+ Name string
+ Type specexpr.Type
+}
+
+func (f *Func) Signature() string {
+ var buf strings.Builder
+ buf.WriteString("func ")
+ argList := func(args []Arg, canShort bool) {
+ if canShort {
+ if len(args) == 0 {
+ return
+ } else if len(args) == 1 && args[0].Name == "" {
+ buf.WriteString(args[0].Type.String())
+ return
+ }
+ }
+ buf.WriteByte('(')
+ for i, arg := range args {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ if arg.Name == "" {
+ panic("empty parameter/result name")
+ }
+ fmt.Fprintf(&buf, "%s %s", arg.Name, arg.Type)
+ }
+ buf.WriteByte(')')
+ }
+ if f.Recv.Type != nil {
+ fmt.Fprintf(&buf, "(%s %s) ", f.Recv.Name, f.Recv.Type)
+ }
+ buf.WriteString(f.Name)
+ argList(f.In, false)
+ if len(f.Out) > 0 {
+ buf.WriteByte(' ')
+ argList(f.Out, true)
+ }
+ return buf.String()
+}
+
+func (f *Func) Decl() string {
+ var buf strings.Builder
+ if f.Doc != "" {
+ for line := range strings.SplitSeq(strings.TrimRight(f.Doc, "\n"), "\n") {
+ fmt.Fprintf(&buf, "// %s\n", line)
+ }
+ }
+ buf.WriteString(f.Signature())
+ buf.WriteByte('\n')
+ return buf.String()
+}
diff --git a/src/simd/archsimd/_gen/specgen/expand.go b/src/simd/archsimd/_gen/specgen/expand.go
new file mode 100644
index 0000000..0b51558
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/expand.go
@@ -0,0 +1,149 @@
+// 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 specgen
+
+import (
+ "_gen/specgen/specexpr"
+ "fmt"
+ "go/types"
+ "regexp"
+ "strings"
+)
+
+func (sFn *specFunc) expand(ctx context, opts *LoadOptions) []*Func {
+ ctx = ctx.at(sFn.Pos)
+
+ var solver specexpr.Solver
+
+ if opts.Trace != nil {
+ fmt.Fprintf(opts.Trace, "## %s%s\n", sFn.Name, sFn.Sig)
+ solver.SetTrace(opts.Trace)
+ }
+
+ // Declare domains of type parameters
+ typeParamVars := make(map[*types.TypeParam]specexpr.Variable)
+ for _, param := range sFn.TypeParams {
+ types, err := constraintToDomain(sFn.Pkg, param.Constraint())
+ if err != nil {
+ panic(err)
+ }
+ varDef := specexpr.Variable("$" + param.String())
+ solver.Declare(varDef, types)
+ typeParamVars[param] = varDef
+ }
+ // Bind shapes of all function parameters and results of Vec type
+ b := &argBinder{ctx, sFn.Pkg, &solver, typeParamVars}
+ argVars := make(map[*types.Var]specexpr.Variable)
+ ok := true
+ for _, v := range sFn.Params {
+ sv, ok1 := b.bindArg(v.Name(), v.Type())
+ argVars[v] = sv
+ ok = ok && ok1
+ }
+ for _, v := range sFn.Results {
+ sv, ok1 := b.bindArg(v.Name(), v.Type())
+ argVars[v] = sv
+ ok = ok && ok1
+ }
+ // Add requirements to the solver
+ for _, expr := range sFn.Requirements {
+ solver.Assert(expr)
+ }
+
+ // Find solutions.
+ defer func() {
+ p := recover()
+ if p != nil {
+ var buf strings.Builder
+ solver.Fprint(&buf)
+ panic(fmt.Sprintf("%s: %s\n%s", ctx.root.fset.Position(sFn.Pos), p, buf.String()))
+ }
+ }()
+ var funcs []*Func
+ for soln, err := range solver.Solve() {
+ if err != nil {
+ ctx.errorf("%s", err)
+ continue
+ }
+
+ fn := sFn.instantiate(ctx, soln, argVars)
+ if fn == nil {
+ continue
+ }
+
+ funcs = append(funcs, fn)
+ }
+
+ if len(funcs) == 0 {
+ ctx.errorf("impossible constraints (try -f %s -trace)", sFn.Name)
+ }
+
+ return funcs
+}
+
+func (sFn *specFunc) instantiate(ctx context, b *specexpr.Bindings, argVars map[*types.Var]specexpr.Variable) *Func {
+ var f Func
+
+ // Function or method?
+ var method bool
+ if len(sFn.Params) > 0 {
+ if t, ok := sFn.Params[0].Type().(*types.Named); ok {
+ if t.Origin() == sFn.Pkg.VecType {
+ method = true
+ }
+ }
+ }
+
+ // Instantiate name
+ name := sFn.NameTmpl.expand(func(s string) string {
+ val := b.Get(specexpr.Variable(s))
+ if val == nil {
+ ctx.errorf("unknown variable %q in function name", s)
+ return ""
+ }
+ str := fmt.Sprint(val)
+ // Make sure str starts with an upper-case letter so it maintains
+ // CamelCase in the overall identifier.
+ str = strings.ToTitle(str[:1]) + str[1:]
+ return str
+ })
+ f.Name = name
+
+ // Instantiate doc
+ doc := sFn.Doc.expand(func(s string) string {
+ val := b.Get(specexpr.Variable(s))
+ if val == nil {
+ ctx.errorf("unknown variable %q in doc", s)
+ return ""
+ }
+ return fmt.Sprint(val)
+ })
+ // Replace name in doc
+ if f.Name == sFn.Name {
+ f.Doc = doc
+ } else {
+ f.Doc = regexp.MustCompile(`\b`+regexp.QuoteMeta(sFn.Name)+`\b`).ReplaceAllLiteralString(doc, f.Name)
+ }
+
+ // Instantiate parameter and result types
+ //
+ // TODO: Should the loader keep these grouped like the original source so
+ // the transformed version keeps the same grouping (modulo pulling off the
+ // receiver)?
+ for _, v := range sFn.Params {
+ t := b.Get(argVars[v]).(specexpr.Type)
+ f.In = append(f.In, Arg{v.Name(), t})
+ }
+ if method && len(f.In) > 0 {
+ f.Recv = f.In[0]
+ f.In = f.In[1:]
+ }
+ for _, v := range sFn.Results {
+ t := b.Get(argVars[v]).(specexpr.Type)
+ f.Out = append(f.Out, Arg{v.Name(), t})
+ }
+
+ return &f
+}
diff --git a/src/simd/archsimd/_gen/specgen/load.go b/src/simd/archsimd/_gen/specgen/load.go
new file mode 100644
index 0000000..be6e6ca
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/load.go
@@ -0,0 +1,119 @@
+// 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 specgen
+
+import (
+ "_gen/specgen/specexpr"
+ "cmp"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "io"
+ "maps"
+ "slices"
+ "strings"
+)
+
+type LoadOptions struct {
+ // Filter, if non-nil, causes Load to process only spec functions satisfying
+ // Filter.
+ Filter func(*ast.FuncDecl) bool
+
+ // Trace, if non-nil, causes Load to log a debug trace of solver steps to
+ // Trace.
+ Trace io.Writer
+}
+
+// Load loads a Go SIMD spec from the package in directory dir. This is the main
+// entrypoint to this package.
+func Load(dir string, opts *LoadOptions) ([]*Func, error) {
+ if opts == nil {
+ opts = new(LoadOptions)
+ }
+
+ var root contextRoot
+ ctx := context{root: &root}
+
+ pkg := loadSpecPackage(ctx, dir, opts)
+ if err := root.gatherErrors(); err != nil {
+ return nil, err
+ }
+
+ var allFuncs []*Func
+ type funcKey struct {
+ recv specexpr.Type
+ name string
+ }
+ funcSet := make(map[funcKey]*Func)
+ for _, sFn := range pkg.Funcs {
+ expanded := sFn.expand(ctx, opts)
+
+ // Check for duplicates
+ for _, fn := range expanded {
+ key := funcKey{fn.Recv.Type, fn.Name}
+ if ofn := funcSet[key]; ofn != nil {
+ ctx.at(sFn.Pos).errorf("conflicting functions:\n\t%s\n\t%s", fn.Signature(), ofn.Signature())
+ continue
+ }
+ funcSet[key] = fn
+ allFuncs = append(allFuncs, fn)
+ }
+ }
+
+ return allFuncs, root.gatherErrors()
+}
+
+type contextRoot struct {
+ fset token.FileSet
+ errors map[srcError]struct{}
+}
+
+type context struct {
+ root *contextRoot
+ pos token.Pos
+ fn string
+}
+
+func (c context) at(pos token.Pos) context {
+ c.pos = pos
+ return c
+}
+
+func (c context) errorf(msg string, args ...any) {
+ if c.root.errors == nil {
+ c.root.errors = make(map[srcError]struct{})
+ }
+ err := srcError{c.pos, c.fn, fmt.Sprintf(msg, args...)}
+ c.root.errors[err] = struct{}{}
+}
+
+func (r *contextRoot) gatherErrors() error {
+ if len(r.errors) == 0 {
+ return nil
+ }
+ var errs []error
+ var buf strings.Builder
+ for _, err := range slices.SortedFunc(maps.Keys(r.errors), func(a, b srcError) int {
+ return cmp.Or(cmp.Compare(a.pos, b.pos), cmp.Compare(a.msg, b.msg))
+ }) {
+ if err.pos.IsValid() {
+ fmt.Fprintf(&buf, "%s: ", r.fset.Position(err.pos))
+ }
+ buf.WriteString(err.msg)
+ if err.fn != "" {
+ fmt.Fprintf(&buf, " in %s", err.fn)
+ }
+ errs = append(errs, fmt.Errorf("%s", buf.String()))
+ buf.Reset()
+ }
+ return errors.Join(errs...)
+}
+
+type srcError struct {
+ pos token.Pos
+ fn string
+ msg string
+}
diff --git a/src/simd/archsimd/_gen/specgen/loadspec.go b/src/simd/archsimd/_gen/specgen/loadspec.go
new file mode 100644
index 0000000..fa76b5b
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/loadspec.go
@@ -0,0 +1,262 @@
+// 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 specgen
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "strings"
+
+ "golang.org/x/tools/go/packages"
+
+ "_gen/specgen/specexpr"
+)
+
+// specPackage represents the parsed _gen/spec package.
+type specPackage struct {
+ Fset *token.FileSet
+ Pkg *types.Package
+ TypesInfo *types.Info
+ Funcs []*specFunc
+
+ ElemTypes map[types.Type]specexpr.Basic
+ WidthTypes map[types.Type]specexpr.Width
+
+ VecType types.Type // Uninstantiated Vec type
+ ArrayType types.Type // Uninstantiated Array type
+ UintLType types.Type // Uninstantiated UintL type
+}
+
+// specFunc represents an exported function in the spec source.
+type specFunc struct {
+ Pkg *specPackage
+ Name string // Source name
+ NameTmpl specTemplate // API name template from `//specgen:name` directive, or same as Name.
+ Pos token.Pos
+ Doc specTemplate
+ Sig *types.Signature
+ TypeParams []*types.TypeParam
+ Params []*types.Var
+ Results []*types.Var
+ Requirements []specexpr.Expr
+}
+
+// specTemplate is a template string, with placeholders of the form `{var}`,
+// which will be replaced with variable values from the solver.
+type specTemplate struct {
+ tmpl string // raw template string including patterns
+ fields [][2]int // start:end ranges of fields, including '{}'s, in ascending order
+}
+
+// loadSpecPackage parses the spec package in the given directory path.
+func loadSpecPackage(ctx context, dir string, opts *LoadOptions) *specPackage {
+ cfg := &packages.Config{
+ Mode: packages.LoadSyntax,
+ Dir: dir,
+ Fset: &ctx.root.fset,
+ }
+
+ pkgs, err := packages.Load(cfg, ".")
+ if err != nil {
+ ctx.errorf("failed to load package: %s", err)
+ return nil
+ }
+ if len(pkgs) == 0 {
+ ctx.errorf("no package found in directory %s", dir)
+ return nil
+ }
+ if len(pkgs[0].Errors) > 0 {
+ for _, err := range pkgs[0].Errors {
+ ctx.errorf("%s", err)
+ }
+ return nil
+ }
+
+ srcPkg := pkgs[0]
+ fset := srcPkg.Fset
+ info := srcPkg.TypesInfo
+
+ var pkg specPackage
+
+ // Gather exported functions
+ var funcs []*specFunc
+ for _, file := range srcPkg.Syntax {
+ for _, decl := range file.Decls {
+ d, ok := decl.(*ast.FuncDecl)
+ if !ok || !d.Name.IsExported() {
+ continue
+ }
+ if opts.Filter != nil && !opts.Filter(d) {
+ continue
+ }
+
+ obj := srcPkg.Types.Scope().Lookup(d.Name.Name)
+ if obj == nil {
+ continue
+ }
+ fn, ok := obj.(*types.Func)
+ if !ok {
+ continue
+ }
+
+ sig := fn.Type().(*types.Signature)
+
+ var typeParams []*types.TypeParam
+ tparams := sig.TypeParams()
+ for tparam := range tparams.TypeParams() {
+ typeParams = append(typeParams, tparam)
+ }
+
+ var params []*types.Var
+ p := sig.Params()
+ for v := range p.Variables() {
+ params = append(params, v)
+ }
+
+ var results []*types.Var
+ r := sig.Results()
+ for v := range r.Variables() {
+ results = append(results, v)
+ }
+
+ f := &specFunc{
+ Pkg: &pkg,
+ Name: d.Name.Name,
+ Pos: decl.Pos(),
+ Sig: sig,
+ TypeParams: typeParams,
+ Params: params,
+ Results: results,
+ }
+ f.NameTmpl = specTemplate{tmpl: f.Name}
+ if d.Doc != nil {
+ f.Doc, err = newSpecTemplate(d.Doc.Text())
+ if err != nil {
+ ctx.at(d.Doc.Pos()).errorf("malformed doc comment: %s", err)
+ }
+ for _, comment := range d.Doc.List {
+ if dir, ok := ast.ParseDirective(comment.Slash, comment.Text); ok && dir.Tool == "specgen" {
+ switch dir.Name {
+ case "name":
+ f.NameTmpl, err = newSpecTemplate(dir.Args)
+ if err != nil {
+ ctx.at(dir.Pos()).errorf("malformed //specgen:name directive: %s", err)
+ }
+ case "require":
+ args, err := dir.ParseArgs()
+ if err != nil {
+ ctx.at(dir.Pos()).errorf("malformed //specgen:require directive: %s", err)
+ break
+ }
+ for _, arg := range args {
+ expr, err := specexpr.ParseExpr(arg.Arg)
+ if err != nil {
+ ctx.at(arg.Pos).errorf("failed to parse require argument %q: %s", arg.Arg, err)
+ continue
+ }
+ f.Requirements = append(f.Requirements, expr)
+ }
+ }
+ }
+ }
+ }
+
+ funcs = append(funcs, f)
+ }
+ }
+
+ lookupType := func(name string) types.Type {
+ obj := srcPkg.Types.Scope().Lookup(name)
+ if obj == nil {
+ ctx.errorf("type %q missing from package %s", name, srcPkg.PkgPath)
+ return nil
+ }
+ tn, ok := obj.(*types.TypeName)
+ if !ok {
+ ctx.at(obj.Pos()).errorf("%s expected to be a type", obj.String())
+ return nil
+ }
+ return tn.Type()
+ }
+
+ // Gather types corresponding to shape constraints
+ elemTypes := make(map[types.Type]specexpr.Basic)
+ if eltOrMask := lookupType("EltOrMask"); eltOrMask != nil {
+ for _, elt := range typeSet(eltOrMask) {
+ elemTypes[elt] = shapeElemType(elt)
+ }
+ }
+ widthTypes := make(map[types.Type]specexpr.Width)
+ if width := lookupType("Width"); width != nil {
+ for _, width := range typeSet(width) {
+ val := shapeWidthVal(width)
+ widthTypes[width] = val
+ }
+ }
+
+ // Gather other known types
+ vecType := lookupType("Vec")
+ arrayType := lookupType("Array")
+ uintLType := lookupType("UintL")
+
+ pkg = specPackage{
+ Fset: fset,
+ Pkg: srcPkg.Types,
+ TypesInfo: info,
+ Funcs: funcs,
+ ElemTypes: elemTypes,
+ WidthTypes: widthTypes,
+ VecType: vecType,
+ ArrayType: arrayType,
+ UintLType: uintLType,
+ }
+ return &pkg
+}
+
+// newSpecTemplate parses spec template.
+func newSpecTemplate(tmpl string) (specTemplate, error) {
+ if !strings.ContainsAny(tmpl, "{}") {
+ return specTemplate{tmpl, nil}, nil
+ }
+
+ var fields [][2]int
+ for i := 0; i < len(tmpl); i++ {
+ switch tmpl[i] {
+ case '{':
+ j := i + strings.IndexByte(tmpl[i:], '}') + 1
+ if j <= i {
+ return specTemplate{}, fmt.Errorf("unclosed '{' in template %q", tmpl)
+ }
+ fields = append(fields, [2]int{i, j})
+ i = j - 1
+ case '}':
+ return specTemplate{}, fmt.Errorf("unmatched '}' in template %q", tmpl)
+ }
+ }
+ return specTemplate{
+ tmpl: tmpl,
+ fields: fields,
+ }, nil
+}
+
+// expand replaces placeholders in template s by calling the lookup function to
+// resolve their values.
+func (s *specTemplate) expand(lookup func(string) string) string {
+ if len(s.fields) == 0 {
+ return s.tmpl
+ }
+ var buf strings.Builder
+ pos := 0
+ for _, field := range s.fields {
+ buf.WriteString(s.tmpl[pos:field[0]])
+ val := lookup(s.tmpl[field[0]+1 : field[1]-1])
+ buf.WriteString(val)
+ pos = field[1]
+ }
+ buf.WriteString(s.tmpl[pos:])
+ return buf.String()
+}
diff --git a/src/simd/archsimd/_gen/specgen/loadspec_test.go b/src/simd/archsimd/_gen/specgen/loadspec_test.go
new file mode 100644
index 0000000..c1021f6
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/loadspec_test.go
@@ -0,0 +1,196 @@
+// 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 specgen
+
+import (
+ "strings"
+ "sync"
+ "testing"
+)
+
+var (
+ specPkg *specPackage
+ specLoadErr error
+ loadSpecOnce sync.Once
+)
+
+func loadSpec(t *testing.T) *specPackage {
+ loadSpecOnce.Do(func() {
+ var root contextRoot
+ ctx := context{root: &root}
+
+ specPkg = loadSpecPackage(ctx, "../spec", &LoadOptions{})
+ if err := root.gatherErrors(); err != nil {
+ specLoadErr = err
+ }
+ })
+ if specLoadErr != nil {
+ t.Fatalf("failed to load spec: %v", specLoadErr)
+ }
+ return specPkg
+}
+
+func TestLoadSpec(t *testing.T) {
+ pkg := loadSpec(t)
+
+ // Verify we parsed functions
+ if len(pkg.Funcs) == 0 {
+ t.Errorf("expected parsed functions, got 0")
+ }
+
+ var foundAdd, foundExtend bool
+ for _, f := range pkg.Funcs {
+ if f.Name == "Add" {
+ foundAdd = true
+ if len(f.TypeParams) != 2 {
+ t.Errorf("Add should have 2 type parameters, got %d", len(f.TypeParams))
+ } else {
+ if f.TypeParams[0].Obj().Name() != "E" || !strings.Contains(f.TypeParams[0].Constraint().String(), "Nums") {
+ t.Errorf("unexpected Add type param 0: %s %s", f.TypeParams[0].Obj().Name(), f.TypeParams[0].Constraint())
+ }
+ if f.TypeParams[1].Obj().Name() != "W" || !strings.Contains(f.TypeParams[1].Constraint().String(), "Width") {
+ t.Errorf("unexpected Add type param 1: %s %s", f.TypeParams[1].Obj().Name(), f.TypeParams[1].Constraint())
+ }
+ }
+ if len(f.Params) != 2 {
+ t.Errorf("Add should have 2 params, got %d", len(f.Params))
+ } else {
+ if f.Params[0].Name() != "x" || !strings.Contains(f.Params[0].Type().String(), "Vec[") {
+ t.Errorf("unexpected Add param 0: %s %s", f.Params[0].Name(), f.Params[0].Type())
+ }
+ if f.Params[1].Name() != "y" || !strings.Contains(f.Params[1].Type().String(), "Vec[") {
+ t.Errorf("unexpected Add param 1: %s %s", f.Params[1].Name(), f.Params[1].Type())
+ }
+ }
+ if len(f.Results) != 1 {
+ t.Errorf("Add should have 1 result, got %d", len(f.Results))
+ } else {
+ if !strings.Contains(f.Results[0].Type().String(), "Vec[") {
+ t.Errorf("unexpected Add result type: %s", f.Results[0].Type())
+ }
+ }
+ }
+
+ if f.Name == "ExtendLoLToZ" {
+ foundExtend = true
+ if len(f.Requirements) != 2 {
+ t.Errorf("expected 2 requirements for ExtendLoLToZ, got %d", len(f.Requirements))
+ } else {
+ if f.Requirements[0] == nil || f.Requirements[1] == nil {
+ t.Errorf("expected non-nil parsed requirements")
+ }
+ }
+ }
+ }
+
+ if !foundAdd {
+ t.Errorf("failed to find function Add in parsed package")
+ }
+ if !foundExtend {
+ t.Errorf("failed to find function ExtendLoLToZ in parsed package")
+ }
+}
+
+func TestLoadSpecNameTmpl(t *testing.T) {
+ pkg := loadSpec(t)
+ var found bool
+ for _, f := range pkg.Funcs {
+ if f.Name == "MaskFromBits" {
+ found = true
+ want := "{z}FromBits"
+ if f.NameTmpl.tmpl != want {
+ t.Errorf("MaskFromBits: expected NameTmpl.tmpl %q, got %q", want, f.NameTmpl.tmpl)
+ }
+ break
+ }
+ }
+ if !found {
+ t.Errorf("failed to find function MaskFromBits in parsed package")
+ }
+}
+
+func TestNewSpecTemplate(t *testing.T) {
+ tests := []struct {
+ tmpl string
+ want specTemplate
+ wantErr bool
+ }{
+ {
+ tmpl: "",
+ want: specTemplate{tmpl: "", fields: nil},
+ },
+ {
+ tmpl: "Convert",
+ want: specTemplate{tmpl: "Convert", fields: nil},
+ },
+ {
+ tmpl: "Convert{zL}To{zB}{zN}",
+ want: specTemplate{
+ tmpl: "Convert{zL}To{zB}{zN}",
+ fields: [][2]int{{7, 11}, {13, 17}, {17, 21}},
+ },
+ },
+ {
+ tmpl: "Convert{zL",
+ wantErr: true,
+ },
+ {
+ tmpl: "Convert}",
+ wantErr: true,
+ },
+ {
+ tmpl: "Convert{a{b}}",
+ wantErr: true,
+ },
+ }
+
+ for _, tc := range tests {
+ got, err := newSpecTemplate(tc.tmpl)
+ if (err != nil) != tc.wantErr {
+ t.Errorf("newSpecTemplate(%q) returned error: %v, wantErr: %v", tc.tmpl, err, tc.wantErr)
+ continue
+ }
+ if tc.wantErr {
+ continue
+ }
+ if got.tmpl != tc.want.tmpl {
+ t.Errorf("newSpecTemplate(%q) tmpl = %q, want %q", tc.tmpl, got.tmpl, tc.want.tmpl)
+ }
+ if len(got.fields) != len(tc.want.fields) {
+ t.Errorf("newSpecTemplate(%q) fields len = %d, want %d", tc.tmpl, len(got.fields), len(tc.want.fields))
+ } else {
+ for i := range got.fields {
+ if got.fields[i] != tc.want.fields[i] {
+ t.Errorf("newSpecTemplate(%q) fields[%d] = %v, want %v", tc.tmpl, i, got.fields[i], tc.want.fields[i])
+ }
+ }
+ }
+ }
+}
+
+func TestSpecTemplateExpand(t *testing.T) {
+ tmpl, err := newSpecTemplate("Convert{zL}To{zB}{zN}")
+ if err != nil {
+ t.Fatalf("unexpected error parsing template: %v", err)
+ }
+
+ lookup := func(name string) string {
+ switch name {
+ case "zL":
+ return "4"
+ case "zB":
+ return "Float"
+ case "zN":
+ return "32"
+ }
+ return ""
+ }
+
+ got := tmpl.expand(lookup)
+ want := "Convert4ToFloat32"
+ if got != want {
+ t.Errorf("expected expanded string %q, got %q", want, got)
+ }
+}
diff --git a/src/simd/archsimd/_gen/specgen/types.go b/src/simd/archsimd/_gen/specgen/types.go
new file mode 100644
index 0000000..4930fc5
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/types.go
@@ -0,0 +1,289 @@
+// 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 specgen
+
+import (
+ "fmt"
+ "go/types"
+ "regexp"
+ "strconv"
+ "strings"
+ "sync"
+
+ "_gen/specgen/specexpr"
+
+ "golang.org/x/tools/go/types/typeutil"
+)
+
+// constraintToDomain enumerates all types explicitly listed as satisfying
+// constraint (which must be a type parameter constraint), and translates them
+// to a [specexpr] domain.
+func constraintToDomain(pkg *specPackage, constraint types.Type) ([]any, error) {
+ var vals []any
+ for _, typ := range typeSet(constraint) {
+ elem, ok := pkg.ElemTypes[typ]
+ if ok {
+ vals = append(vals, elem)
+ } else if width, ok := pkg.WidthTypes[typ]; ok {
+ vals = append(vals, width)
+ } else {
+ return nil, fmt.Errorf("type %s satisfies constraint %s, but isn't a known shape type", typ, constraint)
+ }
+ }
+ return vals, nil
+}
+
+var typeSetMemo sync.Map
+
+// typeSet enumerates all concrete types in t's type set.
+func typeSet(t types.Type) []types.Type {
+ // In general types.Types are not comparable, but every type we're dealing
+ // with here has pointer identity, and also there's no correctness issue if
+ // we miss in the memo.
+ ts, ok := typeSetMemo.Load(t)
+ if !ok {
+ var o orderedTypeSet
+ typeSet1(t, &o)
+ ts, _ = typeSetMemo.LoadOrStore(t, o.types)
+ }
+ return ts.([]types.Type)
+}
+
+type orderedTypeSet struct {
+ types []types.Type
+ set typeutil.Map
+}
+
+func (set *orderedTypeSet) add(t types.Type) {
+ if set.set.At(t) == nil {
+ set.set.Set(t, true)
+ set.types = append(set.types, t)
+ }
+}
+
+func (set *orderedTypeSet) intersect(o orderedTypeSet) {
+ i, j := 0, 0
+ for ; i < len(set.types); i++ {
+ t := set.types[i]
+ if o.set.At(t) != nil {
+ set.types[j] = t
+ j++
+ } else {
+ set.set.Delete(t)
+ }
+ }
+ set.types = set.types[:j]
+}
+
+func typeSet1(t types.Type, o *orderedTypeSet) {
+ switch u := t.Underlying().(type) {
+ case *types.Interface:
+ switch u.NumEmbeddeds() {
+ case 0:
+ return
+ case 1:
+ // Fast path for common case
+ typeSet1(u.EmbeddedType(0), o)
+ return
+ }
+ var intersection orderedTypeSet
+ first := true
+ for etyp := range u.EmbeddedTypes() {
+ if first {
+ typeSet1(etyp, &intersection)
+ first = false
+ } else {
+ var tmp orderedTypeSet
+ typeSet1(etyp, &tmp)
+ intersection.intersect(tmp)
+ }
+ }
+ // TODO: This doesn't check method satisfaction. We could do that with
+ // types.Satisfies filter here, but it doesn't matter for our needs.
+ for _, etyp := range intersection.types {
+ o.add(etyp)
+ }
+
+ case *types.Union:
+ for term := range u.Terms() {
+ typeSet1(term.Type(), o)
+ }
+
+ default:
+ o.add(t)
+ }
+}
+
+var basicRe = regexp.MustCompile(`^([a-z]+)([0-9]*)$`)
+
+// shapeElemType parses a basic or mask element spec type.
+func shapeElemType(t types.Type) specexpr.Basic {
+ var name string
+ switch t := t.(type) {
+ case *types.Basic: // E.g., uint32
+ name = t.Name()
+ case *types.Named: // E.g., mask16
+ name = t.Obj().Name()
+ default:
+ panic(fmt.Sprintf("not a shape element type: %s", t))
+ }
+ m := basicRe.FindStringSubmatch(name)
+ if m == nil {
+ panic(fmt.Sprintf("failed to parse element type %s", name))
+ }
+ bits := 0
+ if m[2] != "" {
+ bits, _ = strconv.Atoi(m[2])
+ }
+ return specexpr.Basic{Base: m[1], Bits: bits}
+}
+
+// shapeWidthVal parses a spec width type.
+func shapeWidthVal(t types.Type) specexpr.Width {
+ named, ok := t.(*types.Named)
+ if !ok {
+ panic(fmt.Sprintf("not a shape width type: %s", t))
+ }
+ name := named.Obj().Name()
+ if name == "WidthScalable" {
+ return specexpr.UnitWidth()
+ }
+ var err error
+ if suffix, ok := strings.CutPrefix(name, "Width"); ok {
+ var val int
+ val, err = strconv.Atoi(suffix)
+ if err == nil {
+ return specexpr.FixedWidth(val)
+ }
+ } else {
+ err = fmt.Errorf("does not start with 'Width'")
+ }
+ panic(fmt.Sprintf("parsing width type %s: %s", t, err))
+}
+
+type argBinder struct {
+ ctx context
+ pkg *specPackage
+ s *specexpr.Solver
+ typeParams map[*types.TypeParam]specexpr.Variable
+}
+
+// bindArg assigns all solver variables related to an argument called "name" of
+// type t. It returns the bound variable and an ok bool.
+func (b *argBinder) bindArg(name string, t types.Type) (specexpr.Variable, bool) {
+ expr := b.bind1(name, t)
+ if expr == nil {
+ return specexpr.Variable(""), false
+ }
+ return b.s.Assign(specexpr.Variable(name), expr), true
+}
+
+// bind1 deconstructs t and binds any components to variables derived from
+// "name", and returns the (not yet bound!) expression for t. The caller is
+// expected to bind "name" to the returned expression, or pass it up. It works
+// this way so we can unwrap things like pointer and slice types without
+// creating intermediate names for each level.
+func (b *argBinder) bind1(name string, t types.Type) specexpr.Expr {
+ switch t := t.(type) {
+ case *types.Basic:
+ return &specexpr.Literal{Val: shapeElemType(t)}
+
+ case *types.Pointer:
+ elem := b.bind1(name, t.Elem())
+ if elem == nil {
+ return nil
+ }
+ return specexpr.MakePointer(elem)
+
+ case *types.Slice:
+ elem := b.bind1(name, t.Elem())
+ if elem == nil {
+ return nil
+ }
+ return specexpr.MakeSlice(elem)
+
+ case *types.Named:
+ if types.Identical(t.Origin(), b.pkg.VecType) {
+ xE, xW, _ := b.bindVecLike(name, t)
+ if xE == nil {
+ return nil
+ }
+ return specexpr.MakeVector(xE, xW)
+ }
+ if types.Identical(t.Origin(), b.pkg.ArrayType) {
+ xE, xW, xL := b.bindVecLike(name, t)
+ if xE == nil {
+ return nil
+ }
+ b.s.Assert(&specexpr.BinExpr{Op: specexpr.OpNotEqual, X: xW, Y: &specexpr.Literal{Val: specexpr.UnitWidth()}})
+ return specexpr.MakeArray(xE, widthToInt(xL))
+ }
+ if types.Identical(t.Origin(), b.pkg.UintLType) {
+ xE, xW, xL := b.bindVecLike(name, t)
+ if xE == nil {
+ return nil
+ }
+ b.s.Assert(&specexpr.BinExpr{Op: specexpr.OpNotEqual, X: xW, Y: &specexpr.Literal{Val: specexpr.UnitWidth()}})
+ return specexpr.MakeBasic(&specexpr.Literal{Val: "uint"}, widthToInt(xL))
+ }
+
+ case *types.TypeParam:
+ return b.typeParams[t]
+ }
+
+ b.ctx.errorf("cannot convert spec type %s into API type", t)
+ return nil
+}
+
+var widthToInt = specexpr.MakeFunc1("widthToInt", func(w specexpr.Width) (any, error) {
+ if w, ok := w.(specexpr.FixedWidth); ok {
+ return int(w), nil
+ }
+ return nil, fmt.Errorf("cannot convert scalable width %s to int", w)
+})
+
+func (b *argBinder) bindVecLike(name string, t *types.Named) (xE, xW, xL specexpr.Expr) {
+ args := t.TypeArgs()
+ if args.Len() != 2 {
+ b.ctx.errorf("expected exactly 2 type arguments, got %d", args.Len())
+ return nil, nil, nil
+ }
+
+ // Assign the element type
+ elem := b.bind1(name+"E", args.At(0))
+ if elem == nil {
+ return nil, nil, nil
+ }
+ xE = b.s.Assign(specexpr.Variable(name+"E"), elem)
+
+ // Get the width
+ var wExpr specexpr.Expr
+ switch wt := args.At(1).(type) {
+ case *types.TypeParam:
+ wExpr = b.typeParams[wt]
+ case *types.Named:
+ wVal := b.pkg.WidthTypes[wt]
+ if wVal == nil {
+ b.ctx.errorf("width type argument is not a width")
+ return nil, nil, nil
+ }
+ wExpr = &specexpr.Literal{Val: wVal}
+ default:
+ b.ctx.errorf("width type arguments not a type parameter or named type")
+ return nil, nil, nil
+ }
+ xW = b.s.Assign(specexpr.Variable(name+"W"), wExpr)
+
+ // Bind other variables
+ basicBase := specexpr.MakeField[specexpr.Basic]("Base")
+ basicBits := specexpr.MakeField[specexpr.Basic]("Bits")
+ b.s.Assign(specexpr.Variable(name+"B"), basicBase.Apply(xE))
+ xN := b.s.Assign(specexpr.Variable(name+"N"), basicBits.Apply(xE))
+ xL = b.s.Assign(specexpr.Variable(name+"L"), &specexpr.BinExpr{
+ Op: specexpr.OpDiv, X: xW, Y: xN,
+ })
+
+ return xE, xW, xL
+}
diff --git a/src/simd/archsimd/_gen/specgen/types_test.go b/src/simd/archsimd/_gen/specgen/types_test.go
new file mode 100644
index 0000000..5dd2f34
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/types_test.go
@@ -0,0 +1,47 @@
+// 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 specgen
+
+import (
+ "go/types"
+ "testing"
+)
+
+func TestTypeSet(t *testing.T) {
+ pkg := loadSpec(t)
+
+ obj := pkg.Pkg.Scope().Lookup("Nums")
+ if obj == nil {
+ t.Fatalf("failed to find 'Nums' in spec package scope")
+ }
+
+ typeName, ok := obj.(*types.TypeName)
+ if !ok {
+ t.Fatalf("Nums is not a TypeName, got %T", obj)
+ }
+
+ typesList := typeSet(typeName.Type())
+
+ expected := []string{
+ "float32", "float64",
+ "int8", "int16", "int32", "int64",
+ "uint8", "uint16", "uint32", "uint64",
+ }
+
+ if len(typesList) != len(expected) {
+ t.Errorf("expected %d types, got %d", len(expected), len(typesList))
+ }
+
+ found := make(map[string]bool)
+ for _, ty := range typesList {
+ found[ty.String()] = true
+ }
+
+ for _, exp := range expected {
+ if !found[exp] {
+ t.Errorf("expected type %s not found in satisfying list", exp)
+ }
+ }
+}
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
The longtest failure is real, but I don't understand why THIS CL triggered it. We have plenty of other imports within simd/archsimd/_gen already.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
The longtest failure is real, but I don't understand why THIS CL triggered it. We have plenty of other imports within simd/archsimd/_gen already.
CL 798560 modified `src/simd/archsimd/_gen/go.mod` to use `module simd/archsimd/_gen`.
Will changing the imports of
"_gen/specgen/specexpr"
to fully-qualified
"simd/archsimd/_gen/specgen/specexpr"
help?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
func MustFindSpecDir() string {
path, err := FindSpecDir()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())This is a must happen function, should we exit here?
out, err := exec.Command("go", "env", "GOROOT").Output()If the user is using a release toolchain, will this be problematic?
out, err := exec.Command("go", "env", "GOROOT").Output()If the user is using a release toolchain, will this be problematic?
Agreed, I had to edit _gen/main.go to not do this. This code is not necessarily compiled with a Go compiler that has the specification directory.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
func MustFindSpecDir() string {
path, err := FindSpecDir()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())This is a must happen function, should we exit here?
Done
out, err := exec.Command("go", "env", "GOROOT").Output()David ChaseIf the user is using a release toolchain, will this be problematic?
Agreed, I had to edit _gen/main.go to not do this. This code is not necessarily compiled with a Go compiler that has the specification directory.
Why? If you don't have the spec directory, it doesn't make any sense to run specgen.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Austin Clements would like David Chase and Junyang Shao to review this change.
simd/archsimd/_gen/specgen: spec to SIMD API generator
This package processes the SIMD spec package to produce the SIMD API
description.
The basic mechanism is that, for each exported function in the spec
function, this iterates through every combination of type parameter
values, keeps any combinations that satisfy additional constraint
expressions, and plugs in the concrete types that worked.
diff --git a/src/simd/archsimd/_gen/go.mod b/src/simd/archsimd/_gen/go.mod
index 515f54f..e100626 100644
index 0000000..0a2c0cc
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/api.go
@@ -0,0 +1,78 @@
+// 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 specgen
+
+import (
+ "fmt"
+ "simd/archsimd/_gen/specgen/specexpr"+ return buf.String()
+}
diff --git a/src/simd/archsimd/_gen/specgen/expand.go b/src/simd/archsimd/_gen/specgen/expand.go
new file mode 100644
index 0000000..06e3376
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/expand.go
@@ -0,0 +1,152 @@
+// 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 specgen
+
+import (
+ "fmt"
+ "go/types"
+ "regexp"
+ "simd/archsimd/_gen/specgen/specexpr"+ argGet := make(map[*types.Var]func(*specexpr.Bindings) specexpr.Type)
+ ok := true
+ for _, v := range sFn.Params {
+ get := b.bindArg(v.Name(), v.Type())
+ ok = ok && (get != nil)
+ argGet[v] = get
+ }
+ for _, v := range sFn.Results {
+ get := b.bindArg(v.Name(), v.Type())
+ ok = ok && (get != nil)
+ argGet[v] = get
+ }
+ // Add requirements to the solver
+ for _, expr := range sFn.Requirements {
+ solver.Assert(expr)
+ }
+ if !ok {
+ return nil
+ }
+
+ // Find solutions.
+ defer func() {
+ p := recover()
+ if p != nil {
+ var buf strings.Builder
+ solver.Fprint(&buf)
+ panic(fmt.Sprintf("%s: %s\n%s", ctx.root.fset.Position(sFn.Pos), p, buf.String()))
+ }
+ }()
+ var funcs []*Func
+ for soln, err := range solver.Solve() {
+ if err != nil {
+ ctx.errorf("%s", err)
+ continue
+ }
+
+ fn := sFn.instantiate(ctx, soln, argGet)
+ if fn == nil {
+ continue
+ }
+
+ funcs = append(funcs, fn)
+ }
+
+ if len(funcs) == 0 {
+ ctx.errorf("impossible constraints (try -f %s -trace)", sFn.Name)
+ }
+
+ return funcs
+}
+
+func (sFn *specFunc) instantiate(ctx context, b *specexpr.Bindings, argGet map[*types.Var]func(*specexpr.Bindings) specexpr.Type) *Func {+ t := argGet[v](b)
+ f.In = append(f.In, Arg{v.Name(), t})
+ }
+ if method && len(f.In) > 0 {
+ f.Recv = f.In[0]
+ f.In = f.In[1:]
+ }
+ for _, v := range sFn.Results {
+ t := argGet[v](b)
+ f.Out = append(f.Out, Arg{v.Name(), t})
+ }
+
+ return &f
+}
diff --git a/src/simd/archsimd/_gen/specgen/flag.go b/src/simd/archsimd/_gen/specgen/flag.go
new file mode 100644
index 0000000..ad333da
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/flag.go
@@ -0,0 +1,44 @@
+// 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 specgen
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+// FindSpecDir returns the path to the standard spec package
+// $GOROOT/simd/internal/spec.
+func FindSpecDir() (string, error) {
+ goroot, err := goEnvGoroot()
+ if err != nil {
+ return "", fmt.Errorf("could not find GOROOT: %w", err)
+ }
+ path := filepath.Join(goroot, "src/simd/internal/spec")
+ if _, err := os.Stat(path); err != nil {
+ return "", fmt.Errorf("could not find spec package: %w", err)
+ }
+ return path, nil
+}
+
+func MustFindSpecDir() string {
+ path, err := FindSpecDir()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ os.Exit(1)
+ }
+ return path
+}
+
+func goEnvGoroot() (string, error) {
+ out, err := exec.Command("go", "env", "GOROOT").Output()
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(string(out)), nil
+}
diff --git a/src/simd/archsimd/_gen/specgen/load.go b/src/simd/archsimd/_gen/specgen/load.go
new file mode 100644
index 0000000..4e935bb
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/load.go
@@ -0,0 +1,119 @@
+// 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 specgen
+
+import (
+ "cmp"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "io"
+ "maps"
+ "simd/archsimd/_gen/specgen/specexpr"index 0000000..4b96e30
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/loadspec.go
@@ -0,0 +1,264 @@
+// 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 specgen
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "simd/archsimd/_gen/specgen/specexpr"
+ "strings"
+
+ "golang.org/x/tools/go/packages"
+)
+
+// specPackage represents the parsed _gen/spec package.
+type specPackage struct {
+ Fset *token.FileSet
+ Pkg *types.Package
+ TypesInfo *types.Info
+ Funcs []*specFunc
+
+ TypeElems map[types.Type]specexpr.Basic
+ TypeWidths map[types.Type]specexpr.Num
+
+ VecType types.Type // Uninstantiated Vec type
+ ArrayType types.Type // Uninstantiated Array type
+ UintNType types.Type // Uninstantiated UintN type+ default:
+ ctx.at(dir.Pos()).errorf("unknown //specgen directive")
+ typeElems := make(map[types.Type]specexpr.Basic)
+ if eltOrMask := lookupType("EltOrMask"); eltOrMask != nil {
+ for _, elt := range typeSet(eltOrMask) {
+ basic := shapeElemType(elt)
+ typeElems[elt] = basic
+ }
+ }
+ typeWidths := make(map[types.Type]specexpr.Num)
+ if width := lookupType("Width"); width != nil {
+ for _, width := range typeSet(width) {
+ val := shapeWidthVal(width)
+ typeWidths[width] = val
+ }
+ }
+
+ // Gather other known types
+ vecType := lookupType("Vec")
+ arrayType := lookupType("Array")
+ uintNType := lookupType("UintN")
+
+ pkg = specPackage{
+ Fset: fset,
+ Pkg: srcPkg.Types,
+ TypesInfo: info,
+ Funcs: funcs,
+ TypeElems: typeElems,
+ TypeWidths: typeWidths,
+ VecType: vecType,
+ ArrayType: arrayType,
+ UintNType: uintNType,index 0000000..f556523+ specPkg = loadSpecPackage(ctx, "../../../internal/spec", &LoadOptions{})index 0000000..07b0a7b
--- /dev/null
+++ b/src/simd/archsimd/_gen/specgen/types.go
@@ -0,0 +1,294 @@
+// 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 specgen
+
+import (
+ "fmt"
+ "go/types"
+ "regexp"
+ "simd/archsimd/_gen/specgen/specexpr"
+ "strconv"
+ "strings"
+ "sync"
+
+ "golang.org/x/tools/go/types/typeutil"
+)
+
+// constraintToDomain enumerates all types explicitly listed as satisfying
+// constraint (which must be a type parameter constraint), and translates them
+// to a [specexpr] domain.
+func constraintToDomain(pkg *specPackage, constraint types.Type) ([]any, error) {
+ var vals []any
+ for _, typ := range typeSet(constraint) {
+ elem, ok := pkg.TypeElems[typ]
+ if ok {
+ vals = append(vals, elem)
+ } else if width, ok := pkg.TypeWidths[typ]; ok {+var basicRe = regexp.MustCompile(`^([a-z]+|Mask)([0-9]*)$`)
+
+// shapeElemType parses a basic or mask element spec type.
+func shapeElemType(t types.Type) specexpr.Basic {
+ var name string
+ switch t := t.(type) {
+ case *types.Basic: // E.g., uint32
+ name = t.Name()
+ case *types.Named: // E.g., Mask16
+ name = t.Obj().Name()
+ default:
+ panic(fmt.Sprintf("not a shape element type: %s", t))
+ }
+ m := basicRe.FindStringSubmatch(name)
+ if m == nil {
+ panic(fmt.Sprintf("failed to parse element type %s", name))
+ }
+ bits := 0
+ if m[2] != "" {
+ bits, _ = strconv.Atoi(m[2])
+ }
+ return specexpr.Basic{Base: m[1], Bits: specexpr.Int(bits)}
+}
+
+// shapeWidthVal parses a spec width type.
+func shapeWidthVal(t types.Type) specexpr.Num {
+ named, ok := t.(*types.Named)
+ if !ok {
+ panic(fmt.Sprintf("not a shape width type: %s", t))
+ }
+ name := named.Obj().Name()
+ if name == "WidthScalable" {
+ return specexpr.VW()
+ }
+ var err error
+ if suffix, ok := strings.CutPrefix(name, "Width"); ok {
+ var val int
+ val, err = strconv.Atoi(suffix)
+ if err == nil {
+ return specexpr.Int(val)
+ }
+ } else {
+ err = fmt.Errorf("does not start with 'Width'")
+ }
+ panic(fmt.Sprintf("parsing width type %s: %s", t, err))
+}
+
+type argBinder struct {
+ ctx context
+ pkg *specPackage
+ s *specexpr.Solver
+ typeParams map[*types.TypeParam]specexpr.Variable
+}
+
+// bindArg assigns all solver variables related to an argument called "name" of
+// type t. It returns a function that retrieves the resolved type, or nil on
+// error.
+func (b *argBinder) bindArg(name string, t types.Type) func(*specexpr.Bindings) specexpr.Type {
+ expr := b.bind1(name, t)
+ if expr == nil {
+ return nil
+ }
+ v := b.s.Assign(specexpr.Variable(name), expr)
+ return func(b *specexpr.Bindings) specexpr.Type {
+ return b.Get(v).(specexpr.Type)
+ }
+ case *types.Array:
+ elem := b.bind1(name, t.Elem())
+ if elem == nil {
+ return nil
+ }
+ return specexpr.MakeArray(elem, specexpr.Int(t.Len()))
+
+ case *types.Named:
+ if types.Identical(t.Origin(), b.pkg.VecType) {
+ xE, xW, _ := b.bindVecLike(name, t)
+ if xE == nil {
+ return nil
+ }
+ return specexpr.MakeVector(xE, xW)
+ }
+ if types.Identical(t.Origin(), b.pkg.ArrayType) {
+ xE, xW, xL := b.bindVecLike(name, t)
+ if xE == nil {
+ return nil
+ }
+ b.s.Assert(funcAssertFixed(xW))
+ return specexpr.MakeArray(xE, xL)
+ }
+ if types.Identical(t.Origin(), b.pkg.UintNType) {
+ xN := specexpr.Variable(name + "N")
+ b.s.Declare(xN, []any{8, 16, 32, 64})
+ b.s.Assert(funcAssertFixed(xN))
+ return specexpr.MakeBasic(&specexpr.Literal{Val: "uint"}, xN)
+ }
+
+ case *types.TypeParam:
+ return b.typeParams[t]
+ }
+
+ b.ctx.errorf("cannot convert spec type %s into API type", t)
+ return nil
+}
+
+var funcAssertFixed = specexpr.MakeFunc1("assertFixed", func(w specexpr.Num) (any, error) {
+ _, ok := w.(specexpr.Int)
+ return ok, nil
+})
+
+func (b *argBinder) bindVecLike(name string, t *types.Named) (xE, xW, xL specexpr.Expr) {
+ args := t.TypeArgs()
+ if args.Len() != 2 {
+ b.ctx.errorf("expected exactly 2 type arguments, got %d", args.Len())
+ return nil, nil, nil
+ }
+
+ // Assign the element type
+ elem := b.bind1(name+"E", args.At(0))
+ if elem == nil {
+ return nil, nil, nil
+ }
+ xE = b.s.Assign(specexpr.Variable(name+"E"), elem)
+
+ // Get the width
+ var wExpr specexpr.Expr
+ switch wt := args.At(1).(type) {
+ case *types.TypeParam:
+ wExpr = b.typeParams[wt]
+ case *types.Named:
+ wExpr = b.pkg.TypeWidths[wt]
+ if wExpr == nil {
+ b.ctx.errorf("width type argument is not a width")
+ return nil, nil, nil
+ }
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |