blob: bc1a5c9a1805f976ed4298aad3b314f2a539795c [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.
// 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")
}