[go/dev.simd] [dev.simd] simd/archsimd/_gen/cmd/refgen: generate reference implementation

3 views
Skip to first unread message

Austin Clements (Gerrit)

unread,
Jul 12, 2026, 9:30:17 PMJul 12
to goph...@pubsubhelper.golang.org, Austin Clements, golang-co...@googlegroups.com

Austin Clements has uploaded the change for review

Commit message

[dev.simd] simd/archsimd/_gen/cmd/refgen: generate reference implementation

This generates a reference implementation of the SIMD API backed by
the spec implementation.
Change-Id: Iaadde2d898aadf6076c06b214127d61fa26db7b8

Change diff

diff --git a/src/simd/archsimd/_gen/cmd/refgen/main.go b/src/simd/archsimd/_gen/cmd/refgen/main.go
new file mode 100644
index 0000000..af1937a
--- /dev/null
+++ b/src/simd/archsimd/_gen/cmd/refgen/main.go
@@ -0,0 +1,282 @@
+// 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/format"
+ "go/types"
+ "io"
+ "log"
+ "maps"
+ "os"
+ "slices"
+ "strings"
+
+ "_gen/specgen"
+ "_gen/specgen/specexpr"
+)
+
+func main() {
+ 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)
+ }
+
+ src := new(bytes.Buffer)
+
+ 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 {
+ sw := &srcWriter{src, 0}
+
+ fmt.Fprintf(src, "%s {\n", fn.Decl())
+
+ 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, sw))
+ }
+ for _, in := range fn.In {
+ args = append(args, toSpec(in.Type, specParams.At(len(args)).Type(), in.Name, sw))
+ }
+
+ 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, sw))
+ 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, sw))
+ }
+ 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")
+ }
+
+ // Emit source
+ out, err := format.Source(src.Bytes())
+ if err != nil {
+ fmt.Printf("%s", src.Bytes())
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Printf("%s", out)
+}
+
+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 ""
+}
+
+type srcWriter struct {
+ io.Writer
+ id int
+}
+
+func (w *srcWriter) genIdent() string {
+ ident := fmt.Sprintf("tmp%d", w.id)
+ w.id++
+ return ident
+}
+
+// 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")
+}
diff --git a/src/simd/archsimd/_gen/specgen/api.go b/src/simd/archsimd/_gen/specgen/api.go
index f7788c7..87391f0 100644
--- a/src/simd/archsimd/_gen/specgen/api.go
+++ b/src/simd/archsimd/_gen/specgen/api.go
@@ -7,6 +7,7 @@
import (
"_gen/specgen/specexpr"
"fmt"
+ "go/types"
"strings"
)

@@ -23,6 +24,12 @@

In []Arg
Out []Arg
+
+ // specFunc and instance describe the underlying spec function and its
+ // instantiation that led to this API function.
+ specFunc *specFunc
+ typeParamVars map[*types.TypeParam]specexpr.Variable
+ instance *specexpr.Bindings
}

type Arg struct {
@@ -76,3 +83,30 @@
buf.WriteString(f.Signature())
return buf.String()
}
+
+// SpecFunc returns information about the spec package function that generated
+// f. name is the name of the function in the spec package, sig is its
+// uninstantiated signature type, and typeArgs is a slice of the type arguments
+// it was instantiated on to construct f.
+func (f *Func) SpecFunc() (name string, sig *types.Signature, typeArgs []types.Type) {
+ sFn := f.specFunc
+
+ // Build instantiated signature
+ for _, tparam := range sFn.TypeParams {
+ val := f.instance.Get(f.typeParamVars[tparam])
+ switch val := val.(type) {
+ case specexpr.Type:
+ typeArgs = append(typeArgs, specTypeToType(sFn.Pkg, val))
+ case specexpr.Num:
+ wt := sFn.Pkg.WidthTypes[val]
+ if wt == nil {
+ panic(fmt.Sprintf("no spec package type for width %s", val))
+ }
+ typeArgs = append(typeArgs, wt)
+ default:
+ panic("unexpected type parameter value")
+ }
+ }
+
+ return sFn.Name, sFn.Sig, typeArgs
+}
diff --git a/src/simd/archsimd/_gen/specgen/expand.go b/src/simd/archsimd/_gen/specgen/expand.go
index 3be9fb7..f2935e0 100644
--- a/src/simd/archsimd/_gen/specgen/expand.go
+++ b/src/simd/archsimd/_gen/specgen/expand.go
@@ -75,6 +75,7 @@
if fn == nil {
continue
}
+ fn.typeParamVars = typeParamVars

funcs = append(funcs, fn)
}
@@ -89,6 +90,9 @@
func (sFn *specFunc) instantiate(ctx context, b *specexpr.Bindings, argGet map[*types.Var]func(*specexpr.Bindings) specexpr.Type) *Func {
var f Func

+ f.specFunc = sFn
+ f.instance = b
+
// Function or method?
var method bool
if len(sFn.Params) > 0 {
diff --git a/src/simd/archsimd/_gen/specgen/loadspec.go b/src/simd/archsimd/_gen/specgen/loadspec.go
index 72ed1f7..e0aba3a 100644
--- a/src/simd/archsimd/_gen/specgen/loadspec.go
+++ b/src/simd/archsimd/_gen/specgen/loadspec.go
@@ -26,6 +26,9 @@
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
@@ -187,17 +190,21 @@

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

@@ -213,6 +220,8 @@
Funcs: funcs,
TypeElems: typeElems,
TypeWidths: typeWidths,
+ ElemTypes: elemTypes,
+ WidthTypes: widthTypes,
VecType: vecType,
ArrayType: arrayType,
UintNType: uintNType,
diff --git a/src/simd/archsimd/_gen/specgen/types.go b/src/simd/archsimd/_gen/specgen/types.go
index 6cf7ed3..8c02ebc 100644
--- a/src/simd/archsimd/_gen/specgen/types.go
+++ b/src/simd/archsimd/_gen/specgen/types.go
@@ -163,6 +163,41 @@
panic(fmt.Sprintf("parsing width type %s: %s", t, err))
}

+var basicToBasic = map[specexpr.Basic]types.BasicKind{
+ {Base: "int", Bits: 0}: types.Int,
+ {Base: "int", Bits: 8}: types.Int8,
+ {Base: "int", Bits: 16}: types.Int16,
+ {Base: "int", Bits: 32}: types.Int32,
+ {Base: "int", Bits: 64}: types.Int64,
+ {Base: "uint", Bits: 0}: types.Uint,
+ {Base: "uint", Bits: 8}: types.Uint8,
+ {Base: "uint", Bits: 16}: types.Uint16,
+ {Base: "uint", Bits: 32}: types.Uint32,
+ {Base: "uint", Bits: 64}: types.Uint64,
+ {Base: "float", Bits: 32}: types.Float32,
+ {Base: "float", Bits: 64}: types.Float64,
+}
+
+func specTypeToType(pkg *specPackage, t specexpr.Type) types.Type {
+ switch t := t.(type) {
+ case specexpr.Basic:
+ var t2 types.Type
+ if t.Base == "Mask" {
+ t2 = pkg.ElemTypes[t]
+ } else {
+ if kind, ok := basicToBasic[t]; ok {
+ t2 = types.Typ[kind]
+ }
+ }
+ if t2 == nil {
+ panic(fmt.Sprintf("unknown basic type %s", t))
+ }
+ return t2
+ }
+ // TODO: Implement other specexpr.Type types if we need them
+ panic(fmt.Sprintf("unimplemented specTypeToType for %T", t))
+}
+
type argBinder struct {
ctx context
pkg *specPackage

Change information

Files:
  • A src/simd/archsimd/_gen/cmd/refgen/main.go
  • M src/simd/archsimd/_gen/specgen/api.go
  • M src/simd/archsimd/_gen/specgen/expand.go
  • M src/simd/archsimd/_gen/specgen/loadspec.go
  • M src/simd/archsimd/_gen/specgen/types.go
Change size: L
Delta: 5 files changed, 364 insertions(+), 0 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: go
Gerrit-Branch: dev.simd
Gerrit-Change-Id: Iaadde2d898aadf6076c06b214127d61fa26db7b8
Gerrit-Change-Number: 799980
Gerrit-PatchSet: 1
Gerrit-Owner: Austin Clements <aus...@google.com>
Gerrit-Reviewer: Austin Clements <aus...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Austin Clements (Gerrit)

unread,
Jul 12, 2026, 9:54:12 PMJul 12
to Austin Clements, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
Attention needed from Austin Clements

Austin Clements uploaded new patchset

Austin Clements uploaded patch set #2 to this change.
Following approvals got outdated and were removed:
Open in Gerrit

Related details

Attention is currently required from:
  • Austin Clements
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newpatchset
Gerrit-Project: go
Gerrit-Branch: dev.simd
Gerrit-Change-Id: Iaadde2d898aadf6076c06b214127d61fa26db7b8
Gerrit-Change-Number: 799980
Gerrit-PatchSet: 2
Gerrit-Owner: Austin Clements <aus...@google.com>
Gerrit-Reviewer: Austin Clements <aus...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

David Chase (Gerrit)

unread,
Jul 20, 2026, 4:43:44 PMJul 20
to Austin Clements, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
Attention needed from Austin Clements

David Chase added 2 comments

File src/simd/archsimd/_gen/cmd/refgen/main.go
Line 55, Patchset 2 (Latest):
David Chase . unresolved

technically an unwanted whitespace, but that will be fixed by formatted output.

Line 151, Patchset 2 (Latest): // This may be used as a defer. On panic, flush what we have an repanic.
David Chase . unresolved

"and"

Open in Gerrit

Related details

Attention is currently required from:
  • Austin Clements
Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: go
    Gerrit-Branch: dev.simd
    Gerrit-Change-Id: Iaadde2d898aadf6076c06b214127d61fa26db7b8
    Gerrit-Change-Number: 799980
    Gerrit-PatchSet: 2
    Gerrit-Owner: Austin Clements <aus...@google.com>
    Gerrit-Reviewer: Austin Clements <aus...@google.com>
    Gerrit-CC: David Chase <drc...@google.com>
    Gerrit-Attention: Austin Clements <aus...@google.com>
    Gerrit-Comment-Date: Mon, 20 Jul 2026 20:43:40 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    satisfied_requirement
    open
    diffy

    Austin Clements (Gerrit)

    unread,
    Aug 6, 2026, 2:58:58 PM (yesterday) Aug 6
    to Austin Clements, goph...@pubsubhelper.golang.org, David Chase, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
    Attention needed from David Chase

    Austin Clements added 2 comments

    File src/simd/archsimd/_gen/cmd/refgen/main.go
    Line 55, Patchset 2:
    David Chase . resolved

    technically an unwanted whitespace, but that will be fixed by formatted output.

    Austin Clements

    Done

    Line 151, Patchset 2: // This may be used as a defer. On panic, flush what we have an repanic.
    David Chase . resolved

    "and"

    Austin Clements

    Done

    Open in Gerrit

    Related details

    Attention is currently required from:
    • David Chase
    Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement is not satisfiedTryBots-Pass
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: go
      Gerrit-Branch: dev.simd
      Gerrit-Change-Id: Iaadde2d898aadf6076c06b214127d61fa26db7b8
      Gerrit-Change-Number: 799980
      Gerrit-PatchSet: 3
      Gerrit-Owner: Austin Clements <aus...@google.com>
      Gerrit-Reviewer: Austin Clements <aus...@google.com>
      Gerrit-CC: David Chase <drc...@google.com>
      Gerrit-Attention: David Chase <drc...@google.com>
      Gerrit-Comment-Date: Thu, 06 Aug 2026 18:58:49 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      Comment-In-Reply-To: David Chase <drc...@google.com>
      unsatisfied_requirement
      satisfied_requirement
      open
      diffy

      Austin Clements (Gerrit)

      unread,
      Aug 6, 2026, 2:58:58 PM (yesterday) Aug 6
      to Austin Clements, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
      Attention needed from David Chase

      Austin Clements uploaded new patchset

      Austin Clements uploaded patch set #3 to this change.
      Following approvals got outdated and were removed:
      Open in Gerrit

      Related details

      Attention is currently required from:
      • David Chase
      Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement is not satisfiedTryBots-Pass
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: newpatchset
      unsatisfied_requirement
      satisfied_requirement
      open
      diffy

      Austin Clements (Gerrit)

      unread,
      Aug 6, 2026, 3:07:30 PM (yesterday) Aug 6
      to Austin Clements, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
      Attention needed from David Chase

      Austin Clements uploaded new patchset

      Austin Clements uploaded patch set #4 to this change.
      Open in Gerrit

      Related details

      Attention is currently required from:
      • David Chase
      Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement is not satisfiedTryBots-Pass
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: newpatchset
      Gerrit-Project: go
      Gerrit-Branch: dev.simd
      Gerrit-Change-Id: Iaadde2d898aadf6076c06b214127d61fa26db7b8
      Gerrit-Change-Number: 799980
      Gerrit-PatchSet: 4
      unsatisfied_requirement
      satisfied_requirement
      open
      diffy

      Austin Clements (Gerrit)

      unread,
      5:16 PM (5 hours ago) 5:16 PM
      to David Chase, Junyang Shao, goph...@pubsubhelper.golang.org, Cherry Mui, Austin Clements, golang-co...@googlegroups.com
      Attention needed from David Chase and Junyang Shao

      Austin Clements has uploaded the change for review

      Austin Clements would like David Chase and Junyang Shao to review this change.

      Commit message

      simd/archsimd/_gen/cmd/refgen: generate reference implementation

      This generates a reference implementation of the SIMD API backed by
      the spec implementation.
      Change-Id: Iaadde2d898aadf6076c06b214127d61fa26db7b8

      Change diff

      diff --git a/src/simd/archsimd/_gen/cmd/refgen/main.go b/src/simd/archsimd/_gen/cmd/refgen/main.go
      new file mode 100644
      index 0000000..6db306a
      --- /dev/null
      +++ b/src/simd/archsimd/_gen/cmd/refgen/main.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.
      +
      +// refgen produces a reference implementation of the SIMD API backed by the spec
      +// implementation.
      +package main
      +
      +import (
      + "bytes"
      + "cmp"
      + "flag"
      + "fmt"
      + "go/format"
      + "go/types"
      +	"log"
      + "maps"
      + "os"
      + "slices"
      + "strings"
      +
      +	"simd/archsimd/_gen/specgen"
      + "simd/archsimd/_gen/specgen/specexpr"
      +	src := new(srcWriter)
      + defer src.flushOrDie()
      +		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 (w *srcWriter) flushOrDie() {
      + // This may be used as a defer. On panic, flush what we have and repanic.
      + if p := recover(); p != nil {
      + os.Stdout.Write(w.Bytes())
      + panic(p)
      + }
      +
      + out, err := format.Source(w.Bytes())

      + if err != nil {
      +		os.Stdout.Write(w.Bytes())

      + fmt.Fprintln(os.Stderr, err)
      + os.Exit(1)
      + }
      +	os.Stdout.Write(out)
      + w.Reset()

      +}
      +
      +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 ""
      +}
      +
      index 0a2c0cc..f55c5cf 100644
      --- a/src/simd/archsimd/_gen/specgen/api.go
      +++ b/src/simd/archsimd/_gen/specgen/api.go
      @@ -6,6 +6,7 @@

      import (
      "fmt"
      + "go/types"
      "simd/archsimd/_gen/specgen/specexpr"
      index 06e3376..6f6f195 100644

      --- a/src/simd/archsimd/_gen/specgen/expand.go
      +++ b/src/simd/archsimd/_gen/specgen/expand.go
      @@ -75,6 +75,7 @@
      if fn == nil {
      continue
      }
      + fn.typeParamVars = typeParamVars

      funcs = append(funcs, fn)
      }
      @@ -89,6 +90,9 @@
      func (sFn *specFunc) instantiate(ctx context, b *specexpr.Bindings, argGet map[*types.Var]func(*specexpr.Bindings) specexpr.Type) *Func {
      var f Func

      + f.specFunc = sFn
      + f.instance = b
      +
      // Function or method?
      var method bool
      if len(sFn.Params) > 0 {
      diff --git a/src/simd/archsimd/_gen/specgen/loadspec.go b/src/simd/archsimd/_gen/specgen/loadspec.go
      index 4b96e30..84e9f6c 100644
      --- a/src/simd/archsimd/_gen/specgen/loadspec.go
      +++ b/src/simd/archsimd/_gen/specgen/loadspec.go
      @@ -25,6 +25,9 @@

      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
      @@ -186,17 +189,21 @@


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

      @@ -212,6 +219,8 @@

      Funcs: funcs,
      TypeElems: typeElems,
      TypeWidths: typeWidths,
      + ElemTypes: elemTypes,
      + WidthTypes: widthTypes,
      VecType: vecType,
      ArrayType: arrayType,
      UintNType: uintNType,
      diff --git a/src/simd/archsimd/_gen/specgen/types.go b/src/simd/archsimd/_gen/specgen/types.go
      index 07b0a7b..4c1551c 100644
      --- a/src/simd/archsimd/_gen/specgen/types.go
      +++ b/src/simd/archsimd/_gen/specgen/types.go
      @@ -162,6 +162,41 @@
      diff --git a/src/simd/internal/spec/TASKS.md b/src/simd/internal/spec/TASKS.md
      index 37c3fb7..532f061 100644
      --- a/src/simd/internal/spec/TASKS.md
      +++ b/src/simd/internal/spec/TASKS.md
      @@ -25,7 +25,7 @@
      Maybe the hand-written code still lives directly in archsimd, but as
      unexported function, and the generator writes the trivial exported API glue
      for these.
      -- [ ] Generate a full reference implementation for testing that provides the
      - SIMD API but just wraps the spec package.
      +- [x] Generate a full reference implementation for testing that provides the
      + SIMD API but just wraps the spec package. (Done: _gen/cmd/refgen)
      - [ ] Generate conformance tests of the archsimd API against the spec testing
      layer.

      Change information

      Files:
        • A src/simd/archsimd/_gen/cmd/refgen/main.go
        • M src/simd/archsimd/_gen/specgen/api.go
        • M src/simd/archsimd/_gen/specgen/expand.go
        • M src/simd/archsimd/_gen/specgen/loadspec.go
        • M src/simd/archsimd/_gen/specgen/types.go
        • M src/simd/internal/spec/TASKS.md
        Change size: L
        Delta: 6 files changed, 373 insertions(+), 2 deletions(-)
        Open in Gerrit

        Related details

        Attention is currently required from:
        • David Chase
        • Junyang Shao
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newchange
        Gerrit-Project: go
        Gerrit-Branch: master
        Gerrit-Change-Id: Iaadde2d898aadf6076c06b214127d61fa26db7b8
        Gerrit-Change-Number: 812064
        Gerrit-PatchSet: 1
        Gerrit-Owner: Austin Clements <aus...@google.com>
        Gerrit-Reviewer: Austin Clements <aus...@google.com>
        Gerrit-Reviewer: David Chase <drc...@google.com>
        Gerrit-Reviewer: Junyang Shao <shaoj...@google.com>
        Gerrit-CC: Cherry Mui <cher...@google.com>
        Gerrit-Attention: David Chase <drc...@google.com>
        Gerrit-Attention: Junyang Shao <shaoj...@google.com>
        unsatisfied_requirement
        satisfied_requirement
        open
        diffy
        Reply all
        Reply to author
        Forward
        0 new messages