blob: 9fdf262e4749138efba0836997927746fef4c4af [file] [log] [blame] [edit]
// 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 (
"flag"
"fmt"
"log"
"os"
"sort"
"strings"
"golang.org/x/arch/x86/xeddata"
)
// instGroup holds a list of instructions with same opcode.
type instGroup struct {
opcode string
list []*instruction
}
// context is x86avxgen program execution state.
type context struct {
db *xeddata.Database
groups []*instGroup
optabs map[string]*optab
ytabLists map[string]*ytabList
// Command line arguments:
xedPath string
}
func main() {
log.SetPrefix("x86avxgen: ")
log.SetFlags(log.Lshortfile)
var ctx context
runSteps(&ctx,
parseFlags,
openDatabase,
buildTables,
printTables)
}
func buildTables(ctx *context) {
// Order of steps is significant.
runSteps(ctx,
decodeGroups,
mergeRegMem,
addGoSuffixes,
mergeWIG,
assignZforms,
sortGroups,
generateOptabs)
}
func runSteps(ctx *context, steps ...func(*context)) {
for _, f := range steps {
f(ctx)
}
}
func parseFlags(ctx *context) {
flag.StringVar(&ctx.xedPath, "xedPath", "./xedpath",
"XED datafiles location")
flag.Parse()
}
func openDatabase(ctx *context) {
db, err := xeddata.NewDatabase(ctx.xedPath)
if err != nil {
log.Fatalf("open database: %v", err)
}
ctx.db = db
}
// mergeRegMem merges reg-only with mem-only instructions.
// For example: {MOVQ reg, mem} + {MOVQ reg, reg} = {MOVQ reg, reg/mem}.
func mergeRegMem(ctx *context) {
mergeKey := func(inst *instruction) string {
return strings.Join([]string{
fmt.Sprint(len(inst.args)),
inst.enc.opbyte,
inst.enc.opdigit,
inst.enc.vex.P,
inst.enc.vex.L,
inst.enc.vex.M,
inst.enc.vex.W,
}, " ")
}
for _, g := range ctx.groups {
regOnly := make(map[string]*instruction)
memOnly := make(map[string]*instruction)
list := g.list[:0]
for _, inst := range g.list {
switch {
case inst.pset.Is("RegOnly"):
regOnly[mergeKey(inst)] = inst
case inst.pset.Is("MemOnly"):
memOnly[mergeKey(inst)] = inst
default:
if len(inst.args) == 0 {
list = append(list, inst)
continue
}
log.Fatalf("%s: unexpected MOD value", inst)
}
}
for k, m := range memOnly {
r := regOnly[k]
if r != nil {
index := m.ArgIndexByZkind("reg/mem")
arg := m.args[index]
switch ytype := r.args[index].ytype; ytype {
case "Yrl":
arg.ytype = "Yml"
case "Yxr":
arg.ytype = "Yxm"
case "YxrEvex":
arg.ytype = "YxmEvex"
case "Yyr":
arg.ytype = "Yym"
case "YyrEvex":
arg.ytype = "YymEvex"
case "Yzr":
arg.ytype = "Yzm"
case "Yk":
arg.ytype = "Ykm"
default:
log.Fatalf("%s: unexpected register type: %s", r, ytype)
}
// Merge EVEX flags into m.
m.enc.evex.SAE = m.enc.evex.SAE || r.enc.evex.SAE
m.enc.evex.Rounding = m.enc.evex.Rounding || r.enc.evex.Rounding
m.enc.evex.Zeroing = m.enc.evex.Zeroing || r.enc.evex.Zeroing
delete(regOnly, k)
}
list = append(list, m)
}
for _, r := range regOnly {
list = append(list, r)
}
g.list = list
}
}
// mergeWIG merges [E]VEX.W0 + [E]VEX.W1 into [E]VEX.WIG.
func mergeWIG(ctx *context) {
mergeKey := func(inst *instruction) string {
return strings.Join([]string{
fmt.Sprint(len(inst.args)),
inst.enc.opbyte,
inst.enc.opdigit,
inst.enc.vex.P,
inst.enc.vex.L,
inst.enc.vex.M,
}, " ")
}
for _, g := range ctx.groups {
w0map := make(map[string]*instruction)
w1map := make(map[string]*instruction)
list := g.list[:0]
for _, inst := range g.list {
switch w := inst.enc.vex.W; w {
case "evexW0", "vexW0":
w0map[mergeKey(inst)] = inst
case "evexW1", "vexW1":
w1map[mergeKey(inst)] = inst
default:
log.Fatalf("%s: unexpected vex.W: %s", inst, w)
}
}
for k, w0 := range w0map {
w1 := w1map[k]
if w1 != nil {
w0.enc.vex.W = strings.Replace(w0.enc.vex.W, "W0", "WIG", 1)
delete(w1map, k)
}
list = append(list, w0)
}
for _, w1 := range w1map {
list = append(list, w1)
}
g.list = list
}
}
// assignZforms initializes zform field of every instruction in ctx.
func assignZforms(ctx *context) {
for _, g := range ctx.groups {
for _, inst := range g.list {
var parts []string
if inst.pset.Is("EVEX") {
parts = append(parts, "evex")
}
for _, arg := range inst.args {
parts = append(parts, arg.zkind)
}
if inst.enc.opdigit != "" {
parts = append(parts, "opdigit")
}
inst.zform = strings.Join(parts, " ")
}
}
}
// sortGroups sorts each instruction group by opcode as well as instructions
// inside groups by special rules (see below).
//
// The order of instructions inside group determine ytab
// elements order inside ytabList.
//
// We want these rules to be satisfied:
// - EVEX-encoded entries go after VEX-encoded entries.
// This way, VEX forms are selected over EVEX variants.
// - EVEX forms with SAE/RC must go before forms without them.
// This helps to avoid problems with reg-reg instructions
// that encode either of them in ModRM.R/M which causes
// ambiguity in ytabList (more than 1 ytab can match args).
// If first matching ytab has SAE/RC, problem will not occur.
// - Memory argument position affects order.
// Required to be in sync with XED encoder when there
// are multiple choices of how to encode instruction.
func sortGroups(ctx *context) {
sort.SliceStable(ctx.groups, func(i, j int) bool {
return ctx.groups[i].opcode < ctx.groups[j].opcode
})
for _, g := range ctx.groups {
sortInstList(g.list)
}
}
func sortInstList(insts []*instruction) {
// Use strings for sorting to get reliable transitive "less".
order := make(map[*instruction]string)
for _, inst := range insts {
encTag := 'a'
if inst.pset.Is("EVEX") {
encTag = 'b'
}
memTag := 'a'
if index := inst.ArgIndexByZkind("reg/mem"); index != -1 {
memTag = 'z' - rune(index)
}
rcsaeTag := 'a'
if !(inst.enc.evex.SAE || inst.enc.evex.Rounding) {
rcsaeTag = 'b'
}
order[inst] = fmt.Sprintf("%c%c%c %s",
encTag, memTag, rcsaeTag, inst.YtypeListString())
}
sort.SliceStable(insts, func(i, j int) bool {
return order[insts[i]] < order[insts[j]]
})
}
// addGoSuffixes splits some groups into several groups by introducing a suffix.
// For example, ANDN group becomes ANDNL and ANDNQ (ANDN becomes empty itself).
// Empty groups are removed.
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,
}
}
newGroups := make(map[string][]*instruction)
for _, g := range ctx.groups {
kv := opcodeSuffixMatchers[g.opcode]
if kv == nil {
continue
}
list := g.list[:0]
for _, inst := range g.list {
newOp := inst.opcode + inst.pset.Match(kv...)
if newOp != inst.opcode {
inst.opcode = newOp
newGroups[newOp] = append(newGroups[newOp], inst)
} else {
list = append(list, inst)
}
}
g.list = list
}
groups := ctx.groups[:0] // Filled with non-empty groups
// Some groups may become empty due to opcode split.
for _, g := range ctx.groups {
if len(g.list) != 0 {
groups = append(groups, g)
}
}
for op, insts := range newGroups {
groups = append(groups, &instGroup{
opcode: op,
list: insts,
})
}
ctx.groups = groups
}
func printTables(ctx *context) {
writeTables(os.Stdout, ctx)
}