// 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.

package ppc64asm

import (
	"bytes"
	"fmt"
	"strings"
)

var (
	// bit 3 of index is a negated check.
	condBit = [8]string{
		"lt", "gt", "eq", "so",
		"ge", "le", "ne", "ns"}
)

// GNUSyntax returns the GNU assembler syntax for the instruction, as defined by GNU binutils.
// This form typically matches the syntax defined in the Power ISA Reference Manual.
func GNUSyntax(inst Inst, pc uint64) string {
	var buf bytes.Buffer
	// When there are all 0s, identify them as the disassembler
	// in binutils would.
	if inst.Enc == 0 {
		return ".long 0x0"
	} else if inst.Op == 0 {
		return "error: unknown instruction"
	}

	PC := pc
	// Special handling for some ops
	startArg := 0
	sep := " "
	opName := inst.Op.String()
	argList := inst.Args[:]

	switch opName {
	case "bc", "bcl", "bca", "bcla", "bclr", "bclrl", "bcctr", "bcctrl", "bctar", "bctarl":
		sfx := inst.Op.String()[2:]
		bo := int(inst.Args[0].(Imm))
		bi := inst.Args[1].(CondReg)
		atsfx := [4]string{"", "?", "-", "+"}
		decsfx := [2]string{"dnz", "dz"}

		//BO field is... complicated (z == ignored bit, at == prediction hint)
		//Paraphrased from ISA 3.1 Book I Section 2.4:
		//
		//0000z -> decrement ctr, b if ctr != 0 and CRbi == 0
		//0001z -> decrement ctr, b if ctr == 0 and CRbi == 0
		//001at -> b if CRbi == 0
		//0100z -> decrement ctr, b if ctr != 0 and CRbi == 1
		//0101z -> decrement ctr, b if ctr == 0 and CRbi == 1
		//011at -> b if CRbi == 1
		//1a00t -> decrement ctr, b if ctr != 0
		//1a01t -> decrement ctr, b if ctr == 0
		//1z1zz -> b always

		// Decoding (in this order) we get
		// BO & 0b00100 == 0b00000 -> dz if BO[1], else dnz (not simplified for bcctrl forms)
		// BO & 0b10000 == 0b10000 -> (bc and bca forms not simplified), at = B[4]B[0] if B[2] != 0, done
		// BO & 0b10000 == 0b00000 -> t if BO[3], else f
		// BO & 0b10100 == 0b00100 -> at = B[0:1]

		// BI fields rename as follows:
		// less than            : lt BI%4==0 && test == t
		// less than or equal   : le BI%4==1 && test == f
		// equal 		: eq BI%4==2 && test == t
		// greater than or equal: ge BI%4==0 && test == f
		// greater than		: gt BI%4==1 && test == t
		// not less than	: nl BI%4==0 && test == f
		// not equal		: ne BI%4==2 && test == f
		// not greater than	: ng BI%4==1 && test == f
		// summary overflow	: so BI%4==3 && test == t
		// not summary overflow : ns BI%4==3 && test == f
		// unordered		: un BI%4==3 && test == t
		// not unordered	: nu BI%4==3 && test == f
		//
		// Note, there are only 8 possible tests, but quite a few more
		// ways to name fields.  For simplicity, we choose those in condBit.

		at := 0   // 0 == no hint, 1 == reserved, 2 == not likely, 3 == likely
		form := 1 // 1 == n/a,  0 == cr bit not set, 4 == cr bit set
		cr := (bi - Cond0LT) / 4
		bh := -1 // Only for lr/tar/ctr variants.
		switch opName {
		case "bclr", "bclrl", "bcctr", "bcctrl", "bctar", "bctarl":
			bh = int(inst.Args[2].(Imm))
		}

		if bo&0x14 == 0x14 {
			if bo == 0x14 && bi == Cond0LT { // preferred form of unconditional branch
				// Likewise, avoid printing fake b/ba/bl/bla
				if opName != "bc" && opName != "bca" && opName != "bcl" && opName != "bcla" {
					startArg = 2
				}
			}
		} else if bo&0x04 == 0 { // ctr is decremented
			if opName != "bcctr" && opName != "bcctrl" {
				startArg = 1
				tf := ""
				if bo&0x10 == 0x00 {
					tf = "f"
					if bo&0x08 == 0x08 {
						tf = "t"
					}
				}
				sfx = decsfx[(bo>>1)&1] + tf + sfx
			}
			if bo&0x10 == 0x10 {
				if opName != "bcctr" && opName != "bcctrl" {
					startArg = 2
				}
				if bi != Cond0LT {
					// A non-zero BI bit was encoded, but ignored by BO
					startArg = 0
				}
				at = ((bo & 0x8) >> 2) | (bo & 0x1)
			} else if bo&0x4 == 0x4 {
				at = bo & 0x3
			}
		} else if bo&0x10 == 0x10 { // BI field is not used
			if opName != "bca" && opName != "bc" {
				at = ((bo & 0x8) >> 2) | (bo & 0x1)
				startArg = 2
			}
			// If BI is encoded as a bit other than 0, no mnemonic.
			if bo&0x14 == 0x14 {
				startArg = 0
			}
		} else {
			form = (bo & 0x8) >> 1
			startArg = 2
			if bo&0x14 == 0x04 {
				at = bo & 0x3
			}
		}
		sfx += atsfx[at]

		if form != 1 {
			bit := int((bi-Cond0LT)%4) | (^form)&0x4
			sfx = condBit[bit] + sfx
		}

		if at != 1 && startArg > 0 && bh <= 0 {
			str := fmt.Sprintf("b%s", sfx)
			if startArg > 1 && (cr != 0 || bh > 0) {
				str += fmt.Sprintf(" cr%d", cr)
				sep = ","
			}
			buf.WriteString(str)
			if startArg < 2 && bh == 0 {
				str := fmt.Sprintf(" %s",
					gnuArg(&inst, 1, inst.Args[1], PC))
				buf.WriteString(str)
				startArg = 3
			} else if bh == 0 {
				startArg = 3
			}
		} else {
			if startArg == 0 || bh > 0 || at == 1 {
				buf.WriteString(inst.Op.String())
				buf.WriteString(atsfx[at])
				startArg = 0
			} else {
				buf.WriteString("b" + sfx)
			}
			if bh == 0 {
				str := fmt.Sprintf(" %d,%s", bo, gnuArg(&inst, 1, inst.Args[1], PC))
				buf.WriteString(str)
				startArg = 3
			}
		}

	case "mtspr":
		opcode := inst.Op.String()
		buf.WriteString(opcode[0:2])
		switch spr := inst.Args[0].(type) {
		case SpReg:
			switch spr {
			case 1:
				buf.WriteString("xer")
				startArg = 1
			case 8:
				buf.WriteString("lr")
				startArg = 1
			case 9:
				buf.WriteString("ctr")
				startArg = 1
			default:
				buf.WriteString("spr")
			}
		default:
			buf.WriteString("spr")
		}

	case "mfspr":
		opcode := inst.Op.String()
		buf.WriteString(opcode[0:2])
		arg := inst.Args[0]
		switch spr := inst.Args[1].(type) {
		case SpReg:
			switch spr {
			case 1:
				buf.WriteString("xer ")
				buf.WriteString(gnuArg(&inst, 0, arg, PC))
				startArg = 2
			case 8:
				buf.WriteString("lr ")
				buf.WriteString(gnuArg(&inst, 0, arg, PC))
				startArg = 2
			case 9:
				buf.WriteString("ctr ")
				buf.WriteString(gnuArg(&inst, 0, arg, PC))
				startArg = 2
			case 268:
				buf.WriteString("tb ")
				buf.WriteString(gnuArg(&inst, 0, arg, PC))
				startArg = 2
			default:
				buf.WriteString("spr")
			}
		default:
			buf.WriteString("spr")
		}

	case "mtfsfi", "mtfsfi.":
		buf.WriteString(opName)
		l := inst.Args[2].(Imm)
		if l == 0 {
			// L == 0 is an extended mnemonic for the same.
			asm := fmt.Sprintf(" %s,%s",
				gnuArg(&inst, 0, inst.Args[0], PC),
				gnuArg(&inst, 1, inst.Args[1], PC))
			buf.WriteString(asm)
			startArg = 3
		}

	case "paste.":
		buf.WriteString(opName)
		l := inst.Args[2].(Imm)
		if l == 1 {
			// L == 1 is an extended mnemonic for the same.
			asm := fmt.Sprintf(" %s,%s",
				gnuArg(&inst, 0, inst.Args[0], PC),
				gnuArg(&inst, 1, inst.Args[1], PC))
			buf.WriteString(asm)
			startArg = 3
		}

	case "mtfsf", "mtfsf.":
		buf.WriteString(opName)
		l := inst.Args[3].(Imm)
		if l == 0 {
			// L == 0 is an extended mnemonic for the same.
			asm := fmt.Sprintf(" %s,%s,%s",
				gnuArg(&inst, 0, inst.Args[0], PC),
				gnuArg(&inst, 1, inst.Args[1], PC),
				gnuArg(&inst, 2, inst.Args[2], PC))
			buf.WriteString(asm)
			startArg = 4
		}

	case "sync":
		lsc := inst.Args[0].(Imm)<<4 | inst.Args[1].(Imm)
		switch lsc {
		case 0x00:
			buf.WriteString("hwsync")
			startArg = 2
		case 0x10:
			buf.WriteString("lwsync")
			startArg = 2
		default:
			buf.WriteString(opName)
		}

	case "lbarx", "lharx", "lwarx", "ldarx":
		// If EH == 0, omit printing EH.
		eh := inst.Args[3].(Imm)
		if eh == 0 {
			argList = inst.Args[:3]
		}
		buf.WriteString(inst.Op.String())

	case "paddi":
		// There are several extended mnemonics.  Notably, "pla" is
		// the only valid mnemonic for paddi (R=1), In this case, RA must
		// always be 0.  Otherwise it is invalid.
		r := inst.Args[3].(Imm)
		ra := inst.Args[1].(Reg)
		str := opName
		if ra == R0 {
			name := []string{"pli", "pla"}
			str = fmt.Sprintf("%s %s,%s",
				name[r&1],
				gnuArg(&inst, 0, inst.Args[0], PC),
				gnuArg(&inst, 2, inst.Args[2], PC))
			startArg = 4
		} else {
			str = fmt.Sprintf("%s %s,%s,%s", opName,
				gnuArg(&inst, 0, inst.Args[0], PC),
				gnuArg(&inst, 1, inst.Args[1], PC),
				gnuArg(&inst, 2, inst.Args[2], PC))
			startArg = 4
			if r == 1 {
				// This is an illegal encoding (ra != 0 && r == 1) on ISA 3.1.
				v := uint64(inst.Enc)<<32 | uint64(inst.SuffixEnc)
				return fmt.Sprintf(".quad 0x%x", v)
			}
		}
		buf.WriteString(str)

	default:
		// Prefixed load/stores do not print the displacement register when R==1 (they are PCrel).
		// This also implies RA should be 0.  Likewise, when R==0, printing of R can be omitted.
		if strings.HasPrefix(opName, "pl") || strings.HasPrefix(opName, "pst") {
			r := inst.Args[3].(Imm)
			ra := inst.Args[2].(Reg)
			d := inst.Args[1].(Offset)
			if r == 1 && ra == R0 {
				str := fmt.Sprintf("%s %s,%d", opName, gnuArg(&inst, 0, inst.Args[0], PC), d)
				buf.WriteString(str)
				startArg = 4
			} else {
				str := fmt.Sprintf("%s %s,%d(%s)", opName,
					gnuArg(&inst, 0, inst.Args[0], PC),
					d,
					gnuArg(&inst, 2, inst.Args[2], PC))
				if r == 1 {
					// This is an invalid encoding (ra != 0 && r == 1) on ISA 3.1.
					v := uint64(inst.Enc)<<32 | uint64(inst.SuffixEnc)
					return fmt.Sprintf(".quad 0x%x", v)
				}
				buf.WriteString(str)
				startArg = 4
			}
		} else {
			buf.WriteString(opName)
		}
	}
	for i, arg := range argList {
		if arg == nil {
			break
		}
		if i < startArg {
			continue
		}
		text := gnuArg(&inst, i, arg, PC)
		if text == "" {
			continue
		}
		buf.WriteString(sep)
		sep = ","
		buf.WriteString(text)
	}
	return buf.String()
}

// gnuArg formats arg (which is the argIndex's arg in inst) according to GNU rules.
// NOTE: because GNUSyntax is the only caller of this func, and it receives a copy
// of inst, it's ok to modify inst.Args here.
func gnuArg(inst *Inst, argIndex int, arg Arg, pc uint64) string {
	// special cases for load/store instructions
	if _, ok := arg.(Offset); ok {
		if argIndex+1 == len(inst.Args) || inst.Args[argIndex+1] == nil {
			panic(fmt.Errorf("wrong table: offset not followed by register"))
		}
	}
	switch arg := arg.(type) {
	case Reg:
		if isLoadStoreOp(inst.Op) && argIndex == 1 && arg == R0 {
			return "0"
		}
		return arg.String()
	case CondReg:
		// The CondReg can either be found in a CMP, where the
		// condition register field is being set, or in an instruction
		// like a branch or isel that is testing a bit in a condition
		// register field.
		if arg == CR0 && strings.HasPrefix(inst.Op.String(), "cmp") {
			return "" // don't show cr0 for cmp instructions
		} else if arg >= CR0 {
			return fmt.Sprintf("cr%d", int(arg-CR0))
		}
		bit := condBit[(arg-Cond0LT)%4]
		if arg <= Cond0SO {
			return bit
		}
		return fmt.Sprintf("4*cr%d+%s", int(arg-Cond0LT)/4, bit)
	case Imm:
		return fmt.Sprintf("%d", arg)
	case SpReg:
		switch int(arg) {
		case 1:
			return "xer"
		case 8:
			return "lr"
		case 9:
			return "ctr"
		case 268:
			return "tb"
		default:
			return fmt.Sprintf("%d", int(arg))
		}
	case PCRel:
		// If the arg is 0, use the relative address format.
		// Otherwise the pc is meaningful, use absolute address.
		if int(arg) == 0 {
			return fmt.Sprintf(".%+#x", int(arg))
		}
		addr := pc + uint64(int64(arg))
		return fmt.Sprintf("%#x", addr)
	case Label:
		return fmt.Sprintf("%#x", uint32(arg))
	case Offset:
		reg := inst.Args[argIndex+1].(Reg)
		removeArg(inst, argIndex+1)
		if reg == R0 {
			return fmt.Sprintf("%d(0)", int(arg))
		}
		return fmt.Sprintf("%d(r%d)", int(arg), reg-R0)
	}
	return fmt.Sprintf("???(%v)", arg)
}

// removeArg removes the arg in inst.Args[index].
func removeArg(inst *Inst, index int) {
	for i := index; i < len(inst.Args); i++ {
		if i+1 < len(inst.Args) {
			inst.Args[i] = inst.Args[i+1]
		} else {
			inst.Args[i] = nil
		}
	}
}

// isLoadStoreOp returns true if op is a load or store instruction
func isLoadStoreOp(op Op) bool {
	switch op {
	case LBZ, LBZU, LBZX, LBZUX:
		return true
	case LHZ, LHZU, LHZX, LHZUX:
		return true
	case LHA, LHAU, LHAX, LHAUX:
		return true
	case LWZ, LWZU, LWZX, LWZUX:
		return true
	case LWA, LWAX, LWAUX:
		return true
	case LD, LDU, LDX, LDUX:
		return true
	case LQ:
		return true
	case STB, STBU, STBX, STBUX:
		return true
	case STH, STHU, STHX, STHUX:
		return true
	case STW, STWU, STWX, STWUX:
		return true
	case STD, STDU, STDX, STDUX:
		return true
	case STQ:
		return true
	case LHBRX, LWBRX, STHBRX, STWBRX:
		return true
	case LBARX, LWARX, LHARX, LDARX:
		return true
	}
	return false
}
