blob: 667f77c4ac4457891a98fc0dc8d6c9f3dc853040 [file] [log] [blame]
// 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 ""
}
}