// Copyright 2018 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 ( "fmt" "log" "regexp" "strings" "golang.org/x/arch/x86/xeddata" ) // encoding is decoded XED instruction pattern. type encoding struct { // opbyte is opcode byte (one that follows [E]VEX prefix). // It's called "opcode" in Intel manual, but we use that for // instruction name (iclass in XED terms). opbyte string // opdigit is ModRM.Reg field used to encode opcode extension. // In Intel manual, "/digit" notation is used. opdigit string // vex represents [E]VEX fields that are used in a first [E]VEX // opBytes element (see prefixExpr function). vex struct { P string // 66/F2/F3 L string // 128/256/512 M string // 0F/0F38/0F3A W string // W0/W1 } // evexScale is a scaling factor used to calculate compact disp-8. evexScale string // evexBcstScale is like evexScale, but used during broadcasting. // Empty for optab entries that do not have broadcasting support. evexBcstScale string // evex describes which features of EVEX can be used by optab entry. // All flags are "false" for VEX-encoded insts. evex struct { // There is no "broadcast" flag because it's inferred // from non-empty evexBcstScale. SAE bool // EVEX.b controls SAE for reg-reg insts Rounding bool // EVEX.b + EVEX.RC (VL) control rounding for FP insts Zeroing bool // Instruction can use zeroing. } } type decoder struct { ctx *context insts []*instruction } // decodeGroups fills ctx.groups with decoded instruction groups. // // Reads XED objects from ctx.xedPath. func decodeGroups(ctx *context) { d := decoder{ctx: ctx} groups := make(map[string][]*instruction) for _, inst := range d.DecodeAll() { groups[inst.opcode] = append(groups[inst.opcode], inst) } for op, insts := range groups { ctx.groups = append(ctx.groups, &instGroup{ opcode: op, list: insts, }) } } // DecodeAll decodes every XED instruction. func (d *decoder) DecodeAll() []*instruction { err := xeddata.WalkInsts(d.ctx.xedPath, func(inst *xeddata.Inst) { inst.Pattern = xeddata.ExpandStates(d.ctx.db, inst.Pattern) pset := xeddata.NewPatternSet(inst.Pattern) opcode := inst.Iclass switch { case inst.HasAttribute("AMDONLY") || inst.Extension == "XOP": return // Only VEX and EVEX are supported case !pset.Is("VEX") && !pset.Is("EVEX"): return // Skip non-AVX instructions case inst.RealOpcode == "N": return // Skip unstable instructions } // Expand some patterns to simplify decodePattern. pset.Replace("FIX_ROUND_LEN128()", "VL=0") pset.Replace("FIX_ROUND_LEN512()", "VL=2") mask, args := d.decodeArgs(pset, inst) d.insts = append(d.insts, &instruction{ pset: pset, opcode: opcode, mask: mask, args: args, enc: d.decodePattern(pset, inst), }) }) if err != nil { log.Fatalf("walk insts: %v", err) } return d.insts } // registerArgs maps XED argument name RHS to its decoded version. var registerArgs = map[string]argument{ "GPR32_R()": {"Yrl", "reg"}, "GPR64_R()": {"Yrl", "reg"}, "VGPR32_R()": {"Yrl", "reg"}, "VGPR64_R()": {"Yrl", "reg"}, "VGPR32_N()": {"Yrl", "regV"}, "VGPR64_N()": {"Yrl", "regV"}, "GPR32_B()": {"Yrl", "reg/mem"}, "GPR64_B()": {"Yrl", "reg/mem"}, "VGPR32_B()": {"Yrl", "reg/mem"}, "VGPR64_B()": {"Yrl", "reg/mem"}, "XMM_R()": {"Yxr", "reg"}, "XMM_R3()": {"YxrEvex", "reg"}, "XMM_N()": {"Yxr", "regV"}, "XMM_N3()": {"YxrEvex", "regV"}, "XMM_B()": {"Yxr", "reg/mem"}, "XMM_B3()": {"YxrEvex", "reg/mem"}, "XMM_SE()": {"Yxr", "regIH"}, "YMM_R()": {"Yyr", "reg"}, "YMM_R3()": {"YyrEvex", "reg"}, "YMM_N()": {"Yyr", "regV"}, "YMM_N3()": {"YyrEvex", "regV"}, "YMM_B()": {"Yyr", "reg/mem"}, "YMM_B3()": {"YyrEvex", "reg/mem"}, "YMM_SE()": {"Yyr", "regIH"}, "ZMM_R3()": {"Yzr", "reg"}, "ZMM_N3()": {"Yzr", "regV"}, "ZMM_B3()": {"Yzr", "reg/mem"}, "MASK_R()": {"Yk", "reg"}, "MASK_N()": {"Yk", "regV"}, "MASK_B()": {"Yk", "reg/mem"}, "MASKNOT0()": {"Yknot0", "kmask"}, // Handled specifically in "generate". "MASK1()": {"MASK1()", "MASK1()"}, } func (d *decoder) decodeArgs(pset xeddata.PatternSet, inst *xeddata.Inst) (mask *argument, args []*argument) { for i, f := range strings.Fields(inst.Operands) { xarg, err := xeddata.NewOperand(d.ctx.db, f) if err != nil { log.Fatalf("%s: args[%d]: %v", inst, i, err) } switch { case xarg.Action == "": continue // Skip meta args like EMX_BROADCAST_1TO32_8 case !xarg.IsVisible(): continue } arg := &argument{} args = append(args, arg) switch xarg.NameLHS() { case "IMM0": if xarg.Width != "b" { log.Fatalf("%s: args[%d]: expected width=b, found %s", inst, i, xarg.Width) } if pset["IMM0SIGNED=1"] { arg.ytype = "Yi8" } else { arg.ytype = "Yu8" } arg.zkind = "imm8" case "REG0", "REG1", "REG2", "REG3": rhs := xarg.NameRHS() if rhs == "MASK1()" { mask = arg } *arg = registerArgs[rhs] if arg.ytype == "" { log.Fatalf("%s: args[%d]: unexpected %s reg", inst, i, rhs) } if xarg.Attributes["MULTISOURCE4"] { arg.ytype += "Multi4" } case "MEM0": arg.ytype = pset.MatchOrDefault("Ym", "VMODRM_XMM()", "Yxvm", "VMODRM_YMM()", "Yyvm", "UISA_VMODRM_XMM()", "YxvmEvex", "UISA_VMODRM_YMM()", "YyvmEvex", "UISA_VMODRM_ZMM()", "Yzvm", ) arg.zkind = "reg/mem" default: log.Fatalf("%s: args[%d]: unexpected %s", inst, i, xarg.NameRHS()) } } // Reverse args. for i := len(args)/2 - 1; i >= 0; i-- { j := len(args) - 1 - i args[i], args[j] = args[j], args[i] } return mask, args } func (d *decoder) decodePattern(pset xeddata.PatternSet, inst *xeddata.Inst) *encoding { var enc encoding enc.opdigit = d.findOpdigit(pset) enc.opbyte = d.findOpbyte(pset, inst) if strings.Contains(inst.Attributes, "DISP8_") { enc.evexScale = d.findEVEXScale(pset) enc.evexBcstScale = d.findEVEXBcstScale(pset, inst) } enc.vex.P = pset.Match( "VEX_PREFIX=1", "66", "VEX_PREFIX=2", "F2", "VEX_PREFIX=3", "F3") enc.vex.M = pset.Match( "MAP=1", "0F", "MAP=2", "0F38", "MAP=3", "0F3A") enc.vex.L = pset.MatchOrDefault("128", "VL=0", "128", "VL=1", "256", "VL=2", "512") enc.vex.W = pset.MatchOrDefault("W0", "REXW=0", "W0", "REXW=1", "W1") if pset.Is("EVEX") { enc.evex.SAE = strings.Contains(inst.Operands, "TXT=SAESTR") enc.evex.Rounding = strings.Contains(inst.Operands, "TXT=ROUNDC") enc.evex.Zeroing = strings.Contains(inst.Operands, "TXT=ZEROSTR") } // Prefix each non-empty part with vex or evex. parts := [...]*string{ &enc.evexScale, &enc.evexBcstScale, &enc.vex.P, &enc.vex.M, &enc.vex.L, &enc.vex.W, } for _, p := range parts { if *p == "" { continue } if pset.Is("EVEX") { *p = "evex" + *p } else { *p = "vex" + *p } } return &enc } func (d *decoder) findOpdigit(pset xeddata.PatternSet) string { reg := pset.Index( "REG[0b000]", "REG[0b001]", "REG[0b010]", "REG[0b011]", "REG[0b100]", "REG[0b101]", "REG[0b110]", "REG[0b111]", ) // Fixed ModRM.Reg field means that it is used for opcode extension. if reg != -1 { return fmt.Sprintf("0%d", reg) } return "" } // opbyteRE matches uint8 hex literal. var opbyteRE = regexp.MustCompile(`0x[0-9A-F]{2}`) func (d *decoder) findOpbyte(pset xeddata.PatternSet, inst *xeddata.Inst) string { opbyte := "" for k := range pset { if opbyteRE.MatchString(k) { if opbyte == "" { opbyte = k } else { log.Fatalf("%s: multiple opbytes", inst) } } } return opbyte } func (d *decoder) findEVEXScale(pset xeddata.PatternSet) string { switch { case pset["NELEM_FULL()"], pset["NELEM_FULLMEM()"]: return pset.Match( "VL=0", "N16", "VL=1", "N32", "VL=2", "N64") case pset["NELEM_MOVDDUP()"]: return pset.Match( "VL=0", "N8", "VL=1", "N32", "VL=2", "N64") case pset["NELEM_HALF()"], pset["NELEM_HALFMEM()"]: return pset.Match( "VL=0", "N8", "VL=1", "N16", "VL=2", "N32") case pset["NELEM_QUARTERMEM()"]: return pset.Match( "VL=0", "N4", "VL=1", "N8", "VL=2", "N16") case pset["NELEM_EIGHTHMEM()"]: return pset.Match( "VL=0", "N2", "VL=1", "N4", "VL=2", "N8") case pset["NELEM_TUPLE2()"]: return pset.Match( "ESIZE_32_BITS()", "N8", "ESIZE_64_BITS()", "N16") case pset["NELEM_TUPLE4()"]: return pset.Match( "ESIZE_32_BITS()", "N16", "ESIZE_64_BITS()", "N32") case pset["NELEM_TUPLE8()"]: return "N32" case pset["NELEM_MEM128()"], pset["NELEM_TUPLE1_4X()"]: return "N16" } // Explicit list is required to make it possible to // detect unhandled nonterminals for the caller. scalars := [...]string{ "NELEM_SCALAR()", "NELEM_GSCAT()", "NELEM_GPR_READER()", "NELEM_GPR_READER_BYTE()", "NELEM_GPR_READER_WORD()", "NELEM_GPR_WRITER_STORE()", "NELEM_GPR_WRITER_STORE_BYTE()", "NELEM_GPR_WRITER_STORE_WORD()", "NELEM_GPR_WRITER_LDOP_D()", "NELEM_GPR_WRITER_LDOP_Q()", "NELEM_TUPLE1()", "NELEM_TUPLE1_BYTE()", "NELEM_TUPLE1_WORD()", } for _, scalar := range scalars { if pset[scalar] { return pset.Match( "ESIZE_8_BITS()", "N1", "ESIZE_16_BITS()", "N2", "ESIZE_32_BITS()", "N4", "ESIZE_64_BITS()", "N8") } } return "" } func (d *decoder) findEVEXBcstScale(pset xeddata.PatternSet, inst *xeddata.Inst) string { // Only FULL and HALF tuples are affected by the broadcasting. switch { case pset["NELEM_FULL()"]: return pset.Match( "ESIZE_32_BITS()", "BcstN4", "ESIZE_64_BITS()", "BcstN8") case pset["NELEM_HALF()"]: return "BcstN4" default: if inst.HasAttribute("BROADCAST_ENABLED") { log.Fatalf("%s: unexpected tuple for bcst", inst) } return "" } }