| // 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" | |
| "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 | |
| } |