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