go/src/simd/genfiles.go

288 lines
7.9 KiB
Go
Raw Normal View History

[dev.simd] simd: move test generation into Go repo This pairs with CL 689275 which removes test generation from simdgen This uses generics and attempts to encode the tests as compactly as possible. Some files, *_helpers_test.go, are generated. Use t.Helper() to get the line number right for a failure. Adds helper error return values and early exits to only report a single test failure per operations and vector shape, for the generated test failures. Include the entire got and wanted vectors for that failure. Provide an option to include the input vectors to failures, also report the type of the test. Sample failure test output (obtained by intentionally breaking the "want" value for AndNot): === RUN TestAndNot binary_test.go:214: For int16 vector elements: binary_test.go:214: got =[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] binary_test.go:214: want=[-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1] binary_test.go:214: x=[1 -1 0 2 4 8 1024 3 5 7 11 13 3000 5555 7777 11111] binary_test.go:214: y=[1 -1 0 2 4 8 1024 3 5 7 11 13 3000 5555 7777 11111] binary_test.go:214: at index 0, got=0, want=-1 binary_test.go:215: For int16 vector elements: binary_test.go:215: got =[0 0 0 0 0 0 0 0] binary_test.go:215: want=[-1 -1 -1 -1 -1 -1 -1 -1] binary_test.go:215: x=[1 -1 0 2 4 8 1024 3] binary_test.go:215: y=[1 -1 0 2 4 8 1024 3] binary_test.go:215: at index 0, got=0, want=-1 binary_test.go:216: For int32 vector elements: binary_test.go:216: got =[0 0 0 0] binary_test.go:216: want=[-1 -1 -1 -1] binary_test.go:216: x=[1 -1 0 2] binary_test.go:216: y=[1 -1 0 2] binary_test.go:216: at index 0, got=0, want=-1 (etc) Change-Id: I0f6ee8390ebe7a2333002e9415b4d71527fa3c38 Reviewed-on: https://go-review.googlesource.com/c/go/+/686057 Reviewed-by: Junyang Shao <shaojunyang@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
2025-07-07 17:48:24 -04:00
// Copyright 2025 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.
//go:build ignore
package main
// this generates type-instantiated boilerplate code for
// slice operations and tests
import (
"bytes"
"flag"
"fmt"
"go/format"
"io"
"os"
"strings"
"text/template"
)
func oneTemplate(t *template.Template, baseType string, width, count int, out io.Writer) {
b := width * count
if b < 128 || b > 512 {
return
}
BaseType := strings.ToUpper(baseType[:1]) + baseType[1:]
eType := fmt.Sprintf("%s%d", baseType, width)
wxc := fmt.Sprintf("%dx%d", width, count)
vType := fmt.Sprintf("%s%s", BaseType, wxc)
aOrAn := "a"
if strings.Contains("aeiou", baseType[:1]) {
aOrAn = "an"
}
t.Execute(out, struct {
Vec string
AOrAn string
Width int
Count int
WxC string
Type string
}{
Vec: vType,
AOrAn: aOrAn,
Width: width,
Count: count,
WxC: wxc,
Type: eType,
})
}
func forTemplates(t *template.Template, out io.Writer) {
vecs := []int{128, 256, 512}
ints := []int{8, 16, 32, 64}
floats := []int{32, 64}
for _, v := range vecs {
for _, w := range ints {
c := v / w
oneTemplate(t, "int", w, c, out)
oneTemplate(t, "uint", w, c, out)
}
for _, w := range floats {
c := v / w
oneTemplate(t, "float", w, c, out)
}
}
}
func prologue(s string, out io.Writer) {
fmt.Fprintf(out,
`// Code generated by '%s'; DO NOT EDIT.
//go:build goexperiment.simd
package simd
`, s)
}
func testPrologue(t, s string, out io.Writer) {
fmt.Fprintf(out,
`// Code generated by '%s'; DO NOT EDIT.
//go:build goexperiment.simd
// This file contains functions testing %s.
// Each function in this file is specialized for a
// particular simd type <BaseType><Width>x<Count>.
package simd_test
import (
"simd"
"testing"
)
`, s, t)
}
func curryTestPrologue(t string) func(s string, out io.Writer) {
return func(s string, out io.Writer) {
testPrologue(t, s, out)
}
}
// //go:noescape
// func LoadUint8x16Slice(s []uint8) Uint8x16 {
// return LoadUint8x16((*[16]uint8)(s[:16]))
// }
// //go:noescape
// func (x Uint8x16) StoreSlice(s []uint8) {
// x.Store((*[16]uint8)(s[:16]))
// }
func templateOf(name, temp string) *template.Template {
return template.Must(template.New(name).Parse(temp))
}
var sliceTemplate = templateOf("slice", `
// Load{{.Vec}}Slice loads {{.AOrAn}} {{.Vec}} from a slice of at least {{.Count}} {{.Type}}s
func Load{{.Vec}}Slice(s []{{.Type}}) {{.Vec}} {
return Load{{.Vec}}((*[{{.Count}}]{{.Type}})(s))
}
// StoreSlice stores x into a slice of at least {{.Count}} {{.Type}}s
func (x {{.Vec}}) StoreSlice(s []{{.Type}}) {
x.Store((*[{{.Count}}]{{.Type}})(s))
}
`)
var unaryTemplate = templateOf("unary_helpers", `
// test{{.Vec}}Unary tests the simd unary method f against the expected behavior generated by want
func test{{.Vec}}Unary(t *testing.T, f func(_ simd.{{.Vec}}) simd.{{.Vec}}, want func(_ []{{.Type}}) []{{.Type}}) {
n := {{.Count}}
t.Helper()
forSlice(t, {{.Type}}s, n, func(x []{{.Type}}) bool {
t.Helper()
a := simd.Load{{.Vec}}Slice(x)
g := make([]{{.Type}}, n)
f(a).StoreSlice(g)
w := want(x)
return checkSlicesLogInput(t, g, w, func() {t.Helper(); t.Logf("x=%v", x)})
})
}
`)
var binaryTemplate = templateOf("binary_helpers", `
// test{{.Vec}}Binary tests the simd binary method f against the expected behavior generated by want
func test{{.Vec}}Binary(t *testing.T, f func(_, _ simd.{{.Vec}}) simd.{{.Vec}}, want func(_, _ []{{.Type}}) []{{.Type}}) {
n := {{.Count}}
t.Helper()
forSlicePair(t, {{.Type}}s, n, func(x, y []{{.Type}}) bool {
t.Helper()
a := simd.Load{{.Vec}}Slice(x)
b := simd.Load{{.Vec}}Slice(y)
g := make([]{{.Type}}, n)
f(a, b).StoreSlice(g)
w := want(x, y)
return checkSlicesLogInput(t, g, w, func() {t.Helper(); t.Logf("x=%v", x); t.Logf("y=%v", y); })
})
}
`)
var ternaryTemplate = templateOf("ternary_helpers", `
// test{{.Vec}}Ternary tests the simd ternary method f against the expected behavior generated by want
func test{{.Vec}}Ternary(t *testing.T, f func(_, _, _ simd.{{.Vec}}) simd.{{.Vec}}, want func(_, _, _ []{{.Type}}) []{{.Type}}) {
n := {{.Count}}
t.Helper()
forSliceTriple(t, {{.Type}}s, n, func(x, y, z []{{.Type}}) bool {
t.Helper()
a := simd.Load{{.Vec}}Slice(x)
b := simd.Load{{.Vec}}Slice(y)
c := simd.Load{{.Vec}}Slice(z)
g := make([]{{.Type}}, n)
f(a, b, c).StoreSlice(g)
w := want(x, y, z)
return checkSlicesLogInput(t, g, w, func() {t.Helper(); t.Logf("x=%v", x); t.Logf("y=%v", y); t.Logf("z=%v", z); })
})
}
`)
var compareTemplate = templateOf("compare_helpers", `
// test{{.Vec}}Compare tests the simd comparison method f against the expected behavior generated by want
func test{{.Vec}}Compare(t *testing.T, f func(_, _ simd.{{.Vec}}) simd.Mask{{.WxC}}, want func(_, _ []{{.Type}}) []int64) {
n := {{.Count}}
t.Helper()
forSlicePair(t, {{.Type}}s, n, func(x, y []{{.Type}}) bool {
t.Helper()
a := simd.Load{{.Vec}}Slice(x)
b := simd.Load{{.Vec}}Slice(y)
g := make([]int{{.Width}}, n)
f(a, b).AsInt{{.WxC}}().StoreSlice(g)
w := want(x, y)
return checkSlicesLogInput(t, s64(g), w, func() {t.Helper(); t.Logf("x=%v", x); t.Logf("y=%v", y); })
})
}
`)
// TODO this has not been tested yet.
var compareMaskedTemplate = templateOf("comparemasked_helpers", `
// test{{.Vec}}CompareMasked tests the simd masked comparison method f against the expected behavior generated by want
// The mask is applied to the output of want; anything not in the mask, is zeroed.
func test{{.Vec}}CompareMasked(t *testing.T,
f func(_, _ simd.{{.Vec}}, m simd.Mask{{.WxC}}) simd.Mask{{.WxC}},
want func(_, _ []{{.Type}}) []int64) {
n := {{.Count}}
t.Helper()
forSlicePairMasked(t, {{.Type}}s, n, func(x, y []{{.Type}}, m []bool) bool {
t.Helper()
a := simd.Load{{.Vec}}Slice(x)
b := simd.Load{{.Vec}}Slice(y)
k := simd.LoadInt{{.WxC}}Slice(toVect[int{{.Width}}](m)).AsMask{{.WxC}}()
g := make([]int{{.Width}}, n)
f(a, b, k).AsInt{{.WxC}}().StoreSlice(g)
w := want(x, y)
for i := range m {
if !m[i] {
w[i] = 0
}
}
return checkSlicesLogInput(t, s64(g), w, func() {t.Helper(); t.Logf("x=%v", x); t.Logf("y=%v", y); t.Logf("m=%v", m); })
})
}
`)
func main() {
sl := flag.String("sl", "slice_amd64.go", "file name for slice operations")
bh := flag.String("bh", "binary_helpers_test.go", "file name for binary test helpers")
uh := flag.String("uh", "unary_helpers_test.go", "file name for unary test helpers")
th := flag.String("th", "ternary_helpers_test.go", "file name for ternary test helpers")
ch := flag.String("ch", "compare_helpers_test.go", "file name for compare test helpers")
cmh := flag.String("cmh", "comparemasked_helpers_test.go", "file name for compare-masked test helpers")
flag.Parse()
if *sl != "" {
one(*sl, prologue, sliceTemplate)
}
if *uh != "" {
one(*uh, curryTestPrologue("unary simd methods"), unaryTemplate)
}
if *bh != "" {
one(*bh, curryTestPrologue("binary simd methods"), binaryTemplate)
}
if *th != "" {
one(*th, curryTestPrologue("ternary simd methods"), ternaryTemplate)
}
if *ch != "" {
one(*ch, curryTestPrologue("simd methods that compare two operands"), compareTemplate)
}
if *cmh != "" {
one(*cmh, curryTestPrologue("simd methods that compare two operands under a mask"), compareMaskedTemplate)
}
}
func one(filename string, prologue func(s string, out io.Writer), t *template.Template) {
if filename == "" {
return
}
ofile := os.Stdout
if filename != "-" {
var err error
ofile, err = os.Create(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not create the output file %s for the generated code, %v", filename, err)
os.Exit(1)
}
}
out := new(bytes.Buffer)
prologue("go run genfiles.go", out)
forTemplates(t, out)
b, err := format.Source(out.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "There was a problem formatting the generated code for %s, %v", filename, err)
os.Exit(1)
} else {
ofile.Write(b)
ofile.Close()
}
}