| // 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/ast" |
| "go/build" |
| "go/importer" |
| "go/parser" |
| "go/token" |
| "go/types" |
| "path/filepath" |
| "simd/archsimd/_gen/specgen/specexpr" |
| ) |
| |
| // 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 |
| |
| ElemTypes map[specexpr.Basic]types.Type |
| WidthTypes map[specexpr.Num]types.Type |
| |
| VecType types.Type // Uninstantiated Vec type |
| ArrayType types.Type // Uninstantiated Array type |
| UintNType types.Type // Uninstantiated UintN 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 // Comment markers removed; \n-terminated unless "" |
| Category string |
| Commutative bool |
| Sig *types.Signature |
| TypeParams []*types.TypeParam |
| Params []*types.Var |
| Results []*types.Var |
| Requirements []specexpr.Expr |
| } |
| |
| // specGoVersion is the oldest Go toolchain version that must be able to parse |
| // and type-check the spec package. |
| const specGoVersion = "go1.26" |
| |
| // loadAndTypeCheck imports and parses the spec package in dir, ensuring it is |
| // target-independent, and type-checks it. |
| func loadAndTypeCheck(ctx context, dir string) (*types.Package, *types.Info, []*ast.File) { |
| bp, err := build.ImportDir(dir, 0) |
| if err != nil { |
| ctx.errorf("failed to import spec directory %s: %s", dir, err) |
| return nil, nil, nil |
| } |
| if len(bp.AllTags) > 0 { |
| ctx.errorf("internal/spec must be target-independent, but found build tags: %v", bp.AllTags) |
| return nil, nil, nil |
| } |
| |
| var astFiles []*ast.File |
| for _, name := range bp.GoFiles { |
| filePath := filepath.Join(bp.Dir, name) |
| file, err := parser.ParseFile(&ctx.root.fset, filePath, nil, parser.ParseComments) |
| if err != nil { |
| ctx.errorf("failed to parse %s: %s", filePath, err) |
| continue |
| } |
| astFiles = append(astFiles, file) |
| } |
| |
| if len(astFiles) == 0 { |
| ctx.errorf("no Go source files found in directory %s", dir) |
| return nil, nil, nil |
| } |
| |
| info := &types.Info{ |
| Types: make(map[ast.Expr]types.TypeAndValue), |
| Defs: make(map[*ast.Ident]types.Object), |
| Uses: make(map[*ast.Ident]types.Object), |
| Implicits: make(map[ast.Node]types.Object), |
| Selections: make(map[*ast.SelectorExpr]*types.Selection), |
| Scopes: make(map[ast.Node]*types.Scope), |
| Instances: make(map[*ast.Ident]types.Instance), |
| } |
| |
| // Note: Imported packages resolve against the running toolchain's standard |
| // library, not the dev tree's. For math and math/bits that is immaterial. |
| // Constraint: internal/spec may import only long-stable standard library packages. |
| conf := types.Config{ |
| GoVersion: specGoVersion, |
| Importer: importer.ForCompiler(&ctx.root.fset, "source", nil), |
| Error: func(err error) { |
| ctx.errorf("%s", err) |
| }, |
| } |
| |
| typesPkg, err := conf.Check("simd/internal/spec", &ctx.root.fset, astFiles, info) |
| if err != nil && typesPkg == nil { |
| return nil, nil, nil |
| } |
| if len(ctx.root.errors) > 0 { |
| return nil, nil, nil |
| } |
| |
| return typesPkg, info, astFiles |
| } |
| |
| // loadSpecPackage parses the spec package in the given directory path. |
| func loadSpecPackage(ctx context, dir string, opts *LoadOptions) *specPackage { |
| typesPkg, info, astFiles := loadAndTypeCheck(ctx, dir) |
| if typesPkg == nil { |
| return nil |
| } |
| |
| var pkg specPackage |
| |
| // Gather exported functions |
| var funcs []*specFunc |
| for _, file := range astFiles { |
| // Gather directives |
| var category string |
| directives := make(map[*ast.Comment]ast.Directive) |
| for _, cg := range file.Comments { |
| for _, comment := range cg.List { |
| if dir, ok := ast.ParseDirective(comment.Slash, comment.Text); ok && dir.Tool == "specgen" { |
| switch dir.Name { |
| case "category": |
| // File-level directive |
| if category != "" { |
| ctx.at(dir.Pos()).errorf("multiple category directives in file") |
| } |
| category = dir.Args |
| case "name", "commutative", "require": |
| // Gather other directives to process with decls |
| directives[comment] = dir |
| default: |
| ctx.at(dir.Pos()).errorf("unknown //specgen directive") |
| } |
| } |
| } |
| } |
| |
| for _, decl := range file.Decls { |
| d, ok := decl.(*ast.FuncDecl) |
| if !ok || !d.Name.IsExported() { |
| continue |
| } |
| switch d.Name.Name { |
| case "ScalableWidth", "SetScalableWidth": |
| continue |
| } |
| |
| obj := typesPkg.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, |
| Category: category, |
| } |
| f.NameTmpl = specTemplate{raw: f.Name} |
| if d.Doc != nil { |
| var err error |
| 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 := directives[comment]; ok { |
| delete(directives, comment) |
| switch dir.Name { |
| default: |
| panic("directive lists out of sync") |
| case "name": |
| f.NameTmpl, err = newSpecTemplate(dir.Args) |
| if err != nil { |
| ctx.at(dir.Pos()).errorf("malformed //specgen:name directive: %s", err) |
| } |
| case "commutative": |
| if dir.Args != "" { |
| ctx.at(dir.Pos()).errorf("malformed //specgen:commutative directive: expected no argument") |
| } |
| f.Commutative = true |
| 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) |
| } |
| } |
| } |
| } |
| } |
| |
| if opts.Filter != nil && !opts.Filter(d) { |
| continue |
| } |
| |
| funcs = append(funcs, f) |
| } |
| |
| for _, dir := range directives { |
| ctx.at(dir.Pos()).errorf("//%s:%s directive must be attached to a function", dir.Tool, dir.Name) |
| } |
| } |
| |
| lookupType := func(name string) types.Type { |
| obj := typesPkg.Scope().Lookup(name) |
| if obj == nil { |
| ctx.errorf("type %q missing from package %s", name, typesPkg.Path()) |
| 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 |
| typeElems := make(map[types.Type]specexpr.Basic) |
| elemTypes := make(map[specexpr.Basic]types.Type) |
| if eltOrMask := lookupType("EltOrMask"); eltOrMask != nil { |
| for _, elt := range typeSet(eltOrMask) { |
| basic := shapeElemType(elt) |
| typeElems[elt] = basic |
| elemTypes[basic] = elt |
| } |
| } |
| typeWidths := make(map[types.Type]specexpr.Num) |
| widthTypes := make(map[specexpr.Num]types.Type) |
| if width := lookupType("Width"); width != nil { |
| for _, width := range typeSet(width) { |
| val := shapeWidthVal(width) |
| typeWidths[width] = val |
| widthTypes[val] = width |
| } |
| } |
| |
| // Gather other known types |
| vecType := lookupType("Vec") |
| arrayType := lookupType("Array") |
| uintNType := lookupType("UintN") |
| |
| pkg = specPackage{ |
| Fset: &ctx.root.fset, |
| Pkg: typesPkg, |
| TypesInfo: info, |
| Funcs: funcs, |
| TypeElems: typeElems, |
| TypeWidths: typeWidths, |
| ElemTypes: elemTypes, |
| WidthTypes: widthTypes, |
| VecType: vecType, |
| ArrayType: arrayType, |
| UintNType: uintNType, |
| } |
| return &pkg |
| } |