blob: 98d223fedd9b1650e604496aa2808a9afba947af [file] [edit]
// 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.
package main
import (
"bytes"
"fmt"
"go/format"
"log"
"math/rand/v2"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"
"text/template"
"golang.org/x/arch/arm64/instgen/xmlspec"
)
type tmplData struct {
EncodingFuncs []funcData
Insts []instData
InstsByName []instsByName
// All possible arg patterns are aggregated here.
// They are unique with regard to [Class] and [Elms].
// [Asm] is not considered for uniqueness.
UniqueArgs []argData
// All possible operand combinations are aggregated here.
// They are unique with regard to the list of [Args].
UniqueOpCombs []opCombData
Constants []constantData
}
type instsByName struct {
Name string
Insts []instData // All these insts share the same GoOp code.
}
type constantData struct {
ConstName string
BinName string
}
type funcData struct {
Comments []string
Body string
Name string
}
type instData struct {
GoOp string
FixedBits string
Args []argData
Ops opCombData
Asm string
highFeat int
}
type argData struct {
// The global variable name of this arg pattern.
Name string
Class string
Asm string
Elms []elmData
}
type opCombData struct {
Name string
OpAsms string
ArgNames []string
BetterName string
}
type elmData struct {
FuncName string
EncName string
}
type e2eData struct {
GoOp string
Asm string
Binary string
// highFeat is the feature order (see [operandTypeOrders]), it's used to
// sort the e2e test data.
highFeat int
}
const genTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'. DO NOT EDIT.
// The following constants are generated from the XML specification.
package arm64
// insts are grouped by [goOp].
var insts = [][]instEncoder{
{{- range .InstsByName}}
// {{.Name}}
{
{{- range .Insts}}
// {{.Asm}} {{.Ops.OpAsms}}
{
goOp: {{.GoOp}},
fixedBits: {{.FixedBits}},
args: {{.Ops.BetterName}},
},
{{- end}}
},
{{- end}}
}
{{range .UniqueArgs}}
var {{.Name}} = operand{
class: {{.Class}}, elemEncoders: []elemEncoder{
{{- range .Elms}}
{{"{"}}{{.FuncName}}, {{.EncName}}{{"}"}},
{{- end}}
},
}
{{end}}
{{range .UniqueOpCombs}}
var {{.BetterName}} = []operand{
{{- range .ArgNames}}
{{.}},
{{- end}}
}
{{end}}
`
const encodingTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'.
package arm64
import "cmd/internal/obj"
// stripRawZ first checks if v is a raw register number(0 to 31).
// If v is not a raw Z register, it will check if it's a Z register in the ARM64 range:
// - if within the range, it returns true, and set v to be the raw Z register.
// - otherwise it returns false
// If v is a raw register number, it returns true and leaves v unchanged.
func stripRawZ(v *uint32) (bool) {
if *v >= obj.RBaseARM64 {
if !(*v >= REG_Z0 && *v <= REG_Z31) && !(*v >= REG_ZARNG && *v < REG_ZARNGELEM) {
return false
}
}
*v = *v & 31
return true
}
// checkIsR checks if v is a scalar register:
// - if v is a raw register number or is within the ARM64 scalar register range,
// it returns true.
// - otherwise it returns false.
func checkIsR(v uint32) (bool) {
if v > REG_R31 && v != REG_RSP {
return false
}
return true
}
const (
enc_NIL component = iota
{{- range .Constants}}
{{.ConstName}}
{{- end}}
)
{{- range .EncodingFuncs}}
// {{.Name}} is the implementation of the following encoding logic:
{{- range .Comments}}
// {{.}}
{{- end}}
func {{.Name}}(v uint32) (uint32, bool) {
{{.Body}}
}
{{- end}}
`
const goopsTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'. DO NOT EDIT.
package arm64
import "cmd/internal/obj"
const (
{{- range .}}
{{.}}
{{- end}}
ALAST
)
`
const anamesTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'. DO NOT EDIT.
package arm64
var sveAnames = []string{
"SVESTART",
{{- range .}}
"{{.}}",
{{- end}}
"LAST",
}
func init() {
Anames = append(Anames, sveAnames...)
}
`
const e2edataTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'. DO NOT EDIT.
#include "../../../../../runtime/textflag.h"
TEXT asmtest(SB),DUPOK|NOSPLIT,$-8
{{- range .}}
{{.Asm}}{{.Binary}}
{{- end}}
RET
`
var operandTypeOrders = map[string]int{
"AC_ARNG": 0,
"AC_PREG": 0,
"AC_PREGZ": 0,
"AC_PREGZM": 0,
"AC_ZREG": 0,
"AC_SPZGREG": 1,
"AC_VREG": 1,
"AC_ARNGIDX": 2,
"AC_ZREGIDX": 2,
"AC_PREGIDX": 2,
"AC_IMM": 3,
"AC_REGLIST1": 4,
"AC_REGLIST2": 4,
"AC_REGLIST3": 4,
"AC_REGLIST4": 4,
"AC_MEMEXT": 5,
"AC_SPECIAL": 6,
"AC_MEMOFF": 7,
"AC_MEMOFFMULVL": 8,
"AC_REGLIST_RANGE": 9,
}
func readExistingGoOps(aoutPath string) map[string]bool {
ops := make(map[string]bool)
content, err := os.ReadFile(aoutPath)
if err != nil {
return ops
}
re := regexp.MustCompile(`\b(A[A-Z0-9_]+)\b`)
matches := re.FindAllStringSubmatch(string(content), -1)
for _, m := range matches {
ops[m[1]] = true
}
return ops
}
func convertToGoID(v string) string {
v = strings.ReplaceAll(v, " :: ", "_")
v = strings.ReplaceAll(v, "(", "")
v = strings.ReplaceAll(v, ")", "")
v = strings.ReplaceAll(v, "[", "")
v = strings.ReplaceAll(v, "]", "")
v = strings.ReplaceAll(v, "#", "c")
v = strings.ReplaceAll(v, "-", "_")
return v
}
// formatAndFlush executes the given template and writes it to the given file,
// the go file is formatted using gofmt.
// If outputDir is empty, it does nothing.
func formatAndFlush(outputDir string, filename string, data any, tmpl string) {
if outputDir == "" {
return
}
t, err := template.New("template").Parse(tmpl)
if err != nil {
log.Fatalf("Failed to parse template %s: %v", filename, err)
}
var buf bytes.Buffer
if err := t.Execute(&buf, data); err != nil {
log.Fatalf("Failed to execute template %s: %v", filename, err)
}
formatted := buf.Bytes()
if strings.HasSuffix(filename, ".go") {
formatted, err = format.Source(formatted)
if err != nil {
log.Printf("Failed to format generated source: %v", err)
formatted = buf.Bytes()
}
}
outputPath := filepath.Join(outputDir, filename)
if err := os.WriteFile(outputPath, formatted, 0644); err != nil {
log.Fatalf("Failed to write output file %s: %v", outputPath, err)
}
log.Printf("Generated %s", outputPath)
}
// Generate generates the Go code for the instruction table.
func Generate(insts []*xmlspec.InstructionParsed, outputDir string, genE2E bool) {
// process encodingImpls, field them to make the comparison easier
for k, v := range encodingImpls {
uniformed := strings.Join(strings.Fields(k), " ")
encodingImpls[uniformed] = v
}
// Collect constants
encodingFuncs := make(map[string]int) // Map textExp to index
encodingFuncComments := make(map[string][]string)
// Pre-populate EncodingFuncs
sortedEncodings := make([]string, 0, len(xmlspec.AllEncodingDescs))
for k := range xmlspec.AllEncodingDescs {
sortedEncodings = append(sortedEncodings, k)
}
sort.Strings(sortedEncodings)
// Create map to store names early
funcNames := make(map[int]string)
encNames := make(map[int]string)
for i, k := range sortedEncodings {
encodingFuncs[k] = i + 1
encodingFuncComments[k] = strings.Split(k, "\n")
kUniformed := strings.Join(strings.Fields(k), " ")
if b, ok := encodingImpls[kUniformed]; ok {
funcNames[i+1] = b.name
encNames[i+1] = b.encodedIn
} else {
funcNames[i+1] = fmt.Sprintf("encodeGen%d", i+1)
}
}
// Collect A64Types and A64Feats, and Insts
var instsData []instData
newGoOps := make(map[string]bool)
existingGoOps := readExistingGoOps(filepath.Join(outputDir, "a.out.go"))
usedFuncIndices := make(map[int]bool)
e2eTestData := make([]e2eData, 0)
e2eErrorData := make([]e2eData, 0)
for _, inst := range insts {
if inst == nil {
continue
}
isAlias := false
for _, doc := range inst.DocVars {
if doc.Key == "alias_mnemonic" {
isAlias = true
break
}
}
if isAlias {
// Alias instructions are not fully specified in their own XML files,
// skip them.
continue
}
for _, iclass := range inst.Classes.Iclass {
if !iclass.RegDiagram.Parsed {
continue
}
for _, encodingRaw := range iclass.Encodings {
if !encodingRaw.Parsed {
continue
}
if encodingRaw.Alias {
// Alias encodings are not fully specified in their own section,
// skip them.
continue
}
encodings := xmlspec.SplitInstByRegWidth(encodingRaw)
for _, encoding := range encodings {
// Filtering logic:
// 1. If 0 operands (len <= 1), keep with warning.
// 2. If > 0 operands, ALL must be in operandTypeOrders.
keep := true
if len(encoding.Operands) <= 1 {
log.Printf("Warning: instruction with 0 operands kept: %s", encoding.Asm)
} else {
for _, op := range encoding.Operands[1:] {
if _, ok := operandTypeOrders[op.Typ]; !ok {
keep = false
break
}
}
}
if !keep {
continue
}
if !existingGoOps[encoding.GoOp] {
newGoOps[encoding.GoOp] = true
} else {
log.Printf("Warning: goOp %s already exists in a.out.go", encoding.GoOp)
}
validData, errData := constructInstance(&encoding)
e2eTestData = append(e2eTestData, *validData)
if errData != nil {
e2eErrorData = append(e2eErrorData, *errData)
}
highFeat := 0
for _, op := range encoding.Operands[1:] {
highFeat = max(highFeat, operandTypeOrders[op.Typ])
}
instData := instData{
Asm: encoding.GoOp[1:],
GoOp: encoding.GoOp,
FixedBits: fmt.Sprintf("%#x", encoding.Binary),
highFeat: highFeat,
}
for _, op := range encoding.Operands[1:] {
arg := argData{
Asm: op.Name,
Class: op.Typ,
}
if len(op.Elems) > 0 {
for _, elm := range op.Elems {
idx, ok := encodingFuncs[elm.TextExpWithRanges]
if !ok {
log.Printf("Warning: encoding func not found for %s", elm.TextExpWithRanges)
continue
}
enc := encNames[idx]
if enc == "" {
// Fallback
sym := xmlspec.EncodingDescsToEncodedIn[elm.TextExpWithRanges]
// We actually do not have encodedInMap here yet, so just prefix "enc_"
if sym == "" || sym == "nil" {
enc = "enc_NIL"
} else {
enc = fmt.Sprintf("enc_%s", convertToGoID(sym))
}
}
arg.Elms = append(arg.Elms, elmData{
FuncName: funcNames[idx],
EncName: enc,
})
usedFuncIndices[idx] = true
}
}
instData.Args = append(instData.Args, arg)
}
instsData = append(instsData, instData)
}
}
}
}
// Prepare template data
data := tmplData{
Insts: instsData,
}
// Collect constants
encodedInMap := make(map[string]string) // raw string -> constant name
var uniqueEncodedIn []string
// Collect ALL constants from global map to ensure invariant names (enc_X)
for _, v := range xmlspec.EncodingDescsToEncodedIn {
if v == "" || v == "nil" {
continue
}
if _, ok := encodedInMap[v]; !ok {
encodedInMap[v] = ""
uniqueEncodedIn = append(uniqueEncodedIn, v)
}
}
sort.Strings(uniqueEncodedIn)
// Identify used constants
neededEncodedIn := make(map[string]bool)
for k, idx := range encodingFuncs {
if usedFuncIndices[idx] {
if v := xmlspec.EncodingDescsToEncodedIn[k]; v != "" && v != "nil" {
neededEncodedIn[v] = true
}
}
}
var constants []constantData
for _, v := range uniqueEncodedIn {
name := fmt.Sprintf("enc_%s", convertToGoID(v))
encodedInMap[v] = name
// Only add to output if used
if neededEncodedIn[v] {
constants = append(constants, constantData{
ConstName: name,
BinName: v,
})
}
}
for i, k := range sortedEncodings {
if !usedFuncIndices[i+1] {
continue
}
sym := xmlspec.EncodingDescsToEncodedIn[k]
if name, ok := encodedInMap[sym]; ok {
sym = name
} else {
sym = "enc_NIL"
}
var body string
kUniformed := strings.Join(strings.Fields(k), " ")
var name string
if b, ok := encodingImpls[kUniformed]; ok {
body = b.body
name = b.name
} else {
log.Printf("Warning: encoding func not found for\n%s\n", k)
body = defaultEncodingTmpl
name = fmt.Sprintf("encodeGen%d", i+1)
}
data.EncodingFuncs = append(data.EncodingFuncs, funcData{
Comments: encodingFuncComments[k],
Body: body,
Name: name,
})
}
data.Constants = constants
// Now aggregate all arg patterns to unique globals.
argMap := make(map[string]bool)
var uniqueArgs []argData
for i, inst := range instsData {
for j, arg := range inst.Args {
name := "a_" + arg.Class[3:] // remove AC_ prefix
for _, elm := range arg.Elms {
name += "_" + strings.TrimPrefix(elm.FuncName, "encode")
}
instsData[i].Args[j].Name = name
arg.Name = name
if !argMap[name] {
argMap[name] = true
uniqueArgs = append(uniqueArgs, arg)
}
}
}
data.UniqueArgs = uniqueArgs
// Now aggregate all arg lists to unique globals.
opCombMap := make(map[string]opCombData)
var uniqueOpCombs []opCombData
for i, inst := range instsData {
name := "oc"
argNames := []string{}
argAsms := []string{}
for _, arg := range inst.Args {
name += "_" + arg.Name[2:] // remove a_ prefix
for _, elm := range arg.Elms {
name += "_" + strings.TrimPrefix(elm.FuncName, "encode")
}
argNames = append(argNames, arg.Name)
argAsms = append(argAsms, arg.Asm)
}
if ops, ok := opCombMap[name]; ok {
instsData[i].Ops = ops
} else {
ops := opCombData{
Name: name,
ArgNames: argNames,
OpAsms: strings.Join(argAsms, ", "),
}
opCombMap[name] = ops
uniqueOpCombs = append(uniqueOpCombs, ops)
instsData[i].Ops = ops
}
}
data.UniqueOpCombs = uniqueOpCombs
// Figure out better names for the slices of asm operand
// checkers/encoders.
// Goal is to replace an "ugly name" with a sanitized
// version of the instruction operand types; however, sometimes
// there is more than one ugly name mapping to the same
// nominal operands (an ugly name corresponds to an encoding).
// Detect this, and for those cases where there's more than
// one ugly name for the same operands, append a sequence
// number.
operandsToName := func(s string) string {
s = strings.Replace(s, "<", "", -1)
s = strings.Replace(s, ">", "", -1)
s = strings.Replace(s, "/", "", -1)
s = strings.Replace(s, ",", "_", -1)
s = strings.Replace(s, ".", "_", -1)
s = strings.Replace(s, " ", "_", -1)
s = strings.Replace(s, "|", "", -1)
s = strings.Replace(s, "[", "_", -1)
s = strings.Replace(s, "]", "_", -1)
s = strings.Replace(s, "{", "", -1)
s = strings.Replace(s, "}", "", -1)
s = strings.Replace(s, "#", "c", -1)
s = strings.Replace(s, "-", "_", -1)
s = strings.TrimPrefix(s, "_")
return s
}
ot2ugly2Count := make(map[string]map[string]int)
otSlice := []string{} // insertion order of operand types
uglyInsertionOrder := make(map[string]int)
for _, inst := range data.Insts {
uglyName := inst.Ops.Name
if x := uglyInsertionOrder[uglyName]; x == 0 {
uglyInsertionOrder[uglyName] = 1 + len(uglyInsertionOrder)
}
operandTypes := inst.Ops.OpAsms
if ot2ugly2Count[operandTypes] == nil {
otSlice = append(otSlice, operandTypes)
ot2ugly2Count[operandTypes] = make(map[string]int)
}
// keep track of how many instructions with this operand type
// sequence use the exact same encoding.
ot2ugly2Count[operandTypes][uglyName] += 1
}
// Map from ugly name to better name
ugly2better := make(map[string]string)
// There is sometimes only one ugly name for multiple operand type sequences
// that have different semantics, we need to assign them version numbers.
usedNames := make(map[string]bool)
for _, x := range otSlice {
// Get a canonical order of ugly names
var uglies []string
u2c := ot2ugly2Count[x]
for c := range u2c {
uglies = append(uglies, c)
}
baseName := operandsToName(x)
if len(uglies) > 1 {
// if there is more than one, sort into decreasing
// frequency order, then sort by insertion order.
sort.Slice(uglies, func(i, j int) bool {
if c := u2c[uglies[j]] - u2c[uglies[i]]; c != 0 {
return c < 0
}
return uglyInsertionOrder[uglies[i]] < uglyInsertionOrder[uglies[j]]
})
for j, u := range uglies {
name := fmt.Sprintf("%s__%d", baseName, j+1)
if usedNames[name] {
v := 2
for usedNames[fmt.Sprintf("%s_V%d__%d", baseName, v, j+1)] {
v++
}
name = fmt.Sprintf("%s_V%d__%d", baseName, v, j+1)
}
ugly2better[u] = name
usedNames[name] = true
}
} else {
// if only one, do not add a __N suffix.
name := baseName
if usedNames[name] {
v := 2
for usedNames[fmt.Sprintf("%s_V%d", baseName, v)] {
v++
}
name = fmt.Sprintf("%s_V%d", baseName, v)
}
ugly2better[uglies[0]] = name
usedNames[name] = true
}
}
// Apply better name translations.
for i, inst := range data.Insts {
if arg := ugly2better[inst.Ops.Name]; arg != "" {
data.Insts[i].Ops.BetterName = arg
} else {
data.Insts[i].Ops.BetterName = inst.Ops.Name
}
}
for i, uoc := range data.UniqueOpCombs {
if arg := ugly2better[uoc.Name]; arg != "" {
data.UniqueOpCombs[i].BetterName = arg
} else {
data.UniqueOpCombs[i].BetterName = uoc.Name
}
}
// Put these two slices into sensible orders.
sort.Slice(data.UniqueOpCombs, func(i, j int) bool {
return data.UniqueOpCombs[i].BetterName < data.UniqueOpCombs[j].BetterName
})
sort.Slice(data.UniqueArgs, func(i, j int) bool {
return data.UniqueArgs[i].Name < data.UniqueArgs[j].Name
})
// Group instructions by name
// Also sort them by their Go mnemonic, stably.
slices.SortFunc(data.Insts, func(i, j instData) int {
if c := strings.Compare(i.GoOp, j.GoOp); c != 0 {
return c
}
if i.highFeat != j.highFeat {
return i.highFeat - j.highFeat
}
if c := strings.Compare(i.Ops.BetterName, j.Ops.BetterName); c != 0 {
return c
}
if c := strings.Compare(i.Ops.OpAsms, j.Ops.OpAsms); c != 0 {
return c
}
return strings.Compare(i.FixedBits, j.FixedBits)
})
ibn := []instsByName{}
prevGoOp := ""
for _, inst := range data.Insts {
if inst.GoOp != prevGoOp {
ibn = append(ibn, instsByName{
Name: inst.GoOp[1:],
Insts: []instData{},
})
prevGoOp = inst.GoOp
}
ibn[len(ibn)-1].Insts = append(ibn[len(ibn)-1].Insts, inst)
}
data.InstsByName = ibn
// Generate inst_gen.go
formatAndFlush(outputDir, "src/cmd/internal/obj/arm64/inst_gen.go", data, genTmpl)
// Generate encoding_gen.go
formatAndFlush(outputDir, "src/cmd/internal/obj/arm64/encoding_gen.go", data, encodingTmpl)
// Generate goops_gen.go
var sortedNewGoOps []string
for k := range newGoOps {
sortedNewGoOps = append(sortedNewGoOps, k)
}
sort.Strings(sortedNewGoOps)
goopsPrefix := " obj.As = ASVESTART + 1 + iota"
sortedNewGoOps[0] += goopsPrefix
formatAndFlush(outputDir, "src/cmd/internal/obj/arm64/goops_gen.go", sortedNewGoOps, goopsTmpl)
// Generate anames_gen.go
for i := range sortedNewGoOps {
sortedNewGoOps[i] = strings.TrimSuffix(sortedNewGoOps[i], goopsPrefix)
sortedNewGoOps[i] = strings.TrimPrefix(sortedNewGoOps[i], "A")
}
formatAndFlush(outputDir, "src/cmd/internal/obj/arm64/anames_gen.go", sortedNewGoOps, anamesTmpl)
if genE2E {
slices.SortStableFunc(e2eTestData, func(i, j e2eData) int {
if i.highFeat != j.highFeat {
return i.highFeat - j.highFeat
}
return strings.Compare(i.GoOp, j.GoOp)
})
slices.SortStableFunc(e2eErrorData, func(i, j e2eData) int {
if i.highFeat != j.highFeat {
return i.highFeat - j.highFeat
}
return strings.Compare(i.GoOp, j.GoOp)
})
// Generate arm64sveenc.s
formatAndFlush(outputDir, "src/cmd/asm/internal/asm/testdata/arm64sveenc.s", e2eTestData, e2edataTmpl)
// Generate arm64sveerror.s
formatAndFlush(outputDir, "src/cmd/asm/internal/asm/testdata/arm64sveerror.s", e2eErrorData, e2edataTmpl)
}
}
// Mappings for operand patterns to concrete registers
var regMap = map[string]string{
"Zd": "Z",
"Zda": "Z",
"Zdn": "Z",
"Zn": "Z",
"Zm": "Z",
"Za": "Z",
"Zk": "Z",
"Zt": "Z",
"Pd": "P",
"Pdn": "P",
"Pdm": "P",
"Pn": "P",
"Pm": "P",
"Pg": "P",
"Pt": "P",
"PNd": "PN",
"PNn": "PN",
"PNg": "PN",
"Pv": "P",
"Vd": "V",
"Vn": "V",
"Vm": "V",
}
// Arrangement variables, also includes predication cases.
var arrMap = map[string]bool{
"Tb": true, // arrangement variables
"T": true, // arrangement variables
"ZM": true, // predication variables
}
// Z, P reg arrangements, used for variable mutation.
var sveArr = []string{"B", "H", "S", "D", "Q"}
var svePred = []string{"M", "Z"}
// V reg arrangements, used for variable mutation.
var neonArrGNU = []string{"16B", "1D", "4H", "8H", "2S", "4S", "2D", "1Q"}
var neonArrGo = []string{"B16", "D1", "H4", "H8", "S2", "S4", "D2", "Q1"}
// assembleGNU assembles the given assembly string using GNU toolchain and returns the hex.
func assembleGNU(s string) (string, error) {
// Create temp file
tmpName := "temp_probe.s"
if err := os.WriteFile(tmpName, []byte(s+"\n"), 0644); err != nil {
return "", err
}
defer os.Remove(tmpName)
// Run as
cmd := exec.Command("aarch64-linux-gnu-as", "-march=all", tmpName, "-o", "temp_probe.o")
if out, err := cmd.CombinedOutput(); err != nil {
return "", fmt.Errorf("as failed: %s", out)
}
defer exec.Command("rm", "temp_probe.o").Run()
// Run objdump
cmd = exec.Command("aarch64-linux-gnu-objdump", "-d", "temp_probe.o")
out, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
// Parse output
lines := strings.Split(string(out), "\n")
for _, l := range lines {
l = strings.TrimSpace(l)
// The first text line contains the hex.
if strings.Contains(l, "0:") {
parts := strings.Fields(l)
// parts[0] is offset:, parts[1] is hex
if len(parts) >= 2 {
if len(parts[1]) == 8 {
return parts[1], nil
}
}
}
}
return "", fmt.Errorf("no hex found")
}
// reverseHex reverses the byte order of a hex string.
// this is required for Go's e2e test.
func reverseHex(h string) string {
if len(h) != 8 {
return h
}
return h[6:8] + h[4:6] + h[2:4] + h[0:2]
}
var prefetchOps = []string{
"PLDL1KEEP", "PLDL1STRM", "PLDL2KEEP", "PLDL2STRM",
"PLDL3KEEP", "PLDL3STRM", "PSTL1KEEP", "PSTL1STRM",
"PSTL2KEEP", "PSTL2STRM", "PSTL3KEEP", "PSTL3STRM",
}
var vlOps = []string{"VLx2", "VLx4"}
// constructInstance takes an [Encoding] and tries to generate a
// valid Go assembly instance of it and returns the e2eData for this instance.
// The construction is deterministic.
//
// This function requires the availablity of the GNU toolchain.
// Special instructions are elided, they are documented in the comments.
// Technically we can skip them early before we do the construction, but
// we kept them after the construction for future verification.
func constructInstance(enc *xmlspec.EncodingParsed) (*e2eData, *e2eData) {
var name string
// Try to construct two assembly, one in Go the other in GNU.
// The trial succeeds once the GNU one is accepted by the GNU toolchain.
rng := rand.New(rand.NewPCG(uint64(42), uint64(1024)))
// Try 100 times until we find one that's accepted by the GNU toolchain.
// constructInstance does not understand the encoding level constraint, so
// it just try until a valid one is found.
var highFeat int
var gnuErr error
var errCase *e2eData
var validCase *e2eData
for range 100 {
goAsmOps := make([]string, 0)
gnuAsmOps := make([]string, 0)
// Useful for pruning AC_ARNG, AC_PREG, AC_PREGZM, AC_ZREG.
// ABS <Zd>.<T>, <Pg>/Z, <Zn>.<T>
// the 2 instances of <T> should be exactly the same...
// Key is the random number selected.
regCache := map[string]int{}
arrCache := map[string]int{}
immCache := map[string]int{}
cachedOrNew := func(cache map[string]int, key string, n int) int {
if v, ok := cache[key]; ok {
if errCase != nil || len(enc.Operands) == 0 {
// Give the error case a chance to be triggered.
return v
}
}
v := rng.IntN(n)
cache[key] = v
return v
}
for i, op := range enc.Operands {
if i == 0 {
// First operand is the instruction name.
name = op.Name
continue
}
opName := op.Name
if _, ok := operandTypeOrders[op.Typ]; !ok {
log.Fatalf("Unsupported operand type in constructInstance: %s, enc: %s", op.Typ, enc.String())
}
highFeat = max(highFeat, operandTypeOrders[op.Typ])
generateSVERegOp := func(opName string) (string, string, string, int, string) {
var parts []string
var isPred bool
if strings.Contains(opName, ".") {
parts = strings.Split(opName, ".")
} else {
parts = strings.Split(opName, "/")
isPred = true
}
if len(parts) == 0 {
log.Fatalf("Unrecognized operand: %s", opName)
}
regName := strings.TrimSuffix(strings.TrimPrefix(parts[0], "<"), ">")
var arrName string
if len(parts) > 1 {
arrName = strings.TrimSuffix(strings.TrimPrefix(parts[1], "<"), ">")
}
resolvedArr := arrName
regNameSanitized := strings.TrimRight(regName, "0123456789")
reg := regMap[regNameSanitized]
if reg == "" {
reg = regMap[regName]
}
var goAsmOp, gnuAsmOp string
var regIdx int
if arrMap[arrName] || errCase == nil {
// Give error case a random mutation to trigger an error.
// Variables, needs mutation
switch reg {
case "Z":
regIdx = cachedOrNew(regCache, regName, 32)
arrIdx := cachedOrNew(arrCache, arrName, len(sveArr))
resolvedArr = sveArr[arrIdx]
goAsmOp = fmt.Sprintf("Z%d.%s", regIdx, resolvedArr)
gnuAsmOp = goAsmOp
case "P", "PN":
regIdx = cachedOrNew(regCache, regName, 15)
if isPred {
arrIdx := cachedOrNew(arrCache, arrName, len(svePred))
resolvedArr = svePred[arrIdx]
gnuAsmOp = fmt.Sprintf("%s%d/%s", reg, regIdx, resolvedArr)
goAsmOp = fmt.Sprintf("%s%d.%s", reg, regIdx, resolvedArr)
} else {
arrIdx := cachedOrNew(arrCache, arrName, len(sveArr))
resolvedArr = sveArr[arrIdx]
goAsmOp = fmt.Sprintf("%s%d.%s", reg, regIdx, resolvedArr)
gnuAsmOp = goAsmOp
}
case "V":
regIdx = cachedOrNew(regCache, regName, 32)
arrIdx := cachedOrNew(arrCache, arrName, len(neonArrGNU))
resolvedArr = neonArrGNU[arrIdx]
gnuAsmOp = fmt.Sprintf("V%d.%s", regIdx, neonArrGNU[arrIdx])
goAsmOp = fmt.Sprintf("V%d.%s", regIdx, neonArrGo[arrIdx])
}
} else {
// Fixed arrangement or predications
var dotArr string
if arrName != "" {
dotArr = "." + arrName
}
switch reg {
case "Z":
regIdx = cachedOrNew(regCache, regName, 32)
goAsmOp = fmt.Sprintf("Z%d%s", regIdx, dotArr)
gnuAsmOp = goAsmOp
case "P", "PN":
regIdx = cachedOrNew(regCache, regName, 15)
if isPred {
var arrNameGNU string
if arrName != "" {
arrNameGNU = "/" + arrName
}
gnuAsmOp = fmt.Sprintf("%s%d%s", reg, regIdx, arrNameGNU)
goAsmOp = fmt.Sprintf("%s%d%s", reg, regIdx, dotArr)
} else {
goAsmOp = fmt.Sprintf("%s%d%s", reg, regIdx, dotArr)
gnuAsmOp = goAsmOp
}
case "V":
log.Fatalf("Unexpected V with fixed arrangement: %s", opName)
}
}
return goAsmOp, gnuAsmOp, reg, regIdx, resolvedArr
}
if op.Typ == "AC_ARNG" || op.Typ == "AC_PREG" || op.Typ == "AC_PREGZM" || op.Typ == "AC_ZREG" {
goAsmOp, gnuAsmOp, _, _, _ := generateSVERegOp(opName)
gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...)
goAsmOps = append(goAsmOps, goAsmOp)
} else if op.Typ == "AC_ARNGIDX" || op.Typ == "AC_ZREGIDX" || op.Typ == "AC_PREGIDX" {
// Operands that takes SVE/SIMD registers and arrangement with index.
// reg.T[index]
var regPrefix string
var regName string
var arrangement string
index := rng.IntN(4)
if op.Typ == "AC_ARNGIDX" {
parts := strings.Split(opName, ".")
regName = strings.TrimSuffix(strings.TrimPrefix(parts[0], "<"), ">")
regPrefix = regMap[regName]
if regPrefix == "" {
log.Fatalf("Unknown register prefix: %s", regName)
}
parts2 := strings.Split(parts[1], "[")
arrangement = strings.TrimSuffix(strings.TrimPrefix(parts2[0], "<"), ">")
} else if op.Typ == "AC_ZREGIDX" || op.Typ == "AC_PREGIDX" {
re := regexp.MustCompile(`<([A-Z][A-Za-z0-9]*)>`)
matches := re.FindStringSubmatch(opName)
if len(matches) > 1 {
regName = matches[1]
regPrefix = regMap[regName]
if regPrefix == "" {
log.Fatalf("Unknown register: %s", regName)
}
} else {
log.Fatalf("Unknown AC_[PZ]REGIDX: %s", opName)
}
}
var goAsmOp, gnuAsmOp string
limit := 32
if regPrefix == "P" || regPrefix == "PN" {
limit = 16
}
regIdx := cachedOrNew(regCache, regName, limit)
if regPrefix == "V" {
log.Fatalf("Unexpected V with index: %s", opName)
}
if arrangement != "" {
if arrMap[arrangement] {
arrIdx := cachedOrNew(arrCache, arrangement, len(sveArr))
goAsmOp = fmt.Sprintf("%s%d.%s[%d]", regPrefix, regIdx, sveArr[arrIdx], index)
gnuAsmOp = goAsmOp
} else {
// Fixed arrangement
goAsmOp = fmt.Sprintf("%s%d.%s[%d]", regPrefix, regIdx, arrangement, index)
gnuAsmOp = goAsmOp
}
} else {
goAsmOp = fmt.Sprintf("%s%d[%d]", regPrefix, regIdx, index)
gnuAsmOp = goAsmOp
}
gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...)
goAsmOps = append(goAsmOps, goAsmOp)
} else if op.Typ == "AC_SPZGREG" || op.Typ == "AC_VREG" {
// Operands that takes scalar or NEON vector registers.
// There arn't that much of shapes, just enumerate them...
var goPrefix string
switch op.Typ {
case "AC_SPZGREG":
goPrefix = "R"
case "AC_VREG":
goPrefix = "V"
}
var gnuPrefix string
switch op.Typ {
case "AC_SPZGREG":
if strings.Contains(op.Name, "W") {
gnuPrefix = "W"
} else if strings.Contains(op.Name, "<R>") {
// We know the parser will append the width to the GoOp as a suffix.
switch enc.GoOp[len(enc.GoOp)-1] {
case 'W':
gnuPrefix = "W"
default:
// X does not have a suffix, as we know from the parser rules.
gnuPrefix = "X"
}
} else {
gnuPrefix = "X"
}
case "AC_VREG":
if strings.Contains(op.Name, "<V>") || strings.Contains(op.Name, "<Dd>") {
// We know the parser will append the width to the GoOp as a suffix.
// Also <Dd> literally means the width is D.
suffix := enc.GoOp[len(enc.GoOp)-1]
switch suffix {
case 'B', 'H', 'S', 'D':
gnuPrefix = string(suffix)
default:
log.Fatalf("Unrecognized SIMD vector width: %c, enc: %s", suffix, enc.String())
}
} else {
gnuPrefix = "V"
}
}
var goAsmOp, gnuAsmOp string
switch op.Name {
case "<R><n|SP>", "<Xd|SP>", "<Xn|SP>", "<R><d>", "<R><dn>", "<R><m>",
"<R><n>", "<Wdn>", "<Xd>", "<Xdn>", "<Xm>", "<Xn>", "<Dd>", "<V><d>",
"<V><dn>", "<V><m>", "<V><n>":
// R32 is RSP
regIdx := cachedOrNew(regCache, op.Name, 64)
goAsmOp = fmt.Sprintf("%s%d", goPrefix, regIdx)
gnuAsmOp = fmt.Sprintf("%s%d", gnuPrefix, regIdx)
if regIdx >= 31 && regIdx < 48 {
goAsmOp = "RSP"
if gnuPrefix == "W" {
gnuAsmOp = "WSP"
} else {
gnuAsmOp = "SP"
}
}
if regIdx >= 56 {
goAsmOp = "ZR"
if gnuPrefix == "W" {
gnuAsmOp = "WZR"
} else if gnuPrefix == "X" {
gnuAsmOp = "XZR"
}
}
default:
log.Fatalf("Unrecognized operand: %s", op.Name)
}
gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...)
goAsmOps = append(goAsmOps, goAsmOp)
} else if op.Typ == "AC_IMM" {
var imm string
var isFloat bool
// Check if this is a float imm or an integer imm.
if op.Name == "#0.0" {
imm = "0.0"
isFloat = true
} else {
for _, e := range op.Elems {
if strings.Contains(e.TextExpWithRanges, "float") {
isFloat = true
break
}
}
if isFloat {
// Usually these floats are 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5...
imm = fmt.Sprintf("%.1f", float64(cachedOrNew(immCache, op.Name, 20))/5.0)
} else {
intImm := cachedOrNew(immCache, op.Name, 16)
if intImm > 12 {
// Some instructions likes 90, 270; let's generate them to make those instructions happy.
imm = fmt.Sprintf("%d", (intImm-12)*90)
} else {
imm = fmt.Sprintf("%d", intImm)
}
}
}
if isFloat {
goAsmOps = append(goAsmOps, fmt.Sprintf("$(%s)", imm))
} else {
goAsmOps = append(goAsmOps, fmt.Sprintf("$%s", imm))
}
gnuAsmOps = append([]string{fmt.Sprintf("#%s", imm)}, gnuAsmOps...)
} else if op.Typ == "AC_SPECIAL" {
var opStr string
if op.Name == "<prfop>" {
idx := cachedOrNew(immCache, op.Name, len(prefetchOps))
opStr = prefetchOps[idx]
} else if op.Name == "<vl>" {
idx := cachedOrNew(immCache, op.Name, len(vlOps))
opStr = vlOps[idx]
} else {
log.Fatalf("Unrecognized AC_SPECIAL operand: %s", op.Name)
}
goAsmOps = append(goAsmOps, opStr)
gnuAsmOps = append([]string{opStr}, gnuAsmOps...)
} else if op.Typ == "AC_REGLIST1" || op.Typ == "AC_REGLIST2" || op.Typ == "AC_REGLIST3" || op.Typ == "AC_REGLIST4" {
// Register list, they must be contiguous modulo 32 (16 for P registers).
// Only AC_REGLIST2 has a P register variant though.
// { <Zn>.<T> }
// { <Pd1>.<T>, <Pd2>.<T> }
// { <Zn1>.B, <Zn2>.B }
// { <Zt1>.B, <Zt2>.B, <Zt3>.B }
// { <Zt1>.B, <Zt2>.B, <Zt3>.B, <Zt4>.B }
// We can probably reuse the logic for these registers in AC_ARNG case.
// In Go, the register list is wrapped around [], while in GNU it's wrapped around {}.
nRegs := 1
switch op.Typ {
case "AC_REGLIST2":
nRegs = 2
case "AC_REGLIST3":
nRegs = 3
case "AC_REGLIST4":
nRegs = 4
}
// Extract the first operand inside the list.
trimmedName := strings.Trim(op.Name, "{} ")
parts := strings.Split(trimmedName, ",")
firstOp := strings.TrimSpace(parts[0])
_, _, regPrefix, regIdx, arr := generateSVERegOp(firstOp)
limit := 32
if regPrefix == "P" || regPrefix == "PN" {
limit = 16
}
goRegs := []string{}
gnuRegs := []string{}
for i := 0; i < nRegs; i++ {
currIdx := (regIdx + i) % limit
var goReg, gnuReg string
if arr != "" {
goReg = fmt.Sprintf("%s%d.%s", regPrefix, currIdx, arr)
gnuReg = goReg
} else {
goReg = fmt.Sprintf("%s%d", regPrefix, currIdx)
gnuReg = goReg
}
goRegs = append(goRegs, goReg)
gnuRegs = append(gnuRegs, gnuReg)
}
goAsmOp := "[" + strings.Join(goRegs, ", ") + "]"
gnuAsmOp := "{" + strings.Join(gnuRegs, ", ") + "}"
goAsmOps = append(goAsmOps, goAsmOp)
gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...)
} else if op.Typ == "AC_REGLIST_RANGE" {
nRegs := 2
if strings.Contains(op.Name, "4") {
nRegs = 4
}
trimmedName := strings.Trim(op.Name, "{} ")
parts := strings.Split(trimmedName, "-")
firstOp := strings.TrimSpace(parts[0])
_, _, regPrefix, regIdx, arr := generateSVERegOp(firstOp)
limit := 32
if regPrefix == "P" || regPrefix == "PN" {
limit = 16
}
reg1Idx := regIdx
reg2Idx := (regIdx + nRegs - 1) % limit
var goReg1, goReg2, gnuReg1, gnuReg2 string
if arr != "" {
goReg1 = fmt.Sprintf("%s%d.%s", regPrefix, reg1Idx, arr)
goReg2 = fmt.Sprintf("%s%d.%s", regPrefix, reg2Idx, arr)
gnuReg1 = goReg1
gnuReg2 = goReg2
} else {
goReg1 = fmt.Sprintf("%s%d", regPrefix, reg1Idx)
goReg2 = fmt.Sprintf("%s%d", regPrefix, reg2Idx)
gnuReg1 = goReg1
gnuReg2 = goReg2
}
goAsmOp := fmt.Sprintf("[%s-%s]", goReg1, goReg2)
gnuAsmOp := fmt.Sprintf("{%s-%s}", gnuReg1, gnuReg2)
goAsmOps = append(goAsmOps, goAsmOp)
gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...)
} else if op.Typ == "AC_PREGSEL" {
// GNU: <preg>.<T>[<selreg>, <imm>]
// Go: [selreg.T1, $idximm](preg.T2)
regIdx := cachedOrNew(regCache, "P_pregsel", 16)
arrIdx := rng.IntN(len(sveArr))
resolvedArr := sveArr[arrIdx]
selIdx := cachedOrNew(regCache, "sel_pregsel", 32)
if selIdx == 18 {
selIdx = 19
}
immVal := cachedOrNew(immCache, "imm_pregsel", 4)
gnuAsmOp := fmt.Sprintf("P%d.%s[W%d, %d]", regIdx, resolvedArr, selIdx, immVal)
goAsmOp := fmt.Sprintf("[R%d, $%d](P%d.%s)", selIdx, immVal, regIdx, resolvedArr)
gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...)
goAsmOps = append(goAsmOps, goAsmOp)
} else if op.Typ == "AC_MEMEXT" {
var goReg1, gnuReg1 string
var goReg2, gnuReg2 string
var mod, amount string
// 1. Determine reg1 (Base)
if strings.Contains(op.Name, "<Xn|SP>") {
regIdx := cachedOrNew(regCache, "Xn|SP", 40)
if regIdx > 31 {
goReg1 = "RSP"
gnuReg1 = "SP"
} else {
if regIdx == 18 {
// R18 is reserved as the platform register.
regIdx = 19
}
goReg1 = fmt.Sprintf("R%d", regIdx)
gnuReg1 = fmt.Sprintf("X%d", regIdx)
}
} else if strings.Contains(op.Name, "<Zn>") {
regIdx := cachedOrNew(regCache, "Zn", 32)
arr := "D"
if strings.Contains(op.Name, ".S") {
arr = "S"
} else if strings.Contains(op.Name, ".D") {
arr = "D"
} else if strings.Contains(op.Name, ".<T>") {
arr = sveArr[rng.IntN(len(sveArr))]
}
goReg1 = fmt.Sprintf("Z%d.%s", regIdx, arr)
gnuReg1 = goReg1
}
// 2. Determine reg2 (Offset)
if strings.Contains(op.Name, "<Xm>") {
regIdx := cachedOrNew(regCache, "Xm", 32)
if regIdx == 18 {
regIdx = 19
}
goReg2 = fmt.Sprintf("R%d", regIdx)
gnuReg2 = fmt.Sprintf("X%d", regIdx)
} else if strings.Contains(op.Name, "<Zm>") {
regIdx := cachedOrNew(regCache, "Zm", 32)
arr2 := "D"
if strings.Contains(op.Name, ".S") {
arr2 = "S"
} else if strings.Contains(op.Name, ".D") {
arr2 = "D"
} else if strings.Contains(op.Name, ".<T>") {
arr2 = sveArr[rng.IntN(len(sveArr))]
}
goReg2 = fmt.Sprintf("Z%d.%s", regIdx, arr2)
gnuReg2 = goReg2
}
// 3. Determine mod
if strings.Contains(op.Name, "LSL") {
mod = "LSL"
} else if strings.Contains(op.Name, "UXTW") {
mod = "UXTW"
} else if strings.Contains(op.Name, "SXTW") {
mod = "SXTW"
} else if strings.Contains(op.Name, "<mod>") {
mods := []string{"", "LSL", "UXTW", "SXTW"}
mod = mods[rng.IntN(len(mods))]
}
// 4. Determine amount
if strings.Contains(op.Name, "#1") {
amount = "1"
} else if strings.Contains(op.Name, "#2") {
amount = "2"
} else if strings.Contains(op.Name, "#3") {
amount = "3"
} else if strings.Contains(op.Name, "#4") {
amount = "4"
} else if strings.Contains(op.Name, "<amount>") || strings.Contains(op.Name, "{<amount>}") {
if mod != "" {
amount = fmt.Sprintf("%d", rng.IntN(3)+1) // 1, 2, 3
}
}
// Enforce rule: mod and amount must be nil together, or non nil together
if strings.Contains(op.Name, "<mod>") && strings.Contains(op.Name, "<amount>") {
if mod == "" {
amount = ""
} else if amount == "" {
amount = fmt.Sprintf("%d", rng.IntN(3)+1)
}
}
// 5. Construct GNU string
gnuParts := []string{gnuReg1}
if gnuReg2 != "" {
gnuParts = append(gnuParts, gnuReg2)
}
if mod != "" && amount != "" {
gnuParts = append(gnuParts, fmt.Sprintf("%s #%s", mod, amount))
} else if mod != "" {
gnuParts = append(gnuParts, mod)
}
gnuAsmOp := "[" + strings.Join(gnuParts, ", ") + "]"
// 6. Construct Go string
var goOffset string
if goReg2 != "" {
goOffset = goReg2
if mod == "LSL" {
if amount != "" {
goOffset = fmt.Sprintf("%s<<%s", goReg2, amount)
}
} else if mod != "" {
if amount != "" {
goOffset = fmt.Sprintf("%s.%s<<%s", goReg2, mod, amount)
} else {
goOffset = fmt.Sprintf("%s.%s", goReg2, mod)
}
}
}
var goAsmOp string
if goOffset != "" {
goAsmOp = fmt.Sprintf("(%s)(%s)", goOffset, goReg1)
} else {
goAsmOp = fmt.Sprintf("(%s)", goReg1)
}
goAsmOps = append(goAsmOps, goAsmOp)
gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...)
} else if op.Typ == "AC_MEMOFF" {
var goReg, gnuReg string
var imm string
if strings.Contains(op.Name, "<Xn|SP>") {
regIdx := cachedOrNew(regCache, "Xn|SP", 40)
if regIdx > 31 {
goReg = "RSP"
gnuReg = "SP"
} else {
if regIdx == 18 {
regIdx = 19
}
if regIdx == 28 {
// R28 is G...
regIdx = 29
}
goReg = fmt.Sprintf("R%d", regIdx)
gnuReg = fmt.Sprintf("X%d", regIdx)
}
} else if strings.Contains(op.Name, "<Zn>") {
regIdx := cachedOrNew(regCache, "Zn", 32)
arr := "D"
if strings.Contains(op.Name, ".S") {
arr = "S"
} else if strings.Contains(op.Name, ".D") {
arr = "D"
}
goReg = fmt.Sprintf("Z%d.%s", regIdx, arr)
gnuReg = goReg
}
intImm := rng.IntN(16)
if intImm == 0 {
imm = ""
} else {
imm = fmt.Sprintf("%d", intImm)
}
var goAsmOp, gnuAsmOp string
if imm != "" {
goAsmOp = fmt.Sprintf("%s(%s)", imm, goReg)
gnuAsmOp = fmt.Sprintf("[%s, #%s]", gnuReg, imm)
} else {
goAsmOp = fmt.Sprintf("(%s)", goReg)
gnuAsmOp = fmt.Sprintf("[%s]", gnuReg)
}
goAsmOps = append(goAsmOps, goAsmOp)
gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...)
} else if op.Typ == "AC_MEMOFFMULVL" {
var goReg, gnuReg string
var imm string
if strings.Contains(op.Name, "<Xn|SP>") {
regIdx := cachedOrNew(regCache, "Xn|SP", 40)
if regIdx > 31 {
goReg = "RSP"
gnuReg = "SP"
} else {
if regIdx == 18 {
regIdx = 19
}
if regIdx == 28 {
regIdx = 29
}
goReg = fmt.Sprintf("R%d", regIdx)
gnuReg = fmt.Sprintf("X%d", regIdx)
}
}
intImm := rng.IntN(16) - 8
if intImm == 0 {
intImm = 1
}
imm = fmt.Sprintf("%d", intImm)
var goAsmOp, gnuAsmOp string
if intImm < 0 {
goAsmOp = fmt.Sprintf("(-VL*%d)(%s)", -intImm, goReg)
} else {
goAsmOp = fmt.Sprintf("(VL*%d)(%s)", intImm, goReg)
}
gnuAsmOp = fmt.Sprintf("[%s, #%s, MUL VL]", gnuReg, imm)
goAsmOps = append(goAsmOps, goAsmOp)
gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...)
}
}
// Try to assemble the GNU version.
gnuAsm := fmt.Sprintf(".arch_extension sve2p1\n%s %s", name, strings.Join(gnuAsmOps, ", "))
hex, err := assembleGNU(gnuAsm)
goAsmStr := fmt.Sprintf("%s %s", enc.GoOp[1:], strings.Join(goAsmOps, ", "))
if err != nil {
gnuErr = err
if errCase == nil {
errCase = &e2eData{
GoOp: enc.GoOp[1:],
Asm: fmt.Sprintf("%-50s", goAsmStr),
Binary: "// ERROR \"illegal combination from SVE\"",
highFeat: highFeat,
}
}
continue
}
if validCase == nil {
validCase = &e2eData{
GoOp: enc.GoOp[1:],
Asm: fmt.Sprintf("%-50s", goAsmStr),
Binary: "// " + reverseHex(hex),
highFeat: highFeat,
}
}
if name == "CMPLT" || name == "CMPLE" || name == "CMPLO" || name == "CMPLS" {
// These instructions has an aliased instruction that's not accepted by the GNU assembler.
// However at this moment Go does not support the aliased instruction.
// These e2e test cases will be compared to another non-aliased instruction of the same
// mnemonic, causing a test failure.
// Before we support alias in Go, we skip these test cases.
todoCase := e2eData{GoOp: enc.GoOp[1:], Asm: fmt.Sprintf("// TODO: %s", name), highFeat: highFeat}
return &todoCase, errCase
}
if validCase == nil || (errCase == nil && len(gnuAsmOps) != 0) {
// We must collect at least one valid and one error case.
// if the instruction has 0 operands, we can't generate an error case.
continue
}
if enc.Asm == "INDEX <Zd>.<T>, <R><n>, #<imm>" {
// Don't generate error case for this, the GNU assembler has a strict scalar and vector register width
// constraint, i.e. <R> must be W if <T> is B, H, S. The Go assembler doesn't have this constraint.
// So we can't generate an error case for this.
errCase = &e2eData{GoOp: enc.GoOp[1:], Asm: fmt.Sprintf("// TODO: %s", name), highFeat: highFeat}
}
return validCase, errCase
}
if name == "ADDQP" || name == "ADDSUBP" || name == "SCVTFLT" || name == "UCVTFLT" || name == "LUTI6" || name == "FCVTZSN" ||
name == "FCVTZUN" {
// Very new instructions
// GNU toolchain 2.45 doesn't know about these instruction yet.
todoCase := e2eData{GoOp: enc.GoOp[1:], Asm: fmt.Sprintf("// TODO: %s", name), highFeat: highFeat}
return &todoCase, &todoCase
}
if enc.Asm == "BFMMLA <Zda>.H, <Zn>.H, <Zm>.H" ||
enc.Asm == "FMMLA <Zda>.H, <Zn>.H, <Zm>.H" ||
enc.Asm == "SABAL <Zda>.<T>, <Zn>.<Tb>, <Zm>.<Tb>" ||
enc.Asm == "SCVTF <Zd>.<T>, <Zn>.<Tb>" ||
enc.Asm == "SDOT <Zda>.H, <Zn>.B, <Zm>.B" ||
enc.Asm == "SUBP <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T>" ||
enc.Asm == "UABAL <Zda>.<T>, <Zn>.<Tb>, <Zm>.<Tb>" ||
enc.Asm == "UCVTF <Zd>.<T>, <Zn>.<Tb>" ||
enc.Asm == "UDOT <Zda>.H, <Zn>.B, <Zm>.B" ||
enc.Asm == "SDOT <Zda>.H, <Zn>.B, <Zm>.B[<imm>]" ||
enc.Asm == "UDOT <Zda>.H, <Zn>.B, <Zm>.B[<imm>]" ||
enc.Asm == "SQRSHRN <Zd>.B, { <Zn1>.H-<Zn2>.H }, #<const>" ||
enc.Asm == "SQRSHRUN <Zd>.B, { <Zn1>.H-<Zn2>.H }, #<const>" ||
enc.Asm == "SQSHRN <Zd>.<T>, { <Zn1>.<Tb>-<Zn2>.<Tb> }, #<const>" ||
enc.Asm == "SQSHRUN <Zd>.<T>, { <Zn1>.<Tb>-<Zn2>.<Tb> }, #<const>" ||
enc.Asm == "UQRSHRN <Zd>.B, { <Zn1>.H-<Zn2>.H }, #<const>" ||
enc.Asm == "UQSHRN <Zd>.<T>, { <Zn1>.<Tb>-<Zn2>.<Tb> }, #<const>" {
// Very new instruction encodings
// GNU toolchain 2.45 doesn't know about these specific encodings yet.
todoCase := e2eData{GoOp: enc.GoOp[1:], Asm: fmt.Sprintf("// TODO: %s", enc.Asm), highFeat: highFeat}
return &todoCase, &todoCase
}
log.Fatalf("Failed to construct instance for %s: %v", enc.Asm, gnuErr)
return &e2eData{GoOp: enc.GoOp[1:]}, &e2eData{GoOp: enc.GoOp[1:]}
}