blob: 93460612a8b24903acaf77b75799066af72bfa25 [file] [log] [blame]
// Copyright 2014 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.
// ppc64map constructs the ppc64 opcode map from the instruction set CSV file.
//
// Usage:
// ppc64map [-fmt=format] ppc64.csv
//
// The known output formats are:
//
// text (default) - print decoding tree in text form
// decoder - print decoding tables for the ppc64asm package
package main
import (
"bytes"
"encoding/csv"
"flag"
"fmt"
gofmt "go/format"
"log"
"math/bits"
"os"
"regexp"
"sort"
"strconv"
"strings"
"text/template"
asm "golang.org/x/arch/ppc64/ppc64asm"
)
var format = flag.String("fmt", "text", "output format: text, decoder, asm")
var debug = flag.Bool("debug", false, "enable debugging output")
var inputFile string
func usage() {
fmt.Fprintf(os.Stderr, "usage: ppc64map [-fmt=format] ppc64.csv\n")
os.Exit(2)
}
func main() {
log.SetFlags(0)
log.SetPrefix("ppc64map: ")
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
usage()
}
inputFile = flag.Arg(0)
var print func(*Prog)
switch *format {
default:
log.Fatalf("unknown output format %q", *format)
case "text":
print = printText
case "decoder":
print = printDecoder
case "asm":
print = printASM
}
p, err := readCSV(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
log.Printf("Parsed %d instruction forms.", len(p.Insts))
print(p)
}
// readCSV reads the CSV file and returns the corresponding Prog.
// It may print details about problems to standard error using the log package.
func readCSV(file string) (*Prog, error) {
// Read input.
// Skip leading blank and # comment lines.
f, err := os.Open(file)
if err != nil {
return nil, err
}
csvReader := csv.NewReader(f)
csvReader.Comment = '#'
table, err := csvReader.ReadAll()
if err != nil {
return nil, fmt.Errorf("parsing %s: %v", file, err)
}
if len(table) == 0 {
return nil, fmt.Errorf("empty csv input")
}
if len(table[0]) < 4 {
return nil, fmt.Errorf("csv too narrow: need at least four columns")
}
p := &Prog{}
for _, row := range table {
// TODO: add support for prefixed instructions. Ignore for now.
if row[2][0] == ',' {
continue
}
add(p, row[0], row[1], row[2], row[3])
}
return p, nil
}
type Prog struct {
Insts []Inst
OpRanges map[string]string
}
type Field struct {
Name string
BitFields asm.BitFields
Type asm.ArgType
Shift uint8
}
func (f Field) String() string {
return fmt.Sprintf("%v(%s%v)", f.Type, f.Name, f.BitFields)
}
type Inst struct {
Text string
Encoding string
Op string
Mask uint32
Value uint32
DontCare uint32
Fields []Field
}
func (i Inst) String() string {
return fmt.Sprintf("%s (%s) %08x/%08x[%08x] %v (%s)", i.Op, i.Encoding, i.Value, i.Mask, i.DontCare, i.Fields, i.Text)
}
type Arg struct {
Name string
Bits int8
Offs int8
}
func (a Arg) String() string {
return fmt.Sprintf("%s[%d:%d]", a.Name, a.Offs, a.Offs+a.Bits-1)
}
func (a Arg) Maximum() int {
return 1<<uint8(a.Bits) - 1
}
func (a Arg) BitMask() uint32 {
return uint32(a.Maximum()) << a.Shift()
}
func (a Arg) Shift() uint8 {
return uint8(32 - a.Offs - a.Bits)
}
type Args []Arg
func (as Args) String() string {
ss := make([]string, len(as))
for i := range as {
ss[i] = as[i].String()
}
return strings.Join(ss, "|")
}
func (as Args) Find(name string) int {
for i := range as {
if as[i].Name == name {
return i
}
}
return -1
}
func (as *Args) Append(a Arg) {
*as = append(*as, a)
}
func (as *Args) Delete(i int) {
*as = append((*as)[:i], (*as)[i+1:]...)
}
func (as Args) Clone() Args {
return append(Args{}, as...)
}
func (a Arg) isDontCare() bool {
return a.Name[0] == '/' && a.Name == strings.Repeat("/", len(a.Name))
}
type instArray []Inst
func (i instArray) Len() int {
return len(i)
}
func (i instArray) Swap(j, k int) {
i[j], i[k] = i[k], i[j]
}
// Sort by decreasing number of mask bits to ensure extended mnemonics
// are always found first when scanning the table.
func (i instArray) Less(j, k int) bool {
return bits.OnesCount32(i[j].Mask) > bits.OnesCount32(i[k].Mask)
}
// add adds the entry from the CSV described by text, mnemonics, encoding, and tags
// to the program p.
func add(p *Prog, text, mnemonics, encoding, tags string) {
// Parse encoding, building size and offset of each field.
// The first field in the encoding is the smallest offset.
// And note the MSB is bit 0, not bit 31.
// Example: "31@0|RS@6|RA@11|///@16|26@21|Rc@31|"
var args Args
var err error
fields := strings.Split(encoding, "|")
for i, f := range fields {
name, off := "", -1
if f == "" {
off = 32
if i == 0 || i != len(fields)-1 {
fmt.Fprintf(os.Stderr, "%s: wrong %d-th encoding field: %q\n", text, i, f)
return
}
} else {
j := strings.Index(f, "@")
if j < 0 {
fmt.Fprintf(os.Stderr, "%s: wrong %d-th encoding field: %q\n", text, i, f)
continue
}
k := strings.Index(f[j+1:], " ")
if k >= 0 {
if strings.HasSuffix(f[j+1:], " 31") {
f = f[:len(f)-3]
}
}
off, err = strconv.Atoi(f[j+1:])
if err != nil {
fmt.Fprintf(os.Stderr, "err for: %s has: %s for %s\n", f[:j], err, f[j+1:])
}
name = f[:j]
}
if len(args) > 0 {
args[len(args)-1].Bits += int8(off)
}
if name != "" {
arg := Arg{Name: name, Offs: int8(off), Bits: int8(-off)}
args.Append(arg)
}
}
var mask, value, dontCare uint32
for i := 0; i < len(args); i++ {
arg := args[i]
v, err := strconv.Atoi(arg.Name)
switch {
case err == nil: // is a numbered field
if v < 0 || v > arg.Maximum() {
fmt.Fprintf(os.Stderr, "%s: field %s value (%d) is out of range (%d-bit)\n", text, arg, v, arg.Bits)
}
mask |= arg.BitMask()
value |= uint32(v) << arg.Shift()
args.Delete(i)
i--
case arg.Name[0] == '/': // is don't care
if arg.Name != strings.Repeat("/", len(arg.Name)) {
log.Fatalf("%s: arg %v named like a don't care bit, but it's not", text, arg)
}
dontCare |= arg.BitMask()
args.Delete(i)
i--
default:
continue
}
}
// rename duplicated fields (e.g. 30@0|RS@6|RA@11|sh@16|mb@21|0@27|sh@30|Rc@31|)
// but only support two duplicated fields
for i := 1; i < len(args); i++ {
if args[:i].Find(args[i].Name) >= 0 {
args[i].Name += "2"
}
if args[:i].Find(args[i].Name) >= 0 {
log.Fatalf("%s: more than one duplicated fields: %s", text, args)
}
}
// sanity checks
if mask&dontCare != 0 {
log.Fatalf("%s: mask (%08x) and don't care (%08x) collide", text, mask, dontCare)
}
if value&^mask != 0 {
log.Fatalf("%s: value (%08x) out of range of mask (%08x)", text, value, mask)
}
var argMask uint32
for _, arg := range args {
if arg.Bits <= 0 || arg.Bits > 32 || arg.Offs > 31 || arg.Offs <= 0 {
log.Fatalf("%s: arg %v has wrong bit field spec", text, arg)
}
if mask&arg.BitMask() != 0 {
log.Fatalf("%s: mask (%08x) intersect with arg %v", text, mask, arg)
}
if argMask&arg.BitMask() != 0 {
log.Fatalf("%s: arg %v overlap with other args %v", text, arg, args)
}
argMask |= arg.BitMask()
}
if 1<<32-1 != mask|dontCare|argMask {
log.Fatalf("%s: args %v fail to cover all 32 bits", text, args)
}
// split mnemonics into individual instructions
// example: "b target_addr (AA=0 LK=0)|ba target_addr (AA=1 LK=0)|bl target_addr (AA=0 LK=1)|bla target_addr (AA=1 LK=1)"
insts := strings.Split(categoryRe.ReplaceAllString(mnemonics, ""), "|")
foundInst := []Inst{}
for _, inst := range insts {
value, mask := value, mask
args := args.Clone()
if inst == "" {
continue
}
// amend mask and value
parts := instRe.FindStringSubmatch(inst)
if parts == nil {
log.Fatalf("%v couldn't match %s", instRe, inst)
}
conds := condRe.FindAllStringSubmatch(parts[2], -1)
isPCRel := true
for _, cond := range conds {
i := args.Find(cond[1])
v, _ := strconv.ParseInt(cond[2], 16, 32) // the regular expression has checked the number format
if i < 0 {
log.Fatalf("%s: %s don't contain arg %s used in %s", text, args, cond[1], inst)
}
if cond[1] == "AA" && v == 1 {
isPCRel = false
}
mask |= args[i].BitMask()
value |= uint32(v) << args[i].Shift()
args.Delete(i)
}
inst := Inst{Text: text, Encoding: parts[1], Value: value, Mask: mask, DontCare: dontCare}
// order inst.Args according to mnemonics order
for i, opr := range operandRe.FindAllString(parts[1], -1) {
if i == 0 { // operation
inst.Op = opr
continue
}
field := Field{Name: opr}
typ := asm.TypeUnknown
var shift uint8
opr2 := ""
opr3 := ""
switch opr {
case "target_addr":
shift = 2
if isPCRel {
typ = asm.TypePCRel
} else {
typ = asm.TypeLabel
}
if args.Find("LI") >= 0 {
opr = "LI"
} else {
opr = "BD"
}
case "UI", "BO", "BH", "TH", "LEV", "NB", "L", "TO", "FXM", "FC", "U", "W", "FLM", "UIM", "IMM8", "RIC", "PRS", "SHB", "SHW", "ST", "SIX", "PS", "DCM", "DGM", "RMC", "R", "SP", "S", "DM", "CT", "EH", "E", "MO", "WC", "A", "IH", "OC", "DUI", "DUIS", "CY", "SC", "PL", "MP", "N", "IMM", "DRM", "RM":
typ = asm.TypeImmUnsigned
if i := args.Find(opr); i < 0 {
opr = "D"
}
case "bm":
opr = "b0"
opr2 = "b1"
opr3 = "b2"
typ = asm.TypeImmUnsigned
case "SH":
typ = asm.TypeImmUnsigned
if args.Find("sh2") >= 0 { // sh2 || sh
opr = "sh2"
opr2 = "sh"
}
case "MB", "ME":
typ = asm.TypeImmUnsigned
if n := strings.ToLower(opr); args.Find(n) >= 0 {
opr = n // xx[5] || xx[0:4]
}
case "SI", "SIM", "TE":
typ = asm.TypeImmSigned
if i := args.Find(opr); i < 0 {
opr = "D"
}
case "DCMX":
typ = asm.TypeImmUnsigned
// Some instructions encode this consecutively.
if i := args.Find(opr); i >= 0 {
break
}
typ = asm.TypeImmUnsigned
opr = "dc"
opr2 = "dm"
opr3 = "dx"
case "DS":
typ = asm.TypeOffset
shift = 2
case "DQ":
typ = asm.TypeOffset
shift = 4
case "D":
if i := args.Find(opr); i >= 0 {
typ = asm.TypeOffset
break
}
if i := args.Find("UI"); i >= 0 {
typ = asm.TypeImmUnsigned
opr = "UI"
break
}
if i := args.Find("SI"); i >= 0 {
typ = asm.TypeImmSigned
opr = "SI"
break
}
if i := args.Find("d0"); i >= 0 {
typ = asm.TypeImmSigned
// DX-form
opr = "d0"
opr2 = "d1"
opr3 = "d2"
}
case "RA", "RB", "RC", "RS", "RSp", "RT", "RTp":
typ = asm.TypeReg
case "BT", "BA", "BB", "BC", "BI":
if strings.HasPrefix(inst.Op, "mtfs") {
// mtfsb[01] instructions use BT, but they specify fields in the fpscr.
typ = asm.TypeImmUnsigned
} else {
typ = asm.TypeCondRegBit
}
case "BF", "BFA":
if strings.HasPrefix(inst.Op, "mtfs") {
// mtfsfi[.] instructions use BF, but they specify fields in the fpscr.
typ = asm.TypeImmUnsigned
} else {
typ = asm.TypeCondRegField
}
case "FRA", "FRB", "FRBp", "FRC", "FRS", "FRSp", "FRT", "FRTp", "FRAp":
typ = asm.TypeFPReg
case "XA", "XB", "XC", "XS", "XT": // 5-bit, split field
typ = asm.TypeVecSReg
opr2 = opr[1:]
opr = opr[1:] + "X"
case "XTp", "XSp": // 5-bit, split field
//XTp encodes 5 bits, VSR is XT*32 + TP<<1
typ = asm.TypeVecSpReg
opr2 = opr[1:2] + "p"
opr = opr[1:2] + "X"
case "XAp":
// XAp in MMA encodes a regular VSR, but is only valid
// if it is even, and does not overlap the accumulator.
typ = asm.TypeVecSReg
opr2 = opr[1:2] + "p"
opr = opr[1:2] + "X"
case "AT", "AS":
typ = asm.TypeMMAReg
case "VRA", "VRB", "VRC", "VRS", "VRT":
typ = asm.TypeVecReg
case "SPR", "DCRN", "BHRBE", "TBR", "SR", "TMR", "PMRN": // Note: if you add to this list and the register field needs special handling, add it to switch statement below
typ = asm.TypeSpReg
switch opr {
case "DCRN":
opr = "DCR"
}
if n := strings.ToLower(opr); n != opr && args.Find(n) >= 0 {
opr = n // spr[5:9] || spr[0:4]
}
}
if typ == asm.TypeUnknown {
log.Fatalf("%s %s unknown type for opr %s", text, inst, opr)
}
field.Type = typ
field.Shift = shift
var f1, f2, f3 asm.BitField
switch {
case opr3 != "":
b0 := args.Find(opr)
b1 := args.Find(opr2)
b2 := args.Find(opr3)
f1.Offs, f1.Bits = uint8(args[b0].Offs), uint8(args[b0].Bits)
f2.Offs, f2.Bits = uint8(args[b1].Offs), uint8(args[b1].Bits)
f3.Offs, f3.Bits = uint8(args[b2].Offs), uint8(args[b2].Bits)
case opr2 != "":
ext := args.Find(opr)
if ext < 0 {
log.Fatalf("%s: couldn't find extended field %s in %s", text, opr, args)
}
f1.Offs, f1.Bits = uint8(args[ext].Offs), uint8(args[ext].Bits)
base := args.Find(opr2)
if base < 0 {
log.Fatalf("%s: couldn't find base field %s in %s", text, opr2, args)
}
f2.Offs, f2.Bits = uint8(args[base].Offs), uint8(args[base].Bits)
case opr == "mb", opr == "me": // xx[5] || xx[0:4]
i := args.Find(opr)
if i < 0 {
log.Fatalf("%s: couldn't find special 'm[be]' field for %s in %s", text, opr, args)
}
f1.Offs, f1.Bits = uint8(args[i].Offs+args[i].Bits)-1, 1
f2.Offs, f2.Bits = uint8(args[i].Offs), uint8(args[i].Bits)-1
case opr == "spr", opr == "tbr", opr == "tmr", opr == "dcr": // spr[5:9] || spr[0:4]
i := args.Find(opr)
if i < 0 {
log.Fatalf("%s: couldn't find special 'spr' field for %s in %s", text, opr, args)
}
if args[i].Bits != 10 {
log.Fatalf("%s: special 'spr' field is not 10-bit: %s", text, args)
}
f1.Offs, f1.Bits = uint8(args[i].Offs)+5, 5
f2.Offs, f2.Bits = uint8(args[i].Offs), 5
default:
i := args.Find(opr)
if i < 0 {
log.Fatalf("%s: couldn't find %s in %s", text, opr, args)
}
f1.Offs, f1.Bits = uint8(args[i].Offs), uint8(args[i].Bits)
}
field.BitFields.Append(f1)
if f2.Bits > 0 {
field.BitFields.Append(f2)
}
if f3.Bits > 0 {
field.BitFields.Append(f3)
}
inst.Fields = append(inst.Fields, field)
}
if *debug {
fmt.Printf("%v\n", inst)
}
foundInst = append(foundInst, inst)
}
// Sort mnemonics by bitcount. This ensures more specific mnemonics are picked
// up before generic ones (e.g li vs addi, or cmpld/cmplw vs cmpl)
sort.Sort(instArray(foundInst))
p.Insts = append(p.Insts, foundInst...)
}
// condRegexp is a regular expression that matches condition in mnemonics (e.g. "AA=1")
const condRegexp = `\s*([[:alpha:]]+)=([0-9a-f]+)\s*`
// condRe matches condition in mnemonics (e.g. "AA=1")
var condRe = regexp.MustCompile(condRegexp)
// instRe matches instruction with potentially multiple conditions in mnemonics
var instRe = regexp.MustCompile(`^(.*?)\s?(\((` + condRegexp + `)+\))?$`)
// categoryRe matches intruction category notices in mnemonics
var categoryRe = regexp.MustCompile(`(\s*\[Category:[^]]*\]\s*)|(\s*\[Co-requisite[^]]*\]\s*)|(\s*\(\s*0[Xx][[0-9A-Fa-f_]{9}\s*\)\s*)`)
// operandRe matches each operand (including opcode) in instruction mnemonics
var operandRe = regexp.MustCompile(`([[:alpha:]][[:alnum:]_]*\.?)`)
// printText implements the -fmt=text mode, which is not implemented (yet?).
func printText(p *Prog) {
log.Fatal("-fmt=text not implemented")
}
// printASM implements the -fmt=asm mode. This prints out a gnu assembler file
// which can be used to used to generate test output to verify the golang
// disassembler's gnu output matches gnu binutils. This is used as an input to
// ppc64util to generate the decode_generated.txt test case.
func printASM(p *Prog) {
fmt.Printf("#include \"hack.h\"\n")
fmt.Printf(".text\n")
for _, inst := range p.Insts {
fmt.Printf("\t%s\n", inst.Encoding)
}
}
// opName translate an opcode to a valid Go identifier all-cap op name.
func opName(op string) string {
return strings.ToUpper(strings.Replace(op, ".", "CC", 1))
}
// argFieldName constructs a name for the argField
func argFieldName(f Field) string {
ns := []string{"ap", f.Type.String()}
for _, b := range f.BitFields {
ns = append(ns, fmt.Sprintf("%d_%d", b.Offs, b.Offs+b.Bits-1))
}
if f.Shift > 0 {
ns = append(ns, fmt.Sprintf("shift%d", f.Shift))
}
return strings.Join(ns, "_")
}
var funcBodyTmpl = template.Must(template.New("funcBody").Parse(``))
// printDecoder implements the -fmt=decoder mode.
// It emits the tables.go for package armasm's decoder.
func printDecoder(p *Prog) {
var buf bytes.Buffer
fmt.Fprintf(&buf, "// DO NOT EDIT\n")
fmt.Fprintf(&buf, "// generated by: ppc64map -fmt=decoder %s\n", inputFile)
fmt.Fprintf(&buf, "\n")
fmt.Fprintf(&buf, "package ppc64asm\n\n")
// Build list of opcodes, using the csv order (which corresponds to ISA docs order)
m := map[string]bool{}
fmt.Fprintf(&buf, "const (\n\t_ Op = iota\n")
for _, inst := range p.Insts {
name := opName(inst.Op)
if ok := m[name]; ok {
continue
}
m[name] = true
fmt.Fprintf(&buf, "\t%s\n", name)
}
fmt.Fprint(&buf, ")\n\n\n")
// Emit slice mapping opcode number to name string.
m = map[string]bool{}
fmt.Fprintf(&buf, "var opstr = [...]string{\n")
for _, inst := range p.Insts {
name := opName(inst.Op)
if ok := m[name]; ok {
continue
}
m[name] = true
fmt.Fprintf(&buf, "\t%s: %q,\n", opName(inst.Op), inst.Op)
}
fmt.Fprint(&buf, "}\n\n\n")
// print out argFields
fmt.Fprintf(&buf, "var (\n")
m = map[string]bool{}
for _, inst := range p.Insts {
for _, f := range inst.Fields {
name := argFieldName(f)
if ok := m[name]; ok {
continue
}
m[name] = true
fmt.Fprintf(&buf, "\t%s = &argField{Type: %#v, Shift: %d, BitFields: BitFields{", name, f.Type, f.Shift)
for _, b := range f.BitFields {
fmt.Fprintf(&buf, "{%d, %d},", b.Offs, b.Bits)
}
fmt.Fprintf(&buf, "}}\n")
}
}
fmt.Fprint(&buf, ")\n\n\n")
// Emit decoding table.
fmt.Fprintf(&buf, "var instFormats = [...]instFormat{\n")
for _, inst := range p.Insts {
fmt.Fprintf(&buf, "\t{ %s, %#x, %#x, %#x,", opName(inst.Op), inst.Mask, inst.Value, inst.DontCare)
fmt.Fprintf(&buf, " // %s (%s)\n\t\t[5]*argField{", inst.Text, inst.Encoding)
for _, f := range inst.Fields {
fmt.Fprintf(&buf, "%s, ", argFieldName(f))
}
fmt.Fprintf(&buf, "}},\n")
}
fmt.Fprint(&buf, "}\n\n")
out, err := gofmt.Source(buf.Bytes())
if err != nil {
log.Fatalf("gofmt error: %v", err)
fmt.Printf("%s", buf.Bytes())
} else {
fmt.Printf("%s", out)
}
}