// 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) }