blob: f43e1eb788abf6591954ec7218366e0762e5971c [file] [log] [blame]
// 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.
package main
import (
"bytes"
"fmt"
"slices"
"sort"
"strings"
)
type simdType struct {
Name string // The go type name of this simd type, for example Int32x4.
Lanes int // The number of elements in this vector/mask.
Base string // The element's type, like for Int32x4 it will be int32.
Fields string // The struct fields, it should be right formatted.
Type string // Either "mask" or "vreg"
VectorCounterpart string // For mask use only: just replacing the "Mask" in [simdType.Name] with "Int"
ReshapedVectorWithAndOr string // For mask use only: vector AND and OR are only available in some shape with element width 32.
Size int // The size of the type
}
func compareSimdTypes(x, y simdType) int {
// "vreg" then "mask"
if c := -compareNatural(x.Type, y.Type); c != 0 {
return c
}
// want "flo" < "int" < "uin" (and then 8 < 16 < 32 < 64),
// not "int16" < "int32" < "int64" < "int8")
// so limit comparison to first 3 bytes in string.
if c := compareNatural(x.Base[:3], y.Base[:3]); c != 0 {
return c
}
// base type size, 8 < 16 < 32 < 64
if c := x.Size/x.Lanes - y.Size/y.Lanes; c != 0 {
return c
}
// vector size last
return x.Size - y.Size
}
type simdTypeMap map[int][]simdType
type simdTypePair struct {
Tsrc simdType
Tdst simdType
}
func compareSimdTypePairs(x, y simdTypePair) int {
c := compareSimdTypes(x.Tsrc, y.Tsrc)
if c != 0 {
return c
}
return compareSimdTypes(x.Tdst, y.Tdst)
}
const simdTypesTemplates = `{{define "fileHeader"}}// Code generated by x/arch/internal/simdgen using 'go run . -xedPath $XED_PATH -o godefs -goroot $GOROOT go.yaml types.yaml categories.yaml'; DO NOT EDIT.
//go:build goexperiment.simd
package simd
{{end}}
{{define "sizeTmpl"}}
// v{{.}} is a tag type that tells the compiler that this is really {{.}}-bit SIMD
type v{{.}} struct {
_{{.}} struct{}
}
{{end}}
{{define "typeTmpl"}}
// {{.Name}} is a {{.Size}}-bit SIMD vector of {{.Lanes}} {{.Base}}
type {{.Name}} struct {
{{.Fields}}
}
{{- if ne .Type "mask"}}
// Len returns the number of elements in a {{.Name}}
func (x {{.Name}}) Len() int { return {{.Lanes}} }
// Load{{.Name}} loads a {{.Name}} from an array
//
//go:noescape
func Load{{.Name}}(y *[{{.Lanes}}]{{.Base}}) {{.Name}}
// Store stores a {{.Name}} to an array
//
//go:noescape
func (x {{.Name}}) Store(y *[{{.Lanes}}]{{.Base}})
{{- end}}
{{end}}
`
const simdStubsTmpl = `{{define "fileHeader"}}// Code generated by x/arch/internal/simdgen using 'go run . -xedPath $XED_PATH -o godefs -goroot $GOROOT go.yaml types.yaml categories.yaml'; DO NOT EDIT.
//go:build goexperiment.simd
package simd
{{end}}
{{define "op1"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op0NameAndType "x"}}) {{.Go}}() {{.GoType}}
{{end}}
{{define "op2"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op0NameAndType "x"}}) {{.Go}}({{.Op1NameAndType "y"}}) {{.GoType}}
{{end}}
{{define "op2_21"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.Op0NameAndType "y"}}) {{.GoType}}
{{end}}
{{define "op2_21Type1"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.Op0NameAndType "y"}}) {{.GoType}}
{{end}}
{{define "op3"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op0NameAndType "x"}}) {{.Go}}({{.Op1NameAndType "y"}}, {{.Op2NameAndType "z"}}) {{.GoType}}
{{end}}
{{define "op3_31"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op2NameAndType "x"}}) {{.Go}}({{.Op1NameAndType "y"}}, {{.Op0NameAndType "z"}}) {{.GoType}}
{{end}}
{{define "op3_21"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.Op0NameAndType "y"}}, {{.Op2NameAndType "z"}}) {{.GoType}}
{{end}}
{{define "op3_21Type1"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.Op0NameAndType "y"}}, {{.Op2NameAndType "z"}}) {{.GoType}}
{{end}}
{{define "op3_231Type1"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.Op2NameAndType "y"}}, {{.Op0NameAndType "z"}}) {{.GoType}}
{{end}}
{{define "op2VecAsScalar"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op0NameAndType "x"}}) {{.Go}}(y uint{{(index .In 1).TreatLikeAScalarOfSize}}) {{(index .Out 0).Go}}
{{end}}
{{define "op3VecAsScalar"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op0NameAndType "x"}}) {{.Go}}(y uint{{(index .In 1).TreatLikeAScalarOfSize}}, {{.Op2NameAndType "z"}}) {{(index .Out 0).Go}}
{{end}}
{{define "op4"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op0NameAndType "x"}}) {{.Go}}({{.Op1NameAndType "y"}}, {{.Op2NameAndType "z"}}, {{.Op3NameAndType "u"}}) {{.GoType}}
{{end}}
{{define "op4_231Type1"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.Op2NameAndType "y"}}, {{.Op0NameAndType "z"}}, {{.Op3NameAndType "u"}}) {{.GoType}}
{{end}}
{{define "op4_31"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op2NameAndType "x"}}) {{.Go}}({{.Op1NameAndType "y"}}, {{.Op0NameAndType "z"}}, {{.Op3NameAndType "u"}}) {{.GoType}}
{{end}}
{{define "op1Imm8"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// {{.ImmName}} is expected to be a constant, non-constant value will trigger a runtime panic.
//
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.ImmName}} uint8) {{.GoType}}
{{end}}
{{define "op2Imm8"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// {{.ImmName}} is expected to be a constant, non-constant value will trigger a runtime panic.
//
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.ImmName}} uint8, {{.Op2NameAndType "y"}}) {{.GoType}}
{{end}}
{{define "op2Imm8_2I"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// {{.ImmName}} is expected to be a constant, non-constant value will trigger a runtime panic.
//
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.Op2NameAndType "y"}}, {{.ImmName}} uint8) {{.GoType}}
{{end}}
{{define "op3Imm8"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// {{.ImmName}} is expected to be a constant, non-constant value will trigger a runtime panic.
//
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.ImmName}} uint8, {{.Op2NameAndType "y"}}, {{.Op3NameAndType "z"}}) {{.GoType}}
{{end}}
{{define "op3Imm8_2I"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// {{.ImmName}} is expected to be a constant, non-constant value will trigger a runtime panic.
//
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.Op2NameAndType "y"}}, {{.ImmName}} uint8, {{.Op3NameAndType "z"}}) {{.GoType}}
{{end}}
{{define "op4Imm8"}}
{{if .Documentation}}{{.Documentation}}
//{{end}}
// {{.ImmName}} is expected to be a constant, non-constant value will trigger a runtime panic.
//
// Asm: {{.Asm}}, CPU Feature: {{.CPUFeature}}
func ({{.Op1NameAndType "x"}}) {{.Go}}({{.ImmName}} uint8, {{.Op2NameAndType "y"}}, {{.Op3NameAndType "z"}}, {{.Op4NameAndType "u"}}) {{.GoType}}
{{end}}
{{define "vectorConversion"}}
// {{.Tdst.Name}} converts from {{.Tsrc.Name}} to {{.Tdst.Name}}
func (from {{.Tsrc.Name}}) As{{.Tdst.Name}}() (to {{.Tdst.Name}})
{{end}}
{{define "mask"}}
// converts from {{.Name}} to {{.VectorCounterpart}}
func (from {{.Name}}) As{{.VectorCounterpart}}() (to {{.VectorCounterpart}})
// converts from {{.VectorCounterpart}} to {{.Name}}
func (from {{.VectorCounterpart}}) As{{.Name}}() (to {{.Name}})
func (x {{.Name}}) And(y {{.Name}}) {{.Name}}
func (x {{.Name}}) Or(y {{.Name}}) {{.Name}}
{{end}}
`
const simdTestsWrapperTmpl = `{{define "fileHeader"}}// Code generated by x/arch/internal/simdgen using 'go run . -xedPath $XED_PATH -o godefs -goroot $GOROOT go.yaml types.yaml categories.yaml'; DO NOT EDIT.
//go:build goexperiment.simd
package simd_test
import (
"simd"
"testing"
)
{{end}}
{{define "op"}}
func test{{.OpShape}}(t *testing.T, {{.BaseArgDefList}}, want []{{.ResBaseType}}, which string) {
t.Helper()
var gotv simd.{{.ResVecType}}
got := make([]{{.ResBaseType}}, len(want)){{range $i, $a := .ArgVecTypes}}
vec{{$i}} := simd.Load{{$a}}Slice(v{{$i}}){{end}}
switch which {
{{range .Ops}}case "{{.}}":
gotv = vec0.{{.}}({{$.VecArgList}}){{$.OptionalMaskToInt}}
{{end}}
default:
t.Errorf("Unknown method: {{.Arg0VecType}}.%s", which)
}
gotv.StoreSlice(got)
for i := range len(want) {
if got[i] != want[i] {
t.Errorf("Result at %d incorrect: want %v, got %v", i, want[i], got[i])
}
}
}
{{end}}
{{define "untestedOpHeader"}}
/* The operations below cannot be tested via wrappers, please test them directly */
{{end}}
{{define "untestedOp"}}
// {{.}}{{end}}
`
// writeSIMDTestsWrapper generates the test wrappers and writes it to simd_amd64_testwrappers.go
// within the specified directory.
func writeSIMDTestsWrapper(ops []Operation) *bytes.Buffer {
t := templateOf(simdTestsWrapperTmpl, "simdTestWrappers")
buffer := new(bytes.Buffer)
if err := t.ExecuteTemplate(buffer, "fileHeader", nil); err != nil {
panic(fmt.Errorf("failed to execute fileHeader template: %w", err))
}
// The comment shows an example of Uint8x64.Add
type opData struct {
OpShape string // "Uint8x64Uint8x64Uint8x64"
BaseArgDefList string // "v0 uint8[], v1 uint8[]"
VecArgList string // "vec1"
ResBaseType string // "uint8"
ResVecType string // "Uint8x64"
Arg0VecType string // "Uint8x64"
ArgVecTypes []string // ["Uint8x64", "Uint8x64"]
OptionalMaskToInt string // ".AsInt8x64()" or ""
Ops []string // ["Add", "Sub"]
}
opsByShape := make(map[string]opData)
opsSkipped := map[string]struct{}{}
outerLoop:
for _, o := range ops {
_, _, _, immType, gOp := o.shape()
if immType == VarImm || immType == ConstVarImm {
// Operations with variable immediates should be called directly
// instead of through wrappers.
opsSkipped[o.Go] = struct{}{}
continue
}
if vasIdx, err := checkVecAsScalar(o); err != nil {
panic(err)
} else if vasIdx != -1 {
// TODO: these could be tested via wrappers, implement this.
opsSkipped[o.Go] = struct{}{}
continue
}
if o.OperandOrder != nil {
// We need to check if the customize order change the function signature.
// It is only safe to proceed generating the test wrappers if the function
// signature stays the same.
// Filtering out unqualified cases as a hack now, this test wrapper
// infrastrcuture should be changing soon so it should be fine.
switch *o.OperandOrder {
case "21":
// No op because it's only set in AndNot, and opr[2] and opr[1] has the same shape
default:
opsSkipped[o.Go] = struct{}{}
continue outerLoop
}
}
var shape string
var baseArgDefList []string
var vecArgList []string
var argVecTypes []string
var vec string
var vecOp Operand
allSameVec := true
masked := strings.HasSuffix(gOp.Go, "Masked")
skippedMaskCnt := 0
vecCnt := 0
for i, in := range gOp.In {
baseArgDefList = append(baseArgDefList, fmt.Sprintf("v%d []%s%d", i, *in.Base, *in.ElemBits))
if i != 0 {
maskConversion := ""
if in.Class == "mask" {
maskConversion = fmt.Sprintf(".As%s()", *in.Go)
}
vecArgList = append(vecArgList, fmt.Sprintf("vec%d%s", i, maskConversion))
}
// gOp will only have either mask or vreg operand, so the following check
// is sufficient to detect whether it's a pure vreg or masked pure vreg operation
// with all the same vectors.
if in.Class == "mask" {
if masked && skippedMaskCnt == 0 {
skippedMaskCnt++
} else {
allSameVec = false
}
} else {
if len(vec) > 0 {
if vec != *in.Go {
allSameVec = false
}
}
vecCnt++
vec = *in.Go
vecOp = in
}
shape += *in.Go
argVecTypes = append(argVecTypes, strings.ReplaceAll(*in.Go, "Mask", "Int"))
}
isCompare := false
isWiden := false
outOp := gOp.Out[0]
if *outOp.Go != vec {
if allSameVec && outOp.Class == "mask" && *outOp.Bits == *vecOp.Bits && *outOp.Lanes == *vecOp.Lanes {
isCompare = true
}
if allSameVec && outOp.Class == "vreg" && *outOp.Bits == *vecOp.Bits && *outOp.Base == *vecOp.Base && *outOp.Lanes == *vecOp.Lanes/2 {
isWiden = true
}
if !isCompare && !isWiden {
allSameVec = false
}
}
shape += *gOp.Out[0].Go
if allSameVec {
numToName := map[int]string{1: "Unary", 2: "Binary", 3: "Ternary"}
if _, ok := numToName[vecCnt]; !ok {
panic(fmt.Errorf("unknown shape: %s", shape))
}
shape = vec + numToName[vecCnt]
if masked {
shape += "Masked"
}
if isCompare {
if vecCnt == 2 {
// Remove "Binary"
shape = strings.ReplaceAll(shape, "Binary", "")
}
shape += "Compare"
}
if isWiden {
shape += "Widen"
}
}
optionalMaskToInt := ""
if gOp.Out[0].Class == "mask" {
optionalMaskToInt = fmt.Sprintf(".As%s()", strings.ReplaceAll(*gOp.Out[0].Go, "Mask", "Int"))
}
if _, ok := opsByShape[shape]; !ok {
opsByShape[shape] = opData{
OpShape: shape,
BaseArgDefList: strings.Join(baseArgDefList, ", "),
VecArgList: strings.Join(vecArgList, ", "),
ResBaseType: fmt.Sprintf("%s%d", *gOp.Out[0].Base, *gOp.Out[0].ElemBits),
ResVecType: strings.ReplaceAll(*gOp.Out[0].Go, "Mask", "Int"),
Arg0VecType: *gOp.In[0].Go,
ArgVecTypes: argVecTypes,
OptionalMaskToInt: optionalMaskToInt,
}
}
data := opsByShape[shape]
data.Ops = append(data.Ops, gOp.Go)
opsByShape[shape] = data
}
compareOpData := func(x, y opData) int {
return compareNatural(x.OpShape, y.OpShape)
}
data := make([]opData, 0)
for _, d := range opsByShape {
slices.SortFunc(d.Ops, compareNatural)
data = append(data, d)
}
slices.SortFunc(data, compareOpData)
for _, d := range data {
if err := t.ExecuteTemplate(buffer, "op", d); err != nil {
panic(fmt.Errorf("failed to execute op template for op shape %s: %w", d.OpShape, err))
}
}
if len(opsSkipped) != 0 {
if err := t.ExecuteTemplate(buffer, "untestedOpHeader", nil); err != nil {
panic(fmt.Errorf("failed to execute untestedOpHeader"))
}
opsK := []string{}
for k := range opsSkipped {
opsK = append(opsK, k)
}
slices.SortFunc(opsK, strings.Compare)
for _, k := range opsK {
if err := t.ExecuteTemplate(buffer, "untestedOp", k); err != nil {
panic(fmt.Errorf("failed to execute untestedOp"))
}
}
}
return buffer
}
// parseSIMDTypes groups go simd types by their vector sizes, and
// returns a map whose key is the vector size, value is the simd type.
func parseSIMDTypes(ops []Operation) simdTypeMap {
// TODO: maybe instead of going over ops, let's try go over types.yaml.
ret := map[int][]simdType{}
seen := map[string]struct{}{}
processArg := func(arg Operand) {
if arg.Class == "immediate" || arg.Class == "greg" {
// Immediates are not encoded as vector types.
return
}
if _, ok := seen[*arg.Go]; ok {
return
}
seen[*arg.Go] = struct{}{}
lanes := *arg.Lanes
base := fmt.Sprintf("%s%d", *arg.Base, *arg.ElemBits)
tagFieldNameS := fmt.Sprintf("%sx%d", base, lanes)
tagFieldS := fmt.Sprintf("%s v%d", tagFieldNameS, *arg.Bits)
valFieldS := fmt.Sprintf("vals%s[%d]%s", strings.Repeat(" ", len(tagFieldNameS)-3), lanes, base)
fields := fmt.Sprintf("\t%s\n\t%s", tagFieldS, valFieldS)
if arg.Class == "mask" {
vectorCounterpart := strings.ReplaceAll(*arg.Go, "Mask", "Int")
reshapedVectorWithAndOr := fmt.Sprintf("Int32x%d", *arg.Bits/32)
ret[*arg.Bits] = append(ret[*arg.Bits], simdType{*arg.Go, lanes, base, fields, arg.Class, vectorCounterpart, reshapedVectorWithAndOr, *arg.Bits})
// In case the vector counterpart of a mask is not present, put its vector counterpart typedef into the map as well.
if _, ok := seen[vectorCounterpart]; !ok {
seen[vectorCounterpart] = struct{}{}
ret[*arg.Bits] = append(ret[*arg.Bits], simdType{vectorCounterpart, lanes, base, fields, "vreg", "", "", *arg.Bits})
}
} else {
ret[*arg.Bits] = append(ret[*arg.Bits], simdType{*arg.Go, lanes, base, fields, arg.Class, "", "", *arg.Bits})
}
}
for _, op := range ops {
for _, arg := range op.In {
processArg(arg)
}
for _, arg := range op.Out {
processArg(arg)
}
}
return ret
}
func vConvertFromTypeMap(typeMap simdTypeMap) []simdTypePair {
v := []simdTypePair{}
for _, ts := range typeMap {
for i, tsrc := range ts {
for j, tdst := range ts {
if i != j && tsrc.Type == tdst.Type && tsrc.Type == "vreg" &&
tsrc.Lanes > 1 && tdst.Lanes > 1 {
v = append(v, simdTypePair{tsrc, tdst})
}
}
}
}
slices.SortFunc(v, compareSimdTypePairs)
return v
}
func masksFromTypeMap(typeMap simdTypeMap) []simdType {
m := []simdType{}
for _, ts := range typeMap {
for _, tsrc := range ts {
if tsrc.Type == "mask" {
m = append(m, tsrc)
}
}
}
slices.SortFunc(m, compareSimdTypes)
return m
}
func typesFromTypeMap(typeMap simdTypeMap) []simdType {
m := []simdType{}
for _, ts := range typeMap {
for _, tsrc := range ts {
if tsrc.Lanes > 1 {
m = append(m, tsrc)
}
}
}
slices.SortFunc(m, compareSimdTypes)
return m
}
// writeSIMDTypes generates the simd vector types into a bytes.Buffer
func writeSIMDTypes(typeMap simdTypeMap) *bytes.Buffer {
t := templateOf(simdTypesTemplates, "types_amd64")
buffer := new(bytes.Buffer)
if err := t.ExecuteTemplate(buffer, "fileHeader", nil); err != nil {
panic(fmt.Errorf("failed to execute fileHeader template: %w", err))
}
sizes := make([]int, 0, len(typeMap))
for size, types := range typeMap {
slices.SortFunc(types, compareSimdTypes)
sizes = append(sizes, size)
}
sort.Ints(sizes)
for _, size := range sizes {
if size <= 64 {
// these are scalar
continue
}
if err := t.ExecuteTemplate(buffer, "sizeTmpl", size); err != nil {
panic(fmt.Errorf("failed to execute size template for size %d: %w", size, err))
}
for _, typeDef := range typeMap[size] {
if typeDef.Lanes == 1 {
continue
}
if err := t.ExecuteTemplate(buffer, "typeTmpl", typeDef); err != nil {
panic(fmt.Errorf("failed to execute type template for type %s: %w", typeDef.Name, err))
}
}
}
return buffer
}
// writeSIMDStubs generates the simd vector intrinsic stubs and writes it to stubs_amd64.go
// within the specified directory.
func writeSIMDStubs(ops []Operation, typeMap simdTypeMap) *bytes.Buffer {
t := templateOf(simdStubsTmpl, "simdStubs")
buffer := new(bytes.Buffer)
if err := t.ExecuteTemplate(buffer, "fileHeader", nil); err != nil {
panic(fmt.Errorf("failed to execute fileHeader template: %w", err))
}
slices.SortFunc(ops, compareOperations)
for i, op := range ops {
idxVecAsScalar, err := checkVecAsScalar(op)
if err != nil {
panic(err)
}
if s, op, err := classifyOp(op); err == nil {
if idxVecAsScalar != -1 {
if s == "op2" || s == "op3" {
s += "VecAsScalar"
} else {
panic(fmt.Errorf("simdgen only supports op2 or op3 with TreatLikeAScalarOfSize"))
}
}
if i == 0 || op.Go != ops[i-1].Go {
fmt.Fprintf(buffer, "\n/* %s */\n", op.Go)
}
if err := t.ExecuteTemplate(buffer, s, op); err != nil {
panic(fmt.Errorf("failed to execute template %s for op %v: %w", s, op, err))
}
} else {
panic(fmt.Errorf("failed to classify op %v: %w", op.Go, err))
}
}
vectorConversions := vConvertFromTypeMap(typeMap)
for _, conv := range vectorConversions {
if err := t.ExecuteTemplate(buffer, "vectorConversion", conv); err != nil {
panic(fmt.Errorf("failed to execute vectorConversion template: %w", err))
}
}
masks := masksFromTypeMap(typeMap)
for _, mask := range masks {
if err := t.ExecuteTemplate(buffer, "mask", mask); err != nil {
panic(fmt.Errorf("failed to execute mask template for mask %s: %w", mask.Name, err))
}
}
return buffer
}