// 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.
		// Stores are not reversed in Go, so we reverse them back.
		if strings.HasPrefix(name, "ST") {
			for i, j := 0, len(gnuAsmOps)-1; i < j; i, j = i+1, j-1 {
				gnuAsmOps[i], gnuAsmOps[j] = gnuAsmOps[j], gnuAsmOps[i]
			}
		}
		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:]}
}
