src/simd/archsimd/_gen/simdgen/xed.go GO 1,065 lines View on github.com → Search inside
1// Copyright 2025 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package main67import (8	"fmt"9	"log"10	"maps"11	"reflect"12	"regexp"13	"slices"14	"strconv"15	"strings"1617	"simd/archsimd/_gen/unify"1819	"golang.org/x/arch/x86/xeddata"20	"gopkg.in/yaml.v3"21)2223const (24	NOT_REG_CLASS = iota // not a register25	VREG_CLASS           // classify as a vector register; see26	GREG_CLASS           // classify as a general register27)2829// instVariant is a bitmap indicating a variant of an instruction that has30// optional parameters.31type instVariant uint83233const (34	instVariantNone instVariant = 03536	// instVariantMasked indicates that this is the masked variant of an37	// optionally-masked instruction.38	instVariantMasked instVariant = 1 << iota39)4041var operandRemarks int4243var skipMemOpsInstrs = map[string]bool{44	// The SHA instructions has confusing semantics which we are too complicated45	// to support.46	"SHA1MSG1":    true,47	"SHA1MSG2":    true,48	"SHA1RNDS4":   true,49	"SHA1NEXTE":   true,50	"SHA256MSG1":  true,51	"SHA256MSG2":  true,52	"SHA256RNDS2": true,53	// We don't support 3 input instructions with a memory operand (this appears54	// to be the only one).55	"VPBLENDVB": true,56}5758// TODO: Doc. Returns Values with Def domains.59func loadXED(xedPath string) []*unify.Value {60	// TODO: Obviously a bunch more to do here.6162	db, err := xeddata.NewDatabase(xedPath)63	if err != nil {64		log.Fatalf("open database: %v", err)65	}6667	var defs []*unify.Value68	type opData struct {69		inst *xeddata.Inst70		ops  []operand71		mem  string72	}73	// Maps from opcode to opdata(s).74	memOps := make(map[string][]opData, 0)75	otherOps := make(map[string][]opData, 0)76	appendDefs := func(inst *xeddata.Inst, ops []operand, addFields map[string]string) {77		applyQuirks(inst, ops)7879		defsPos := len(defs)80		defs = append(defs, instToUVal(inst, ops, addFields)...)8182		if *flagDebugXED {83			for i := defsPos; i < len(defs); i++ {84				y, _ := yaml.Marshal(defs[i])85				fmt.Printf("==>\n%s\n", y)86			}87		}88	}89	err = xeddata.WalkInsts(xedPath, func(inst *xeddata.Inst) {90		inst.Pattern = xeddata.ExpandStates(db, inst.Pattern)9192		switch {93		case inst.RealOpcode == "N":94			return // Skip unstable instructions95		case !(strings.HasPrefix(inst.Extension, "AVX") || strings.HasPrefix(inst.Extension, "SHA") ||96			inst.Extension == "FMA" || inst.Extension == "VAES"):97			// We're only interested in AVX and SHA instructions.98			return99		}100101		if *flagDebugXED {102			fmt.Printf("%s:\n%+v\n", inst.Pos, inst)103		}104105		ops, err := decodeOperands(db, strings.Fields(inst.Operands))106		if err != nil {107			operandRemarks++108			if *Verbose {109				log.Printf("%s: [%s] %s", inst.Pos, inst.Opcode(), err)110			}111			return112		}113		var data map[string][]opData114		opcode := inst.Opcode()115		mem := checkMem(ops)116		if mem == "hasMem" && !skipMemOpsInstrs[opcode] {117			// A pure vreg variant might exist, wait for later to see if we can118			// merge them119			data = memOps120		} else {121			data = otherOps122		}123		if _, ok := data[opcode]; !ok {124			s := make([]opData, 1)125			s[0] = opData{inst, ops, mem}126			data[opcode] = s127		} else {128			data[opcode] = append(data[opcode], opData{inst, ops, mem})129		}130	})131	for _, s := range otherOps {132		for _, o := range s {133			addFields := map[string]string{}134			if o.mem == "noMem" {135				opcode := o.inst.Opcode()136				// Checking if there is a vbcst variant of this operation exist137				// First check the opcode138				// Keep this logic in sync with [decodeOperands]139				if ms, ok := memOps[opcode]; ok && !strings.HasPrefix(opcode, "VMOV") {140					feat1, ok1 := decodeCPUFeature(o.inst)141					// Then check if there exist such an operation that for all vreg142					// shapes they are the same at the same index143					var feat1Match, feat2Match string144					matchIdx := -1145					var featMismatchCnt int146				outer:147					for i, m := range ms {148						// Their CPU feature should match first149						var featMismatch bool150						feat2, ok2 := decodeCPUFeature(m.inst)151						if !ok1 || !ok2 {152							continue153						}154						if feat1 != feat2 {155							featMismatch = true156							featMismatchCnt++157						}158						if len(o.ops) == len(m.ops) {159							for j := range o.ops {160								if reflect.TypeOf(o.ops[j]) == reflect.TypeOf(m.ops[j]) {161									v1, ok3 := o.ops[j].(operandVReg)162									v2, _ := m.ops[j].(operandVReg)163									if !ok3 {164										continue165									}166									if v1.vecShape != v2.vecShape {167										// A mismatch, skip this memOp168										continue outer169									}170								} else {171									_, ok3 := o.ops[j].(operandVReg)172									_, ok4 := m.ops[j].(operandMem)173									// The only difference must be the vreg and mem, and the operand must be a read operand.174									if !ok3 || !ok4 || !o.ops[j].common().action.r {175										// A mismatch, skip this memOp176										continue outer177									}178								}179							}180							// Found a match, break early181							matchIdx = i182							feat1Match = feat1183							feat2Match = feat2184							if featMismatchCnt > 1 {185								panic(fmt.Sprintf("multiple feature mismatch vbcst memops detected for %s, simdgen failed to distinguish", opcode))186							}187							if !featMismatch {188								// Mismatch feat is ok but should prioritize matching cases.189								break190							}191						}192					}193					// Remove the match from memOps, it's now merged to this pure vreg operation194					if matchIdx != -1 {195						memOps[opcode] = append(memOps[opcode][:matchIdx], memOps[opcode][matchIdx+1:]...)196						// Merge is done by adding a new field197						// Right now we only have vbcst198						addFields["memFeatures"] = "vbcst"199						if feat1Match != feat2Match {200							addFields["memFeaturesData"] = fmt.Sprintf("feat1=%s;feat2=%s", feat1Match, feat2Match)201						}202					}203				}204			}205			appendDefs(o.inst, o.ops, addFields)206		}207	}208	for _, ms := range memOps {209		for _, m := range ms {210			if *Verbose {211				log.Printf("mem op not merged: %s, %v\n", m.inst.Opcode(), m)212			}213			appendDefs(m.inst, m.ops, nil)214		}215	}216	if err != nil {217		log.Fatalf("walk insts: %v", err)218	}219220	if len(unknownFeatures) > 0 {221		if !*Verbose {222			nInst := 0223			for _, insts := range unknownFeatures {224				nInst += len(insts)225			}226			log.Printf("%d unhandled CPU features for %d instructions (use -v for details)", len(unknownFeatures), nInst)227		} else {228			keys := slices.Sorted(maps.Keys(unknownFeatures))229			for _, key := range keys {230				log.Printf("unhandled ISASet %s", key)231				log.Printf("  opcodes: %s", slices.Sorted(maps.Keys(unknownFeatures[key])))232			}233		}234	}235236	return defs237}238239var (240	maskRequiredRe = regexp.MustCompile(`VPCOMPRESS[BWDQ]|VCOMPRESSP[SD]|VPEXPAND[BWDQ]|VEXPANDP[SD]`)241	maskOptionalRe = regexp.MustCompile(`VPCMP(EQ|GT|U)?[BWDQ]|VCMPP[SD]`)242)243244func applyQuirks(inst *xeddata.Inst, ops []operand) {245	opc := inst.Opcode()246	switch {247	case maskRequiredRe.MatchString(opc):248		// The mask on these instructions is marked optional, but the249		// instruction is pointless without the mask.250		for i, op := range ops {251			if op, ok := op.(operandMask); ok {252				op.optional = false253				ops[i] = op254			}255		}256257	case maskOptionalRe.MatchString(opc):258		// Conversely, these masks should be marked optional and aren't.259		for i, op := range ops {260			if op, ok := op.(operandMask); ok && op.action.r {261				op.optional = true262				ops[i] = op263			}264		}265	}266}267268type operandCommon struct {269	action operandAction270}271272// operandAction defines whether this operand is read and/or written.273//274// TODO: Should this live in [xeddata.Operand]?275type operandAction struct {276	r  bool // Read277	w  bool // Written278	cr bool // Read is conditional (implies r==true)279	cw bool // Write is conditional (implies w==true)280}281282type operandMem struct {283	operandCommon284	vecShape285	elemBaseType scalarBaseType286	// The following fields are not flushed to the final output287	// Supports full-vector broadcasting; implies the operand having a "vv"(vector vector) type specified in width and288	// the instruction is with attribute TXT=BCASTSTR.289	vbcst   bool290	unknown bool // unknown kind291}292293type vecShape struct {294	elemBits  int    // Element size in bits295	bits      int    // Register width in bits (total vector bits)296	fixedName string // the fixed register name297}298299type operandVReg struct { // Vector register300	operandCommon301	vecShape302	elemBaseType scalarBaseType303}304305type operandGReg struct { // Vector register306	operandCommon307	vecShape308	elemBaseType scalarBaseType309}310311// operandMask is a vector mask.312//313// Regardless of the actual mask representation, the [vecShape] of this operand314// corresponds to the "bit for bit" type of mask. That is, elemBits gives the315// element width covered by each mask element, and bits/elemBits gives the total316// number of mask elements. (bits gives the total number of bits as if this were317// a bit-for-bit mask, which may be meaningless on its own.)318type operandMask struct {319	operandCommon320	vecShape321	// Bits in the mask is w/bits.322323	allMasks bool // If set, size cannot be inferred because all operands are masks.324325	// Mask can be omitted, in which case it defaults to K0/"no mask"326	optional bool327}328329type operandImm struct {330	operandCommon331	bits int // Immediate size in bits332}333334type operand interface {335	common() operandCommon336	addToDef(b *unify.DefBuilder)337}338339func strVal(s any) *unify.Value {340	return unify.NewValue(unify.NewStringExact(fmt.Sprint(s)))341}342343func (o operandCommon) common() operandCommon {344	return o345}346347func (o operandMem) addToDef(b *unify.DefBuilder) {348	b.Add("class", strVal("memory"))349	if o.unknown {350		return351	}352	baseDomain, err := unify.NewStringRegex(o.elemBaseType.regex())353	if err != nil {354		panic("parsing baseRe: " + err.Error())355	}356	b.Add("base", unify.NewValue(baseDomain))357	b.Add("bits", strVal(o.bits))358	if o.elemBits != o.bits {359		b.Add("elemBits", strVal(o.elemBits))360	}361}362363func (o operandVReg) addToDef(b *unify.DefBuilder) {364	baseDomain, err := unify.NewStringRegex(o.elemBaseType.regex())365	if err != nil {366		panic("parsing baseRe: " + err.Error())367	}368	b.Add("class", strVal("vreg"))369	b.Add("bits", strVal(o.bits))370	b.Add("base", unify.NewValue(baseDomain))371	// If elemBits == bits, then the vector can be ANY shape. This happens with,372	// for example, logical ops.373	if o.elemBits != o.bits {374		b.Add("elemBits", strVal(o.elemBits))375	}376	if o.fixedName != "" {377		b.Add("fixedReg", strVal(o.fixedName))378	}379}380381func (o operandGReg) addToDef(b *unify.DefBuilder) {382	baseDomain, err := unify.NewStringRegex(o.elemBaseType.regex())383	if err != nil {384		panic("parsing baseRe: " + err.Error())385	}386	b.Add("class", strVal("greg"))387	b.Add("bits", strVal(o.bits))388	b.Add("base", unify.NewValue(baseDomain))389	if o.elemBits != o.bits {390		b.Add("elemBits", strVal(o.elemBits))391	}392	if o.fixedName != "" {393		b.Add("fixedReg", strVal(o.fixedName))394	}395}396397func (o operandMask) addToDef(b *unify.DefBuilder) {398	b.Add("class", strVal("mask"))399	if o.allMasks {400		// If all operands are masks, omit sizes and let unification determine mask sizes.401		return402	}403	b.Add("elemBits", strVal(o.elemBits))404	b.Add("bits", strVal(o.bits))405	if o.fixedName != "" {406		b.Add("fixedReg", strVal(o.fixedName))407	}408}409410func (o operandImm) addToDef(b *unify.DefBuilder) {411	b.Add("class", strVal("immediate"))412	b.Add("bits", strVal(o.bits))413}414415var actionEncoding = map[string]operandAction{416	"r":   {r: true},417	"cr":  {r: true, cr: true},418	"w":   {w: true},419	"cw":  {w: true, cw: true},420	"rw":  {r: true, w: true},421	"crw": {r: true, w: true, cr: true},422	"rcw": {r: true, w: true, cw: true},423}424425func decodeOperand(db *xeddata.Database, operand string) (operand, error) {426	op, err := xeddata.NewOperand(db, operand)427	if err != nil {428		log.Fatalf("parsing operand %q: %v", operand, err)429	}430	if *flagDebugXED {431		fmt.Printf("  %+v\n", op)432	}433434	if strings.HasPrefix(op.Name, "EMX_BROADCAST") {435		// This refers to a set of macros defined in all-state.txt that set a436		// BCAST operand to various fixed values. But the BCAST operand is437		// itself suppressed and "internal", so I think we can just ignore this438		// operand.439		return nil, nil440	}441442	// TODO: See xed_decoded_inst_operand_action. This might need to be more443	// complicated.444	action, ok := actionEncoding[op.Action]445	if !ok {446		return nil, fmt.Errorf("unknown action %q", op.Action)447	}448	common := operandCommon{action: action}449450	lhs := op.NameLHS()451	if strings.HasPrefix(lhs, "MEM") {452		// looks like XED data has an inconsistency on VPADDD, marking attribute453		// VPBROADCASTD instead of the canonical BCASTSTR.454		if op.Width == "vv" && (op.Attributes["TXT=BCASTSTR"] ||455			op.Attributes["TXT=VPBROADCASTD"]) {456			baseType, elemBits, ok := decodeType(op)457			if !ok {458				return nil, fmt.Errorf("failed to decode memory width %q", operand)459			}460			// This operand has two possible width([bits]):461			// 1. the same as the other operands462			// 2. the element width as the other operands (broaccasting)463			// left it default to 2, later we will set a new field in the operation464			// to indicate this dual-width property.465			shape := vecShape{elemBits: elemBits, bits: elemBits}466			return operandMem{467				operandCommon: common,468				vecShape:      shape,469				elemBaseType:  baseType,470				vbcst:         true,471				unknown:       false,472			}, nil473		} else {474			baseType, elemBits, ok := decodeType(op)475			if !ok {476				return nil, fmt.Errorf("failed to decode memory width %q", operand)477			}478			sizeStr := db.WidthSize(op.Width, xeddata.OpSize64)479			bytes, err := strconv.Atoi(sizeStr)480			if err != nil {481				return nil, fmt.Errorf("failed to decode memory width %q: %s", operand, err)482			}483			if bytes > 0 {484				memBits := bytes * 8485				shape := vecShape{elemBits: elemBits, bits: memBits}486				return operandMem{487					operandCommon: common,488					vecShape:      shape,489					elemBaseType:  baseType,490					vbcst:         false,491					unknown:       false,492				}, nil493			}494		}495		return operandMem{496			operandCommon: common,497			unknown:       true,498		}, nil499	} else if strings.HasPrefix(lhs, "REG") {500		if op.Width == "mskw" {501			// The mask operand doesn't specify a width. We have to infer it.502			//503			// XED uses the marker ZEROSTR to indicate that a mask operand is504			// optional and, if omitted, implies K0, aka "no mask".505			return operandMask{506				operandCommon: common,507				optional:      op.Attributes["TXT=ZEROSTR"],508			}, nil509		} else {510			class, regBits, fixedReg := decodeReg(op)511			if class == NOT_REG_CLASS {512				return nil, fmt.Errorf("failed to decode register %q", operand)513			}514			baseType, elemBits, ok := decodeType(op)515			if !ok {516				return nil, fmt.Errorf("failed to decode register width %q", operand)517			}518			shape := vecShape{elemBits: elemBits, bits: regBits, fixedName: fixedReg}519			if class == VREG_CLASS {520				return operandVReg{521					operandCommon: common,522					vecShape:      shape,523					elemBaseType:  baseType,524				}, nil525			}526			// general register527			m := min(shape.bits, shape.elemBits)528			shape.bits, shape.elemBits = m, m529			return operandGReg{530				operandCommon: common,531				vecShape:      shape,532				elemBaseType:  baseType,533			}, nil534535		}536	} else if strings.HasPrefix(lhs, "IMM") {537		_, bits, ok := decodeType(op)538		if !ok {539			return nil, fmt.Errorf("failed to decode register width %q", operand)540		}541		return operandImm{542			operandCommon: common,543			bits:          bits,544		}, nil545	}546547	// TODO: BASE and SEG548	return nil, fmt.Errorf("unknown operand LHS %q in %q", lhs, operand)549}550551func decodeOperands(db *xeddata.Database, operands []string) (ops []operand, err error) {552	// Decode the XED operand descriptions.553	for _, o := range operands {554		op, err := decodeOperand(db, o)555		if err != nil {556			return nil, err557		}558		if op != nil {559			ops = append(ops, op)560		}561	}562563	// XED doesn't encode the size of mask operands. If there are mask operands,564	// try to infer their sizes from other operands.565	if err := inferMaskSizes(ops); err != nil {566		return nil, fmt.Errorf("%w in operands %+v", err, operands)567	}568569	return ops, nil570}571572func inferMaskSizes(ops []operand) error {573	// This is a heuristic and it falls apart in some cases:574	//575	// - Mask operations like KAND[BWDQ] have *nothing* in the XED to indicate576	// mask size.577	//578	// - VINSERT*, VPSLL*, VPSRA*, and VPSRL* and some others naturally have579	// mixed input sizes and the XED doesn't indicate which operands the mask580	// applies to.581	//582	// - VPDP* and VP4DP* have really complex mixed operand patterns.583	//584	// I think for these we may just have to hand-write a table of which585	// operands each mask applies to.586	inferMask := func(r, w bool) error {587		var masks []int588		var rSizes, wSizes, sizes []vecShape589		allMasks := true590		hasWMask := false591		for i, op := range ops {592			action := op.common().action593			if _, ok := op.(operandMask); ok {594				if action.r && action.w {595					return fmt.Errorf("unexpected rw mask")596				}597				if action.r == r || action.w == w {598					masks = append(masks, i)599				}600				if action.w {601					hasWMask = true602				}603			} else {604				allMasks = false605				if reg, ok := op.(operandVReg); ok {606					if action.r {607						rSizes = append(rSizes, reg.vecShape)608					}609					if action.w {610						wSizes = append(wSizes, reg.vecShape)611					}612				}613			}614		}615		if len(masks) == 0 {616			return nil617		}618619		if r {620			sizes = rSizes621			if len(sizes) == 0 {622				sizes = wSizes623			}624		}625		if w {626			sizes = wSizes627			if len(sizes) == 0 {628				sizes = rSizes629			}630		}631632		if len(sizes) == 0 {633			// If all operands are masks, leave the mask inferrence to the users.634			if allMasks {635				for _, i := range masks {636					m := ops[i].(operandMask)637					m.allMasks = true638					ops[i] = m639				}640				return nil641			}642			return fmt.Errorf("cannot infer mask size: no register operands")643		}644		shape, ok := singular(sizes)645		if !ok {646			if !hasWMask && len(wSizes) == 1 && len(masks) == 1 {647				// This pattern looks like predicate mask, so its shape should align with the648				// output. TODO: verify this is a safe assumption.649				shape = wSizes[0]650			} else {651				return fmt.Errorf("cannot infer mask size: multiple register sizes %v", sizes)652			}653		}654		for _, i := range masks {655			m := ops[i].(operandMask)656			m.vecShape = shape657			ops[i] = m658		}659		return nil660	}661	if err := inferMask(true, false); err != nil {662		return err663	}664	if err := inferMask(false, true); err != nil {665		return err666	}667	return nil668}669670// addOperandsToDef adds "in", "inVariant", and "out" to an instruction Def.671//672// Optional mask input operands are added to the inVariant field if673// variant&instVariantMasked, and omitted otherwise.674func addOperandsToDef(ops []operand, instDB *unify.DefBuilder, variant instVariant) {675	var inVals, inVar, outVals []*unify.Value676	asmPos := 0677	for _, op := range ops {678		var db unify.DefBuilder679		op.addToDef(&db)680		db.Add("asmPos", unify.NewValue(unify.NewStringExact(fmt.Sprint(asmPos))))681682		action := op.common().action683		asmCount := 1 // # of assembly operands; 0 or 1684		if action.r {685			inVal := unify.NewValue(db.Build())686			// If this is an optional mask, put it in the input variant tuple.687			if mask, ok := op.(operandMask); ok && mask.optional {688				if variant&instVariantMasked != 0 {689					inVar = append(inVar, inVal)690				} else {691					// This operand doesn't appear in the assembly at all.692					asmCount = 0693				}694			} else {695				// Just a regular input operand.696				inVals = append(inVals, inVal)697			}698		}699		if action.w {700			outVal := unify.NewValue(db.Build())701			outVals = append(outVals, outVal)702		}703704		asmPos += asmCount705	}706707	instDB.Add("in", unify.NewValue(unify.NewTuple(inVals...)))708	instDB.Add("inVariant", unify.NewValue(unify.NewTuple(inVar...)))709	instDB.Add("out", unify.NewValue(unify.NewTuple(outVals...)))710	memFeatures := checkMem(ops)711	if memFeatures != "noMem" {712		instDB.Add("memFeatures", unify.NewValue(unify.NewStringExact(memFeatures)))713	}714}715716// checkMem checks the shapes of memory operand in the operation and returns the shape.717// Keep this function in sync with [decodeOperand].718func checkMem(ops []operand) string {719	memState := "noMem"720	var mem *operandMem721	memCnt := 0722	for _, op := range ops {723		if m, ok := op.(operandMem); ok {724			mem = &m725			memCnt++726		}727	}728	if mem != nil {729		if mem.unknown {730			memState = "unknown"731		} else if memCnt > 1 {732			memState = "tooManyMem"733		} else {734			// This shape has an indication that [bits] fields has two possible value:735			// 1. The element broadcast width, which is its peer vreg operand's [elemBits] (default val in the parsed XED data)736			// 2. The full vector width, which is its peer vreg operand's [bits] (godefs should be aware of this)737			memState = "hasMem"738		}739	}740	return memState741}742743func instToUVal(inst *xeddata.Inst, ops []operand, addFields map[string]string) []*unify.Value {744	feature, ok := decodeCPUFeature(inst)745	if !ok {746		return nil747	}748749	var vals []*unify.Value750	vals = append(vals, instToUVal1(inst, ops, feature, instVariantNone, addFields))751	if hasOptionalMask(ops) {752		vals = append(vals, instToUVal1(inst, ops, feature, instVariantMasked, addFields))753	}754	return vals755}756757func instToUVal1(inst *xeddata.Inst, ops []operand, feature string, variant instVariant, addFields map[string]string) *unify.Value {758	var db unify.DefBuilder759	db.Add("goarch", unify.NewValue(unify.NewStringExact("amd64")))760	db.Add("asm", unify.NewValue(unify.NewStringExact(inst.Opcode())))761	addOperandsToDef(ops, &db, variant)762	db.Add("cpuFeature", unify.NewValue(unify.NewStringExact(feature)))763	for k, v := range addFields {764		db.Add(k, unify.NewValue(unify.NewStringExact(v)))765	}766767	if strings.Contains(inst.Pattern, "ZEROING=0") {768		// This is an EVEX instruction, but the ".Z" (zero-merging)769		// instruction flag is NOT valid. EVEX.z must be zero.770		//771		// This can mean a few things:772		//773		// - The output of an instruction is a mask, so merging modes don't774		// make any sense. E.g., VCMPPS.775		//776		// - There are no masks involved anywhere. (Maybe MASK=0 is also set777		// in this case?) E.g., VINSERTPS.778		//779		// - The operation inherently performs merging. E.g., VCOMPRESSPS780		// with a mem operand.781		//782		// There may be other reasons.783		db.Add("zeroing", unify.NewValue(unify.NewStringExact("false")))784	}785	pos := unify.Pos{Path: inst.Pos.Path, Line: inst.Pos.Line}786	return unify.NewValuePos(db.Build(), pos)787}788789// decodeCPUFeature returns the CPU feature name required by inst. These match790// the names of the "Has*" feature checks in the simd package.791func decodeCPUFeature(inst *xeddata.Inst) (string, bool) {792	isaSet := inst.ISASet793	if isaSet == "" {794		// Older instructions don't have an ISA set. Use their "extension"795		// instead.796		isaSet = inst.Extension797	}798	// We require AVX512VL to use AVX512 at all, so strip off the vector length799	// suffixes.800	if strings.HasPrefix(isaSet, "AVX512") {801		isaSet = isaSetVL.ReplaceAllLiteralString(isaSet, "")802	}803804	feat, ok := cpuFeatureMap[isaSet]805	if !ok {806		imap := unknownFeatures[isaSet]807		if imap == nil {808			imap = make(map[string]struct{})809			unknownFeatures[isaSet] = imap810		}811		imap[inst.Opcode()] = struct{}{}812		return "", false813	}814	if feat == "ignore" {815		return "", false816	}817	return feat, true818}819820var isaSetVL = regexp.MustCompile("_(128N?|256N?|512)$")821822// cpuFeatureMap maps from XED's "ISA_SET" (or "EXTENSION") to a CPU feature823// name to expose in the SIMD feature check API.824//825// See XED's datafiles/*/cpuid.xed.txt for how ISA set names map to CPUID flags.826var cpuFeatureMap = map[string]string{827	"AVX":      "AVX",828	"AVX_VNNI": "AVXVNNI",829	"AVX2":     "AVX2",830	"AVXAES":   "AVXAES",831	"SHA":      "SHA",832	"FMA":      "FMA",833	"VAES":     "VAES",834835	// AVX-512 foundational features. We combine all of these into one "AVX512" feature.836	"AVX512F":  "AVX512",837	"AVX512BW": "AVX512",838	"AVX512CD": "AVX512",839	"AVX512DQ": "AVX512",840	// AVX512VL doesn't appear as its own ISASet; instead, the CPUID flag is841	// required by the *_128 and *_256 ISASets. We fold it into "AVX512" anyway.842843	// AVX-512 extension features844	"AVX512_BITALG":     "AVX512BITALG",845	"AVX512_GFNI":       "AVX512GFNI",846	"AVX512_VBMI":       "AVX512VBMI",847	"AVX512_VBMI2":      "AVX512VBMI2",848	"AVX512_VNNI":       "AVX512VNNI",849	"AVX512_VPOPCNTDQ":  "AVX512VPOPCNTDQ",850	"AVX512_VAES":       "AVX512VAES",851	"AVX512_VPCLMULQDQ": "AVX512VPCLMULQDQ",852853	// AVX 10.2 (not yet supported)854	"AVX10_2_RC": "ignore",855}856857func init() {858	// TODO: In general, Intel doesn't make any guarantees about what flags are859	// set, so this means our feature checks need to ensure these, just to be860	// sure.861	var features = map[string]featureInfo{862		"AVX2":   {Implies: []string{"AVX"}},863		"AVX512": {Implies: []string{"AVX2"}},864865		"AVXAES": {Virtual: true, Implies: []string{"AVX", "AES"}},866		"FMA":    {Implies: []string{"AVX"}},867		"VAES":   {Implies: []string{"AVX"}},868869		// AVX-512 subfeatures.870		"AVX512BITALG":    {Implies: []string{"AVX512"}},871		"AVX512GFNI":      {Implies: []string{"AVX512"}},872		"AVX512VBMI":      {Implies: []string{"AVX512"}},873		"AVX512VBMI2":     {Implies: []string{"AVX512"}},874		"AVX512VNNI":      {Implies: []string{"AVX512"}},875		"AVX512VPOPCNTDQ": {Implies: []string{"AVX512"}},876		"AVX512VAES":      {Implies: []string{"AVX512"}},877878		// AVX-VNNI and AVX-IFMA are "backports" of the AVX512-VNNI/IFMA879		// instructions to VEX encoding, limited to 256 bit vectors. They're880		// intended for lower end CPUs that want to support VNNI/IFMA without881		// supporting AVX-512. As such, they're built on AVX2's VEX encoding.882		"AVXVNNI": {Implies: []string{"AVX2"}},883		"AVXIFMA": {Implies: []string{"AVX2"}},884	}885	registerFeatureInfo("amd64", goarchFeatures{886		featureVar: "X86",887		features:   features,888	})889}890891var unknownFeatures = map[string]map[string]struct{}{}892893// hasOptionalMask returns whether there is an optional mask operand in ops.894func hasOptionalMask(ops []operand) bool {895	for _, op := range ops {896		if op, ok := op.(operandMask); ok && op.optional {897			return true898		}899	}900	return false901}902903func singular[T comparable](xs []T) (T, bool) {904	if len(xs) == 0 {905		return *new(T), false906	}907	for _, x := range xs[1:] {908		if x != xs[0] {909			return *new(T), false910		}911	}912	return xs[0], true913}914915type fixedReg struct {916	class int917	name  string918	width int919}920921var fixedRegMap = map[string]fixedReg{922	"XED_REG_XMM0": {VREG_CLASS, "x0", 128},923}924925// decodeReg returns class (NOT_REG_CLASS, VREG_CLASS, GREG_CLASS, VREG_CLASS_FIXED,926// GREG_CLASS_FIXED), width in bits and reg name(if fixed).927// If the operand cannot be decided as a register, then the clas is NOT_REG_CLASS.928func decodeReg(op *xeddata.Operand) (class, width int, name string) {929	// op.Width tells us the total width, e.g.,:930	//931	//    dq => 128 bits (XMM)932	//    qq => 256 bits (YMM)933	//    mskw => K934	//    z[iuf?](8|16|32|...) => 512 bits (ZMM)935	//936	// But the encoding is really weird and it's not clear if these *always*937	// mean XMM/YMM/ZMM or if other irregular things can use these large widths.938	// Hence, we dig into the register sets themselves.939940	if !strings.HasPrefix(op.NameLHS(), "REG") {941		return NOT_REG_CLASS, 0, ""942	}943	// TODO: We shouldn't be relying on the macro naming conventions. We should944	// use all-dec-patterns.txt, but xeddata doesn't support that table right now.945	rhs := op.NameRHS()946	if !strings.HasSuffix(rhs, "()") {947		if fixedReg, ok := fixedRegMap[rhs]; ok {948			return fixedReg.class, fixedReg.width, fixedReg.name949		}950		return NOT_REG_CLASS, 0, ""951	}952	switch {953	case strings.HasPrefix(rhs, "XMM_"):954		return VREG_CLASS, 128, ""955	case strings.HasPrefix(rhs, "YMM_"):956		return VREG_CLASS, 256, ""957	case strings.HasPrefix(rhs, "ZMM_"):958		return VREG_CLASS, 512, ""959	case strings.HasPrefix(rhs, "GPR64_"), strings.HasPrefix(rhs, "VGPR64_"):960		return GREG_CLASS, 64, ""961	case strings.HasPrefix(rhs, "GPR32_"), strings.HasPrefix(rhs, "VGPR32_"):962		return GREG_CLASS, 32, ""963	}964	return NOT_REG_CLASS, 0, ""965}966967var xtypeRe = regexp.MustCompile(`^([iuf])([0-9]+)$`)968969// scalarBaseType describes the base type of a scalar element. This is a Go970// type, but without the bit width suffix (with the exception of971// scalarBaseIntOrUint).972type scalarBaseType int973974const (975	scalarBaseInt scalarBaseType = iota976	scalarBaseUint977	scalarBaseIntOrUint // Signed or unsigned is unspecified978	scalarBaseFloat979	scalarBaseComplex980	scalarBaseBFloat981	scalarBaseHFloat982)983984func (s scalarBaseType) regex() string {985	switch s {986	case scalarBaseInt:987		return "int"988	case scalarBaseUint:989		return "uint"990	case scalarBaseIntOrUint:991		return "int|uint"992	case scalarBaseFloat:993		return "float"994	case scalarBaseComplex:995		return "complex"996	case scalarBaseBFloat:997		return "BFloat"998	case scalarBaseHFloat:999		return "HFloat"1000	}1001	panic(fmt.Sprintf("unknown scalar base type %d", s))1002}10031004func decodeType(op *xeddata.Operand) (base scalarBaseType, bits int, ok bool) {1005	// The xtype tells you the element type. i8, i16, i32, i64, f32, etc.1006	//1007	// TODO: Things like AVX2 VPAND have an xtype of u256 because they're1008	// element-width agnostic. Do I map that to all widths, or just omit the1009	// element width and let unification flesh it out? There's no u5121010	// (presumably those are all masked, so elem width matters). These are all1011	// Category: LOGICAL, so maybe we could use that info?10121013	// Handle some weird ones.1014	switch op.Xtype {1015	// 8-bit float formats as defined by Open Compute Project "OCP 8-bit1016	// Floating Point Specification (OFP8)".1017	case "bf8": // E5M2 float1018		return scalarBaseBFloat, 8, true1019	case "hf8": // E4M3 float1020		return scalarBaseHFloat, 8, true1021	case "bf16": // bfloat16 float1022		return scalarBaseBFloat, 16, true1023	case "2f16":1024		// Complex consisting of 2 float16s. Doesn't exist in Go, but we can say1025		// what it would be.1026		return scalarBaseComplex, 32, true1027	case "2i8", "2I8":1028		// These just use the lower INT8 in each 16 bit field.1029		// As far as I can tell, "2I8" is a typo.1030		return scalarBaseInt, 8, true1031	case "2u16", "2U16":1032		// some VPDP* has it1033		// TODO: does "z" means it has zeroing?1034		return scalarBaseUint, 16, true1035	case "2i16", "2I16":1036		// some VPDP* has it1037		return scalarBaseInt, 16, true1038	case "4u8", "4U8":1039		// some VPDP* has it1040		return scalarBaseUint, 8, true1041	case "4i8", "4I8":1042		// some VPDP* has it1043		return scalarBaseInt, 8, true1044	}10451046	// The rest follow a simple pattern.1047	m := xtypeRe.FindStringSubmatch(op.Xtype)1048	if m == nil {1049		// TODO: Report unrecognized xtype1050		return 0, 0, false1051	}1052	bits, _ = strconv.Atoi(m[2])1053	switch m[1] {1054	case "i", "u":1055		// XED is rather inconsistent about what's signed, unsigned, or doesn't1056		// matter, so merge them together and let the Go definitions narrow as1057		// appropriate. Maybe there's a better way to do this.1058		return scalarBaseIntOrUint, bits, true1059	case "f":1060		return scalarBaseFloat, bits, true1061	default:1062		panic("unreachable")1063	}1064}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.