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

import (
	"fmt"
	"strings"
)

type SymLookup func(uint64) (string, uint64)

// GoSyntax returns the Go assembler syntax for the instruction.
// The syntax was originally defined by Plan 9.
// The pc is the program counter of the instruction, used for expanding
// PC-relative addresses into absolute ones.
// The symname function queries the symbol table for the program
// being disassembled. Given a target address it returns the name and base
// address of the symbol containing the target, if any; otherwise it returns "", 0.
func GoSyntax(inst Inst, pc uint64, symname SymLookup) string {
	if symname == nil {
		symname = func(uint64) (string, uint64) { return "", 0 }
	}
	var args []string
	for i := len(inst.Args) - 1; i >= 0; i-- {
		a := inst.Args[i]
		if a == nil {
			continue
		}
		args = append(args, plan9Arg(&inst, pc, symname, a))
	}

	var rep string
	var last Prefix
	for _, p := range inst.Prefix {
		if p == 0 || p.IsREX() || p.IsVEX() || p.IsEVEX() {
			break
		}

		switch {
		// Don't show prefixes implied by the instruction text.
		case p&0xFF00 == PrefixImplicit:
			continue
		// Only REP and REPN are recognized repeaters. Plan 9 syntax
		// treats them as separate opcodes.
		case p&0xFF == PrefixREP:
			rep = "REP; "
		case p&0xFF == PrefixREPN:
			rep = "REPNE; "
		default:
			last = p
		}
	}

	prefix := ""
	switch last & 0xFF {
	case 0, 0x66, 0x67:
		// ignore
	default:
		prefix += last.String() + " "
	}

	op := inst.Op.String()
	if inst.Op == VFPCLASSPD || inst.Op == VFPCLASSPS || inst.Op == VCVTTPD2DQ || inst.Op == VCVTTPD2UDQ || inst.Op == VCVTTPD2QQ || inst.Op == VCVTTPD2UQQ || inst.Op == VCVTPD2DQ || inst.Op == VCVTPD2UDQ || inst.Op == VCVTPD2QQ || inst.Op == VCVTPD2UQQ || inst.Op == VCVTPD2PS {
		vexL := 0
		isEvex := false
		for i, p := range inst.Prefix {
			if p.IsEVEX() && i+3 < len(inst.Prefix) && inst.Prefix[i+3] != 0 {
				vexL = int((inst.Prefix[i+3]&0xFF)>>5) & 3
				isEvex = true
				break
			} else if p.IsVEX() && i+2 < len(inst.Prefix) {
				if p&0xFF == 0xC4 {
					vexL = int((inst.Prefix[i+2]&0xFF)>>2) & 1
				} else if p&0xFF == 0xC5 {
					vexL = int((inst.Prefix[i+1]&0xFF)>>2) & 1
				}
				break
			}
		}
		if !isEvex || inst.Op == VFPCLASSPD || inst.Op == VFPCLASSPS || inst.Op == VCVTPD2DQ {
			switch vexL {
			case 0:
				op += "X"
			case 1:
				if inst.Op != VCMPPD && inst.Op != VCMPPS && inst.Op != VCMPPH && inst.Op != VCMPBF16 {
					op += "Y"
				}
			case 2:
				if inst.Op != VCMPPD && inst.Op != VCMPPS && inst.Op != VCMPPH && inst.Op != VCMPBF16 {
					op += "Z"
				}
			}
		}
	} else if inst.Op == VCVTTSD2SI || inst.Op == VCVTTSS2SI || inst.Op == VCVTSD2SI || inst.Op == VCVTSS2SI || inst.Op == VCVTSI2SD || inst.Op == VCVTSI2SS {
		is64 := false
		if (inst.Op == VCVTSI2SD || inst.Op == VCVTSI2SS) && inst.MemBytes == 8 {
			is64 = true
		} else {
			for _, a := range inst.Args {
				if r, ok := a.(Reg); ok && RAX <= r && r <= R15 {
					is64 = true
					break
				}
			}
		}
		if inst.Op == VCVTSI2SD || inst.Op == VCVTSI2SS {
			if is64 {
				op += "Q"
			} else {
				op += "L"
			}
		} else {
			if is64 {
				op += "Q"
			}
		}
	} else if plan9Suffix[inst.Op] {
		s := inst.DataSize
		if inst.MemBytes != 0 {
			s = inst.MemBytes * 8
		} else if inst.Args[1] == nil { // look for register-only 64-bit instruction, like PUSHQ AX
			if r, ok := inst.Args[0].(Reg); ok && RAX <= r && r <= R15 {
				s = 64
			}
		}
		switch s {
		case 8:
			op += "B"
		case 16:
			op += "W"
		case 32:
			op += "L"
		case 64:
			op += "Q"
		}
	}

	if inst.Broadcast {
		op += ".BCST"
	}
	if inst.SAE {
		if hasRC(inst.Op) {
			switch inst.Rounding {
			case 0:
				op += ".RN_SAE"
			case 1:
				op += ".RD_SAE"
			case 2:
				op += ".RU_SAE"
			case 3:
				op += ".RZ_SAE"
			}
		} else {
			op += ".SAE"
		}
	}
	if inst.Zeroing {
		op += ".Z"
	}

	if inst.Op == CMP {
		// Use reads-left-to-right ordering for comparisons.
		// See issue 60920.
		args[0], args[1] = args[1], args[0]
	}

	if args != nil {
		op += " " + strings.Join(args, ", ")
	}

	return rep + prefix + op
}

func plan9Arg(inst *Inst, pc uint64, symname func(uint64) (string, uint64), arg Arg) string {
	switch a := arg.(type) {
	case Reg:
		return plan9Reg[a]
	case Rel:
		if pc == 0 {
			break
		}
		// If the absolute address is the start of a symbol, use the name.
		// Otherwise use the raw address, so that things like relative
		// jumps show up as JMP 0x123 instead of JMP f+10(SB).
		// It is usually easier to search for 0x123 than to do the mental
		// arithmetic to find f+10.
		addr := pc + uint64(inst.Len) + uint64(a)
		if s, base := symname(addr); s != "" && addr == base {
			return fmt.Sprintf("%s(SB)", s)
		}
		return fmt.Sprintf("%#x", addr)

	case Imm:
		if (inst.Op == MOV || inst.Op == PUSH) && inst.DataSize == 32 {
			// Only try to convert an immediate to a symbol in certain
			// special circumstances. See issue 72942.
			//
			// On 64-bit, symbol addresses always hit the Mem case below.
			// Particularly, we use LEAQ to materialize the address of
			// a global or function.
			//
			// On 32-bit, we sometimes use MOVL. Still try to symbolize
			// those immediates.
			if s, base := symname(uint64(a)); s != "" {
				suffix := ""
				if uint64(a) != base {
					suffix = fmt.Sprintf("%+d", uint64(a)-base)
				}
				return fmt.Sprintf("$%s%s(SB)", s, suffix)
			}
		}
		if inst.Mode == 32 {
			return fmt.Sprintf("$%#x", uint32(a))
		}
		if Imm(int32(a)) == a {
			return fmt.Sprintf("$%#x", int64(a))
		}
		return fmt.Sprintf("$%#x", uint64(a))
	case Mem:
		if s, disp := memArgToSymbol(a, pc, inst.Len, symname); s != "" {
			suffix := ""
			if disp != 0 {
				suffix = fmt.Sprintf("%+d", disp)
			}
			return fmt.Sprintf("%s%s(SB)", s, suffix)
		}
		s := ""
		if a.Segment != 0 {
			s += fmt.Sprintf("%s:", plan9Reg[a.Segment])
		}
		if a.Disp != 0 {
			s += fmt.Sprintf("%#x", a.Disp)
		} else {
			s += "0"
		}
		if a.Base != 0 {
			s += fmt.Sprintf("(%s)", plan9Reg[a.Base])
		}
		if a.Index != 0 && a.Scale != 0 {
			s += fmt.Sprintf("(%s*%d)", plan9Reg[a.Index], a.Scale)
		}
		return s
	}
	return arg.String()
}

func memArgToSymbol(a Mem, pc uint64, instrLen int, symname SymLookup) (string, int64) {
	if a.Segment != 0 || a.Disp == 0 || a.Index != 0 || a.Scale != 0 {
		return "", 0
	}

	var disp uint64
	switch a.Base {
	case IP, EIP, RIP:
		disp = uint64(a.Disp + int64(pc) + int64(instrLen))
	case 0:
		disp = uint64(a.Disp)
	default:
		return "", 0
	}

	s, base := symname(disp)
	return s, int64(disp) - int64(base)
}

var plan9Suffix = [maxOp + 1]bool{
	ADC:       true,
	ADD:       true,
	AND:       true,
	BSF:       true,
	BSR:       true,
	BT:        true,
	BTC:       true,
	BTR:       true,
	BTS:       true,
	CMP:       true,
	CMPXCHG:   true,
	CVTSI2SD:  true,
	CVTSI2SS:  true,
	CVTSD2SI:  true,
	CVTSS2SI:  true,
	CVTTSD2SI: true,
	CVTTSS2SI: true,
	DEC:       true,
	DIV:       true,
	FLDENV:    true,
	FRSTOR:    true,
	IDIV:      true,
	IMUL:      true,
	IN:        true,
	INC:       true,
	LEA:       true,
	MOV:       true,
	MOVNTI:    true,
	MUL:       true,
	NEG:       true,
	NOP:       true,
	NOT:       true,
	OR:        true,
	OUT:       true,
	POP:       true,
	POPA:      true,
	POPCNT:    true,
	PUSH:      true,
	PUSHA:     true,
	RCL:       true,
	RCR:       true,
	ROL:       true,
	ROR:       true,
	SAR:       true,
	SBB:       true,
	SHL:       true,
	SHLD:      true,
	SHR:       true,
	SHRD:      true,
	SUB:       true,
	TEST:      true,
	XADD:      true,
	XCHG:      true,
	XOR:       true,
}

var plan9Reg = [...]string{
	AL:   "AL",
	CL:   "CL",
	BL:   "BL",
	DL:   "DL",
	AH:   "AH",
	CH:   "CH",
	BH:   "BH",
	DH:   "DH",
	SPB:  "SP",
	BPB:  "BP",
	SIB:  "SI",
	DIB:  "DI",
	R8B:  "R8",
	R9B:  "R9",
	R10B: "R10",
	R11B: "R11",
	R12B: "R12",
	R13B: "R13",
	R14B: "R14",
	R15B: "R15",
	AX:   "AX",
	CX:   "CX",
	BX:   "BX",
	DX:   "DX",
	SP:   "SP",
	BP:   "BP",
	SI:   "SI",
	DI:   "DI",
	R8W:  "R8",
	R9W:  "R9",
	R10W: "R10",
	R11W: "R11",
	R12W: "R12",
	R13W: "R13",
	R14W: "R14",
	R15W: "R15",
	EAX:  "AX",
	ECX:  "CX",
	EDX:  "DX",
	EBX:  "BX",
	ESP:  "SP",
	EBP:  "BP",
	ESI:  "SI",
	EDI:  "DI",
	R8L:  "R8",
	R9L:  "R9",
	R10L: "R10",
	R11L: "R11",
	R12L: "R12",
	R13L: "R13",
	R14L: "R14",
	R15L: "R15",
	RAX:  "AX",
	RCX:  "CX",
	RDX:  "DX",
	RBX:  "BX",
	RSP:  "SP",
	RBP:  "BP",
	RSI:  "SI",
	RDI:  "DI",
	R8:   "R8",
	R9:   "R9",
	R10:  "R10",
	R11:  "R11",
	R12:  "R12",
	R13:  "R13",
	R14:  "R14",
	R15:  "R15",
	IP:   "IP",
	EIP:  "IP",
	RIP:  "IP",
	F0:   "F0",
	F1:   "F1",
	F2:   "F2",
	F3:   "F3",
	F4:   "F4",
	F5:   "F5",
	F6:   "F6",
	F7:   "F7",
	M0:   "M0",
	M1:   "M1",
	M2:   "M2",
	M3:   "M3",
	M4:   "M4",
	M5:   "M5",
	M6:   "M6",
	M7:   "M7",
	X0:   "X0",
	X1:   "X1",
	X2:   "X2",
	X3:   "X3",
	X4:   "X4",
	X5:   "X5",
	X6:   "X6",
	X7:   "X7",
	X8:   "X8",
	X9:   "X9",
	X10:  "X10",
	X11:  "X11",
	X12:  "X12",
	X13:  "X13",
	X14:  "X14",
	X15:  "X15",
	X16:  "X16",
	X17:  "X17",
	X18:  "X18",
	X19:  "X19",
	X20:  "X20",
	X21:  "X21",
	X22:  "X22",
	X23:  "X23",
	X24:  "X24",
	X25:  "X25",
	X26:  "X26",
	X27:  "X27",
	X28:  "X28",
	X29:  "X29",
	X30:  "X30",
	X31:  "X31",
	Y0:   "Y0",
	Y1:   "Y1",
	Y2:   "Y2",
	Y3:   "Y3",
	Y4:   "Y4",
	Y5:   "Y5",
	Y6:   "Y6",
	Y7:   "Y7",
	Y8:   "Y8",
	Y9:   "Y9",
	Y10:  "Y10",
	Y11:  "Y11",
	Y12:  "Y12",
	Y13:  "Y13",
	Y14:  "Y14",
	Y15:  "Y15",
	Y16:  "Y16",
	Y17:  "Y17",
	Y18:  "Y18",
	Y19:  "Y19",
	Y20:  "Y20",
	Y21:  "Y21",
	Y22:  "Y22",
	Y23:  "Y23",
	Y24:  "Y24",
	Y25:  "Y25",
	Y26:  "Y26",
	Y27:  "Y27",
	Y28:  "Y28",
	Y29:  "Y29",
	Y30:  "Y30",
	Y31:  "Y31",
	Z0:   "Z0",
	Z1:   "Z1",
	Z2:   "Z2",
	Z3:   "Z3",
	Z4:   "Z4",
	Z5:   "Z5",
	Z6:   "Z6",
	Z7:   "Z7",
	Z8:   "Z8",
	Z9:   "Z9",
	Z10:  "Z10",
	Z11:  "Z11",
	Z12:  "Z12",
	Z13:  "Z13",
	Z14:  "Z14",
	Z15:  "Z15",
	Z16:  "Z16",
	Z17:  "Z17",
	Z18:  "Z18",
	Z19:  "Z19",
	Z20:  "Z20",
	Z21:  "Z21",
	Z22:  "Z22",
	Z23:  "Z23",
	Z24:  "Z24",
	Z25:  "Z25",
	Z26:  "Z26",
	Z27:  "Z27",
	Z28:  "Z28",
	Z29:  "Z29",
	Z30:  "Z30",
	Z31:  "Z31",
	K0:   "K0",
	K1:   "K1",
	K2:   "K2",
	K3:   "K3",
	K4:   "K4",
	K5:   "K5",
	K6:   "K6",
	K7:   "K7",
	CS:   "CS",
	SS:   "SS",
	DS:   "DS",
	ES:   "ES",
	FS:   "FS",
	GS:   "GS",
	GDTR: "GDTR",
	IDTR: "IDTR",
	LDTR: "LDTR",
	MSW:  "MSW",
	TASK: "TASK",
	CR0:  "CR0",
	CR1:  "CR1",
	CR2:  "CR2",
	CR3:  "CR3",
	CR4:  "CR4",
	CR5:  "CR5",
	CR6:  "CR6",
	CR7:  "CR7",
	CR8:  "CR8",
	CR9:  "CR9",
	CR10: "CR10",
	CR11: "CR11",
	CR12: "CR12",
	CR13: "CR13",
	CR14: "CR14",
	CR15: "CR15",
	DR0:  "DR0",
	DR1:  "DR1",
	DR2:  "DR2",
	DR3:  "DR3",
	DR4:  "DR4",
	DR5:  "DR5",
	DR6:  "DR6",
	DR7:  "DR7",
	DR8:  "DR8",
	DR9:  "DR9",
	DR10: "DR10",
	DR11: "DR11",
	DR12: "DR12",
	DR13: "DR13",
	DR14: "DR14",
	DR15: "DR15",
	TR0:  "TR0",
	TR1:  "TR1",
	TR2:  "TR2",
	TR3:  "TR3",
	TR4:  "TR4",
	TR5:  "TR5",
	TR6:  "TR6",
	TR7:  "TR7",
}
