blob: 0405c5847dfc36f5f06f93ba31155638d85bc49d [file]
// 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 (
"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 {
c := strings.Compare(x.Name, y.Name)
if c != 0 {
return c
}
return strings.Compare(x.Type, y.Type)
}
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 = `// 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
{{- range .OpsLen1}}
// Asm: {{.Asm}}, Arch: {{.Extension}}{{if .Documentation}}, Doc: {{.Documentation}}{{end}}
func (x {{(index .In 0).Go}}) {{.Go}}() {{(index .Out 0).Go}}
{{- end}}
{{- range .OpsLen2}}
// Asm: {{.Asm}}, Arch: {{.Extension}}{{if .Documentation}}, Doc: {{.Documentation}}{{end}}
func (x {{(index .In 0).Go}}) {{.Go}}(y {{(index .In 1).Go}}) {{(index .Out 0).Go}}
{{- end}}
{{- range .OpsLen3}}
// Asm: {{.Asm}}, Arch: {{.Extension}}{{if .Documentation}}, Doc: {{.Documentation}}{{end}}
func (x {{(index .In 0).Go}}) {{.Go}}(y {{(index .In 1).Go}}, z {{(index .In 2).Go}}) {{(index .Out 0).Go}}
{{- end}}
{{- range .VectorConversions }}
// {{.Tdst.Name}} converts from {{.Tsrc.Name}} to {{.Tdst.Name}}
func (from {{.Tsrc.Name}}) As{{.Tdst.Name}}() (to {{.Tdst.Name}})
{{- end}}
{{- range .Masks }}
// 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}}
`
// 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" {
// Immediates are not encoded as vector types.
return
}
if _, ok := seen[*arg.Go]; ok {
return
}
seen[*arg.Go] = struct{}{}
lanes := *arg.Bits / *arg.ElemBits
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" {
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
}
// writeSIMDTypes generates the simd vector type and writes it to types_amd64.go
// within the specified directory.
func writeSIMDTypes(directory string, typeMap simdTypeMap) error {
file, t, err := openFileAndPrepareTemplate(directory, "src/"+simdPackage+"/types_amd64.go", simdTypesTemplates)
if err != nil {
return err
}
defer file.Close()
if err := t.ExecuteTemplate(file, "fileHeader", nil); err != nil {
return fmt.Errorf("failed to execute fileHeader template: %w", err)
}
sizes := make([]int, 0, len(typeMap))
for size := range typeMap {
sizes = append(sizes, size)
}
sort.Ints(sizes)
for _, size := range sizes {
if err := t.ExecuteTemplate(file, "sizeTmpl", size); err != nil {
return fmt.Errorf("failed to execute size template for size %d: %w", size, err)
}
for _, typeDef := range typeMap[size] {
if err := t.ExecuteTemplate(file, "typeTmpl", typeDef); err != nil {
return fmt.Errorf("failed to execute type template for type %s: %w", typeDef.Name, err)
}
}
}
return nil
}
// writeSIMDStubs generates the simd vector intrinsic stubs and writes it to stubs_amd64.go
// within the specified directory.
func writeSIMDStubs(directory string, ops []Operation, typeMap simdTypeMap) error {
file, t, err := openFileAndPrepareTemplate(directory, "src/"+simdPackage+"/stubs_amd64.go", simdStubsTmpl)
if err != nil {
return err
}
defer file.Close()
opsLen1, opsLen2, opsLen3, err := genericOpsByLen(ops)
if err != nil {
return err
}
type templateData struct {
OpsLen1 []Operation
OpsLen2 []Operation
OpsLen3 []Operation
VectorConversions []simdTypePair
Masks []simdType
}
err = t.Execute(file, templateData{opsLen1, opsLen2, opsLen3, vConvertFromTypeMap(typeMap), masksFromTypeMap(typeMap)})
if err != nil {
return fmt.Errorf("failed to execute template : %w", err)
}
return nil
}