package load

import (
	"errors"
	"fmt"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/mmcloughlin/avo/internal/inst"
	"github.com/mmcloughlin/avo/internal/opcodescsv"
	"github.com/mmcloughlin/avo/internal/opcodesxml"
)

// This file is a mess. Some of this complexity is unavoidable, since the state
// of x86 instruction databases is also a mess, especially when it comes to
// idiosyncrasies of the Go assembler implementation. Some of the complexity is
// probably avoidable by migrating to using Intel XED
// (https://github.com/mmcloughlin/avo/issues/23), but for now this is an unholy
// mix of PeachPy's Opcodes database and Go's x86 CSV file.
//
// The goal is simply to keep as much of the uglyness in this file as possible,
// producing a clean instruction database for the rest of avo to use. Any nasty
// logic here should be backed up with a test somewhere to ensure the result is
// correct, even if the code that produced it is awful.

// Expected data source filenames.
const (
	DefaultCSVName        = "x86.v0.2.csv"
	DefaultOpcodesXMLName = "x86_64.xml"
)

// Loader builds an instruction database from underlying datasources.
type Loader struct {
	X86CSVPath     string
	OpcodesXMLPath string

	alias map[opcodescsv.Alias]string
	order map[string]opcodescsv.OperandOrder
}

// NewLoaderFromDataDir constructs an instruction loader from datafiles in the
// given directory. The files themselves are expected to have the standard
// names: see Default*Name constants.
func NewLoaderFromDataDir(dir string) *Loader {
	return &Loader{
		X86CSVPath:     filepath.Join(dir, DefaultCSVName),
		OpcodesXMLPath: filepath.Join(dir, DefaultOpcodesXMLName),
	}
}

// Load performs instruction loading.
func (l *Loader) Load() ([]inst.Instruction, error) {
	if err := l.init(); err != nil {
		return nil, err
	}

	// Load Opcodes XML file.
	iset, err := opcodesxml.ReadFile(l.OpcodesXMLPath)
	if err != nil {
		return nil, err
	}

	// Load opcodes XML data, grouped by Go opcode.
	im := map[string]*inst.Instruction{}
	for _, i := range iset.Instructions {
		for _, f := range i.Forms {
			if !l.include(f) {
				continue
			}

			for _, opcode := range l.gonames(f) {
				if im[opcode] == nil {
					im[opcode] = &inst.Instruction{
						Opcode:  opcode,
						Summary: i.Summary,
					}
				}
				forms := l.forms(opcode, f)
				im[opcode].Forms = append(im[opcode].Forms, forms...)
			}
		}
	}

	// Add extras to our list.
	for _, e := range extras {
		im[e.Opcode] = e
	}

	// Merge aliased forms. This is primarily for MOVQ (issue #50).
	for _, a := range aliases {
		if existing, found := im[a.From]; found {
			im[a.To].Forms = append(im[a.To].Forms, existing.Forms...)
		}
	}

	// Apply list of aliases.
	for _, a := range aliases {
		cpy := *im[a.To]
		cpy.Opcode = a.From
		cpy.AliasOf = a.To
		im[a.From] = &cpy
	}

	// Dedupe forms.
	for _, i := range im {
		i.Forms = dedupe(i.Forms)
	}

	// Resolve forms that have VEX and EVEX encoded forms.
	for _, i := range im {
		i.Forms, err = vexevex(i.Forms)
		if err != nil {
			return nil, err
		}
	}

	// Convert to a slice. Sort instructions and forms for reproducibility.
	is := make([]inst.Instruction, 0, len(im))
	for _, i := range im {
		is = append(is, *i)
	}

	sort.Slice(is, func(i, j int) bool {
		return is[i].Opcode < is[j].Opcode
	})

	for _, i := range im {
		sortforms(i.Forms)
	}

	return is, nil
}

func (l *Loader) init() error {
	icsv, err := opcodescsv.ReadFile(l.X86CSVPath)
	if err != nil {
		return err
	}

	l.alias, err = opcodescsv.BuildAliasMap(icsv)
	if err != nil {
		return err
	}

	l.order = opcodescsv.BuildOrderMap(icsv)

	return nil
}

// include decides whether to include the instruction form in the avo listing.
// This discards some opcodes that are not supported in Go.
func (l Loader) include(f opcodesxml.Form) bool {
	// Exclude certain ISAs simply not present in Go
	for _, isa := range f.ISA {
		switch isa.ID {
		// AMD-only.
		case "TBM", "CLZERO", "FMA4", "XOP", "SSE4A", "3dnow!", "3dnow!+":
			return false
		// AVX512PF doesn't work without some special case handling, and is only on Knights Landing/Knights Mill.
		case "AVX512PF":
			return false
		// Incomplete support for some prefetching instructions.
		case "PREFETCH", "PREFETCHW", "PREFETCHWT1", "CLWB":
			return false
		// Remaining oddities.
		case "MONITORX", "FEMMS":
			return false
		}
	}

	// Go appears to have skeleton support for MMX instructions. See the many TODO lines in the testcases:
	// Reference: https://github.com/golang/go/blob/649b89377e91ad6dbe710784f9e662082d31a1ff/src/cmd/asm/internal/asm/testdata/amd64enc.s#L3310-L3312
	//
	//		//TODO: PALIGNR $7, (BX), M2            // 0f3a0f1307
	//		//TODO: PALIGNR $7, (R11), M2           // 410f3a0f1307
	//		//TODO: PALIGNR $7, M2, M2              // 0f3a0fd207
	//
	if f.MMXMode == "MMX" {
		return false
	}

	// x86 csv contains a number of CMOV* instructions which are actually not valid
	// Go instructions. The valid Go forms should have different opcodes from GNU.
	// Therefore a decent "heuristic" is CMOV* instructions that do not have
	// aliases.
	if strings.HasPrefix(f.GASName, "cmov") && l.lookupAlias(f) == "" {
		return false
	}

	// Some specific exclusions.
	switch f.GASName {
	// Certain branch instructions appear to not be supported.
	//
	// Reference: https://github.com/golang/go/blob/649b89377e91ad6dbe710784f9e662082d31a1ff/src/cmd/asm/internal/asm/testdata/amd64enc.s#L757
	//
	//		//TODO: CALLQ* (BX)                     // ff13
	//
	// Reference: https://github.com/golang/go/blob/649b89377e91ad6dbe710784f9e662082d31a1ff/src/cmd/asm/internal/asm/testdata/amd64enc.s#L2108
	//
	//		//TODO: LJMPL* (R11)                    // 41ff2b
	//
	case "callq", "jmpl":
		return false
	// MOVABS doesn't seem to be supported either.
	//
	// Reference: https://github.com/golang/go/blob/1ac84999b93876bb06887e483ae45b27e03d7423/src/cmd/asm/internal/asm/testdata/amd64enc.s#L2354
	//
	//		//TODO: MOVABSB 0x123456789abcdef1, AL  // a0f1debc9a78563412
	//
	case "movabs":
		return false
	// Only one XLAT form is supported.
	//
	// Reference: https://github.com/golang/arch/blob/b19384d3c130858bb31a343ea8fce26be71b5998/x86/x86.v0.2.csv#L2221-L2222
	//
	//	"XLATB","XLAT","xlat","D7","V","V","","ignoreREXW","","",""
	//	"XLATB","XLAT","xlat","REX.W D7","N.E.","V","","pseudo","","",""
	//
	// Reference: https://github.com/golang/go/blob/b3294d9491b898396e134bad5412d85337c37b64/src/cmd/internal/obj/x86/asm6.go#L1519
	//
	//		{AXLAT, ynone, Px, opBytes{0xd7}},
	//
	// TODO(mbm): confirm the Px prefix means non REX mode
	case "xlatb":
		return f.Encoding.REX == nil
	}

	return true
}

func (l Loader) lookupAlias(f opcodesxml.Form) string {
	// Attempt lookup with datasize.
	k := opcodescsv.Alias{
		Opcode:      f.GASName,
		DataSize:    datasize(f),
		NumOperands: len(f.Operands),
	}
	if a := l.alias[k]; a != "" {
		return a
	}

	// Fallback to unknown datasize.
	k.DataSize = 0
	return l.alias[k]
}

func (l Loader) gonames(f opcodesxml.Form) []string {
	s := datasize(f)

	// Suspect a "bug" in x86 CSV for the CVTTSD2SQ instruction, as CVTTSD2SL appears twice.
	//
	// Reference: https://github.com/golang/arch/blob/b19384d3c130858bb31a343ea8fce26be71b5998/x86/x86.v0.2.csv#L345-L346
	//
	//	"CVTTSD2SI r32, xmm2/m64","CVTTSD2SL xmm2/m64, r32","cvttsd2si xmm2/m64, r32","F2 0F 2C /r","V","V","SSE2","operand16,operand32","w,r","Y","32"
	//	"CVTTSD2SI r64, xmm2/m64","CVTTSD2SL xmm2/m64, r64","cvttsd2siq xmm2/m64, r64","F2 REX.W 0F 2C /r","N.E.","V","SSE2","","w,r","Y","64"
	//
	// Reference: https://github.com/golang/go/blob/048c9164a0c5572df18325e377473e7893dbfb07/src/cmd/internal/obj/x86/asm6.go#L1073-L1074
	//
	//		{ACVTTSD2SL, yxcvfl, Pf2, opBytes{0x2c}},
	//		{ACVTTSD2SQ, yxcvfq, Pw, opBytes{Pf2, 0x2c}},
	//
	if f.GASName == "cvttsd2si" && s == 64 {
		return []string{"CVTTSD2SQ"}
	}

	// Return alias if available.
	if a := l.lookupAlias(f); a != "" {
		return []string{a}
	}

	// Some odd special cases.
	// TODO(mbm): can this be handled by processing csv entries with slashes /
	if f.GoName == "RET" && len(f.Operands) == 1 {
		return []string{"RETFW", "RETFL", "RETFQ"}
	}

	// IMUL 3-operand forms are not recorded correctly in either x86 CSV or opcodes. They are supposed to be called IMUL3{W,L,Q}
	//
	// Reference: https://github.com/golang/go/blob/649b89377e91ad6dbe710784f9e662082d31a1ff/src/cmd/internal/obj/x86/asm6.go#L1112-L1114
	//
	//		{AIMUL3W, yimul3, Pe, opBytes{0x6b, 00, 0x69, 00}},
	//		{AIMUL3L, yimul3, Px, opBytes{0x6b, 00, 0x69, 00}},
	//		{AIMUL3Q, yimul3, Pw, opBytes{0x6b, 00, 0x69, 00}},
	//
	// Reference: https://github.com/golang/arch/blob/b19384d3c130858bb31a343ea8fce26be71b5998/x86/x86.v0.2.csv#L549
	//
	//	"IMUL r32, r/m32, imm32","IMULL imm32, r/m32, r32","imull imm32, r/m32, r32","69 /r id","V","V","","operand32","rw,r,r","Y","32"
	//
	if strings.HasPrefix(f.GASName, "imul") && len(f.Operands) == 3 {
		return []string{strings.ToUpper(f.GASName[:4] + "3" + f.GASName[4:])}
	}

	// Use go opcode from Opcodes XML where available.
	if f.GoName != "" {
		return []string{f.GoName}
	}

	// Fallback to GAS name.
	n := strings.ToUpper(f.GASName)

	// Some need data sizes added to them.
	n += sizesuffix(n, f)

	return []string{n}
}

func (l Loader) forms(opcode string, f opcodesxml.Form) []inst.Form {
	// Map operands to avo format and ensure correct order.
	ops := operands(f.Operands)

	switch l.order[opcode] {
	case opcodescsv.IntelOrder:
		// Nothing to do.
	case opcodescsv.CMP3Order:
		ops[0], ops[1] = ops[1], ops[0]
	case opcodescsv.UnknownOrder:
		// Instructions not in x86 CSV are assumed to have reverse intel order.
		fallthrough
	case opcodescsv.ReverseIntelOrder:
		for l, r := 0, len(ops)-1; l < r; l, r = l+1, r-1 {
			ops[l], ops[r] = ops[r], ops[l]
		}
	}

	// Handle some exceptions.
	// TODO(mbm): consider if there's some nicer way to handle the list of special cases.
	switch opcode {
	// Go assembler has an internal Yu2 operand type for unsigned 2-bit immediates.
	//
	// Reference: https://github.com/golang/go/blob/6d5caf38e37bf9aeba3291f1f0b0081f934b1187/src/cmd/internal/obj/x86/asm6.go#L109
	//
	//		Yu2 // $x, x fits in uint2
	//
	// Reference: https://github.com/golang/go/blob/6d5caf38e37bf9aeba3291f1f0b0081f934b1187/src/cmd/internal/obj/x86/asm6.go#L858-L864
	//
	//	var yextractps = []ytab{
	//		{Zibr_m, 2, argList{Yu2, Yxr, Yml}},
	//	}
	//
	//	var ysha1rnds4 = []ytab{
	//		{Zibm_r, 2, argList{Yu2, Yxm, Yxr}},
	//	}
	//
	case "SHA1RNDS4", "EXTRACTPS":
		ops[0].Type = "imm2u"
	}

	// Extract implicit operands.
	var implicits []inst.ImplicitOperand
	for _, implicit := range f.ImplicitOperands {
		implicits = append(implicits, inst.ImplicitOperand{
			Register: implicit.ID,
			Action:   inst.ActionFromReadWrite(implicit.Input, implicit.Output),
		})
	}

	// Extract ISA flags.
	var isas []string
	for _, isa := range f.ISA {
		isas = append(isas, isa.ID)
	}
	sort.Strings(isas)

	// Initialize form.
	form := inst.Form{
		ISA:              isas,
		Operands:         ops,
		ImplicitOperands: implicits,
		EncodingType:     enctype(f),
		CancellingInputs: f.CancellingInputs,
	}

	// Apply modification stages to produce final list of forms.
	stages := []func(string, inst.Form) []inst.Form{
		avx512rounding,
		avx512sae,
		avx512bcst,
		avx512masking,
		avx512zeroing,
	}

	forms := []inst.Form{form}
	for _, stage := range stages {
		var next []inst.Form
		for _, f := range forms {
			next = append(next, stage(opcode, f)...)
		}
		forms = next
	}

	return forms
}

// operands maps Opcodes XML operands to avo format. Returned in Intel order.
func operands(ops []opcodesxml.Operand) []inst.Operand {
	n := len(ops)
	r := make([]inst.Operand, n)
	for i, op := range ops {
		r[i] = operand(op)
	}
	return r
}

// operand maps an Opcodes XML operand to avo format.
func operand(op opcodesxml.Operand) inst.Operand {
	return inst.Operand{
		Type:   op.Type,
		Action: inst.ActionFromReadWrite(op.Input, op.Output),
	}
}

// avx512rounding handles AVX-512 embedded rounding. Opcodes database represents
// these as {er} operands, whereas Go uses instruction suffixes. Remove the
// operand if present and set the corresponding flag.
func avx512rounding(opcode string, f inst.Form) []inst.Form {
	i, found := findoperand(f.Operands, "{er}")
	if !found {
		return []inst.Form{f}
	}

	// Delete the {er} operand.
	f.Operands = append(f.Operands[:i], f.Operands[i+1:]...)

	// Create a second form with the rounding flag.
	er := f.Clone()
	er.EmbeddedRounding = true

	return []inst.Form{f, er}
}

// avx512sae handles AVX-512 "suppress all exceptions". Opcodes database
// represents these as {sae} operands, whereas Go uses instruction suffixes.
// Remove the operand if present and set the corresponding flag.
func avx512sae(opcode string, f inst.Form) []inst.Form {
	i, found := findoperand(f.Operands, "{sae}")
	if !found {
		return []inst.Form{f}
	}

	// Delete the {sae} operand.
	f.Operands = append(f.Operands[:i], f.Operands[i+1:]...)

	// Create a second form with the rounding flag.
	sae := f.Clone()
	sae.SuppressAllExceptions = true

	return []inst.Form{f, sae}
}

// avx512bcst handles AVX-512 broadcast. Opcodes database uses operands like
// "m512/m64bcst" to indicate broadcast. Go uses the BCST suffix to enable it.
// Split the form into two, the regular and broadcast versions.
func avx512bcst(opcode string, f inst.Form) []inst.Form {
	// Look for broadcast operand.
	idx := -1
	for i, op := range f.Operands {
		if bcstrx.MatchString(op.Type) {
			idx = i
			break
		}
	}

	if idx < 0 {
		return []inst.Form{f}
	}

	// Create two forms.
	match := bcstrx.FindStringSubmatch(f.Operands[idx].Type)

	mem := f.Clone()
	mem.Operands[idx].Type = match[1]

	bcst := f.Clone()
	bcst.Broadcast = true
	bcst.Operands[idx].Type = match[2]

	return []inst.Form{mem, bcst}
}

var bcstrx = regexp.MustCompile(`^(m\d+)/(m\d+)bcst$`)

// avx512masking handles AVX-512 masking forms.
func avx512masking(opcode string, f inst.Form) []inst.Form {
	// In order to support implicit masking (with K0), Go has two instruction
	// forms, one with the mask and one without. The mask register precedes the
	// output register. The Opcodes database (similar to Intel manuals)
	// represents masking with the {k} operand suffix, possibly with {z} for
	// zeroing.

	// Look for masking with possible zeroing. Zeroing is handled by a later
	// processing stage, but we need to be sure to notice and preserve it here.
	masking := false
	zeroing := false
	idx := -1
	for i := range f.Operands {
		op := &f.Operands[i]
		if strings.HasSuffix(op.Type, "{z}") {
			zeroing = true
			op.Type = strings.TrimSuffix(op.Type, "{z}")
		}
		if strings.HasSuffix(op.Type, "{k}") {
			masking = true
			idx = i
			op.Type = strings.TrimSuffix(op.Type, "{k}")
			break
		}
	}

	// Bail if no masking.
	if !masking {
		return []inst.Form{f}
	}

	// Unmasked variant.
	unmasked := f.Clone()

	// Masked form has "k" operand inserted.
	masked := f.Clone()
	mask := inst.Operand{Type: "k", Action: inst.R}
	ops := append([]inst.Operand(nil), masked.Operands[:idx]...)
	ops = append(ops, mask)
	ops = append(ops, masked.Operands[idx:]...)
	masked.Operands = ops

	// Restore zeroing suffix, so it can he handled later.
	if zeroing {
		masked.Operands[idx+1].Type += "{z}"
	}

	// Almost all instructions take an optional mask, apart from a few
	// special cases.
	if maskrequired[opcode] {
		return []inst.Form{masked}
	}
	return []inst.Form{unmasked, masked}
}

// avx512zeroing handles AVX-512 zeroing forms.
func avx512zeroing(opcode string, f inst.Form) []inst.Form {
	// Zeroing in Go is handled with the Z opcode suffix. Note that zeroing has
	// an important effect on the instruction form, since the merge masking form
	// has an input dependency for the output register, and the zeroing form
	// does not.

	// Look for zeroing operand.
	idx := -1
	for i := range f.Operands {
		op := &f.Operands[i]
		if strings.HasSuffix(op.Type, "{z}") {
			idx = i
			op.Type = strings.TrimSuffix(op.Type, "{z}")
		}
	}

	if idx < 0 {
		return []inst.Form{f}
	}

	// Duplicate into two forms for merging and zeroing.
	merging := f.Clone()
	merging.Operands[idx].Action |= inst.R

	zeroing := f.Clone()
	zeroing.Zeroing = true

	return []inst.Form{merging, zeroing}
}

// findoperand looks for an operand type and returns its index, if found.
func findoperand(ops []inst.Operand, t string) (int, bool) {
	for i, op := range ops {
		if op.Type == t {
			return i, true
		}
	}
	return 0, false
}

// enctype selects the encoding type for the instruction form.
func enctype(f opcodesxml.Form) inst.EncodingType {
	switch {
	case f.Encoding.EVEX != nil:
		return inst.EncodingTypeEVEX
	case f.Encoding.VEX != nil:
		return inst.EncodingTypeVEX
	case f.Encoding.REX != nil:
		return inst.EncodingTypeREX
	default:
		return inst.EncodingTypeLegacy
	}
}

// datasize (intelligently) guesses the datasize of an instruction form.
func datasize(f opcodesxml.Form) int {
	// Determine from encoding bits.
	e := f.Encoding
	if e.VEX != nil && e.VEX.W == nil {
		return 128 << e.VEX.L
	}

	// Guess from operand types.
	size := 0
	for _, op := range f.Operands {
		s := operandsize(op)
		if s != 0 && (size == 0 || op.Output) {
			size = s
		}
	}

	return size
}

func operandsize(op opcodesxml.Operand) int {
	for s := 256; s >= 8; s /= 2 {
		if strings.HasSuffix(op.Type, strconv.Itoa(s)) {
			return s
		}
	}
	return 0
}

// sizesuffix returns an optional size suffix to be added to the opcode name.
func sizesuffix(n string, f opcodesxml.Form) string {
	// Reference: https://github.com/golang/arch/blob/5de9028c2478e6cb4e1c1b1f4386f3f0a93e383a/x86/x86avxgen/main.go#L275-L322
	//
	//	func addGoSuffixes(ctx *context) {
	//		var opcodeSuffixMatchers map[string][]string
	//		{
	//			opXY := []string{"VL=0", "X", "VL=1", "Y"}
	//			opXYZ := []string{"VL=0", "X", "VL=1", "Y", "VL=2", "Z"}
	//			opQ := []string{"REXW=1", "Q"}
	//			opLQ := []string{"REXW=0", "L", "REXW=1", "Q"}
	//
	//			opcodeSuffixMatchers = map[string][]string{
	//				"VCVTPD2DQ":   opXY,
	//				"VCVTPD2PS":   opXY,
	//				"VCVTTPD2DQ":  opXY,
	//				"VCVTQQ2PS":   opXY,
	//				"VCVTUQQ2PS":  opXY,
	//				"VCVTPD2UDQ":  opXY,
	//				"VCVTTPD2UDQ": opXY,
	//
	//				"VFPCLASSPD": opXYZ,
	//				"VFPCLASSPS": opXYZ,
	//
	//				"VCVTSD2SI":  opQ,
	//				"VCVTTSD2SI": opQ,
	//				"VCVTTSS2SI": opQ,
	//				"VCVTSS2SI":  opQ,
	//
	//				"VCVTSD2USI":  opLQ,
	//				"VCVTSS2USI":  opLQ,
	//				"VCVTTSD2USI": opLQ,
	//				"VCVTTSS2USI": opLQ,
	//				"VCVTUSI2SD":  opLQ,
	//				"VCVTUSI2SS":  opLQ,
	//				"VCVTSI2SD":   opLQ,
	//				"VCVTSI2SS":   opLQ,
	//				"ANDN":        opLQ,
	//				"BEXTR":       opLQ,
	//				"BLSI":        opLQ,
	//				"BLSMSK":      opLQ,
	//				"BLSR":        opLQ,
	//				"BZHI":        opLQ,
	//				"MULX":        opLQ,
	//				"PDEP":        opLQ,
	//				"PEXT":        opLQ,
	//				"RORX":        opLQ,
	//				"SARX":        opLQ,
	//				"SHLX":        opLQ,
	//				"SHRX":        opLQ,
	//			}
	//		}
	//

	type rule struct {
		Size   func(opcodesxml.Form) int
		Suffix map[int]string
	}

	var (
		XY  = rule{evexLLsize, map[int]string{128: "X", 256: "Y"}}
		XYZ = rule{evexLLsize, map[int]string{128: "X", 256: "Y", 512: "Z"}}
		Q   = rule{rexWsize, map[int]string{64: "Q"}}
		LQ  = rule{rexWsize, map[int]string{32: "L", 64: "Q"}}
		WLQ = rule{datasize, map[int]string{16: "W", 32: "L", 64: "Q"}}
	)

	rules := map[string]rule{
		"VCVTPD2DQ":   XY,
		"VCVTPD2PS":   XY,
		"VCVTTPD2DQ":  XY,
		"VCVTQQ2PS":   XY,
		"VCVTUQQ2PS":  XY,
		"VCVTPD2UDQ":  XY,
		"VCVTTPD2UDQ": XY,

		"VFPCLASSPD": XYZ,
		"VFPCLASSPS": XYZ,

		"VCVTSD2SI":  Q,
		"VCVTTSD2SI": Q,
		"VCVTTSS2SI": Q,
		"VCVTSS2SI":  Q,

		"VCVTSD2USI":  LQ,
		"VCVTSS2USI":  LQ,
		"VCVTTSD2USI": LQ,
		"VCVTTSS2USI": LQ,
		"VCVTUSI2SD":  LQ,
		"VCVTUSI2SS":  LQ,
		"VCVTSI2SD":   LQ,
		"VCVTSI2SS":   LQ,
		"ANDN":        LQ,
		"BEXTR":       LQ,
		"BLSI":        LQ,
		"BLSMSK":      LQ,
		"BLSR":        LQ,
		"BZHI":        LQ,
		"MULX":        LQ,
		"PDEP":        LQ,
		"PEXT":        LQ,
		"RORX":        LQ,
		"SARX":        LQ,
		"SHLX":        LQ,
		"SHRX":        LQ,

		"RDRAND": LQ,
		"RDSEED": LQ,

		// MOVEBE* instructions seem to be inconsistent with x86 CSV.
		//
		// Reference: https://github.com/golang/arch/blob/b19384d3c130858bb31a343ea8fce26be71b5998/x86/x86spec/format.go#L282-L287
		//
		//		"MOVBE r16, m16": "movbeww",
		//		"MOVBE m16, r16": "movbeww",
		//		"MOVBE m32, r32": "movbell",
		//		"MOVBE r32, m32": "movbell",
		//		"MOVBE m64, r64": "movbeqq",
		//		"MOVBE r64, m64": "movbeqq",
		//
		"MOVBEW": WLQ,
		"MOVBEL": WLQ,
		"MOVBEQ": WLQ,
	}

	r, ok := rules[n]
	if !ok {
		return ""
	}

	s := r.Size(f)
	return r.Suffix[s]
}

func rexWsize(f opcodesxml.Form) int {
	e := f.Encoding
	switch {
	case e.EVEX != nil && e.EVEX.W != nil:
		return 32 << *e.EVEX.W
	default:
		return 32
	}
}

func evexLLsize(f opcodesxml.Form) int {
	e := f.Encoding
	if e.EVEX == nil {
		return 0
	}
	size := map[string]int{"00": 128, "01": 256, "10": 512}
	return size[e.EVEX.LL]
}

// vexevex fixes instructions that have both VEX and EVEX encoded forms with the
// same operand types. Go uses the VEX encoded form unless EVEX-only features
// are used. This function will only keep the VEX encoded version in the case
// where both exist.
//
// Note this is somewhat of a hack. There are real reasons to use the EVEX
// encoded version even when both exist. The main reason to use the EVEX version
// rather than VEX is to use the registers Z16, Z17, ... and up. However, avo
// does not implement the logic to distinguish between the two halfs of the
// vector registers. So in its current state the only reason to need the EVEX
// version is to encode suffixes, and these are represented by other instruction
// forms.
//
// TODO(mbm): restrict use of vector registers https://github.com/mmcloughlin/avo/issues/146
func vexevex(fs []inst.Form) ([]inst.Form, error) {
	// Group forms by deduping ID.
	byid := map[string][]inst.Form{}
	for _, f := range fs {
		id := fmt.Sprintf(
			"%s {%t,%t,%t,%t}",
			strings.Join(f.Signature(), "_"),
			f.Zeroing,
			f.EmbeddedRounding,
			f.SuppressAllExceptions,
			f.Broadcast,
		)
		byid[id] = append(byid[id], f)
	}

	// Resolve overlaps.
	var results []inst.Form
	for id, group := range byid {
		if len(group) < 2 {
			results = append(results, group...)
			continue
		}

		// We expect these conflicts are caused by VEX/EVEX pairs. Bail if it's
		// something else.
		if len(group) > 2 {
			return nil, fmt.Errorf("more than two forms of type %q", id)
		}

		if group[0].EncodingType == inst.EncodingTypeEVEX {
			group[0], group[1] = group[1], group[0]
		}

		if group[0].EncodingType != inst.EncodingTypeVEX || group[1].EncodingType != inst.EncodingTypeEVEX {
			fmt.Println(group)
			return nil, errors.New("expected pair of VEX/EVEX encoded forms")
		}

		vex := group[0]

		// In this case we only keep the VEX encoded form.
		results = append(results, vex)
	}

	return results, nil
}

// dedupe a list of forms.
func dedupe(fs []inst.Form) []inst.Form {
	uniq := make([]inst.Form, 0, len(fs))
	for _, f := range fs {
		have := false
		for _, u := range uniq {
			if reflect.DeepEqual(u, f) {
				have = true
				break
			}
		}
		if !have {
			uniq = append(uniq, f)
		}
	}
	return uniq
}

// sortforms sorts a list of forms.
func sortforms(fs []inst.Form) {
	sort.Slice(fs, func(i, j int) bool {
		return sortkey(fs[i]) < sortkey(fs[j])
	})
}

func sortkey(f inst.Form) string {
	return fmt.Sprintf("%d %v %v", f.EncodingType, f.ISA, f)
}
