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

package riscv64asm

import (
	"strings"
)

// GNUSyntax returns the GNU assembler syntax for the instruction, as defined by GNU binutils.
// This form typically matches the syntax defined in the RISC-V Instruction Set Manual. See
// https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf
func GNUSyntax(inst Inst) string {
	op := strings.ToLower(inst.Op.String())
	var args []string
	for _, a := range inst.Args {
		if a == nil {
			break
		}
		args = append(args, strings.ToLower(a.String()))
	}

	switch inst.Op {
	case ADDI, ADDIW, ANDI, ORI, SLLI, SLLIW, SRAI, SRAIW, SRLI, SRLIW, XORI:
		if inst.Op == ADDI {
			if inst.Args[1].(Reg) == X0 && inst.Args[0].(Reg) != X0 {
				op = "li"
				args[1] = args[2]
				args = args[:len(args)-1]
				break
			}

			if inst.Args[2].(Simm).Imm == 0 {
				if inst.Args[0].(Reg) == X0 && inst.Args[1].(Reg) == X0 {
					op = "nop"
					args = nil
				} else {
					op = "mv"
					args = args[:len(args)-1]
				}
			}
		}

		if inst.Op == ANDI && inst.Args[2].(Simm).Imm == 255 {
			op = "zext.b"
			args = args[:len(args)-1]
		}

		if inst.Op == ADDIW && inst.Args[2].(Simm).Imm == 0 {
			op = "sext.w"
			args = args[:len(args)-1]
		}

		if inst.Op == XORI && inst.Args[2].(Simm).String() == "-1" {
			op = "not"
			args = args[:len(args)-1]
		}

	case ADD:
		if inst.Args[1].(Reg) == X0 {
			op = "mv"
			args[1] = args[2]
			args = args[:len(args)-1]
		}

	case BEQ:
		if inst.Args[1].(Reg) == X0 {
			op = "beqz"
			args[1] = args[2]
			args = args[:len(args)-1]
		}

	case BGE:
		if inst.Args[1].(Reg) == X0 {
			op = "bgez"
			args[1] = args[2]
			args = args[:len(args)-1]
		} else if inst.Args[0].(Reg) == X0 {
			op = "blez"
			args[0], args[1] = args[1], args[2]
			args = args[:len(args)-1]
		}

	case BLT:
		if inst.Args[1].(Reg) == X0 {
			op = "bltz"
			args[1] = args[2]
			args = args[:len(args)-1]
		} else if inst.Args[0].(Reg) == X0 {
			op = "bgtz"
			args[0], args[1] = args[1], args[2]
			args = args[:len(args)-1]
		}

	case BNE:
		if inst.Args[1].(Reg) == X0 {
			op = "bnez"
			args[1] = args[2]
			args = args[:len(args)-1]
		}

	case CSRRC:
		if inst.Args[0].(Reg) == X0 {
			op = "csrc"
			args[0], args[1] = args[1], args[2]
			args = args[:len(args)-1]
		}

	case CSRRCI:
		if inst.Args[0].(Reg) == X0 {
			op = "csrci"
			args[0], args[1] = args[1], args[2]
			args = args[:len(args)-1]
		}

	case CSRRS:
		if inst.Args[2].(Reg) == X0 {
			switch inst.Args[1].(CSR) {
			case FCSR:
				op = "frcsr"
				args = args[:len(args)-2]

			case FFLAGS:
				op = "frflags"
				args = args[:len(args)-2]

			case FRM:
				op = "frrm"
				args = args[:len(args)-2]

			// rdcycleh, rdinstreth and rdtimeh are RV-32 only instructions.
			// So not included there.
			case CYCLE:
				op = "rdcycle"
				args = args[:len(args)-2]

			case INSTRET:
				op = "rdinstret"
				args = args[:len(args)-2]

			case TIME:
				op = "rdtime"
				args = args[:len(args)-2]

			default:
				op = "csrr"
				args = args[:len(args)-1]
			}
		} else if inst.Args[0].(Reg) == X0 {
			op = "csrs"
			args[0], args[1] = args[1], args[2]
			args = args[:len(args)-1]
		}

	case CSRRSI:
		if inst.Args[0].(Reg) == X0 {
			op = "csrsi"
			args[0], args[1] = args[1], args[2]
			args = args[:len(args)-1]
		}

	case CSRRW:
		switch inst.Args[1].(CSR) {
		case FCSR:
			op = "fscsr"
			if inst.Args[0].(Reg) == X0 {
				args[0] = args[2]
				args = args[:len(args)-2]
			} else {
				args[1] = args[2]
				args = args[:len(args)-1]
			}

		case FFLAGS:
			op = "fsflags"
			if inst.Args[0].(Reg) == X0 {
				args[0] = args[2]
				args = args[:len(args)-2]
			} else {
				args[1] = args[2]
				args = args[:len(args)-1]
			}

		case FRM:
			op = "fsrm"
			if inst.Args[0].(Reg) == X0 {
				args[0] = args[2]
				args = args[:len(args)-2]
			} else {
				args[1] = args[2]
				args = args[:len(args)-1]
			}

		case CYCLE:
			if inst.Args[0].(Reg) == X0 && inst.Args[2].(Reg) == X0 {
				op = "unimp"
				args = nil
			}

		default:
			if inst.Args[0].(Reg) == X0 {
				op = "csrw"
				args[0], args[1] = args[1], args[2]
				args = args[:len(args)-1]
			}
		}

	case CSRRWI:
		if inst.Args[0].(Reg) == X0 {
			op = "csrwi"
			args[0], args[1] = args[1], args[2]
			args = args[:len(args)-1]
		}

	// When both pred and succ equals to iorw, the GNU objdump will omit them.
	case FENCE:
		if inst.Args[0].(MemOrder).String() == "iorw" &&
			inst.Args[1].(MemOrder).String() == "iorw" {
			args = nil
		}

	case FSGNJX_D:
		if inst.Args[1].(Reg) == inst.Args[2].(Reg) {
			op = "fabs.d"
			args = args[:len(args)-1]
		}

	case FSGNJX_S:
		if inst.Args[1].(Reg) == inst.Args[2].(Reg) {
			op = "fabs.s"
			args = args[:len(args)-1]
		}

	case FSGNJ_D:
		if inst.Args[1].(Reg) == inst.Args[2].(Reg) {
			op = "fmv.d"
			args = args[:len(args)-1]
		}

	case FSGNJ_S:
		if inst.Args[1].(Reg) == inst.Args[2].(Reg) {
			op = "fmv.s"
			args = args[:len(args)-1]
		}

	case FSGNJN_D:
		if inst.Args[1].(Reg) == inst.Args[2].(Reg) {
			op = "fneg.d"
			args = args[:len(args)-1]
		}

	case FSGNJN_S:
		if inst.Args[1].(Reg) == inst.Args[2].(Reg) {
			op = "fneg.s"
			args = args[:len(args)-1]
		}

	case JAL:
		if inst.Args[0].(Reg) == X0 {
			op = "j"
			args[0] = args[1]
			args = args[:len(args)-1]
		} else if inst.Args[0].(Reg) == X1 {
			op = "jal"
			args[0] = args[1]
			args = args[:len(args)-1]
		}

	case JALR:
		if inst.Args[0].(Reg) == X1 && inst.Args[1].(RegOffset).Ofs.Imm == 0 {
			args[0] = inst.Args[1].(RegOffset).OfsReg.String()
			args = args[:len(args)-1]
		}

		if inst.Args[0].(Reg) == X0 {
			if inst.Args[1].(RegOffset).OfsReg == X1 && inst.Args[1].(RegOffset).Ofs.Imm == 0 {
				op = "ret"
				args = nil
			} else if inst.Args[1].(RegOffset).Ofs.Imm == 0 {
				op = "jr"
				args[0] = inst.Args[1].(RegOffset).OfsReg.String()
				args = args[:len(args)-1]
			} else {
				op = "jr"
				args[0] = inst.Args[1].(RegOffset).String()
				args = args[:len(args)-1]
			}
		}

	case SLTIU:
		if inst.Args[2].(Simm).String() == "1" {
			op = "seqz"
			args = args[:len(args)-1]
		}

	case SLT:
		if inst.Args[1].(Reg) == X0 {
			op = "sgtz"
			args[1] = args[2]
			args = args[:len(args)-1]
		} else if inst.Args[2].(Reg) == X0 {
			op = "sltz"
			args = args[:len(args)-1]
		}

	case SLTU:
		if inst.Args[1].(Reg) == X0 {
			op = "snez"
			args[1] = args[2]
			args = args[:len(args)-1]
		}

	case SUB:
		if inst.Args[1].(Reg) == X0 {
			op = "neg"
			args[1] = args[2]
			args = args[:len(args)-1]
		}

	case SUBW:
		if inst.Args[1].(Reg) == X0 {
			op = "negw"
			args[1] = args[2]
			args = args[:len(args)-1]
		}
	}

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