blob: 3fc89f11995fcadf85426a572ea7202687b762c4 [file] [log] [blame]
// Copyright 2024 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.
// s390xmap constructs the s390x opcode map from the instruction set CSV file.
//
// Usage:
//
// s390map [-fmt=format] s390x.csv
//
// The known output formats are:
//
// text (default) - print decoding tree in text form
// decoder - print decoding tables for the s390xasm package
// encoder - generate a self-contained file which can be used to encode
// go obj.Progs into machine code
// asm - generate a GNU asm file which can be compiled by gcc containing
// all opcodes discovered in s390x.csv using macro friendly arguments.
package main
import (
"bytes"
"encoding/csv"
"flag"
"fmt"
gofmt "go/format"
"log"
"os"
"regexp"
"strconv"
"strings"
asm "golang.org/x/arch/s390x/s390xasm"
)
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: s390xmap [-fmt=format] s390x.csv\n")
os.Exit(2)
}
func main() {
log.SetFlags(0)
log.SetPrefix("s390xmap: ")
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
usage()
}
inputFile = flag.Arg(0)
var printTyp func(*Prog)
switch *format {
default:
log.Fatalf("unknown output format %q", *format)
case "text":
printTyp = printText
case "decoder":
printTyp = printDecoder
case "asm":
printTyp = printASM
case "encoder":
printTyp = printEncoder
}
p, err := readCSV(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
log.Printf("Parsed %d instruction forms.", len(p.Insts))
printTyp(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]) < 3 {
return nil, fmt.Errorf("csv too narrow: need at least four columns")
}
p := &Prog{}
for _, row := range table {
add(p, row[0], row[1], row[2], row[3])
}
return p, nil
}
type Prog struct {
Insts []Inst
OpRanges map[string]string
nextOrder int // Next position value (used for Insts[x].order)
}
type Field struct {
Name string
BitField asm.BitField
Type asm.ArgType
flags uint16
}
func (f Field) String() string {
return fmt.Sprintf("%v(%s%v)", f.Type, f.Name, f.BitField)
}
type Inst struct {
Text string
Encoding string
Op string
Mask uint64
Value uint64
DontCare uint64
Len uint16
Fields []Field
}
func (i Inst) String() string {
return fmt.Sprintf("%s (%s) %08x/%08x %v (%s)", i.Op, i.Encoding, i.Value, i.Mask, 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() uint64 {
return uint64(a.Maximum()) << a.Shift()
}
func (a Arg) Shift() uint8 {
return uint8(64 - 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))
}
// Split the string encoding into an Args. The encoding string loosely matches the regex
// (arg@bitpos|)+
func parseFields(encoding, text string) Args {
var err error
var args Args
fields := strings.Split(encoding, "|")
for i, f := range fields {
name, off := "", -1
if f == "" {
off = 64
if i == 0 || i != len(fields)-1 {
fmt.Fprintf(os.Stderr, "%s: wrong %d-th encoding field: %q\n", text, i, f)
panic("Invalid encoding entry.")
}
} else {
j := strings.Index(f, "@")
if j < 0 {
fmt.Fprintf(os.Stderr, "%s: wrong %d-th encoding field: %q\n", text, i, f)
panic("Invalid encoding entry.")
}
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 != "" && name != "??" {
arg := Arg{Name: name, Offs: int8(off), Bits: int8(-off)}
args.Append(arg)
}
}
return args
}
// Compute the Mask (usually Opcode + secondary Opcode bitfields),
// the Value (the expected value under the mask), and
// reserved bits (i.e the // fields which should be set to 0)
func computeMaskValueReserved(args Args, text string) (mask, value, reserved uint64) {
for i := 0; i < len(args); i++ {
arg := args[i]
v, err := strconv.Atoi(arg.Name)
switch {
case err == nil && v >= 0: // 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 |= uint64(v) << arg.Shift()
args.Delete(i)
i--
case arg.Name[0] == '/': // 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)
}
reserved |= arg.BitMask()
args.Delete(i)
i--
default:
continue
}
}
// sanity checks
if mask&reserved != 0 {
log.Fatalf("%s: mask (%08x) and don't care (%08x) collide", text, mask, reserved)
}
if value&^mask != 0 {
log.Fatalf("%s: value (%08x) out of range of mask (%08x)", text, value, mask)
}
return
}
func Imm_signed_8bit_check(op string) bool {
imm_8 := []string{"ASI", "AGSI", "ALSI", "ALGSI", "CIB", "CGIB", "CIJ", "CGIJ", "NI", "NIY", "OI", "OIY", "XI", "XIY"}
var ret bool
ret = false
for _, str := range imm_8 {
if strings.Compare(op, str) == 0 {
ret = true
break
}
}
return ret
}
func Imm_signed_16bit_check(op string) bool {
imm_16 := []string{"AHI", "AGHI", "ALHSIK", "ALGHSIK", "AHIK", "AGHIK", "LHI", "LGHI", "MVGHI", "CIT", "CGIT", "CGHI", "CGHSI", "CHHSI", "CHI", "CHSI", "CRJ", "CGRJ", "NIHH", "NILL", "NIHL", "NILH", "LLIHH", "LLILL", "LLIHL", "LLILH", "OIHH", "OILL", "OIHL", "OILH", "VLEIB", "VLEIH", "VLEIF", "VLEIG"}
var ret bool
ret = false
for _, str := range imm_16 {
if strings.Compare(op, str) == 0 {
ret = true
break
}
}
return ret
}
func Imm_signed_32bit_check(op string) bool {
imm_32 := []string{"AFI", "AGFI", "AIH", "CIH", "CFI", "CGFI", "CRL", "STRL", "STGRL", "LGFI", "LLIHF", "LLILF", "MSFI", "MSGFI", "MGHI", "MHI", "NIHF", "NILF", "OILF", "OIHF", "XILF", "XIHF"}
var ret bool
ret = false
for _, str := range imm_32 {
if strings.Compare(op, str) == 0 {
ret = true
break
}
}
return ret
}
func check_flags(flags string) bool {
if strings.Contains(flags, "Da") {
return true
} else if strings.Contains(flags, "Db") {
return true
} else if strings.Contains(flags, "Dt") {
return true
} else {
return false
}
}
// Parse a row from the CSV describing the instructions, and place the
// detected instructions into p. One entry may generate multiple intruction
// entries as each extended mnemonic listed in text is treated like a unique
// instruction.
func add(p *Prog, text, mnemonics, encoding, flags 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
args = parseFields(encoding, text)
mask, value, dontCare := computeMaskValueReserved(args, text)
// split mnemonics into individual instructions
inst := Inst{Text: text, Encoding: mnemonics, Value: value, Mask: mask, DontCare: dontCare}
// order inst.Args according to mnemonics order
for i, opr := range operandRe.FindAllString(mnemonics, -1) {
if i == 0 { // operation
inst.Op = opr
continue
}
field := Field{Name: opr}
typ := asm.TypeUnknown
flag := uint16(0)
switch opr {
case "R1", "R2", "R3":
s := strings.Split(mnemonics, " ")
switch opr {
case "R1":
switch s[0] {
case "CPDT", "CPXT", "CDXT", "CZXT", "CZDT":
typ = asm.TypeFPReg
flag = 0x2
case "CUXTR", "EEXTR", "EEDTR", "EFPC", "ESXTR", "ESDTR", "LGDR", "SFPC", "SFASR":
typ = asm.TypeReg
flag = 0x1
case "CPYA", "LAM", "LAMY", "STAM", "STAMY", "SAR", "TAR":
typ = asm.TypeACReg
flag = 0x3
case "LCTL", "LCTLG", "STCTL", "STCTG":
typ = asm.TypeCReg
flag = 0x4
default:
if check_flags(flags) {
if strings.Contains(text, "CONVERT TO") {
typ = asm.TypeReg
flag = 0x1
} else {
typ = asm.TypeFPReg
flag = 0x2
}
} else {
typ = asm.TypeReg
flag = 0x1
}
}
case "R2":
switch s[0] {
case "IEXTR", "IEDTR", "LDGR", "RRXTR", "RRDTR":
typ = asm.TypeReg
flag = 0x1
case "CPYA", "EAR":
typ = asm.TypeACReg
flag = 0x3
default:
if check_flags(flags) {
if strings.Contains(text, "CONVERT FROM") {
typ = asm.TypeReg
flag = 0x1
} else {
typ = asm.TypeFPReg
flag = 0x2
}
} else {
typ = asm.TypeReg
flag = 0x1
}
}
case "R3":
switch s[0] {
case "LAM", "LAMY", "STAM", "STAMY":
typ = asm.TypeACReg
flag = 0x3
case "LCTL", "LCTLG", "STCTL", "STCTG":
typ = asm.TypeCReg
flag = 0x4
default:
if check_flags(flags) {
typ = asm.TypeFPReg
flag = 0x2
} else {
typ = asm.TypeReg
flag = 0x1
}
}
}
case "I", "I1", "I2", "I3", "I4", "I5":
flag = 0x0
switch opr {
case "I", "I1":
typ = asm.TypeImmUnsigned
case "I2":
if Imm_signed_8bit_check(inst.Op) {
typ = asm.TypeImmSigned8
break
} else if Imm_signed_16bit_check(inst.Op) { // "ASI", "AGSI", "ALSI", "ALGSI"
typ = asm.TypeImmSigned16
break
} else if Imm_signed_32bit_check(inst.Op) { // "AHI", "AGHI", "AHIK", "AGHIK", "LHI", "LGHI"
typ = asm.TypeImmSigned32
break
} else {
typ = asm.TypeImmUnsigned
break
}
case "I3", "I4", "I5":
typ = asm.TypeImmUnsigned
}
case "RI2", "RI3", "RI4":
flag = 0x80
i := args.Find(opr)
count := uint8(args[i].Bits)
if count == 12 {
typ = asm.TypeRegImSigned12
break
} else if count == 16 {
typ = asm.TypeRegImSigned16
break
} else if count == 24 {
typ = asm.TypeRegImSigned24
break
} else if count == 32 {
typ = asm.TypeRegImSigned32
break
}
case "M1", "M3", "M4", "M5", "M6":
flag = 0x800
typ = asm.TypeMask
case "B1", "B2", "B3", "B4":
typ = asm.TypeBaseReg
flag = 0x20 | 0x01
case "X2":
typ = asm.TypeIndexReg
flag = 0x40 | 0x01
case "D1", "D2", "D3", "D4":
flag = 0x10
i := args.Find(opr)
if uint8(args[i].Bits) == 20 {
typ = asm.TypeDispSigned20
break
} else {
typ = asm.TypeDispUnsigned
break
}
case "L1", "L2":
typ = asm.TypeLen
flag = 0x10
case "V1", "V2", "V3", "V4", "V5", "V6":
typ = asm.TypeVecReg
flag = 0x08
}
if typ == asm.TypeUnknown {
log.Fatalf("%s %s unknown type for opr %s", text, inst, opr)
}
field.Type = typ
field.flags = flag
var f1 asm.BitField
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.BitField = f1
inst.Fields = append(inst.Fields, field)
}
if strings.HasPrefix(inst.Op, "V") || strings.Contains(inst.Op, "WFC") || strings.Contains(inst.Op, "WFK") { //Check Vector Instructions
Bits := asm.BitField{Offs: 36, Bits: 4}
field := Field{Name: "RXB", BitField: Bits, Type: asm.TypeImmUnsigned, flags: 0xC00}
inst.Fields = append(inst.Fields, field)
}
if *debug {
fmt.Printf("%v\n", inst)
}
p.Insts = append(p.Insts, inst)
}
// 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")
}
// printEncoder implements the -fmt=encoder mode. which is not implemented (yet?).
func printEncoder(p *Prog) {
log.Fatal("-fmt=encoder not implemented")
}
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)
}
}
// argFieldName constructs a name for the argField
func argFieldName(f Field) string {
ns := []string{"ap", f.Type.String()}
b := f.BitField
ns = append(ns, fmt.Sprintf("%d_%d", b.Offs, b.Offs+b.Bits-1))
return strings.Join(ns, "_")
}
// 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, "// Code generated by s390xmap -fmt=decoder %s DO NOT EDIT.\n", inputFile)
fmt.Fprintf(&buf, "\n")
fmt.Fprintf(&buf, "package s390xasm\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 i := 0; i < len(p.Insts); i++ {
name := p.Insts[i].Op
switch name {
case "CUUTF", "CUTFU", "PPNO":
m[name] = false
p.Insts = append(p.Insts[:i], p.Insts[i+1:]...)
i--
default:
m[name] = true
}
if ok := m[name]; !ok {
continue
}
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 := inst.Op
if ok := m[name]; ok {
continue
}
m[name] = true
fmt.Fprintf(&buf, "\t%s: %q,\n", inst.Op, strings.ToLower(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, flags: %#x, BitField: BitField", name, f.Type, f.flags)
b := f.BitField
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 {
m, v, dc := inst.Mask, inst.Value, inst.DontCare
fmt.Fprintf(&buf, "\t{ %s, %#x, %#x, %#x,", inst.Op, m, v, dc)
fmt.Fprintf(&buf, " // %s (%s)\n\t\t[8]*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)
}
}