/* Capstone Disassembly Engine, http://www.capstone-engine.org */
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2013-2022, */
/*    Rot127 <unisono@quyllur.org> 2022-2023 */
/* Automatically translated source file from LLVM. */

/* LLVM-commit: <commit> */
/* LLVM-tag: <tag> */

/* Only small edits allowed. */
/* For multiple similar edits, please create a Patch for the translator. */

/* Capstone's C++ file translator: */
/* https://github.com/capstone-engine/capstone/tree/next/suite/auto-sync */

//===-- RISCVInstPrinter.cpp - Convert RISC-V MCInst to asm syntax --------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This class prints an RISC-V MCInst to a .s file.
//
//===----------------------------------------------------------------------===//

#include <capstone/platform.h>
#include "../../MathExtras.h"

#include "RISCVMapping.h"
#include "RISCVInstPrinter.h"

#define GET_SUBTARGETINFO_ENUM
#include "RISCVGenSubtargetInfo.inc"

#define GET_INSTRINFO_ENUM
#include "RISCVGenInstrInfo.inc"

#define GET_REGINFO_ENUM
#include "RISCVGenRegisterInfo.inc"

#define GET_SysRegsList_IMPL
#include "RISCVGenSystemOperands.inc"

#define GEN_UNCOMPRESS_INSTR
#include "RISCVGenCompressedInstructionsInfo.inc"

#include "RISCVMapping.h"
#include "../../Mapping.h"

#define CONCAT(a, b) CONCAT_(a, b)
#define CONCAT_(a, b) a##_##b

#define DEBUG_TYPE "asm-printer"

static void printCustomAliasOperand(MCInst *MI, uint64_t Address,
				    unsigned OpIdx, unsigned PrintMethodIdx,
				    SStream *OS);
static inline void printRegName(SStream *O, MCRegister Reg);
static inline void printOperand(MCInst *MI, unsigned OpNo, SStream *O);
// Include the auto-generated portion of the assembly writer.
#define PRINT_ALIAS_INSTR
#include "RISCVGenAsmWriter.inc"

// Print architectural register names rather than the ABI names (such as x2
// instead of sp).
// TODO: Make RISCVInstPrinter_doGetRegisterName non-static so that this can a
// member.
static bool ArchRegNames;

const char *doGetRegisterName(MCRegister Reg)
{
	return getRegisterName(Reg, ArchRegNames ? RISCV_NoRegAltName :
						   RISCV_ABIRegAltName);
}

static inline void printRegName(SStream *O, MCRegister Reg)
{
	SStream_concat0(markup_OS(O, Markup_Register), doGetRegisterName(Reg));
}

bool haveRequiredFeatures(const RISCV_SysReg *Reg, MCInst *MI)
{
	// Not in 32-bit mode.
	if (Reg->isRV32Only &&
	    RISCV_getFeatureBits(MI->csh->mode, RISCV_Feature64Bit))
		return false;

	return true;
}

static inline void printOperand(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_Operand, OpNo);

	MCOperand *MO = MCInst_getOperand(MI, (OpNo));

	if (MCOperand_isReg(MO)) {
		printRegName(O, MCOperand_getReg(MO));
		return;
	}

	if (MCOperand_isImm(MO)) {
		printInt64(markup_OS(O, Markup_Immediate),
			   MCOperand_getImm(MO));
		return;
	}

	CS_ASSERT(MCOperand_isExpr(MO) &&
		  "Unknown operand kind in printOperand");
	printExpr(O, MCOperand_getExpr(MO));
}

void printBranchOperand(MCInst *MI, uint64_t Address, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_BranchOperand, OpNo);
	MCOperand *MO = MCInst_getOperand(MI, (OpNo));
	if (!MCOperand_isImm(MO))
		return printOperand(MI, OpNo, O);

	if (MI->csh->PrintBranchImmAsAddress) {
		uint64_t Target = Address + MCOperand_getImm(MO);
		if (!RISCV_getFeatureBits(MI->csh->mode, RISCV_Feature64Bit))
			Target &= 0xffffffff;
		printUInt64(markup_OS(O, Markup_Target), Target);
	} else {
		printInt64(markup_OS(O, Markup_Target), MCOperand_getImm(MO));
	}
}

void printCSRSystemRegister(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_CSRSystemRegister, OpNo);
	unsigned Imm = MCOperand_getImm(MCInst_getOperand(MI, (OpNo)));
	const RISCV_SysReg *SysReg = RISCV_lookupSysRegByEncoding(Imm);
	if (SysReg && haveRequiredFeatures(SysReg, MI))
		SStream_concat0(markup_OS(O, Markup_Register), SysReg->Name);
	else
		printUInt64(markup_OS(O, Markup_Register), Imm);
}

void printFenceArg(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_FenceArg, OpNo);
	unsigned FenceArg = MCOperand_getImm(MCInst_getOperand(MI, (OpNo)));
	CS_ASSERT(((FenceArg >> 4) == 0) &&
		  "Invalid immediate in printFenceArg");

	if ((FenceArg & RISCVFenceField_I) != 0)
		SStream_concat0(O, "i");

	if ((FenceArg & RISCVFenceField_O) != 0)
		SStream_concat0(O, "o");

	if ((FenceArg & RISCVFenceField_R) != 0)
		SStream_concat0(O, "r");

	if ((FenceArg & RISCVFenceField_W) != 0)
		SStream_concat0(O, "w");

	if (FenceArg == 0)
		SStream_concat0(O, "0");
}

void printFRMArg(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_FRMArg, OpNo);
	unsigned FRMArg = MCOperand_getImm(MCInst_getOperand(MI, (OpNo)));
	if (!(MI->csh->syntax & CS_OPT_SYNTAX_NO_ALIAS_TEXT) &&
	    FRMArg == RISCVFPRndMode_DYN)
		return;
	SStream_concat(O, "%s", ", ");
	SStream_concat0(O, RISCVFPRndMode_roundingModeToString(FRMArg));
}

void printFRMArgLegacy(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_FRMArgLegacy, OpNo);
	unsigned FRMArg = MCOperand_getImm(MCInst_getOperand(MI, (OpNo)));
	// Never print rounding mode if it's the default 'rne'. This ensures the
	// output can still be parsed by older tools that erroneously failed to
	// accept a rounding mode.
	if (FRMArg == RISCVFPRndMode_RNE)
		return;
	SStream_concat(O, "%s", ", ");
	SStream_concat0(O, RISCVFPRndMode_roundingModeToString(FRMArg));
}

void printFPImmOperand(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_FPImmOperand, OpNo);
	unsigned Imm = MCOperand_getImm(MCInst_getOperand(MI, (OpNo)));
	if (Imm == 1) {
		SStream_concat0(markup_OS(O, Markup_Immediate), "min");
	} else if (Imm == 30) {
		SStream_concat0(markup_OS(O, Markup_Immediate), "inf");
	} else if (Imm == 31) {
		SStream_concat0(markup_OS(O, Markup_Immediate), "nan");
	} else {
		float FPVal = getFPImm(Imm);
		// If the value is an integer, print a .0 fraction. Otherwise, use %g to
		// which will not print trailing zeros and will use scientific notation
		// if it is shorter than printing as a decimal. The smallest value requires
		// 12 digits of precision including the decimal.
		if (FPVal == (int)(FPVal))
			printfFloat(markup_OS(O, Markup_Immediate), "%.1f",
				    FPVal);
		else
			printfFloat(markup_OS(O, Markup_Immediate), "%.12g",
				    FPVal);
	}
}

void printZeroOffsetMemOp(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_ZeroOffsetMemOp, OpNo);
	MCOperand *MO = MCInst_getOperand(MI, (OpNo));

	CS_ASSERT(MCOperand_isReg(MO) &&
		  "printZeroOffsetMemOp can only print register operands");
	SStream_concat0(O, "(");
	printRegName(O, MCOperand_getReg(MO));
	SStream_concat0(O, ")");
}

void printVTypeI(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_VTypeI, OpNo);
	unsigned Imm = MCOperand_getImm(MCInst_getOperand(MI, (OpNo)));
	// Print the raw immediate for reserved values: vlmul[2:0]=4, vsew[2:0]=0b1xx,
	// or non-zero in bits 8 and above.
	if (RISCVVType_getVLMUL(Imm) == RISCVII_LMUL_RESERVED ||
	    RISCVVType_getSEW(Imm) > 64 || (Imm >> 8) != 0) {
		printUInt64(O, Imm);
		return;
	}
	// Print the text form.
	printVType(Imm, O);
}

void printRlist(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_Rlist, OpNo);
	unsigned Imm = MCOperand_getImm(MCInst_getOperand(MI, (OpNo)));
	SStream_concat0(O, "{");
	switch (Imm) {
	case RISCVZC_RLISTENCODE_RA:
		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x1" : "ra"));
		break;
	case RISCVZC_RLISTENCODE_RA_S0:
		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x1" : "ra"));
		SStream_concat0(O, ", ");
		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x8" : "s0"));
		break;
	case RISCVZC_RLISTENCODE_RA_S0_S1:
		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x1" : "ra"));
		SStream_concat0(O, ", ");
		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x8" : "s0"));
		SStream_concat0(O, "-");

		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x9" : "s1"));
		break;
	case RISCVZC_RLISTENCODE_RA_S0_S2:
		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x1" : "ra"));
		SStream_concat0(O, ", ");
		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x8" : "s0"));
		SStream_concat0(O, "-");

		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x9" : "s2"));
		if (ArchRegNames) {
			SStream_concat0(O, ", ");
			SStream_concat0(markup_OS(O, Markup_Register), "x18");
		}
		break;
	case RISCVZC_RLISTENCODE_RA_S0_S3:
	case RISCVZC_RLISTENCODE_RA_S0_S4:
	case RISCVZC_RLISTENCODE_RA_S0_S5:
	case RISCVZC_RLISTENCODE_RA_S0_S6:
	case RISCVZC_RLISTENCODE_RA_S0_S7:
	case RISCVZC_RLISTENCODE_RA_S0_S8:
	case RISCVZC_RLISTENCODE_RA_S0_S9:
	case RISCVZC_RLISTENCODE_RA_S0_S11:
		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x1" : "ra"));
		SStream_concat0(O, ", ");
		SStream_concat0(markup_OS(O, Markup_Register),
				(ArchRegNames ? "x8" : "s0"));
		SStream_concat0(O, "-");

		if (ArchRegNames) {
			SStream_concat0(markup_OS(O, Markup_Register), "x9");
			SStream_concat0(O, ", ");
			SStream_concat0(markup_OS(O, Markup_Register), "x18");
			SStream_concat0(O, "-");
		}
		SStream_concat0(
			markup_OS(O, Markup_Register),
			doGetRegisterName(
				RISCV_X19 +
				(Imm == RISCVZC_RLISTENCODE_RA_S0_S11 ?
					 8 :
					 Imm - RISCVZC_RLISTENCODE_RA_S0_S3)));
		break;
	default:
		CS_ASSERT(0 && "invalid register list");
	}
	SStream_concat0(O, "}");
}

void printRegReg(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_RegReg, OpNo);
	MCOperand *MO = MCInst_getOperand(MI, (OpNo));

	CS_ASSERT(MCOperand_isReg(MO) &&
		  "printRegReg can only print register operands");
	if (MCOperand_getReg(MO) == RISCV_NoRegister)
		return;
	printRegName(O, MCOperand_getReg(MO));

	SStream_concat0(O, "(");
	MCOperand *MO1 = MCInst_getOperand(MI, (OpNo + 1));
	CS_ASSERT(MCOperand_isReg(MO1) &&
		  "printRegReg can only print register operands");
	printRegName(O, MCOperand_getReg(MO1));
	SStream_concat0(O, ")");
}

void printSpimm(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_Spimm, OpNo);
	int64_t Imm = MCOperand_getImm(MCInst_getOperand(MI, (OpNo)));
	unsigned Opcode = MCInst_getOpcode(MI);
	bool IsRV64 = RISCV_getFeatureBits(MI->csh->mode, RISCV_Feature64Bit);
	bool IsEABI = RISCV_getFeatureBits(MI->csh->mode, RISCV_FeatureRVE);
	int64_t Spimm = 0;
	int64_t RlistVal = MCOperand_getImm(MCInst_getOperand(MI, (0)));
	CS_ASSERT(RlistVal != 16 && "Incorrect rlist.");
	unsigned Base = RISCVZC_getStackAdjBase(RlistVal, IsRV64, IsEABI);
	Spimm = Imm + Base;
	CS_ASSERT((Spimm >= Base && Spimm <= Base + 48) && "Incorrect spimm");
	if (Opcode == RISCV_CM_PUSH)
		Spimm = -Spimm;

	RISCVZC_printSpimm(Spimm, markup_OS(O, Markup_Immediate));
}

void printVMaskReg(MCInst *MI, unsigned OpNo, SStream *O)
{
	RISCV_add_cs_detail_0(MI, RISCV_OP_GROUP_VMaskReg, OpNo);
	MCOperand *MO = MCInst_getOperand(MI, (OpNo));

	CS_ASSERT(MCOperand_isReg(MO) &&
		  "printVMaskReg can only print register operands");
	if (MCOperand_getReg(MO) == RISCV_NoRegister)
		return;
	SStream_concat0(O, ", ");
	printRegName(O, MCOperand_getReg(MO));
	SStream_concat0(O, ".t");
}

void RISCV_LLVM_printInstruction(MCInst *MI, SStream *O,
				 void * /* MCRegisterInfo* */ info)
{
	MI->MRI = (MCRegisterInfo *)info;

	MCInst_setIsAlias(MI, false);
	bool usesAliasDetails = map_use_alias_details(MI);
	MI->flat_insn->usesAliasDetails = usesAliasDetails;

	/* check for a non-compressed instruction */
	MCInst Uncompressed;
	MCInst_Init(&Uncompressed, MI->csh->arch);

	MCInst *McInstr = MI;
	bool is_uncompressed = false;
	// side-effectful check for compressed instructions that creates the equivalent uncompressed instruction in case of true
	// (LLVM doesn't generate an API for doing a pure check)
	if (uncompressInst(&Uncompressed, MI)) {
		McInstr = &Uncompressed;
		Uncompressed.address = MI->address;
		Uncompressed.MRI = MI->MRI;
		Uncompressed.csh = MI->csh;
		Uncompressed.flat_insn = MI->flat_insn;
		is_uncompressed = true;
	}

	// print the exact instruction text and done
	bool print_exact_text =
		(MI->csh->syntax & CS_OPT_SYNTAX_NO_ALIAS_TEXT) ||
		(is_uncompressed &&
		 MI->csh->syntax & CS_OPT_SYNTAX_NO_ALIAS_TEXT_COMPRESSED);
	if (print_exact_text) {
		printInstruction(MI, MI->address, O);
	} else {
		// side-effectful check for alias instructions that prints to the SStream if true
		if (printAliasInstr(McInstr, MI->address, O)) {
			MCInst_setIsAlias(MI, true);
			// do we still want the exact details even if the text is alias ?
			if (!usesAliasDetails && detail_is_set(MI)) {
				// disable actual printing
				SStream_Close(O);
				// discard the alias operands
				memset(MI->flat_insn->detail->riscv.operands, 0,
				       sizeof(MI->flat_insn->detail->riscv
						      .operands));
				MI->flat_insn->detail->riscv.op_count = 0;
				// re-disassemble again with no printing in order to obtain the full details
				// including the whole operands array
				printInstruction(MI, MI->address, O);
				// re-open the stream to restore the usual state
				SStream_Open(O);
			}
		} else // the instruction is not an alias
			printInstruction(McInstr, MI->address, O);
	}
	RISCV_add_groups(MI);
	RISCV_add_missing_write_access(MI);
	RISCV_compact_operands(MI);
	RISCV_set_alias_id(MI, O);
}

const char *getSysRegName(unsigned reg)
{
	const RISCV_SysReg *SysReg = RISCV_lookupSysRegByEncoding(reg);
	return SysReg->Name;
}

const char *RISCV_LLVM_getRegisterName(unsigned RegNo, unsigned AltIdx)
{
	return getRegisterName(RegNo, AltIdx);
}

bool isCompressed(MCInst *MI)
{
	MCInst unused;
	MCInst_Init(&unused, MI->csh->arch);
	return uncompressInst(&unused, MI);
}
