| // 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" |
| "io" |
| "slices" |
| "text/template" |
| ) |
| |
| var ( |
| ruleTemplates = template.Must(template.New("simdRules").Parse(` |
| {{define "pureVreg"}}({{.GoOp}}{{.GoType}} {{.Args}}) => ({{.Asm}} {{.ArgsOut}}) |
| {{end}} |
| {{define "maskIn"}}({{.GoOp}}{{.GoType}} {{.Args}} mask) => ({{.Asm}} {{.ArgsOut}} ({{.MaskInConvert}} <types.TypeMask> mask)) |
| {{end}} |
| {{define "maskOut"}}({{.GoOp}}{{.GoType}} {{.Args}}) => ({{.MaskOutConvert}} ({{.Asm}} {{.ArgsOut}})) |
| {{end}} |
| {{define "maskInMaskOut"}}({{.GoOp}}{{.GoType}} {{.Args}} mask) => ({{.MaskOutConvert}} ({{.Asm}} {{.ArgsOut}} ({{.MaskInConvert}} <types.TypeMask> mask))) |
| {{end}} |
| {{define "sftimm"}}({{.GoOp}}{{.GoType}} x (MOVQconst [c])) => ({{.Asm}}const [uint8(c)] x) |
| {{end}} |
| {{define "masksftimm"}}({{.GoOp}}{{.GoType}} x (MOVQconst [c]) mask) => ({{.Asm}}const [uint8(c)] x ({{.MaskInConvert}} <types.TypeMask> mask)) |
| {{end}} |
| `)) |
| ) |
| |
| type tplRuleData struct { |
| tplName string |
| GoOp string |
| GoType string |
| Args string |
| Asm string |
| ArgsOut string |
| MaskInConvert string |
| MaskOutConvert string |
| } |
| |
| func compareTplRuleData(x, y tplRuleData) int { |
| // TODO should MaskedXYZ compare just after XYZ? |
| if c := compareNatural(x.GoOp, y.GoOp); c != 0 { |
| return c |
| } |
| if c := compareNatural(x.GoType, y.GoType); c != 0 { |
| return c |
| } |
| if c := compareNatural(x.Args, y.Args); c != 0 { |
| return c |
| } |
| return 0 |
| } |
| |
| // writeSIMDRules generates the lowering and rewrite rules for ssa and writes it to simdAMD64.rules |
| // within the specified directory. |
| func writeSIMDRules(ops []Operation) *bytes.Buffer { |
| buffer := new(bytes.Buffer) |
| |
| header := `// 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. |
| |
| ` |
| if _, err := io.WriteString(buffer, header); err != nil { |
| panic(fmt.Errorf("failed to write header: %w", err)) |
| } |
| |
| var allData []tplRuleData |
| |
| for _, opr := range ops { |
| if opr.NoGenericOps != nil && *opr.NoGenericOps == "true" { |
| continue |
| } |
| opInShape, opOutShape, maskType, immType, gOp := opr.shape() |
| asm := machineOpName(maskType, gOp) |
| vregInCnt := len(gOp.In) |
| if maskType == OneMask { |
| vregInCnt-- |
| } |
| |
| data := tplRuleData{ |
| GoOp: gOp.Go, |
| Asm: asm, |
| } |
| |
| if vregInCnt == 1 { |
| data.Args = "x" |
| data.ArgsOut = data.Args |
| } else if vregInCnt == 2 { |
| data.Args = "x y" |
| data.ArgsOut = data.Args |
| } else if vregInCnt == 3 { |
| data.Args = "x y z" |
| data.ArgsOut = data.Args |
| } else { |
| panic(fmt.Errorf("simdgen does not support more than 3 vreg in inputs")) |
| } |
| if immType == ConstImm { |
| data.ArgsOut = fmt.Sprintf("[%s] %s", *opr.In[0].Const, data.ArgsOut) |
| } else if immType == VarImm { |
| data.Args = fmt.Sprintf("[a] %s", data.Args) |
| data.ArgsOut = fmt.Sprintf("[a] %s", data.ArgsOut) |
| } else if immType == ConstVarImm { |
| data.Args = fmt.Sprintf("[a] %s", data.Args) |
| data.ArgsOut = fmt.Sprintf("[a+%s] %s", *opr.In[0].Const, data.ArgsOut) |
| } |
| |
| goType := func(op Operation) string { |
| if op.OperandOrder != nil { |
| switch *op.OperandOrder { |
| case "21Type1", "231Type1": |
| // Permute uses operand[1] for method receiver. |
| return *op.In[1].Go |
| } |
| } |
| return *op.In[0].Go |
| } |
| var tplName string |
| // If class overwrite is happening, that's not really a mask but a vreg. |
| if opOutShape == OneVregOut || opOutShape == OneVregOutAtIn || gOp.Out[0].OverwriteClass != nil { |
| switch opInShape { |
| case OneImmIn: |
| tplName = "pureVreg" |
| data.GoType = goType(gOp) |
| case PureVregIn: |
| tplName = "pureVreg" |
| data.GoType = goType(gOp) |
| case OneKmaskImmIn: |
| fallthrough |
| case OneKmaskIn: |
| tplName = "maskIn" |
| data.GoType = goType(gOp) |
| rearIdx := len(gOp.In) - 1 |
| // Mask is at the end. |
| data.MaskInConvert = fmt.Sprintf("VPMOVVec%dx%dToM", *gOp.In[rearIdx].ElemBits, *gOp.In[rearIdx].Lanes) |
| case PureKmaskIn: |
| panic(fmt.Errorf("simdgen does not support pure k mask instructions, they should be generated by compiler optimizations")) |
| } |
| } else if opOutShape == OneGregOut { |
| tplName = "pureVreg" // TODO this will be wrong |
| data.GoType = goType(gOp) |
| } else { |
| // OneKmaskOut case |
| data.MaskOutConvert = fmt.Sprintf("VPMOVMToVec%dx%d", *gOp.Out[0].ElemBits, *gOp.In[0].Lanes) |
| switch opInShape { |
| case OneImmIn: |
| fallthrough |
| case PureVregIn: |
| tplName = "maskOut" |
| data.GoType = goType(gOp) |
| case OneKmaskImmIn: |
| fallthrough |
| case OneKmaskIn: |
| tplName = "maskInMaskOut" |
| data.GoType = goType(gOp) |
| rearIdx := len(gOp.In) - 1 |
| data.MaskInConvert = fmt.Sprintf("VPMOVVec%dx%dToM", *gOp.In[rearIdx].ElemBits, *gOp.In[rearIdx].Lanes) |
| case PureKmaskIn: |
| panic(fmt.Errorf("simdgen does not support pure k mask instructions, they should be generated by compiler optimizations")) |
| } |
| } |
| |
| if gOp.SpecialLower != nil { |
| if *gOp.SpecialLower == "sftimm" { |
| sftImmData := data |
| if tplName == "maskIn" { |
| sftImmData.tplName = "masksftimm" |
| } else { |
| sftImmData.tplName = "sftimm" |
| } |
| allData = append(allData, sftImmData) |
| } else { |
| panic("simdgen sees unknwon special lower " + *gOp.SpecialLower + ", maybe implement it?") |
| } |
| } else { |
| // SpecialLower rules cannot use "...". |
| if tplName == "pureVreg" && data.Args == data.ArgsOut { |
| data.Args = "..." |
| data.ArgsOut = "..." |
| } |
| } |
| data.tplName = tplName |
| allData = append(allData, data) |
| } |
| |
| slices.SortFunc(allData, compareTplRuleData) |
| |
| for _, data := range allData { |
| if err := ruleTemplates.ExecuteTemplate(buffer, data.tplName, data); err != nil { |
| panic(fmt.Errorf("failed to execute template %s for %s: %w", data.tplName, data.GoOp+data.GoType, err)) |
| } |
| } |
| |
| return buffer |
| } |