| // 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. |
| |
| // refgen produces a reference implementation of the SIMD API backed by the spec |
| // implementation. |
| package main |
| |
| import ( |
| "bytes" |
| "cmp" |
| "flag" |
| "fmt" |
| "go/types" |
| "log" |
| "maps" |
| "os" |
| "slices" |
| "strings" |
| |
| "simd/archsimd/_gen/gentools" |
| "simd/archsimd/_gen/specgen" |
| "simd/archsimd/_gen/specgen/specexpr" |
| ) |
| |
| func main() { |
| gentools.RegisterFlags(nil) |
| |
| flag.Usage = func() { |
| w := flag.CommandLine.Output() |
| fmt.Fprintf(w, "usage: refgen [flags] [spec dir]\n") |
| flag.CommandLine.PrintDefaults() |
| } |
| |
| flag.Parse() |
| var specDir string |
| switch flag.NArg() { |
| case 0: |
| specDir = specgen.MustFindSpecDir() |
| case 1: |
| specDir = flag.Arg(0) |
| default: |
| flag.Usage() |
| os.Exit(1) |
| } |
| |
| funcs, err := specgen.Load(specDir, nil) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "%s\n", err.Error()) |
| os.Exit(1) |
| } |
| |
| var files gentools.Files |
| defer files.FlushOrExit() |
| |
| src := &srcWriter{Buffer: files.NewGoFile("simd/internal/simdref/simdref.go")} |
| |
| fmt.Fprintf(src, `// Code generated by 'refgen'. DO NOT EDIT. |
| |
| package simdref |
| |
| import "simd/internal/spec" |
| |
| `) |
| |
| // Define all vector types |
| vecTypeSet := make(map[specexpr.Vector]bool) |
| for _, fn := range funcs { |
| if fn.Recv.Type != nil { |
| vecTypeSet[fn.Recv.Type.(specexpr.Vector)] = true |
| } |
| } |
| vecTypes := slices.SortedFunc(maps.Keys(vecTypeSet), func(a, b specexpr.Vector) int { |
| if a.Elem != b.Elem { |
| return cmp.Compare(a.Elem.String(), b.Elem.String()) |
| } |
| cmp, ok := a.Width.Compare(b.Width) |
| if ok { |
| return cmp |
| } |
| _, aScale := a.Width.(specexpr.ScalableWidth) |
| _, bScale := b.Width.(specexpr.ScalableWidth) |
| if !aScale && bScale { |
| return 1 |
| } |
| return -1 |
| }) |
| fmt.Fprintf(src, "type (\n") |
| for _, vec := range vecTypes { |
| elem := vec.Elem.String() |
| if vec.Elem.Base == "Mask" { |
| elem = fmt.Sprintf("spec.Mask%d", vec.Elem.Bits) |
| } |
| |
| fmt.Fprintf(src, "\t%s struct { v []%s }\n", vec.String(), elem) |
| } |
| fmt.Fprintf(src, ")\n\n") |
| |
| // Define functions |
| var args []string |
| for _, fn := range funcs { |
| fmt.Fprintf(src, "%s {\n", fn.Decl()) |
| src.id = 0 |
| |
| specName, specSig, typeArgs := fn.SpecFunc() |
| specInst, err := types.Instantiate(nil, specSig, typeArgs, false) |
| if err != nil { |
| panic(fmt.Sprintf("instantiating spec function %s: %s", specName, err)) |
| } |
| |
| specParams := specInst.(*types.Signature).Params() |
| args = args[:0] |
| if fn.Recv.Type != nil { |
| args = append(args, toSpec(fn.Recv.Type, specParams.At(len(args)).Type(), fn.Recv.Name, src)) |
| } |
| for _, in := range fn.In { |
| args = append(args, toSpec(in.Type, specParams.At(len(args)).Type(), in.Name, src)) |
| } |
| |
| call := formatCall(specName, typeArgs, args) |
| |
| specResults := specInst.(*types.Signature).Results() |
| switch len(fn.Out) { |
| case 0: |
| fmt.Fprintf(src, "\t%s\n", call) |
| case 1: |
| fmt.Fprintf(src, "\treturn %s\n", fromSpec(fn.Out[0].Type, specResults.At(0).Type(), call, src)) |
| default: |
| var tmps []string |
| var res []string |
| for i := range fn.Out { |
| tmp := fmt.Sprintf("r%d", i+1) |
| tmps = append(tmps, tmp) |
| res = append(res, fromSpec(fn.Out[i].Type, specResults.At(i).Type(), tmp, src)) |
| } |
| fmt.Fprintf(src, "\t%s := %s\n", strings.Join(tmps, ", "), call) |
| fmt.Fprintf(src, "\treturn %s\n", strings.Join(res, ", ")) |
| } |
| fmt.Fprintf(src, "}\n\n") |
| } |
| } |
| |
| type srcWriter struct { |
| *bytes.Buffer |
| id int |
| } |
| |
| func (w *srcWriter) genIdent() string { |
| ident := fmt.Sprintf("tmp%d", w.id) |
| w.id++ |
| return ident |
| } |
| |
| func formatCall(specName string, typeArgs []types.Type, args []string) string { |
| var callBuf bytes.Buffer |
| fmt.Fprintf(&callBuf, "spec.%s[", specName) |
| for i, typeArg := range typeArgs { |
| if i > 0 { |
| callBuf.WriteString(", ") |
| } |
| types.WriteType(&callBuf, typeArg, specQualifier) |
| } |
| fmt.Fprintf(&callBuf, "](%s)", strings.Join(args, ", ")) |
| return callBuf.String() |
| } |
| |
| func specQualifier(pkg *types.Package) string { |
| if pkg.Path() == "simd/internal/spec" { |
| return "spec" |
| } |
| return "" |
| } |
| |
| // toSpec returns an expression that converts val from the specref Go type for t |
| // to spec type tt. It may write statements to src. |
| func toSpec(t specexpr.Type, tt types.Type, val string, src *srcWriter) string { |
| arrayOrSlice := func(tElem specexpr.Type, ttElem types.Type) string { |
| if eVal := toSpec(tElem, ttElem, val, src); eVal == val { |
| // Easy case: the values don't need to change. |
| return val |
| } |
| // Hard case: we need to map each element |
| tmp := src.genIdent() |
| fmt.Fprintf(src, "var %s %s\n", tmp, types.TypeString(tt, specQualifier)) |
| fmt.Fprintf(src, "for i := range %s {\n", val) |
| eVal := fromSpec(tElem, ttElem, "("+val+")[i]", src) |
| fmt.Fprintf(src, "\t%s[i] = %s\n", tmp, eVal) |
| fmt.Fprintf(src, "}\n") |
| return tmp |
| } |
| |
| switch t := t.(type) { |
| case specexpr.Vector: |
| return val + ".v" |
| case specexpr.Basic: |
| switch tt := tt.(type) { |
| case *types.Named: |
| if tt.Obj().Name() == "UintN" { |
| return "spec.UintN(" + val + ")" |
| } |
| } |
| return val |
| case specexpr.Slice: |
| tt := tt.Underlying().(*types.Slice) |
| return arrayOrSlice(t.Elem, tt.Elem()) |
| case specexpr.Array: |
| switch tt := tt.(type) { |
| case *types.Named: |
| if tt.Obj().Name() == "Array" { |
| return toSpec(t.Elem, tt.TypeArgs().At(0), val, src) + "[:]" |
| } |
| } |
| tt := tt.Underlying().(*types.Array) |
| return arrayOrSlice(t.Elem, tt.Elem()) |
| case specexpr.Pointer: |
| tt := tt.(*types.Pointer) |
| eVal := toSpec(t.Elem, tt.Elem(), val, src) |
| tmp := src.genIdent() |
| fmt.Fprintf(src, "var %s %s = %s\n", tmp, types.TypeString(tt.Elem(), specQualifier), eVal) |
| return "&" + tmp |
| } |
| log.Fatalf("unexpected specexpr type %s (%T)", t, t) |
| panic("not reachable") |
| } |
| |
| // fromSpec returns an expression that converts val from the spec package type |
| // tt to the specref Go type for t. It may write statements to src. |
| func fromSpec(t specexpr.Type, tt types.Type, val string, src *srcWriter) string { |
| arrayOrSlice := func(tElem specexpr.Type, ttElem types.Type) string { |
| if eVal := fromSpec(tElem, ttElem, val, src); eVal == val { |
| // Easy case: the values don't need to change. |
| return val |
| } |
| // Hard case: we need to map each element |
| tmp := src.genIdent() |
| fmt.Fprintf(src, "var %s %s\n", tmp, t) |
| fmt.Fprintf(src, "for i := range %s {\n", val) |
| eVal := fromSpec(tElem, ttElem, "("+val+")[i]", src) |
| fmt.Fprintf(src, "\t%s[i] = %s\n", tmp, eVal) |
| fmt.Fprintf(src, "}\n") |
| return tmp |
| } |
| |
| switch t := t.(type) { |
| case specexpr.Vector: |
| return fmt.Sprintf("%s{%s}", t, val) |
| case specexpr.Basic: |
| switch tt := tt.(type) { |
| case *types.Named: |
| if tt.Obj().Name() == "UintN" { |
| return fmt.Sprintf("%s(%s)", t, val) |
| } |
| } |
| return val |
| case specexpr.Slice: |
| tt := tt.Underlying().(*types.Slice) |
| return arrayOrSlice(t.Elem, tt.Elem()) |
| case specexpr.Array: |
| switch tt := tt.(type) { |
| case *types.Named: |
| if tt.Obj().Name() == "Array" { |
| return fmt.Sprintf("(%s)(%s)", t, fromSpec(t.Elem, tt.TypeArgs().At(0), val, src)) |
| } |
| } |
| tt := tt.Underlying().(*types.Array) |
| return arrayOrSlice(t.Elem, tt.Elem()) |
| case specexpr.Pointer: |
| tt := tt.(*types.Pointer) |
| eVal := fromSpec(t.Elem, tt.Elem(), val, src) |
| tmp := src.genIdent() |
| fmt.Fprintf(src, "var %s %s = %s\n", tmp, t, eVal) |
| return "&" + tmp |
| } |
| log.Fatalf("unexpected specexpr type %s (%T)", t, t) |
| panic("not reachable") |
| } |